diff --git a/DEPS b/DEPS
index 98b1b9a..31e22cc 100644
--- a/DEPS
+++ b/DEPS
@@ -306,15 +306,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': '053490edfa703f3433d97a299ee80f2acc93de71',
+  'skia_revision': 'f82251f0091c52eee7125c06786d3c7e72c15327',
   # 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': 'ba99d7f61f605e0c4c5131f01ff9d4fc36ab269b',
+  'v8_revision': 'b057218023961f8b94b0313dd6ddbcc274c06e74',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': '398cfb4b1421901b8c18be07faf5f54bf14c7633',
+  'angle_revision': 'f8fae1ff4fae14fc6ba0aa1dc3af2e13a6e9a597',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -821,7 +821,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '3d55fc4f1877714a5aefaf5ca58e533d53b61244',
+    'b570761166a159d8217203052bdfc29ec90ebef9',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1664,7 +1664,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'ff3b6318208109a97dc91f5f653edfca466d3cca',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '0c152157409b6d7ba49a83eeeab1a358a1098d20',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1972,7 +1972,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/help_app/app',
-        'version': 'iTUrC08IXdr3eB2cSDBci-X0GlkLFUlGHp3ZFIv9YFQC',
+        'version': '3Qbtr1ROVR8xYc3CYHk5WdgZ3HQLPITOB2NICHFQMG8C',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/android_webview/browser/aw_permission_manager.cc b/android_webview/browser/aw_permission_manager.cc
index b317281..2629922 100644
--- a/android_webview/browser/aw_permission_manager.cc
+++ b/android_webview/browser/aw_permission_manager.cc
@@ -509,7 +509,7 @@
 }
 
 AwPermissionManager::SubscriptionId
-AwPermissionManager::SubscribePermissionStatusChange(
+AwPermissionManager::SubscribeToPermissionStatusChange(
     PermissionType permission,
     content::RenderProcessHost* render_process_host,
     content::RenderFrameHost* render_frame_host,
@@ -518,7 +518,7 @@
   return SubscriptionId();
 }
 
-void AwPermissionManager::UnsubscribePermissionStatusChange(
+void AwPermissionManager::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {}
 
 void AwPermissionManager::CancelPermissionRequest(int request_id) {
diff --git a/android_webview/browser/aw_permission_manager.h b/android_webview/browser/aw_permission_manager.h
index ac133ed..eb7f5a13 100644
--- a/android_webview/browser/aw_permission_manager.h
+++ b/android_webview/browser/aw_permission_manager.h
@@ -67,14 +67,14 @@
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host,
       const url::Origin& requesting_origin) override;
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback)
       override;
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
   void SetOriginCanReadEnumerateDevicesAudioLabels(const url::Origin& origin,
                                                    bool audio);
diff --git a/ash/accelerators/accelerator_commands.cc b/ash/accelerators/accelerator_commands.cc
index 27a27b4..2fb83e8 100644
--- a/ash/accelerators/accelerator_commands.cc
+++ b/ash/accelerators/accelerator_commands.cc
@@ -1326,7 +1326,7 @@
 
   DockedMagnifierController* docked_magnifier_controller =
       shell->docked_magnifier_controller();
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       shell->accessibility_controller();
 
   const bool current_enabled = docked_magnifier_controller->GetEnabled();
@@ -1394,7 +1394,7 @@
 
   FullscreenMagnifierController* magnification_controller =
       shell->fullscreen_magnifier_controller();
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       shell->accessibility_controller();
 
   const bool current_enabled = magnification_controller->IsEnabled();
@@ -1442,7 +1442,7 @@
     return;
   }
 
-  AccessibilityControllerImpl* controller = shell->accessibility_controller();
+  AccessibilityController* controller = shell->accessibility_controller();
   const bool current_enabled = controller->high_contrast().enabled();
   const bool dialog_ever_accepted =
       controller->high_contrast().WasDialogAccepted();
diff --git a/ash/accelerators/accelerator_controller_unittest.cc b/ash/accelerators/accelerator_controller_unittest.cc
index ecd76e4..a4701b0 100644
--- a/ash/accelerators/accelerator_controller_unittest.cc
+++ b/ash/accelerators/accelerator_controller_unittest.cc
@@ -993,7 +993,7 @@
   display::Display display = display::Screen::GetScreen()->GetPrimaryDisplay();
   display::Display::Rotation initial_rotation =
       GetActiveDisplayRotation(display.id());
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
 
   EXPECT_FALSE(accessibility_controller
@@ -1459,7 +1459,7 @@
 }
 
 TEST_F(AcceleratorControllerTest, GlobalAcceleratorsToggleAppList) {
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
 
   // The press event should not toggle the AppList, the release should instead.
@@ -2235,7 +2235,7 @@
 }
 
 TEST_F(AcceleratorControllerTest, DisallowedWithNoWindow) {
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityControllerClient client;
 
@@ -2294,7 +2294,7 @@
 TEST_F(AcceleratorControllerTest, TestDialogCancel) {
   ui::Accelerator accelerator(ui::VKEY_H,
                               ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN);
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   // Pressing cancel on the dialog should have no effect.
   EXPECT_FALSE(accessibility_controller->high_contrast().WasDialogAccepted());
@@ -2316,7 +2316,7 @@
                               ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN);
   // High Contrast Mode Enabled dialog and notification should be shown.
   EXPECT_FALSE(IsConfirmationDialogOpen());
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(accessibility_controller->high_contrast().WasDialogAccepted());
   EXPECT_TRUE(ProcessInController(accelerator));
@@ -2751,7 +2751,7 @@
   EXPECT_FALSE(fullscreen_magnifier_controller()->IsEnabled());
   EXPECT_FALSE(IsConfirmationDialogOpen());
 
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   // Toggle the fullscreen magnifier on/off, dialog should be shown on first use
   // of accelerator.
@@ -2799,7 +2799,7 @@
   EXPECT_FALSE(fullscreen_magnifier_controller()->IsEnabled());
   EXPECT_FALSE(IsConfirmationDialogOpen());
 
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   // Toggle the docked magnifier on/off, dialog should be shown on first use of
   // accelerator.
diff --git a/ash/accelerators/spoken_feedback_toggler.cc b/ash/accelerators/spoken_feedback_toggler.cc
index 3924a9fc..bf1bed8 100644
--- a/ash/accelerators/spoken_feedback_toggler.cc
+++ b/ash/accelerators/spoken_feedback_toggler.cc
@@ -52,7 +52,7 @@
 void SpokenFeedbackToggler::OnKeyHold(const ui::KeyEvent* event) {
   if (!toggled_) {
     toggled_ = true;
-    AccessibilityControllerImpl* controller =
+    AccessibilityController* controller =
         Shell::Get()->accessibility_controller();
     controller->SetSpokenFeedbackEnabled(
         !controller->spoken_feedback().enabled(), A11Y_NOTIFICATION_SHOW);
diff --git a/ash/accelerators/spoken_feedback_toggler_unittest.cc b/ash/accelerators/spoken_feedback_toggler_unittest.cc
index 548e848..791e196 100644
--- a/ash/accelerators/spoken_feedback_toggler_unittest.cc
+++ b/ash/accelerators/spoken_feedback_toggler_unittest.cc
@@ -17,7 +17,7 @@
 
 TEST_F(SpokenFeedbackTogglerTest, Basic) {
   SpokenFeedbackToggler::ScopedEnablerForTest scoped;
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   ui::test::EventGenerator* generator = GetEventGenerator();
   EXPECT_FALSE(controller->spoken_feedback().enabled());
diff --git a/ash/accessibility/accessibility_controller_impl.cc b/ash/accessibility/accessibility_controller_impl.cc
index ea61aed..996a8f2 100644
--- a/ash/accessibility/accessibility_controller_impl.cc
+++ b/ash/accessibility/accessibility_controller_impl.cc
@@ -31,7 +31,6 @@
 #include "ash/keyboard/ui/keyboard_util.h"
 #include "ash/login_status.h"
 #include "ash/policy/policy_recommendation_restorer.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "ash/public/cpp/accessibility_controller_client.h"
 #include "ash/public/cpp/ash_constants.h"
 #include "ash/public/cpp/notification_utils.h"
@@ -55,6 +54,7 @@
 #include "ash/system/power/backlights_forced_off_setter.h"
 #include "ash/system/power/power_status.h"
 #include "ash/system/power/scoped_backlights_forced_off.h"
+#include "base/check_op.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -90,6 +90,8 @@
 namespace ash {
 namespace {
 
+AccessibilityController* g_instance = nullptr;
+
 using FeatureType = A11yFeatureType;
 
 // These classes are used to store the static configuration for a11y features.
@@ -392,7 +394,7 @@
 }
 
 void ShowAccessibilityNotification(
-    const AccessibilityControllerImpl::A11yNotificationWrapper& wrapper) {
+    const AccessibilityController::A11yNotificationWrapper& wrapper) {
   A11yNotificationType type = wrapper.type;
   const auto& replacements = wrapper.replacements;
   message_center::MessageCenter* message_center =
@@ -498,7 +500,7 @@
 
 void RemoveAccessibilityNotification() {
   ShowAccessibilityNotification(
-      AccessibilityControllerImpl::A11yNotificationWrapper(
+      AccessibilityController::A11yNotificationWrapper(
           A11yNotificationType::kNone, std::vector<std::u16string>()));
 }
 
@@ -807,13 +809,13 @@
 
 }  // namespace
 
-AccessibilityControllerImpl::Feature::Feature(
+AccessibilityController::Feature::Feature(
     FeatureType type,
     const std::string& pref_name,
     const gfx::VectorIcon* icon,
     const int name_resource_id,
     const bool toggleable_in_quicksettings,
-    AccessibilityControllerImpl* controller)
+    AccessibilityController* controller)
     : type_(type),
       pref_name_(pref_name),
       icon_(icon),
@@ -827,9 +829,9 @@
   }
 }
 
-AccessibilityControllerImpl::Feature::~Feature() = default;
+AccessibilityController::Feature::~Feature() = default;
 
-void AccessibilityControllerImpl::Feature::SetEnabled(bool enabled) {
+void AccessibilityController::Feature::SetEnabled(bool enabled) {
   PrefService* prefs = owner_->active_user_prefs_;
   if (!prefs)
     return;
@@ -837,24 +839,24 @@
   prefs->CommitPendingWrite();
 }
 
-bool AccessibilityControllerImpl::Feature::IsVisibleInTray() const {
+bool AccessibilityController::Feature::IsVisibleInTray() const {
   return (conflicting_feature_ == FeatureType::kNoConflictingFeature ||
           !owner_->GetFeature(conflicting_feature_).enabled()) &&
          owner_->IsAccessibilityFeatureVisibleInTrayMenu(pref_name_);
 }
 
-bool AccessibilityControllerImpl::Feature::IsEnterpriseIconVisible() const {
+bool AccessibilityController::Feature::IsEnterpriseIconVisible() const {
   return owner_->IsEnterpriseIconVisibleInTrayMenu(pref_name_);
 }
 
-const gfx::VectorIcon& AccessibilityControllerImpl::Feature::icon() const {
+const gfx::VectorIcon& AccessibilityController::Feature::icon() const {
   DCHECK(icon_);
   if (icon_)
     return *icon_;
   return kPaletteTrayIconDefaultIcon;
 }
 
-void AccessibilityControllerImpl::Feature::UpdateFromPref() {
+void AccessibilityController::Feature::UpdateFromPref() {
   PrefService* prefs = owner_->active_user_prefs_;
   DCHECK(prefs);
 
@@ -872,13 +874,13 @@
   owner_->UpdateFeatureFromPref(type_);
 }
 
-void AccessibilityControllerImpl::Feature::SetConflictingFeature(
+void AccessibilityController::Feature::SetConflictingFeature(
     FeatureType feature) {
   DCHECK_EQ(conflicting_feature_, FeatureType::kNoConflictingFeature);
   conflicting_feature_ = feature;
 }
 
-void AccessibilityControllerImpl::Feature::ObserveConflictingFeature() {
+void AccessibilityController::Feature::ObserveConflictingFeature() {
   std::string conflicting_pref_name = "";
   switch (conflicting_feature_) {
     case A11yFeatureType::kSpokenFeedback:
@@ -894,28 +896,28 @@
   pref_change_registrar_->Init(owner_->active_user_prefs_);
   pref_change_registrar_->Add(
       conflicting_pref_name,
-      base::BindRepeating(&AccessibilityControllerImpl::Feature::UpdateFromPref,
+      base::BindRepeating(&AccessibilityController::Feature::UpdateFromPref,
                           base::Unretained(this)));
 }
 
-AccessibilityControllerImpl::FeatureWithDialog::FeatureWithDialog(
+AccessibilityController::FeatureWithDialog::FeatureWithDialog(
     FeatureType type,
     const std::string& pref_name,
     const gfx::VectorIcon* icon,
     const int name_resource_id,
     const bool toggleable_in_quicksettings,
     const Dialog& dialog,
-    AccessibilityControllerImpl* controller)
-    : AccessibilityControllerImpl::Feature(type,
-                                           pref_name,
-                                           icon,
-                                           name_resource_id,
-                                           toggleable_in_quicksettings,
-                                           controller),
+    AccessibilityController* controller)
+    : AccessibilityController::Feature(type,
+                                       pref_name,
+                                       icon,
+                                       name_resource_id,
+                                       toggleable_in_quicksettings,
+                                       controller),
       dialog_(dialog) {}
-AccessibilityControllerImpl::FeatureWithDialog::~FeatureWithDialog() = default;
+AccessibilityController::FeatureWithDialog::~FeatureWithDialog() = default;
 
-void AccessibilityControllerImpl::FeatureWithDialog::SetDialogAccepted() {
+void AccessibilityController::FeatureWithDialog::SetDialogAccepted() {
   PrefService* prefs = owner_->active_user_prefs_;
   if (!prefs)
     return;
@@ -923,13 +925,13 @@
   prefs->CommitPendingWrite();
 }
 
-bool AccessibilityControllerImpl::FeatureWithDialog::WasDialogAccepted() const {
+bool AccessibilityController::FeatureWithDialog::WasDialogAccepted() const {
   PrefService* prefs = owner_->active_user_prefs_;
   DCHECK(prefs);
   return prefs->GetBoolean(dialog_.pref_name);
 }
 
-void AccessibilityControllerImpl::FeatureWithDialog::SetEnabledWithDialog(
+void AccessibilityController::FeatureWithDialog::SetEnabledWithDialog(
     bool enabled,
     base::OnceClosure completion_callback) {
   PrefService* prefs = owner_->active_user_prefs_;
@@ -943,8 +945,8 @@
         l10n_util::GetStringUTF16(IDS_APP_CANCEL),
         // Callback for if the user accepts the dialog
         base::BindOnce(
-            [](base::WeakPtr<AccessibilityControllerImpl> owner,
-               FeatureType type, base::OnceClosure completion_callback) {
+            [](base::WeakPtr<AccessibilityController> owner, FeatureType type,
+               base::OnceClosure completion_callback) {
               if (!owner)
                 return;
 
@@ -965,15 +967,23 @@
   std::move(completion_callback).Run();
 }
 
-void AccessibilityControllerImpl::FeatureWithDialog::SetEnabled(bool enabled) {
+void AccessibilityController::FeatureWithDialog::SetEnabled(bool enabled) {
   if (dialog_.mandatory)
     SetEnabledWithDialog(enabled, base::DoNothing());
   else
     Feature::SetEnabled(enabled);
 }
 
-AccessibilityControllerImpl::AccessibilityControllerImpl()
+// static
+AccessibilityController* AccessibilityController::Get() {
+  return g_instance;
+}
+
+AccessibilityController::AccessibilityController()
     : autoclick_delay_(AutoclickController::GetDefaultAutoclickDelay()) {
+  DCHECK_EQ(nullptr, g_instance);
+  g_instance = this;
+
   Shell::Get()->session_controller()->AddObserver(this);
   display::Screen::GetScreen()->AddObserver(this);
   CreateAccessibilityFeatures();
@@ -982,12 +992,15 @@
       std::make_unique<AccessibilityNotificationController>();
 }
 
-AccessibilityControllerImpl::~AccessibilityControllerImpl() {
+AccessibilityController::~AccessibilityController() {
   floating_menu_controller_.reset();
   accessibility_notification_controller_.reset();
+
+  DCHECK_EQ(this, g_instance);
+  g_instance = nullptr;
 }
 
-void AccessibilityControllerImpl::CreateAccessibilityFeatures() {
+void AccessibilityController::CreateAccessibilityFeatures() {
   DCHECK(VerifyFeaturesData());
   // First, build all features with dialog.
   std::map<FeatureType, Dialog> dialogs;
@@ -1019,7 +1032,7 @@
 }
 
 // static
-void AccessibilityControllerImpl::RegisterProfilePrefs(
+void AccessibilityController::RegisterProfilePrefs(
     PrefRegistrySimple* registry) {
   //
   // Non-syncable prefs.
@@ -1282,7 +1295,7 @@
         user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
 }
 
-void AccessibilityControllerImpl::Shutdown() {
+void AccessibilityController::Shutdown() {
   display::Screen::GetScreen()->RemoveObserver(this);
   Shell::Get()->session_controller()->RemoveObserver(this);
 
@@ -1294,14 +1307,14 @@
     observer.OnAccessibilityControllerShutdown();
 }
 
-bool AccessibilityControllerImpl::
-    HasDisplayRotationAcceleratorDialogBeenAccepted() const {
+bool AccessibilityController::HasDisplayRotationAcceleratorDialogBeenAccepted()
+    const {
   return active_user_prefs_ &&
          active_user_prefs_->GetBoolean(
              prefs::kDisplayRotationAcceleratorDialogHasBeenAccepted2);
 }
 
-void AccessibilityControllerImpl::
+void AccessibilityController::
     SetDisplayRotationAcceleratorDialogBeenAccepted() {
   if (!active_user_prefs_)
     return;
@@ -1310,24 +1323,23 @@
   active_user_prefs_->CommitPendingWrite();
 }
 
-void AccessibilityControllerImpl::AddObserver(AccessibilityObserver* observer) {
+void AccessibilityController::AddObserver(AccessibilityObserver* observer) {
   observers_.AddObserver(observer);
 }
 
-void AccessibilityControllerImpl::RemoveObserver(
-    AccessibilityObserver* observer) {
+void AccessibilityController::RemoveObserver(AccessibilityObserver* observer) {
   observers_.RemoveObserver(observer);
 }
 
-AccessibilityControllerImpl::Feature& AccessibilityControllerImpl::GetFeature(
+AccessibilityController::Feature& AccessibilityController::GetFeature(
     FeatureType type) const {
   size_t feature_index = static_cast<size_t>(type);
   DCHECK(features_[feature_index].get());
   return *features_[feature_index].get();
 }
 
-std::vector<AccessibilityControllerImpl::Feature*>
-AccessibilityControllerImpl::GetEnabledFeaturesInQuickSettings() const {
+std::vector<AccessibilityController::Feature*>
+AccessibilityController::GetEnabledFeaturesInQuickSettings() const {
   std::vector<Feature*> enabled_features;
 
   for (auto& feature : features_) {
@@ -1338,123 +1350,117 @@
   return enabled_features;
 }
 
-base::WeakPtr<AccessibilityControllerImpl>
-AccessibilityControllerImpl::GetWeakPtr() {
+base::WeakPtr<AccessibilityController> AccessibilityController::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
 
-AccessibilityControllerImpl::Feature& AccessibilityControllerImpl::autoclick()
-    const {
+AccessibilityController::Feature& AccessibilityController::autoclick() const {
   return GetFeature(FeatureType::kAutoclick);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::caret_highlight() const {
+AccessibilityController::Feature& AccessibilityController::caret_highlight()
+    const {
   return GetFeature(FeatureType::kCaretHighlight);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::cursor_highlight() const {
+AccessibilityController::Feature& AccessibilityController::cursor_highlight()
+    const {
   return GetFeature(FeatureType::kCursorHighlight);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::cursor_color() const {
+AccessibilityController::Feature& AccessibilityController::cursor_color()
+    const {
   return GetFeature(FeatureType::kCursorColor);
 }
 
-AccessibilityControllerImpl::Feature& AccessibilityControllerImpl::dictation()
-    const {
+AccessibilityController::Feature& AccessibilityController::dictation() const {
   return GetFeature(FeatureType::kDictation);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::color_correction() const {
+AccessibilityController::Feature& AccessibilityController::color_correction()
+    const {
   return GetFeature(FeatureType::kColorCorrection);
 }
 
-AccessibilityControllerImpl::Feature& AccessibilityControllerImpl::face_gaze()
-    const {
+AccessibilityController::Feature& AccessibilityController::face_gaze() const {
   return GetFeature(FeatureType::kFaceGaze);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::focus_highlight() const {
+AccessibilityController::Feature& AccessibilityController::focus_highlight()
+    const {
   return GetFeature(FeatureType::kFocusHighlight);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::floating_menu() const {
+AccessibilityController::Feature& AccessibilityController::floating_menu()
+    const {
   return GetFeature(FeatureType::kFloatingMenu);
 }
 
-AccessibilityControllerImpl::FeatureWithDialog&
-AccessibilityControllerImpl::fullscreen_magnifier() const {
+AccessibilityController::FeatureWithDialog&
+AccessibilityController::fullscreen_magnifier() const {
   return static_cast<FeatureWithDialog&>(
       GetFeature(FeatureType::kFullscreenMagnifier));
 }
 
-AccessibilityControllerImpl::FeatureWithDialog&
-AccessibilityControllerImpl::docked_magnifier() const {
+AccessibilityController::FeatureWithDialog&
+AccessibilityController::docked_magnifier() const {
   return static_cast<FeatureWithDialog&>(
       GetFeature(FeatureType::kDockedMagnifier));
 }
 
-AccessibilityControllerImpl::FeatureWithDialog&
-AccessibilityControllerImpl::high_contrast() const {
+AccessibilityController::FeatureWithDialog&
+AccessibilityController::high_contrast() const {
   return static_cast<FeatureWithDialog&>(
       GetFeature(FeatureType::kHighContrast));
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::large_cursor() const {
+AccessibilityController::Feature& AccessibilityController::large_cursor()
+    const {
   return GetFeature(FeatureType::kLargeCursor);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::live_caption() const {
+AccessibilityController::Feature& AccessibilityController::live_caption()
+    const {
   return GetFeature(FeatureType::kLiveCaption);
 }
 
-AccessibilityControllerImpl::Feature& AccessibilityControllerImpl::mono_audio()
-    const {
+AccessibilityController::Feature& AccessibilityController::mono_audio() const {
   return GetFeature(FeatureType::kMonoAudio);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::spoken_feedback() const {
+AccessibilityController::Feature& AccessibilityController::spoken_feedback()
+    const {
   return GetFeature(FeatureType::kSpokenFeedback);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::select_to_speak() const {
+AccessibilityController::Feature& AccessibilityController::select_to_speak()
+    const {
   return GetFeature(FeatureType::kSelectToSpeak);
 }
 
-AccessibilityControllerImpl::Feature& AccessibilityControllerImpl::sticky_keys()
-    const {
+AccessibilityController::Feature& AccessibilityController::sticky_keys() const {
   return GetFeature(FeatureType::kStickyKeys);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::switch_access() const {
+AccessibilityController::Feature& AccessibilityController::switch_access()
+    const {
   return GetFeature(FeatureType::kSwitchAccess);
 }
 
-AccessibilityControllerImpl::Feature&
-AccessibilityControllerImpl::virtual_keyboard() const {
+AccessibilityController::Feature& AccessibilityController::virtual_keyboard()
+    const {
   return GetFeature(FeatureType::kVirtualKeyboard);
 }
 
-bool AccessibilityControllerImpl::IsAutoclickSettingVisibleInTray() {
+bool AccessibilityController::IsAutoclickSettingVisibleInTray() {
   return autoclick().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForAutoclick() {
+bool AccessibilityController::IsEnterpriseIconVisibleForAutoclick() {
   return autoclick().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsPrimarySettingsViewVisibleInTray() {
+bool AccessibilityController::IsPrimarySettingsViewVisibleInTray() {
   return (IsSpokenFeedbackSettingVisibleInTray() ||
           IsSelectToSpeakSettingVisibleInTray() ||
           IsDictationSettingVisibleInTray() ||
@@ -1469,72 +1475,71 @@
           IsLiveCaptionSettingVisibleInTray());
 }
 
-bool AccessibilityControllerImpl::IsCaretHighlightSettingVisibleInTray() {
+bool AccessibilityController::IsCaretHighlightSettingVisibleInTray() {
   return caret_highlight().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForCaretHighlight() {
+bool AccessibilityController::IsEnterpriseIconVisibleForCaretHighlight() {
   return caret_highlight().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsCursorHighlightSettingVisibleInTray() {
+bool AccessibilityController::IsCursorHighlightSettingVisibleInTray() {
   return cursor_highlight().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForCursorHighlight() {
+bool AccessibilityController::IsEnterpriseIconVisibleForCursorHighlight() {
   return cursor_highlight().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsDictationSettingVisibleInTray() {
+bool AccessibilityController::IsDictationSettingVisibleInTray() {
   return dictation().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForDictation() {
+bool AccessibilityController::IsEnterpriseIconVisibleForDictation() {
   return dictation().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsFaceGazeSettingVisibleInTray() {
+bool AccessibilityController::IsFaceGazeSettingVisibleInTray() {
   return face_gaze().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForFaceGaze() {
+bool AccessibilityController::IsEnterpriseIconVisibleForFaceGaze() {
   return face_gaze().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsFocusHighlightSettingVisibleInTray() {
+bool AccessibilityController::IsFocusHighlightSettingVisibleInTray() {
   return focus_highlight().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForFocusHighlight() {
+bool AccessibilityController::IsEnterpriseIconVisibleForFocusHighlight() {
   return focus_highlight().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsFullScreenMagnifierSettingVisibleInTray() {
+bool AccessibilityController::IsFullScreenMagnifierSettingVisibleInTray() {
   return fullscreen_magnifier().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::
-    IsEnterpriseIconVisibleForFullScreenMagnifier() {
+bool AccessibilityController::IsEnterpriseIconVisibleForFullScreenMagnifier() {
   return fullscreen_magnifier().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsDockedMagnifierSettingVisibleInTray() {
+bool AccessibilityController::IsDockedMagnifierSettingVisibleInTray() {
   return docked_magnifier().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForDockedMagnifier() {
+bool AccessibilityController::IsEnterpriseIconVisibleForDockedMagnifier() {
   return docked_magnifier().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsHighContrastSettingVisibleInTray() {
+bool AccessibilityController::IsHighContrastSettingVisibleInTray() {
   return high_contrast().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForHighContrast() {
+bool AccessibilityController::IsEnterpriseIconVisibleForHighContrast() {
   return high_contrast().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsColorCorrectionSettingVisibleInTray() {
+bool AccessibilityController::IsColorCorrectionSettingVisibleInTray() {
   if (!color_correction().enabled() &&
       Shell::Get()->session_controller()->login_status() ==
           ash::LoginStatus::NOT_LOGGED_IN) {
@@ -1545,41 +1550,41 @@
   return color_correction().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForColorCorrection() {
+bool AccessibilityController::IsEnterpriseIconVisibleForColorCorrection() {
   return color_correction().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsLargeCursorSettingVisibleInTray() {
+bool AccessibilityController::IsLargeCursorSettingVisibleInTray() {
   return large_cursor().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForLargeCursor() {
+bool AccessibilityController::IsEnterpriseIconVisibleForLargeCursor() {
   return large_cursor().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsLiveCaptionSettingVisibleInTray() {
+bool AccessibilityController::IsLiveCaptionSettingVisibleInTray() {
   return captions::IsLiveCaptionFeatureSupported() &&
          base::FeatureList::IsEnabled(
              media::kLiveCaptionSystemWideOnChromeOS) &&
          live_caption().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForLiveCaption() {
+bool AccessibilityController::IsEnterpriseIconVisibleForLiveCaption() {
   return captions::IsLiveCaptionFeatureSupported() &&
          base::FeatureList::IsEnabled(
              media::kLiveCaptionSystemWideOnChromeOS) &&
          live_caption().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsMonoAudioSettingVisibleInTray() {
+bool AccessibilityController::IsMonoAudioSettingVisibleInTray() {
   return mono_audio().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForMonoAudio() {
+bool AccessibilityController::IsEnterpriseIconVisibleForMonoAudio() {
   return mono_audio().IsEnterpriseIconVisible();
 }
 
-void AccessibilityControllerImpl::SetSpokenFeedbackEnabled(
+void AccessibilityController::SetSpokenFeedbackEnabled(
     bool enabled,
     AccessibilityNotificationVisibility notify) {
   spoken_feedback().SetEnabled(enabled);
@@ -1596,28 +1601,27 @@
       A11yNotificationWrapper(type, std::vector<std::u16string>()));
 }
 
-bool AccessibilityControllerImpl::IsSpokenFeedbackSettingVisibleInTray() {
+bool AccessibilityController::IsSpokenFeedbackSettingVisibleInTray() {
   return spoken_feedback().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForSpokenFeedback() {
+bool AccessibilityController::IsEnterpriseIconVisibleForSpokenFeedback() {
   return spoken_feedback().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsSelectToSpeakSettingVisibleInTray() {
+bool AccessibilityController::IsSelectToSpeakSettingVisibleInTray() {
   return select_to_speak().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForSelectToSpeak() {
+bool AccessibilityController::IsEnterpriseIconVisibleForSelectToSpeak() {
   return select_to_speak().IsEnterpriseIconVisible();
 }
 
-void AccessibilityControllerImpl::RequestSelectToSpeakStateChange() {
+void AccessibilityController::RequestSelectToSpeakStateChange() {
   client_->RequestSelectToSpeakStateChange();
 }
 
-void AccessibilityControllerImpl::SetSelectToSpeakState(
-    SelectToSpeakState state) {
+void AccessibilityController::SetSelectToSpeakState(SelectToSpeakState state) {
   select_to_speak_state_ = state;
 
   // Forward the state change event to select_to_speak_event_handler_.
@@ -1630,20 +1634,19 @@
   NotifyAccessibilityStatusChanged();
 }
 
-void AccessibilityControllerImpl::SetSelectToSpeakEventHandlerDelegate(
+void AccessibilityController::SetSelectToSpeakEventHandlerDelegate(
     SelectToSpeakEventHandlerDelegate* delegate) {
   select_to_speak_event_handler_delegate_ = delegate;
   MaybeCreateSelectToSpeakEventHandler();
 }
 
-SelectToSpeakState AccessibilityControllerImpl::GetSelectToSpeakState() const {
+SelectToSpeakState AccessibilityController::GetSelectToSpeakState() const {
   return select_to_speak_state_;
 }
 
-void AccessibilityControllerImpl::ShowSelectToSpeakPanel(
-    const gfx::Rect& anchor,
-    bool is_paused,
-    double speech_rate) {
+void AccessibilityController::ShowSelectToSpeakPanel(const gfx::Rect& anchor,
+                                                     bool is_paused,
+                                                     double speech_rate) {
   if (!select_to_speak_bubble_controller_) {
     select_to_speak_bubble_controller_ =
         std::make_unique<SelectToSpeakMenuBubbleController>();
@@ -1651,14 +1654,14 @@
   select_to_speak_bubble_controller_->Show(anchor, is_paused, speech_rate);
 }
 
-void AccessibilityControllerImpl::HideSelectToSpeakPanel() {
+void AccessibilityController::HideSelectToSpeakPanel() {
   if (!select_to_speak_bubble_controller_) {
     return;
   }
   select_to_speak_bubble_controller_->Hide();
 }
 
-void AccessibilityControllerImpl::OnSelectToSpeakPanelAction(
+void AccessibilityController::OnSelectToSpeakPanelAction(
     SelectToSpeakPanelAction action,
     double value) {
   if (!client_) {
@@ -1667,11 +1670,11 @@
   client_->OnSelectToSpeakPanelAction(action, value);
 }
 
-bool AccessibilityControllerImpl::IsSwitchAccessRunning() const {
+bool AccessibilityController::IsSwitchAccessRunning() const {
   return switch_access().enabled() || switch_access_disable_dialog_showing_;
 }
 
-bool AccessibilityControllerImpl::IsSwitchAccessSettingVisibleInTray() {
+bool AccessibilityController::IsSwitchAccessSettingVisibleInTray() {
   // Switch Access cannot be enabled on the sign-in page because there is no way
   // to configure switches while the device is locked.
   if (!switch_access().enabled() &&
@@ -1682,57 +1685,57 @@
   return switch_access().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForSwitchAccess() {
+bool AccessibilityController::IsEnterpriseIconVisibleForSwitchAccess() {
   return switch_access().IsEnterpriseIconVisible();
 }
 
-void AccessibilityControllerImpl::SetAccessibilityEventRewriter(
+void AccessibilityController::SetAccessibilityEventRewriter(
     AccessibilityEventRewriter* accessibility_event_rewriter) {
   accessibility_event_rewriter_ = accessibility_event_rewriter;
 }
 
-void AccessibilityControllerImpl::HideSwitchAccessBackButton() {
+void AccessibilityController::HideSwitchAccessBackButton() {
   if (IsSwitchAccessRunning())
     switch_access_bubble_controller_->HideBackButton();
 }
 
-void AccessibilityControllerImpl::HideSwitchAccessMenu() {
+void AccessibilityController::HideSwitchAccessMenu() {
   if (IsSwitchAccessRunning())
     switch_access_bubble_controller_->HideMenuBubble();
 }
 
-void AccessibilityControllerImpl::ShowSwitchAccessBackButton(
+void AccessibilityController::ShowSwitchAccessBackButton(
     const gfx::Rect& anchor) {
   switch_access_bubble_controller_->ShowBackButton(anchor);
 }
 
-void AccessibilityControllerImpl::ShowSwitchAccessMenu(
+void AccessibilityController::ShowSwitchAccessMenu(
     const gfx::Rect& anchor,
     std::vector<std::string> actions_to_show) {
   switch_access_bubble_controller_->ShowMenu(anchor, actions_to_show);
 }
 
-bool AccessibilityControllerImpl::IsPointScanEnabled() {
+bool AccessibilityController::IsPointScanEnabled() {
   return point_scan_controller_.get() &&
          point_scan_controller_->IsPointScanEnabled();
 }
 
-void AccessibilityControllerImpl::StartPointScan() {
+void AccessibilityController::StartPointScan() {
   point_scan_controller_->Start();
 }
 
-void AccessibilityControllerImpl::SetA11yOverrideWindow(
+void AccessibilityController::SetA11yOverrideWindow(
     aura::Window* a11y_override_window) {
   if (client_)
     client_->SetA11yOverrideWindow(a11y_override_window);
 }
 
-void AccessibilityControllerImpl::StopPointScan() {
+void AccessibilityController::StopPointScan() {
   if (point_scan_controller_)
     point_scan_controller_->HideAll();
 }
 
-void AccessibilityControllerImpl::SetPointScanSpeedDipsPerSecond(
+void AccessibilityController::SetPointScanSpeedDipsPerSecond(
     int point_scan_speed_dips_per_second) {
   if (point_scan_controller_) {
     point_scan_controller_->SetSpeedDipsPerSecond(
@@ -1740,28 +1743,27 @@
   }
 }
 
-void AccessibilityControllerImpl::
-    DisablePolicyRecommendationRestorerForTesting() {
+void AccessibilityController::DisablePolicyRecommendationRestorerForTesting() {
   Shell::Get()->policy_recommendation_restorer()->DisableForTesting();
 }
 
-bool AccessibilityControllerImpl::IsStickyKeysSettingVisibleInTray() {
+bool AccessibilityController::IsStickyKeysSettingVisibleInTray() {
   return sticky_keys().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForStickyKeys() {
+bool AccessibilityController::IsEnterpriseIconVisibleForStickyKeys() {
   return sticky_keys().IsEnterpriseIconVisible();
 }
 
-bool AccessibilityControllerImpl::IsVirtualKeyboardSettingVisibleInTray() {
+bool AccessibilityController::IsVirtualKeyboardSettingVisibleInTray() {
   return virtual_keyboard().IsVisibleInTray();
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleForVirtualKeyboard() {
+bool AccessibilityController::IsEnterpriseIconVisibleForVirtualKeyboard() {
   return virtual_keyboard().IsEnterpriseIconVisible();
 }
 
-void AccessibilityControllerImpl::ShowFloatingMenuIfEnabled() {
+void AccessibilityController::ShowFloatingMenuIfEnabled() {
   if (floating_menu().enabled() && !floating_menu_controller_) {
     floating_menu_controller_ =
         std::make_unique<FloatingAccessibilityController>(this);
@@ -1772,15 +1774,15 @@
 }
 
 FloatingAccessibilityController*
-AccessibilityControllerImpl::GetFloatingMenuController() {
+AccessibilityController::GetFloatingMenuController() {
   return floating_menu_controller_.get();
 }
 
-PointScanController* AccessibilityControllerImpl::GetPointScanController() {
+PointScanController* AccessibilityController::GetPointScanController() {
   return point_scan_controller_.get();
 }
 
-void AccessibilityControllerImpl::SetTabletModeShelfNavigationButtonsEnabled(
+void AccessibilityController::SetTabletModeShelfNavigationButtonsEnabled(
     bool enabled) {
   if (!active_user_prefs_)
     return;
@@ -1790,35 +1792,35 @@
   active_user_prefs_->CommitPendingWrite();
 }
 
-void AccessibilityControllerImpl::TriggerAccessibilityAlert(
+void AccessibilityController::TriggerAccessibilityAlert(
     AccessibilityAlert alert) {
   if (client_)
     client_->TriggerAccessibilityAlert(alert);
 }
 
-void AccessibilityControllerImpl::TriggerAccessibilityAlertWithMessage(
+void AccessibilityController::TriggerAccessibilityAlertWithMessage(
     const std::string& message) {
   if (client_)
     client_->TriggerAccessibilityAlertWithMessage(message);
 }
 
-void AccessibilityControllerImpl::PlayEarcon(Sound sound_key) {
+void AccessibilityController::PlayEarcon(Sound sound_key) {
   if (client_)
     client_->PlayEarcon(sound_key);
 }
 
-base::TimeDelta AccessibilityControllerImpl::PlayShutdownSound() {
+base::TimeDelta AccessibilityController::PlayShutdownSound() {
   return client_ ? client_->PlayShutdownSound() : base::TimeDelta();
 }
 
-void AccessibilityControllerImpl::HandleAccessibilityGesture(
+void AccessibilityController::HandleAccessibilityGesture(
     ax::mojom::Gesture gesture,
     gfx::PointF location) {
   if (client_)
     client_->HandleAccessibilityGesture(gesture, location);
 }
 
-void AccessibilityControllerImpl::ToggleDictation() {
+void AccessibilityController::ToggleDictation() {
   // Do nothing if dictation is not enabled.
   if (!dictation().enabled())
     return;
@@ -1833,11 +1835,11 @@
   }
 }
 
-void AccessibilityControllerImpl::SetDictationActive(bool is_active) {
+void AccessibilityController::SetDictationActive(bool is_active) {
   dictation_active_ = is_active;
 }
 
-void AccessibilityControllerImpl::ToggleDictationFromSource(
+void AccessibilityController::ToggleDictationFromSource(
     DictationToggleSource source) {
   base::RecordAction(base::UserMetricsAction("Accel_Toggle_Dictation"));
   UMA_HISTOGRAM_ENUMERATION("Accessibility.CrosDictation.ToggleDictationMethod",
@@ -1847,7 +1849,7 @@
   ToggleDictation();
 }
 
-void AccessibilityControllerImpl::EnableOrToggleDictationFromSource(
+void AccessibilityController::EnableOrToggleDictationFromSource(
     DictationToggleSource source) {
   if (::features::IsAccessibilityDictationKeyboardImprovementsEnabled()) {
     if (dictation().enabled()) {
@@ -1872,7 +1874,7 @@
   }
 }
 
-void AccessibilityControllerImpl::ShowDictationKeyboardDialog() {
+void AccessibilityController::ShowDictationKeyboardDialog() {
   if (!::features::IsAccessibilityDictationKeyboardImprovementsEnabled() ||
       !client_) {
     return;
@@ -1906,17 +1908,17 @@
   ShowConfirmationDialog(
       title, description, l10n_util::GetStringUTF16(IDS_APP_CANCEL),
       base::BindOnce(
-          &AccessibilityControllerImpl::OnDictationKeyboardDialogAccepted,
+          &AccessibilityController::OnDictationKeyboardDialogAccepted,
           GetWeakPtr()),
       base::BindOnce(
-          &AccessibilityControllerImpl::OnDictationKeyboardDialogDismissed,
+          &AccessibilityController::OnDictationKeyboardDialogDismissed,
           GetWeakPtr()),
       base::BindOnce(
-          &AccessibilityControllerImpl::OnDictationKeyboardDialogDismissed,
+          &AccessibilityController::OnDictationKeyboardDialogDismissed,
           GetWeakPtr()));
 }
 
-void AccessibilityControllerImpl::OnDictationKeyboardDialogAccepted() {
+void AccessibilityController::OnDictationKeyboardDialogAccepted() {
   if (!::features::IsAccessibilityDictationKeyboardImprovementsEnabled()) {
     return;
   }
@@ -1929,7 +1931,7 @@
   dictation().SetEnabled(true);
 }
 
-void AccessibilityControllerImpl::OnDictationKeyboardDialogDismissed() {
+void AccessibilityController::OnDictationKeyboardDialogDismissed() {
   if (!::features::IsAccessibilityDictationKeyboardImprovementsEnabled()) {
     return;
   }
@@ -1937,7 +1939,7 @@
   dictation_keyboard_dialog_showing_for_testing_ = false;
 }
 
-void AccessibilityControllerImpl::ShowDictationLanguageUpgradedNudge(
+void AccessibilityController::ShowDictationLanguageUpgradedNudge(
     const std::string& dictation_locale,
     const std::string& application_locale) {
   if (features::IsSystemNudgeMigrationEnabled()) {
@@ -1960,43 +1962,42 @@
   dictation_nudge_controller_->ShowNudge();
 }
 
-void AccessibilityControllerImpl::SilenceSpokenFeedback() {
+void AccessibilityController::SilenceSpokenFeedback() {
   if (client_)
     client_->SilenceSpokenFeedback();
 }
 
-void AccessibilityControllerImpl::OnTwoFingerTouchStart() {
+void AccessibilityController::OnTwoFingerTouchStart() {
   if (client_)
     client_->OnTwoFingerTouchStart();
 }
 
-void AccessibilityControllerImpl::OnTwoFingerTouchStop() {
+void AccessibilityController::OnTwoFingerTouchStop() {
   if (client_)
     client_->OnTwoFingerTouchStop();
 }
 
-bool AccessibilityControllerImpl::ShouldToggleSpokenFeedbackViaTouch() const {
+bool AccessibilityController::ShouldToggleSpokenFeedbackViaTouch() const {
   return client_ && client_->ShouldToggleSpokenFeedbackViaTouch();
 }
 
-void AccessibilityControllerImpl::PlaySpokenFeedbackToggleCountdown(
+void AccessibilityController::PlaySpokenFeedbackToggleCountdown(
     int tick_count) {
   if (client_)
     client_->PlaySpokenFeedbackToggleCountdown(tick_count);
 }
 
-bool AccessibilityControllerImpl::IsEnterpriseIconVisibleInTrayMenu(
+bool AccessibilityController::IsEnterpriseIconVisibleInTrayMenu(
     const std::string& path) {
   return active_user_prefs_ &&
          active_user_prefs_->FindPreference(path)->IsManaged();
 }
 
-void AccessibilityControllerImpl::SetClient(
-    AccessibilityControllerClient* client) {
+void AccessibilityController::SetClient(AccessibilityControllerClient* client) {
   client_ = client;
 }
 
-void AccessibilityControllerImpl::SetDarkenScreen(bool darken) {
+void AccessibilityController::SetDarkenScreen(bool darken) {
   if (darken && !scoped_backlights_forced_off_) {
     scoped_backlights_forced_off_ =
         Shell::Get()->backlights_forced_off_setter()->ForceBacklightsOff();
@@ -2005,7 +2006,7 @@
   }
 }
 
-void AccessibilityControllerImpl::BrailleDisplayStateChanged(bool connected) {
+void AccessibilityController::BrailleDisplayStateChanged(bool connected) {
   A11yNotificationType type = A11yNotificationType::kNone;
   if (connected && spoken_feedback().enabled())
     type = A11yNotificationType::kBrailleDisplayConnected;
@@ -2020,32 +2021,32 @@
       A11yNotificationWrapper(type, std::vector<std::u16string>()));
 }
 
-void AccessibilityControllerImpl::SetFocusHighlightRect(
+void AccessibilityController::SetFocusHighlightRect(
     const gfx::Rect& bounds_in_screen) {
   if (!accessibility_highlight_controller_)
     return;
   accessibility_highlight_controller_->SetFocusHighlightRect(bounds_in_screen);
 }
 
-void AccessibilityControllerImpl::SetCaretBounds(
+void AccessibilityController::SetCaretBounds(
     const gfx::Rect& bounds_in_screen) {
   if (!accessibility_highlight_controller_)
     return;
   accessibility_highlight_controller_->SetCaretBounds(bounds_in_screen);
 }
 
-void AccessibilityControllerImpl::SetAccessibilityPanelAlwaysVisible(
+void AccessibilityController::SetAccessibilityPanelAlwaysVisible(
     bool always_visible) {
   GetLayoutManager()->SetAlwaysVisible(always_visible);
 }
 
-void AccessibilityControllerImpl::SetAccessibilityPanelBounds(
+void AccessibilityController::SetAccessibilityPanelBounds(
     const gfx::Rect& bounds,
     AccessibilityPanelState state) {
   GetLayoutManager()->SetPanelBounds(bounds, state);
 }
 
-void AccessibilityControllerImpl::OnSigninScreenPrefServiceInitialized(
+void AccessibilityController::OnSigninScreenPrefServiceInitialized(
     PrefService* prefs) {
   // Make |kA11yPrefsForRecommendedValueOnSignin| observing recommended values
   // on signin screen. See PolicyRecommendationRestorer.
@@ -2058,7 +2059,7 @@
   ObservePrefs(prefs);
 }
 
-void AccessibilityControllerImpl::OnActiveUserPrefServiceChanged(
+void AccessibilityController::OnActiveUserPrefServiceChanged(
     PrefService* prefs) {
   // This is guaranteed to be received after
   // OnSigninScreenPrefServiceInitialized() so only copy the signin prefs if
@@ -2067,7 +2068,7 @@
   ObservePrefs(prefs);
 }
 
-void AccessibilityControllerImpl::OnSessionStateChanged(
+void AccessibilityController::OnSessionStateChanged(
     session_manager::SessionState state) {
   // Everything behind the lock screen is in
   // kShellWindowId_NonLockScreenContainersContainer. If the session state is
@@ -2084,21 +2085,20 @@
 }
 
 AccessibilityEventRewriter*
-AccessibilityControllerImpl::GetAccessibilityEventRewriterForTest() {
+AccessibilityController::GetAccessibilityEventRewriterForTest() {
   return accessibility_event_rewriter_;
 }
 
-void AccessibilityControllerImpl::
+void AccessibilityController::
     DisableSwitchAccessDisableConfirmationDialogTesting() {
   no_switch_access_disable_confirmation_dialog_for_testing_ = true;
 }
 
-void AccessibilityControllerImpl::
-    DisableSwitchAccessEnableNotificationTesting() {
+void AccessibilityController::DisableSwitchAccessEnableNotificationTesting() {
   skip_switch_access_notification_ = true;
 }
 
-void AccessibilityControllerImpl::OnDisplayTabletStateChanged(
+void AccessibilityController::OnDisplayTabletStateChanged(
     display::TabletState state) {
   if (spoken_feedback().enabled()) {
     // Show accessibility notification when tablet mode transition is completed.
@@ -2111,7 +2111,7 @@
   }
 }
 
-void AccessibilityControllerImpl::ObservePrefs(PrefService* prefs) {
+void AccessibilityController::ObservePrefs(PrefService* prefs) {
   DCHECK(prefs);
 
   active_user_prefs_ = prefs;
@@ -2125,9 +2125,8 @@
     DCHECK(feature);
     pref_change_registrar_->Add(
         feature->pref_name(),
-        base::BindRepeating(
-            &AccessibilityControllerImpl::Feature::UpdateFromPref,
-            base::Unretained(feature.get())));
+        base::BindRepeating(&AccessibilityController::Feature::UpdateFromPref,
+                            base::Unretained(feature.get())));
     if (feature->conflicting_feature() != FeatureType::kNoConflictingFeature) {
       feature->ObserveConflictingFeature();
     }
@@ -2137,102 +2136,100 @@
   pref_change_registrar_->Add(
       prefs::kAccessibilityAutoclickDelayMs,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateAutoclickDelayFromPref,
+          &AccessibilityController::UpdateAutoclickDelayFromPref,
           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityAutoclickEventType,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateAutoclickEventTypeFromPref,
+          &AccessibilityController::UpdateAutoclickEventTypeFromPref,
           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityAutoclickRevertToLeftClick,
-      base::BindRepeating(&AccessibilityControllerImpl::
-                              UpdateAutoclickRevertToLeftClickFromPref,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &AccessibilityController::UpdateAutoclickRevertToLeftClickFromPref,
+          base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityAutoclickStabilizePosition,
-      base::BindRepeating(&AccessibilityControllerImpl::
-                              UpdateAutoclickStabilizePositionFromPref,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &AccessibilityController::UpdateAutoclickStabilizePositionFromPref,
+          base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityAutoclickMovementThreshold,
-      base::BindRepeating(&AccessibilityControllerImpl::
-                              UpdateAutoclickMovementThresholdFromPref,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &AccessibilityController::UpdateAutoclickMovementThresholdFromPref,
+          base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityAutoclickMenuPosition,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateAutoclickMenuPositionFromPref,
+          &AccessibilityController::UpdateAutoclickMenuPositionFromPref,
           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityFloatingMenuPosition,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateFloatingMenuPositionFromPref,
+          &AccessibilityController::UpdateFloatingMenuPositionFromPref,
           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityLargeCursorDipSize,
-      base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateLargeCursorFromPref,
-          base::Unretained(this)));
+      base::BindRepeating(&AccessibilityController::UpdateLargeCursorFromPref,
+                          base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityShortcutsEnabled,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateShortcutsEnabledFromPref,
+          &AccessibilityController::UpdateShortcutsEnabledFromPref,
           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilitySwitchAccessSelectDeviceKeyCodes,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateSwitchAccessKeyCodesFromPref,
+          &AccessibilityController::UpdateSwitchAccessKeyCodesFromPref,
           base::Unretained(this), SwitchAccessCommand::kSelect));
   pref_change_registrar_->Add(
       prefs::kAccessibilitySwitchAccessNextDeviceKeyCodes,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateSwitchAccessKeyCodesFromPref,
+          &AccessibilityController::UpdateSwitchAccessKeyCodesFromPref,
           base::Unretained(this), SwitchAccessCommand::kNext));
   pref_change_registrar_->Add(
       prefs::kAccessibilitySwitchAccessPreviousDeviceKeyCodes,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateSwitchAccessKeyCodesFromPref,
+          &AccessibilityController::UpdateSwitchAccessKeyCodesFromPref,
           base::Unretained(this), SwitchAccessCommand::kPrevious));
   pref_change_registrar_->Add(
       prefs::kAccessibilitySwitchAccessAutoScanEnabled,
-      base::BindRepeating(&AccessibilityControllerImpl::
-                              UpdateSwitchAccessAutoScanEnabledFromPref,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &AccessibilityController::UpdateSwitchAccessAutoScanEnabledFromPref,
+          base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilitySwitchAccessAutoScanSpeedMs,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateSwitchAccessAutoScanSpeedFromPref,
+          &AccessibilityController::UpdateSwitchAccessAutoScanSpeedFromPref,
           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilitySwitchAccessAutoScanKeyboardSpeedMs,
-      base::BindRepeating(&AccessibilityControllerImpl::
+      base::BindRepeating(&AccessibilityController::
                               UpdateSwitchAccessAutoScanKeyboardSpeedFromPref,
                           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilitySwitchAccessPointScanSpeedDipsPerSecond,
-      base::BindRepeating(&AccessibilityControllerImpl::
-                              UpdateSwitchAccessPointScanSpeedFromPref,
-                          base::Unretained(this)));
+      base::BindRepeating(
+          &AccessibilityController::UpdateSwitchAccessPointScanSpeedFromPref,
+          base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled,
-      base::BindRepeating(&AccessibilityControllerImpl::
+      base::BindRepeating(&AccessibilityController::
                               UpdateTabletModeShelfNavigationButtonsFromPref,
                           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityCursorColor,
-      base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateCursorColorFromPrefs,
-          base::Unretained(this)));
+      base::BindRepeating(&AccessibilityController::UpdateCursorColorFromPrefs,
+                          base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityColorVisionCorrectionAmount,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateColorCorrectionFromPrefs,
+          &AccessibilityController::UpdateColorCorrectionFromPrefs,
           base::Unretained(this)));
   pref_change_registrar_->Add(
       prefs::kAccessibilityColorVisionCorrectionType,
       base::BindRepeating(
-          &AccessibilityControllerImpl::UpdateColorCorrectionFromPrefs,
+          &AccessibilityController::UpdateColorCorrectionFromPrefs,
           base::Unretained(this)));
 
   // Load current state.
@@ -2258,7 +2255,7 @@
   }
 }
 
-void AccessibilityControllerImpl::UpdateAutoclickDelayFromPref() {
+void AccessibilityController::UpdateAutoclickDelayFromPref() {
   DCHECK(active_user_prefs_);
   base::TimeDelta autoclick_delay = base::Milliseconds(int64_t{
       active_user_prefs_->GetInteger(prefs::kAccessibilityAutoclickDelayMs)});
@@ -2270,12 +2267,12 @@
   Shell::Get()->autoclick_controller()->SetAutoclickDelay(autoclick_delay_);
 }
 
-void AccessibilityControllerImpl::UpdateAutoclickEventTypeFromPref() {
+void AccessibilityController::UpdateAutoclickEventTypeFromPref() {
   Shell::Get()->autoclick_controller()->SetAutoclickEventType(
       GetAutoclickEventType());
 }
 
-void AccessibilityControllerImpl::SetAutoclickEventType(
+void AccessibilityController::SetAutoclickEventType(
     AutoclickEventType event_type) {
   if (!active_user_prefs_)
     return;
@@ -2285,13 +2282,13 @@
   Shell::Get()->autoclick_controller()->SetAutoclickEventType(event_type);
 }
 
-AutoclickEventType AccessibilityControllerImpl::GetAutoclickEventType() {
+AutoclickEventType AccessibilityController::GetAutoclickEventType() {
   DCHECK(active_user_prefs_);
   return static_cast<AutoclickEventType>(
       active_user_prefs_->GetInteger(prefs::kAccessibilityAutoclickEventType));
 }
 
-void AccessibilityControllerImpl::UpdateAutoclickRevertToLeftClickFromPref() {
+void AccessibilityController::UpdateAutoclickRevertToLeftClickFromPref() {
   DCHECK(active_user_prefs_);
   bool revert_to_left_click = active_user_prefs_->GetBoolean(
       prefs::kAccessibilityAutoclickRevertToLeftClick);
@@ -2300,7 +2297,7 @@
       revert_to_left_click);
 }
 
-void AccessibilityControllerImpl::UpdateAutoclickStabilizePositionFromPref() {
+void AccessibilityController::UpdateAutoclickStabilizePositionFromPref() {
   DCHECK(active_user_prefs_);
   bool stabilize_position = active_user_prefs_->GetBoolean(
       prefs::kAccessibilityAutoclickStabilizePosition);
@@ -2309,7 +2306,7 @@
       stabilize_position);
 }
 
-void AccessibilityControllerImpl::UpdateAutoclickMovementThresholdFromPref() {
+void AccessibilityController::UpdateAutoclickMovementThresholdFromPref() {
   DCHECK(active_user_prefs_);
   int movement_threshold = active_user_prefs_->GetInteger(
       prefs::kAccessibilityAutoclickMovementThreshold);
@@ -2318,12 +2315,12 @@
       movement_threshold);
 }
 
-void AccessibilityControllerImpl::UpdateAutoclickMenuPositionFromPref() {
+void AccessibilityController::UpdateAutoclickMenuPositionFromPref() {
   Shell::Get()->autoclick_controller()->SetMenuPosition(
       GetAutoclickMenuPosition());
 }
 
-void AccessibilityControllerImpl::SetAutoclickMenuPosition(
+void AccessibilityController::SetAutoclickMenuPosition(
     FloatingMenuPosition position) {
   if (!active_user_prefs_)
     return;
@@ -2333,25 +2330,25 @@
   Shell::Get()->autoclick_controller()->SetMenuPosition(position);
 }
 
-FloatingMenuPosition AccessibilityControllerImpl::GetAutoclickMenuPosition() {
+FloatingMenuPosition AccessibilityController::GetAutoclickMenuPosition() {
   DCHECK(active_user_prefs_);
   return static_cast<FloatingMenuPosition>(active_user_prefs_->GetInteger(
       prefs::kAccessibilityAutoclickMenuPosition));
 }
 
-void AccessibilityControllerImpl::RequestAutoclickScrollableBoundsForPoint(
+void AccessibilityController::RequestAutoclickScrollableBoundsForPoint(
     const gfx::Point& point_in_screen) {
   if (client_)
     client_->RequestAutoclickScrollableBoundsForPoint(point_in_screen);
 }
 
-void AccessibilityControllerImpl::MagnifierBoundsChanged(
+void AccessibilityController::MagnifierBoundsChanged(
     const gfx::Rect& bounds_in_screen) {
   if (client_)
     client_->MagnifierBoundsChanged(bounds_in_screen);
 }
 
-void AccessibilityControllerImpl::UpdateFloatingPanelBoundsIfNeeded() {
+void AccessibilityController::UpdateFloatingPanelBoundsIfNeeded() {
   Shell* shell = Shell::Get();
   if (shell->accessibility_controller()->autoclick().enabled())
     shell->autoclick_controller()->UpdateAutoclickMenuBoundsIfNeeded();
@@ -2359,17 +2356,17 @@
     shell->sticky_keys_controller()->UpdateStickyKeysOverlayBoundsIfNeeded();
 }
 
-void AccessibilityControllerImpl::UpdateAutoclickMenuBoundsIfNeeded() {
+void AccessibilityController::UpdateAutoclickMenuBoundsIfNeeded() {
   Shell::Get()->autoclick_controller()->UpdateAutoclickMenuBoundsIfNeeded();
 }
 
-void AccessibilityControllerImpl::HandleAutoclickScrollableBoundsFound(
+void AccessibilityController::HandleAutoclickScrollableBoundsFound(
     const gfx::Rect& bounds_in_screen) {
   Shell::Get()->autoclick_controller()->HandleAutoclickScrollableBoundsFound(
       bounds_in_screen);
 }
 
-void AccessibilityControllerImpl::SetFloatingMenuPosition(
+void AccessibilityController::SetFloatingMenuPosition(
     FloatingMenuPosition position) {
   if (!active_user_prefs_)
     return;
@@ -2378,18 +2375,18 @@
   active_user_prefs_->CommitPendingWrite();
 }
 
-void AccessibilityControllerImpl::UpdateFloatingMenuPositionFromPref() {
+void AccessibilityController::UpdateFloatingMenuPositionFromPref() {
   if (floating_menu_controller_)
     floating_menu_controller_->SetMenuPosition(GetFloatingMenuPosition());
 }
 
-FloatingMenuPosition AccessibilityControllerImpl::GetFloatingMenuPosition() {
+FloatingMenuPosition AccessibilityController::GetFloatingMenuPosition() {
   DCHECK(active_user_prefs_);
   return static_cast<FloatingMenuPosition>(active_user_prefs_->GetInteger(
       prefs::kAccessibilityFloatingMenuPosition));
 }
 
-void AccessibilityControllerImpl::UpdateLargeCursorFromPref() {
+void AccessibilityController::UpdateLargeCursorFromPref() {
   DCHECK(active_user_prefs_);
   const bool enabled =
       active_user_prefs_->GetBoolean(prefs::kAccessibilityLargeCursorEnabled);
@@ -2414,7 +2411,7 @@
   shell->UpdateCursorCompositingEnabled();
 }
 
-void AccessibilityControllerImpl::UpdateCursorColorFromPrefs() {
+void AccessibilityController::UpdateCursorColorFromPrefs() {
   DCHECK(active_user_prefs_);
 
   const bool enabled =
@@ -2427,14 +2424,14 @@
   shell->UpdateCursorCompositingEnabled();
 }
 
-void AccessibilityControllerImpl::UpdateFaceGazeFromPrefs() {
+void AccessibilityController::UpdateFaceGazeFromPrefs() {
   if (!::features::IsAccessibilityFaceGazeEnabled()) {
     return;
   }
   // TODO(b/309121742): Start getting camera data.
 }
 
-void AccessibilityControllerImpl::UpdateColorCorrectionFromPrefs() {
+void AccessibilityController::UpdateColorCorrectionFromPrefs() {
   DCHECK(active_user_prefs_);
 
   auto* color_enhancement_controller =
@@ -2462,7 +2459,7 @@
       true);
 }
 
-void AccessibilityControllerImpl::UpdateAccessibilityHighlightingFromPrefs() {
+void AccessibilityController::UpdateAccessibilityHighlightingFromPrefs() {
   if (!caret_highlight().enabled() && !cursor_highlight().enabled() &&
       !focus_highlight().enabled()) {
     accessibility_highlight_controller_.reset();
@@ -2482,7 +2479,7 @@
       focus_highlight().enabled());
 }
 
-void AccessibilityControllerImpl::MaybeCreateSelectToSpeakEventHandler() {
+void AccessibilityController::MaybeCreateSelectToSpeakEventHandler() {
   // Construct the handler as needed when Select-to-Speak is enabled and the
   // delegate is set. Otherwise, destroy the handler when Select-to-Speak is
   // disabled or the delegate has been destroyed.
@@ -2499,7 +2496,7 @@
       select_to_speak_event_handler_delegate_);
 }
 
-void AccessibilityControllerImpl::UpdateSwitchAccessKeyCodesFromPref(
+void AccessibilityController::UpdateSwitchAccessKeyCodesFromPref(
     SwitchAccessCommand command) {
   if (!active_user_prefs_)
     return;
@@ -2541,7 +2538,7 @@
                                                                    command);
 }
 
-void AccessibilityControllerImpl::UpdateSwitchAccessAutoScanEnabledFromPref() {
+void AccessibilityController::UpdateSwitchAccessAutoScanEnabledFromPref() {
   DCHECK(active_user_prefs_);
   const bool enabled = active_user_prefs_->GetBoolean(
       prefs::kAccessibilitySwitchAccessAutoScanEnabled);
@@ -2550,7 +2547,7 @@
   SyncSwitchAccessPrefsToSignInProfile();
 }
 
-void AccessibilityControllerImpl::UpdateSwitchAccessAutoScanSpeedFromPref() {
+void AccessibilityController::UpdateSwitchAccessAutoScanSpeedFromPref() {
   DCHECK(active_user_prefs_);
   const int speed_ms = active_user_prefs_->GetInteger(
       prefs::kAccessibilitySwitchAccessAutoScanSpeedMs);
@@ -2561,7 +2558,7 @@
   SyncSwitchAccessPrefsToSignInProfile();
 }
 
-void AccessibilityControllerImpl::
+void AccessibilityController::
     UpdateSwitchAccessAutoScanKeyboardSpeedFromPref() {
   DCHECK(active_user_prefs_);
   const int speed_ms = active_user_prefs_->GetInteger(
@@ -2573,7 +2570,7 @@
   SyncSwitchAccessPrefsToSignInProfile();
 }
 
-void AccessibilityControllerImpl::UpdateSwitchAccessPointScanSpeedFromPref() {
+void AccessibilityController::UpdateSwitchAccessPointScanSpeedFromPref() {
   // TODO(accessibility): Log histogram for point scan speed
   DCHECK(active_user_prefs_);
   const int point_scan_speed_dips_per_second = active_user_prefs_->GetInteger(
@@ -2583,7 +2580,7 @@
   SyncSwitchAccessPrefsToSignInProfile();
 }
 
-void AccessibilityControllerImpl::SwitchAccessDisableDialogClosed(
+void AccessibilityController::SwitchAccessDisableDialogClosed(
     bool disable_dialog_accepted) {
   switch_access_disable_dialog_showing_ = false;
   // Always deactivate switch access. Turning switch access off ensures it is
@@ -2603,13 +2600,13 @@
   }
 }
 
-void AccessibilityControllerImpl::UpdateKeyCodesAfterSwitchAccessEnabled() {
+void AccessibilityController::UpdateKeyCodesAfterSwitchAccessEnabled() {
   UpdateSwitchAccessKeyCodesFromPref(SwitchAccessCommand::kSelect);
   UpdateSwitchAccessKeyCodesFromPref(SwitchAccessCommand::kNext);
   UpdateSwitchAccessKeyCodesFromPref(SwitchAccessCommand::kPrevious);
 }
 
-void AccessibilityControllerImpl::ActivateSwitchAccess() {
+void AccessibilityController::ActivateSwitchAccess() {
   switch_access_bubble_controller_ =
       std::make_unique<SwitchAccessMenuBubbleController>();
   point_scan_controller_ = std::make_unique<PointScanController>();
@@ -2625,14 +2622,14 @@
                               std::vector<std::u16string>()));
 }
 
-void AccessibilityControllerImpl::DeactivateSwitchAccess() {
+void AccessibilityController::DeactivateSwitchAccess() {
   if (client_)
     client_->OnSwitchAccessDisabled();
   point_scan_controller_.reset();
   switch_access_bubble_controller_.reset();
 }
 
-void AccessibilityControllerImpl::SyncSwitchAccessPrefsToSignInProfile() {
+void AccessibilityController::SyncSwitchAccessPrefsToSignInProfile() {
   if (!active_user_prefs_ || IsSigninPrefService(active_user_prefs_))
     return;
 
@@ -2654,7 +2651,7 @@
   }
 }
 
-void AccessibilityControllerImpl::UpdateShortcutsEnabledFromPref() {
+void AccessibilityController::UpdateShortcutsEnabledFromPref() {
   DCHECK(active_user_prefs_);
   const bool enabled =
       active_user_prefs_->GetBoolean(prefs::kAccessibilityShortcutsEnabled);
@@ -2667,8 +2664,7 @@
   NotifyAccessibilityStatusChanged();
 }
 
-void AccessibilityControllerImpl::
-    UpdateTabletModeShelfNavigationButtonsFromPref() {
+void AccessibilityController::UpdateTabletModeShelfNavigationButtonsFromPref() {
   DCHECK(active_user_prefs_);
   const bool enabled = active_user_prefs_->GetBoolean(
       prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled);
@@ -2681,31 +2677,31 @@
   NotifyAccessibilityStatusChanged();
 }
 
-std::u16string AccessibilityControllerImpl::GetBatteryDescription() const {
+std::u16string AccessibilityController::GetBatteryDescription() const {
   // Pass battery status as string to callback function.
   return PowerStatus::Get()->GetAccessibleNameString(
       /*full_description=*/true);
 }
 
-void AccessibilityControllerImpl::SetVirtualKeyboardVisible(bool is_visible) {
+void AccessibilityController::SetVirtualKeyboardVisible(bool is_visible) {
   if (is_visible)
     Shell::Get()->keyboard_controller()->ShowKeyboard();
   else
     Shell::Get()->keyboard_controller()->HideKeyboard(HideReason::kUser);
 }
 
-void AccessibilityControllerImpl::PerformAcceleratorAction(
+void AccessibilityController::PerformAcceleratorAction(
     AcceleratorAction accelerator_action) {
   AcceleratorController::Get()->PerformActionIfEnabled(accelerator_action,
                                                        /* accelerator = */ {});
 }
 
-void AccessibilityControllerImpl::NotifyAccessibilityStatusChanged() {
+void AccessibilityController::NotifyAccessibilityStatusChanged() {
   for (auto& observer : observers_)
     observer.OnAccessibilityStatusChanged();
 }
 
-bool AccessibilityControllerImpl::IsAccessibilityFeatureVisibleInTrayMenu(
+bool AccessibilityController::IsAccessibilityFeatureVisibleInTrayMenu(
     const std::string& path) {
   if (!active_user_prefs_)
     return true;
@@ -2716,16 +2712,16 @@
   return true;
 }
 
-void AccessibilityControllerImpl::SuspendSwitchAccessKeyHandling(bool suspend) {
+void AccessibilityController::SuspendSwitchAccessKeyHandling(bool suspend) {
   accessibility_event_rewriter_->set_suspend_switch_access_key_handling(
       suspend);
 }
 
-void AccessibilityControllerImpl::EnableChromeVoxVolumeSlideGesture() {
+void AccessibilityController::EnableChromeVoxVolumeSlideGesture() {
   enable_chromevox_volume_slide_gesture_ = true;
 }
 
-void AccessibilityControllerImpl::ShowConfirmationDialog(
+void AccessibilityController::ShowConfirmationDialog(
     const std::u16string& title,
     const std::u16string& description,
     const std::u16string& cancel_name,
@@ -2751,7 +2747,7 @@
   }
 }
 
-void AccessibilityControllerImpl::
+void AccessibilityController::
     UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
         int download_progress) {
   dictation_soda_download_progress_ = download_progress;
@@ -2762,7 +2758,7 @@
       ->UpdateOnSpeechRecognitionDownloadChanged(download_progress);
 }
 
-void AccessibilityControllerImpl::ShowNotificationForDictation(
+void AccessibilityController::ShowNotificationForDictation(
     DictationNotificationType type,
     const std::u16string& display_language) {
   A11yNotificationType notification_type;
@@ -2785,18 +2781,18 @@
       notification_type, std::vector<std::u16string>{display_language}));
 }
 
-AccessibilityControllerImpl::A11yNotificationWrapper::
-    A11yNotificationWrapper() = default;
-AccessibilityControllerImpl::A11yNotificationWrapper::A11yNotificationWrapper(
+AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper() =
+    default;
+AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper(
     A11yNotificationType type_in,
     std::vector<std::u16string> replacements_in)
     : type(type_in), replacements(replacements_in) {}
-AccessibilityControllerImpl::A11yNotificationWrapper::
-    ~A11yNotificationWrapper() = default;
-AccessibilityControllerImpl::A11yNotificationWrapper::A11yNotificationWrapper(
+AccessibilityController::A11yNotificationWrapper::~A11yNotificationWrapper() =
+    default;
+AccessibilityController::A11yNotificationWrapper::A11yNotificationWrapper(
     const A11yNotificationWrapper&) = default;
 
-void AccessibilityControllerImpl::UpdateFeatureFromPref(FeatureType feature) {
+void AccessibilityController::UpdateFeatureFromPref(FeatureType feature) {
   size_t feature_index = static_cast<size_t>(feature);
   bool enabled = features_[feature_index]->enabled();
   bool is_managed = active_user_prefs_->IsManagedPreference(
@@ -2887,10 +2883,10 @@
           new AccessibilityFeatureDisableDialog(
               IDS_ASH_SWITCH_ACCESS_DISABLE_CONFIRMATION_TEXT,
               base::BindOnce(
-                  &AccessibilityControllerImpl::SwitchAccessDisableDialogClosed,
+                  &AccessibilityController::SwitchAccessDisableDialogClosed,
                   weak_ptr_factory_.GetWeakPtr(), true),
               base::BindOnce(
-                  &AccessibilityControllerImpl::SwitchAccessDisableDialogClosed,
+                  &AccessibilityController::SwitchAccessDisableDialogClosed,
                   weak_ptr_factory_.GetWeakPtr(), false));
           switch_access_disable_dialog_showing_ = true;
         }
@@ -2930,7 +2926,7 @@
   NotifyAccessibilityStatusChanged();
 }
 
-void AccessibilityControllerImpl::UpdateDictationBubble(
+void AccessibilityController::UpdateDictationBubble(
     bool visible,
     DictationBubbleIconType icon,
     const std::optional<std::u16string>& text,
@@ -2942,7 +2938,7 @@
 }
 
 DictationBubbleController*
-AccessibilityControllerImpl::GetDictationBubbleControllerForTest() {
+AccessibilityController::GetDictationBubbleControllerForTest() {
   if (!dictation_bubble_controller_) {
     dictation_bubble_controller_ =
         std::make_unique<DictationBubbleController>();
@@ -2951,17 +2947,17 @@
   return dictation_bubble_controller_.get();
 }
 
-void AccessibilityControllerImpl::ShowToast(AccessibilityToastType type) {
+void AccessibilityController::ShowToast(AccessibilityToastType type) {
   accessibility_notification_controller_->ShowToast(type);
 }
 
-void AccessibilityControllerImpl::AddShowToastCallbackForTesting(
+void AccessibilityController::AddShowToastCallbackForTesting(
     base::RepeatingCallback<void(AccessibilityToastType)> callback) {
   accessibility_notification_controller_->AddShowToastCallbackForTesting(
       std::move(callback));
 }
 
-void AccessibilityControllerImpl::AddShowConfirmationDialogCallbackForTesting(
+void AccessibilityController::AddShowConfirmationDialogCallbackForTesting(
     base::RepeatingCallback<void()> callback) {
   show_confirmation_dialog_callback_for_testing_ = std::move(callback);
 }
diff --git a/ash/accessibility/accessibility_controller_impl.h b/ash/accessibility/accessibility_controller_impl.h
index 56d9910..9ec7d30 100644
--- a/ash/accessibility/accessibility_controller_impl.h
+++ b/ash/accessibility/accessibility_controller_impl.h
@@ -6,12 +6,13 @@
 #define ASH_ACCESSIBILITY_ACCESSIBILITY_CONTROLLER_IMPL_H_
 
 #include <memory>
+#include <optional>
 
 #include "ash/accessibility/a11y_feature_type.h"
 #include "ash/accessibility/accessibility_notification_controller.h"
 #include "ash/ash_export.h"
 #include "ash/constants/ash_constants.h"
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/public/cpp/accelerator_actions.h"
 #include "ash/public/cpp/session/session_observer.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
@@ -19,6 +20,7 @@
 #include "base/observer_list.h"
 #include "base/time/time.h"
 #include "ui/display/display_observer.h"
+#include "ui/gfx/geometry/rect.h"
 
 class PrefChangeRegistrar;
 class PrefRegistrySimple;
@@ -47,18 +49,26 @@
 namespace ash {
 
 class AccessibilityConfirmationDialog;
+class AccessibilityControllerClient;
 class AccessibilityEventRewriter;
 class AccessibilityHighlightController;
 class AccessibilityObserver;
+enum class AccessibilityPanelState;
+enum class DictationToggleSource;
 class DictationBubbleController;
+enum class DictationBubbleHintType;
+enum class DictationBubbleIconType;
+enum class DictationNotificationType;
 class DictationNudgeController;
 class FloatingAccessibilityController;
 class PointScanController;
 class ScopedBacklightsForcedOff;
 class SelectToSpeakEventHandler;
+class SelectToSpeakEventHandlerDelegate;
 class SelectToSpeakMenuBubbleController;
-class SwitchAccessMenuBubbleController;
+enum class SelectToSpeakState;
 enum class Sound;
+class SwitchAccessMenuBubbleController;
 
 enum AccessibilityNotificationVisibility {
   A11Y_NOTIFICATION_NONE,
@@ -92,9 +102,8 @@
 // The controller for accessibility features in ash. Features can be enabled
 // in chrome's webui settings or the system tray menu (see TrayAccessibility).
 // Uses preferences to communicate with chrome to support mash.
-class ASH_EXPORT AccessibilityControllerImpl : public AccessibilityController,
-                                               public SessionObserver,
-                                               public display::DisplayObserver {
+class ASH_EXPORT AccessibilityController : public SessionObserver,
+                                           public display::DisplayObserver {
  public:
   // Common interface for all features.
   class Feature {
@@ -104,7 +113,7 @@
             const gfx::VectorIcon* icon,
             const int name_resource_id,
             const bool toggleable_in_quicksettings,
-            AccessibilityControllerImpl* controller);
+            AccessibilityController* controller);
     Feature(const Feature&) = delete;
     Feature& operator=(Feature const&) = delete;
     virtual ~Feature();
@@ -153,7 +162,7 @@
     // available in the quicksettings menu.
     const bool toggleable_in_quicksettings_;
 
-    const raw_ptr<AccessibilityControllerImpl, ExperimentalAsh> owner_;
+    const raw_ptr<AccessibilityController, ExperimentalAsh> owner_;
   };
 
   // Helper struct to store information about a11y dialog -- pref name, resource
@@ -178,7 +187,7 @@
                       const int name_resource_id,
                       const bool toggleable_in_quicksettings,
                       const Dialog& dialog,
-                      AccessibilityControllerImpl* controller);
+                      AccessibilityController* controller);
     ~FeatureWithDialog() override;
 
     // Tries to set the feature enabled, if its dialog is mandatory, shows the
@@ -207,13 +216,14 @@
     std::vector<std::u16string> replacements;
   };
 
-  AccessibilityControllerImpl();
+  static AccessibilityController* Get();
 
-  AccessibilityControllerImpl(const AccessibilityControllerImpl&) = delete;
-  AccessibilityControllerImpl& operator=(const AccessibilityControllerImpl&) =
-      delete;
+  AccessibilityController();
 
-  ~AccessibilityControllerImpl() override;
+  AccessibilityController(const AccessibilityController&) = delete;
+  AccessibilityController& operator=(const AccessibilityController&) = delete;
+
+  ~AccessibilityController() override;
 
   // See Shell::RegisterProfilePrefs().
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
@@ -229,7 +239,7 @@
   // enabled.
   std::vector<Feature*> GetEnabledFeaturesInQuickSettings() const;
 
-  base::WeakPtr<AccessibilityControllerImpl> GetWeakPtr();
+  base::WeakPtr<AccessibilityController> GetWeakPtr();
 
   // Getters for the corresponding features.
   Feature& autoclick() const;
@@ -350,7 +360,8 @@
     return tablet_mode_shelf_navigation_buttons_enabled_;
   }
 
-  void ShowFloatingMenuIfEnabled() override;
+  // Shows floating accessibility menu if it was enabled by policy.
+  void ShowFloatingMenuIfEnabled();
 
   bool dictation_active() const { return dictation_active_; }
 
@@ -415,64 +426,155 @@
   // set the a11y override window back to null.
   void SetA11yOverrideWindow(aura::Window* a11y_override_window);
 
-  // AccessibilityController:
-  void SetClient(AccessibilityControllerClient* client) override;
-  void SetDarkenScreen(bool darken) override;
-  void BrailleDisplayStateChanged(bool connected) override;
-  void SetFocusHighlightRect(const gfx::Rect& bounds_in_screen) override;
-  void SetCaretBounds(const gfx::Rect& bounds_in_screen) override;
-  void SetAccessibilityPanelAlwaysVisible(bool always_visible) override;
+  // Sets the client interface.
+  void SetClient(AccessibilityControllerClient* client);
+
+  // Starts or stops darkening the screen (e.g. to allow chrome a11y extensions
+  // to darken the screen).
+  void SetDarkenScreen(bool darken);
+
+  // Called when braille display state is changed.
+  void BrailleDisplayStateChanged(bool connected);
+
+  // Sets the focus highlight rect using |bounds_in_screen|. Called when focus
+  // changed in page and a11y focus highlight feature is enabled.
+  void SetFocusHighlightRect(const gfx::Rect& bounds_in_screen);
+
+  // Sets the text input caret bounds used to draw the caret highlight effect.
+  // For effciency, only sent when the caret highlight feature is enabled.
+  // Setting off-screen or empty bounds suppresses the highlight.
+  void SetCaretBounds(const gfx::Rect& bounds_in_screen);
+
+  // Sets whether the accessibility panel should always be visible, regardless
+  // of whether the window is fullscreen.
+  void SetAccessibilityPanelAlwaysVisible(bool always_visible);
+
+  // Sets the bounds for the accessibility panel. Overrides current
+  // configuration (i.e. fullscreen, full-width).
   void SetAccessibilityPanelBounds(const gfx::Rect& bounds,
-                                   AccessibilityPanelState state) override;
-  void SetSelectToSpeakState(SelectToSpeakState state) override;
+                                   AccessibilityPanelState state);
+
+  // Sets the current Select-to-Speak state. This should be used by the Select-
+  // to-Speak extension to inform ash of its updated state.
+  void SetSelectToSpeakState(SelectToSpeakState state);
+
+  // Set the delegate used by the Select-to-Speak event handler.
   void SetSelectToSpeakEventHandlerDelegate(
-      SelectToSpeakEventHandlerDelegate* delegate) override;
+      SelectToSpeakEventHandlerDelegate* delegate);
+
+  // Displays the Select-to-Speak panel.
   void ShowSelectToSpeakPanel(const gfx::Rect& anchor,
                               bool is_paused,
-                              double speech_rate) override;
-  void HideSelectToSpeakPanel() override;
+                              double speech_rate);
+
+  // Hides the Select-to-Speak panel.
+  void HideSelectToSpeakPanel();
+
+  // Dispatches event to notify Select-to-speak that a panel action occurred,
+  // with an optional value.
   void OnSelectToSpeakPanelAction(SelectToSpeakPanelAction action,
-                                  double value) override;
-  void HideSwitchAccessBackButton() override;
-  void HideSwitchAccessMenu() override;
-  void ShowSwitchAccessBackButton(const gfx::Rect& anchor) override;
+                                  double value);
+
+  // Hides the Switch Access back button.
+  void HideSwitchAccessBackButton();
+
+  // Hides the Switch Access menu.
+  void HideSwitchAccessMenu();
+
+  // Show the Switch Access back button next to the specified rectangle.
+  void ShowSwitchAccessBackButton(const gfx::Rect& anchor);
+
+  // Show the Switch Access menu with the specified actions.
   void ShowSwitchAccessMenu(const gfx::Rect& anchor,
-                            std::vector<std::string> actions_to_show) override;
-  void StartPointScan() override;
-  void StopPointScan() override;
-  void SetPointScanSpeedDipsPerSecond(
-      int point_scan_speed_dips_per_second) override;
-  void SetDictationActive(bool is_active) override;
-  void ToggleDictationFromSource(DictationToggleSource source) override;
-  void EnableOrToggleDictationFromSource(DictationToggleSource source) override;
+                            std::vector<std::string> actions_to_show);
+
+  // Starts point scanning in Switch Access.
+  void StartPointScan();
+
+  // Stops point scanning in Switch Access.
+  void StopPointScan();
+
+  // Sets point scanning speed in Switch Access.
+  void SetPointScanSpeedDipsPerSecond(int point_scan_speed_dips_per_second);
+
+  // Set whether dictation is active.
+  void SetDictationActive(bool is_active);
+
+  // Starts or stops dictation. Records metrics for toggling via SwitchAccess.
+  void ToggleDictationFromSource(DictationToggleSource source);
+
+  // Enables Dictation if the feature is currently disabled. Toggles (starts or
+  // stops) Dictation if the feature is currently enabled. Note: this behavior
+  // is currently behind a feature flag - if the feature flag is off, then this
+  // method behaves like ToggleDictationFromSource.
+  void EnableOrToggleDictationFromSource(DictationToggleSource source);
+
+  // Shows a nudge explaining that a user's dictation language was upgraded to
+  // work offline.
   void ShowDictationLanguageUpgradedNudge(
       const std::string& dictation_locale,
-      const std::string& application_locale) override;
-  void HandleAutoclickScrollableBoundsFound(
-      const gfx::Rect& bounds_in_screen) override;
-  std::u16string GetBatteryDescription() const override;
-  void SetVirtualKeyboardVisible(bool is_visible) override;
-  void PerformAcceleratorAction(AcceleratorAction accelerator_action) override;
-  void NotifyAccessibilityStatusChanged() override;
-  bool IsAccessibilityFeatureVisibleInTrayMenu(
-      const std::string& path) override;
-  void DisablePolicyRecommendationRestorerForTesting() override;
-  void SuspendSwitchAccessKeyHandling(bool suspend) override;
-  void EnableChromeVoxVolumeSlideGesture() override;
+      const std::string& application_locale);
+
+  // Called when the Automatic Clicks extension finds scrollable bounds.
+  void HandleAutoclickScrollableBoundsFound(const gfx::Rect& bounds_in_screen);
+
+  // Retrieves a string description of the current battery status.
+  std::u16string GetBatteryDescription() const;
+
+  // Shows or hides the virtual keyboard.
+  void SetVirtualKeyboardVisible(bool is_visible);
+
+  // Performs the given accelerator action.
+  void PerformAcceleratorAction(AcceleratorAction accelerator_action);
+
+  // Notify observers that the accessibility status has changed. This is part of
+  // the public interface because a11y features like screen magnifier are
+  // managed outside of this accessibility controller.
+  void NotifyAccessibilityStatusChanged();
+
+  // Returns true if the |path| pref is being controlled by a policy which
+  // enforces turning it on or its not being controlled by any type of policy
+  // and false otherwise.
+  bool IsAccessibilityFeatureVisibleInTrayMenu(const std::string& path);
+
+  // Disables restoring of recommended policy values.
+  void DisablePolicyRecommendationRestorerForTesting();
+
+  // Suspends (or resumes) key handling for Switch Access.
+  void SuspendSwitchAccessKeyHandling(bool suspend);
+
+  // Enables ChromeVox's volume slide gesture.
+  void EnableChromeVoxVolumeSlideGesture();
+
+  // Updates the enabled state, tooltip, and progress ring of the dictation
+  // button in the status tray when speech recognition file download state
+  // changes. `download_progress` indicates SODA download progress and is
+  // guaranteed to be between 0 and 100 (inclusive).
   void UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
-      int download_progress) override;
-  void ShowNotificationForDictation(
-      DictationNotificationType type,
-      const std::u16string& display_language) override;
+      int download_progress);
+
+  // Shows a notification card in the message center informing the user that
+  // speech recognition files have either downloaded successfully or failed.
+  // Specific to the Dictation feature.
+  void ShowNotificationForDictation(DictationNotificationType type,
+                                    const std::u16string& display_language);
+  // Updates the Dictation UI bubble. `text` is optional to allow clients to
+  // clear the bubble's text.
   void UpdateDictationBubble(
       bool visible,
       DictationBubbleIconType icon,
       const std::optional<std::u16string>& text,
-      const std::optional<std::vector<DictationBubbleHintType>>& hints)
-      override;
-  void SilenceSpokenFeedback() override;
-  void ShowToast(AccessibilityToastType type) override;
+      const std::optional<std::vector<DictationBubbleHintType>>& hints);
 
+  // Cancels all of spoken feedback's current and queued speech immediately.
+  void SilenceSpokenFeedback();
+
+  // Shows an accessibility-related toast.
+  void ShowToast(AccessibilityToastType type);
+
+  // Shows a confirmation dialog with the given text, description,
+  // and cancel button name, and calls the relevant callback when the
+  // dialog is confirmed, canceled or closed.
   // A confirmation dialog will be shown the first time an accessibility feature
   // is enabled using the specified accelerator key sequence. Only one dialog
   // will be shown at a time, and will not be shown again if the user has
@@ -490,7 +592,7 @@
                               const std::u16string& cancel_name,
                               base::OnceClosure on_accept_callback,
                               base::OnceClosure on_cancel_callback,
-                              base::OnceClosure on_close_callback) override;
+                              base::OnceClosure on_close_callback);
 
   // SessionObserver:
   void OnSigninScreenPrefServiceInitialized(PrefService* prefs) override;
@@ -502,8 +604,13 @@
   SwitchAccessMenuBubbleController* GetSwitchAccessBubbleControllerForTest() {
     return switch_access_bubble_controller_.get();
   }
-  void DisableSwitchAccessDisableConfirmationDialogTesting() override;
-  void DisableSwitchAccessEnableNotificationTesting() override;
+
+  // Disables the dialog shown when Switch Access is turned off.
+  // Used in tests.
+  void DisableSwitchAccessDisableConfirmationDialogTesting();
+  // Disables the dialog shown when Switch Access is turned off.
+  // Used in tests.
+  void DisableSwitchAccessEnableNotificationTesting();
   SelectToSpeakMenuBubbleController*
   GetSelectToSpeakMenuBubbleControllerForTest() {
     return select_to_speak_bubble_controller_.get();
@@ -677,7 +784,7 @@
   base::RepeatingCallback<void()>
       show_confirmation_dialog_callback_for_testing_;
 
-  base::WeakPtrFactory<AccessibilityControllerImpl> weak_ptr_factory_{this};
+  base::WeakPtrFactory<AccessibilityController> weak_ptr_factory_{this};
 };
 
 }  // namespace ash
diff --git a/ash/accessibility/accessibility_controller_test_api_impl.cc b/ash/accessibility/accessibility_controller_test_api_impl.cc
index ef495c7..f8acb69 100644
--- a/ash/accessibility/accessibility_controller_test_api_impl.cc
+++ b/ash/accessibility/accessibility_controller_test_api_impl.cc
@@ -13,7 +13,7 @@
 
 namespace {
 
-AccessibilityControllerImpl* GetController() {
+AccessibilityController* GetController() {
   return Shell::Get()->accessibility_controller();
 }
 
diff --git a/ash/accessibility/accessibility_controller_unittest.cc b/ash/accessibility/accessibility_controller_unittest.cc
index a5e01bd..481d4228 100644
--- a/ash/accessibility/accessibility_controller_unittest.cc
+++ b/ash/accessibility/accessibility_controller_unittest.cc
@@ -194,7 +194,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetAutoclickEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->autoclick().enabled());
 
@@ -214,7 +214,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetCaretHighlightEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->caret_highlight().enabled());
 
@@ -234,7 +234,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetColorCorrectionEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->color_correction().enabled());
 
@@ -270,7 +270,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetCursorHighlightEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->cursor_highlight().enabled());
 
@@ -290,7 +290,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetFaceGazeEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->face_gaze().enabled());
 
@@ -314,7 +314,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   // Check when the value is true and not being controlled by any policy.
@@ -350,7 +350,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetFocusHighlightEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->focus_highlight().enabled());
 
@@ -370,7 +370,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetHighContrastEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->high_contrast().enabled());
 
@@ -390,7 +390,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetLargeCursorEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->large_cursor().enabled());
 
@@ -414,7 +414,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->large_cursor().SetEnabled(true);
@@ -450,7 +450,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetLiveCaptionEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->live_caption().enabled());
 
@@ -474,7 +474,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->live_caption().SetEnabled(true);
@@ -508,7 +508,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->high_contrast().SetEnabled(true);
@@ -547,7 +547,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->mono_audio().SetEnabled(true);
@@ -586,10 +586,10 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Required to set the dialog to be true to change the value of the pref from
-  // the |AccessibilityControllerImpl|.
+  // the |AccessibilityController|.
   prefs->SetBoolean(prefs::kDictationAcceleratorDialogHasBeenAccepted, true);
   // Check when the value is true and not being controlled by any policy.
   controller->dictation().SetEnabled(true);
@@ -628,7 +628,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->cursor_highlight().SetEnabled(true);
@@ -667,7 +667,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->fullscreen_magnifier().SetEnabled(true);
@@ -706,7 +706,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->docked_magnifier().SetEnabled(true);
@@ -738,7 +738,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->caret_highlight().SetEnabled(true);
@@ -777,7 +777,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->select_to_speak().SetEnabled(true);
@@ -816,7 +816,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->autoclick().SetEnabled(true);
@@ -855,7 +855,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
@@ -894,7 +894,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->virtual_keyboard().SetEnabled(true);
@@ -933,7 +933,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->switch_access().SetEnabled(true);
@@ -972,7 +972,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->color_correction().SetEnabled(true);
@@ -1012,7 +1012,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->focus_highlight().SetEnabled(true);
@@ -1051,7 +1051,7 @@
   // visible in the accessibility tray menu despite its value.
   PrefService* prefs =
       Shell::Get()->session_controller()->GetLastActiveUserPrefService();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Check when the value is true and not being controlled by any policy.
   controller->sticky_keys().SetEnabled(true);
@@ -1132,7 +1132,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetMonoAudioEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->mono_audio().enabled());
 
@@ -1152,7 +1152,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetSpokenFeedbackEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->spoken_feedback().enabled());
 
@@ -1172,7 +1172,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, FeaturesConflictingWithChromeVox) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->spoken_feedback().enabled());
   EXPECT_FALSE(controller->sticky_keys().enabled());
@@ -1211,7 +1211,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetStickyKeysEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->sticky_keys().enabled());
 
@@ -1235,7 +1235,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SetVirtualKeyboardEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->virtual_keyboard().enabled());
 
@@ -1278,7 +1278,7 @@
   ASSERT_FALSE(
       chromeos::FakePowerManagerClient::Get()->backlights_forced_off());
 
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetDarkenScreen(true);
   EXPECT_TRUE(chromeos::FakePowerManagerClient::Get()->backlights_forced_off());
@@ -1292,7 +1292,7 @@
   const std::u16string kChromeVoxEnabledTitle = u"ChromeVox enabled";
   const std::u16string kChromeVoxEnabled =
       u"Press Ctrl + Alt + Z to disable spoken feedback.";
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   // Enabling spoken feedback should show the notification if specified to show
@@ -1324,7 +1324,7 @@
       u"Press Ctrl + Alt + Z to disable spoken feedback.";
   const std::u16string kBrailleConnectedAndChromeVoxEnabledTitle =
       u"Braille and ChromeVox are enabled";
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_SHOW);
@@ -1360,7 +1360,7 @@
 }
 
 TEST_F(AccessibilityControllerTest, SelectToSpeakStateChanges) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityObserver observer;
   controller->AddObserver(&observer);
@@ -1387,7 +1387,7 @@
   const std::u16string kSucceededDescription =
       u"Speech is processed locally and dictation works offline, but some "
       u"voice commands won’t work.";
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   controller->ShowNotificationForDictation(
@@ -1408,7 +1408,7 @@
   const std::u16string kFailedDescription =
       u"Download will be attempted later. Speech will be sent to Google for "
       u"processing for now.";
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   controller->ShowNotificationForDictation(
@@ -1444,7 +1444,7 @@
 // Verifies the behavior of EnableOrToggleDictation without the keyboard
 // improvements feature (current behavior).
 TEST_F(AccessibilityControllerTest, EnableOrToggleDictation) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityControllerClient client;
   controller->SetClient(&client);
@@ -1514,7 +1514,7 @@
 // improvements feature (new behavior).
 TEST_F(AccessibilityControllerDictationKeyboardImprovementsTest,
        EnableOrToggleDictation) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityControllerClient client;
   controller->SetClient(&client);
@@ -1616,7 +1616,7 @@
 TEST_P(AccessibilityControllerSigninTest, EnableOnLoginScreenAndLogin) {
   constexpr float kMagnifierScale = 4.3f;
 
-  AccessibilityControllerImpl* accessibility =
+  AccessibilityController* accessibility =
       Shell::Get()->accessibility_controller();
   DockedMagnifierController* docked_magnifier =
       Shell::Get()->docked_magnifier_controller();
@@ -1710,7 +1710,7 @@
 }
 
 TEST_P(AccessibilityControllerSigninTest, SwitchAccessPrefsSyncToSignIn) {
-  AccessibilityControllerImpl* accessibility =
+  AccessibilityController* accessibility =
       Shell::Get()->accessibility_controller();
 
   SessionControllerImpl* session = Shell::Get()->session_controller();
diff --git a/ash/accessibility/autoclick/autoclick_unittest.cc b/ash/accessibility/autoclick/autoclick_unittest.cc
index c51b221..a6c1d009 100644
--- a/ash/accessibility/autoclick/autoclick_unittest.cc
+++ b/ash/accessibility/autoclick/autoclick_unittest.cc
@@ -814,7 +814,7 @@
 }
 
 TEST_F(AutoclickTest, DoesActionOnBubbleWhenInDifferentModes) {
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   // Enable autoclick from the accessibility controller so that the bubble is
   // constructed too.
diff --git a/ash/accessibility/chromevox/key_accessibility_enabler_unittest.cc b/ash/accessibility/chromevox/key_accessibility_enabler_unittest.cc
index e964453..f3277714 100644
--- a/ash/accessibility/chromevox/key_accessibility_enabler_unittest.cc
+++ b/ash/accessibility/chromevox/key_accessibility_enabler_unittest.cc
@@ -68,7 +68,7 @@
   ui::KeyEvent vol_up_release(ui::ET_KEY_RELEASED, ui::VKEY_VOLUME_UP,
                               ui::EF_NONE);
 
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   ASSERT_FALSE(controller->spoken_feedback().enabled());
diff --git a/ash/accessibility/chromevox/spoken_feedback_enabler.cc b/ash/accessibility/chromevox/spoken_feedback_enabler.cc
index 7786913..d28d450b 100644
--- a/ash/accessibility/chromevox/spoken_feedback_enabler.cc
+++ b/ash/accessibility/chromevox/spoken_feedback_enabler.cc
@@ -35,7 +35,7 @@
   base::TimeTicks now = ui::EventTimeForNow();
   int tick_count = base::ClampRound((now - start_time_) / kTimerDelay);
 
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   CHECK(controller);
   if (tick_count >= kTimerTicksOfFirstSoundFeedback &&
diff --git a/ash/accessibility/chromevox/touch_exploration_manager.cc b/ash/accessibility/chromevox/touch_exploration_manager.cc
index a013696..8c65edb 100644
--- a/ash/accessibility/chromevox/touch_exploration_manager.cc
+++ b/ash/accessibility/chromevox/touch_exploration_manager.cc
@@ -30,7 +30,7 @@
 
 namespace {
 
-AccessibilityControllerImpl* GetA11yController() {
+AccessibilityController* GetA11yController() {
   return Shell::Get()->accessibility_controller();
 }
 
@@ -154,8 +154,9 @@
 
 void TouchExplorationManager::OnTwoFingerTouchStop() {
   // Can be null during shutdown.
-  if (AccessibilityControllerImpl* controller = GetA11yController())
+  if (AccessibilityController* controller = GetA11yController()) {
     controller->OnTwoFingerTouchStop();
+  }
 }
 
 void TouchExplorationManager::PlaySpokenFeedbackToggleCountdown(
diff --git a/ash/accessibility/default_accessibility_delegate.cc b/ash/accessibility/default_accessibility_delegate.cc
index 3ca53ac..bfcb51e 100644
--- a/ash/accessibility/default_accessibility_delegate.cc
+++ b/ash/accessibility/default_accessibility_delegate.cc
@@ -24,7 +24,7 @@
 }
 
 bool DefaultAccessibilityDelegate::ShouldShowAccessibilityMenu() const {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   return controller->spoken_feedback().enabled() || screen_magnifier_enabled_ ||
          controller->autoclick().enabled() ||
diff --git a/ash/accessibility/test_accessibility_controller_client.cc b/ash/accessibility/test_accessibility_controller_client.cc
index f4ae4e50..0612f13 100644
--- a/ash/accessibility/test_accessibility_controller_client.cc
+++ b/ash/accessibility/test_accessibility_controller_client.cc
@@ -6,7 +6,7 @@
 
 #include <utility>
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "base/time/time.h"
 #include "ui/gfx/geometry/point_f.h"
 
diff --git a/ash/accessibility/ui/accessibility_highlight_controller_unittest.cc b/ash/accessibility/ui/accessibility_highlight_controller_unittest.cc
index 71bc3bd..be74242 100644
--- a/ash/accessibility/ui/accessibility_highlight_controller_unittest.cc
+++ b/ash/accessibility/ui/accessibility_highlight_controller_unittest.cc
@@ -311,7 +311,7 @@
   std::unique_ptr<views::Widget> window = CreateTestWidget();
   window->SetBounds(gfx::Rect(5, 5, 300, 300));
 
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   accessibility_controller->caret_highlight().SetEnabled(true);
 
diff --git a/ash/app_list/views/assistant/assistant_page_view_unittest.cc b/ash/app_list/views/assistant/assistant_page_view_unittest.cc
index 9440d61..1ab0acdb 100644
--- a/ash/app_list/views/assistant/assistant_page_view_unittest.cc
+++ b/ash/app_list/views/assistant/assistant_page_view_unittest.cc
@@ -623,13 +623,12 @@
   EXPECT_FALSE(page_view_layer->fills_bounds_opaquely());
   EXPECT_EQ(page_view_layer->background_blur(),
             ColorProvider::kBackgroundBlurSigma);
-  EXPECT_EQ(page_view_layer->GetTargetColor(),
-            ColorProvider::Get()->GetBaseLayerColor(
-                ColorProvider::BaseLayerType::kTransparent80));
+  EXPECT_EQ(
+      page_view_layer->GetTargetColor(),
+      page_view()->GetColorProvider()->GetColor(kColorAshShieldAndBase80));
 }
 
 TEST_F(AssistantPageViewTest, BackgroundColorInDarkLightMode) {
-  auto* color_provider = AshColorProvider::Get();
   auto* dark_light_mode_controller = DarkLightModeControllerImpl::Get();
   dark_light_mode_controller->OnActiveUserPrefServiceChanged(
       Shell::Get()->session_controller()->GetActivePrefService());
@@ -641,18 +640,18 @@
 
   const bool initial_dark_mode_status =
       dark_light_mode_controller->IsDarkModeEnabled();
-  EXPECT_EQ(page_view()->layer()->GetTargetColor(),
-            color_provider->GetBaseLayerColor(
-                ColorProvider::BaseLayerType::kTransparent80));
+  EXPECT_EQ(
+      page_view()->layer()->GetTargetColor(),
+      page_view()->GetColorProvider()->GetColor(kColorAshShieldAndBase80));
 
   // Switch the color mode.
   dark_light_mode_controller->ToggleColorMode();
   ASSERT_NE(initial_dark_mode_status,
             dark_light_mode_controller->IsDarkModeEnabled());
 
-  EXPECT_EQ(page_view()->layer()->GetTargetColor(),
-            color_provider->GetBaseLayerColor(
-                ColorProvider::BaseLayerType::kTransparent80));
+  EXPECT_EQ(
+      page_view()->layer()->GetTargetColor(),
+      page_view()->GetColorProvider()->GetColor(kColorAshShieldAndBase80));
 }
 
 //------------------------------------------------------------------------------
diff --git a/ash/ash_prefs.cc b/ash/ash_prefs.cc
index 3de5b2a2..0b73e57 100644
--- a/ash/ash_prefs.cc
+++ b/ash/ash_prefs.cc
@@ -97,7 +97,7 @@
                           std::string_view country,
                           bool for_test) {
   AcceleratorPrefs::RegisterProfilePrefs(registry);
-  AccessibilityControllerImpl::RegisterProfilePrefs(registry);
+  AccessibilityController::RegisterProfilePrefs(registry);
   AppListControllerImpl::RegisterProfilePrefs(registry);
   AppListNudgeController::RegisterProfilePrefs(registry);
   AshAcceleratorConfiguration::RegisterProfilePrefs(registry);
diff --git a/ash/capture_mode/capture_mode_camera_preview_view.h b/ash/capture_mode/capture_mode_camera_preview_view.h
index bfaa93ab..a6b7ee0 100644
--- a/ash/capture_mode/capture_mode_camera_preview_view.h
+++ b/ash/capture_mode/capture_mode_camera_preview_view.h
@@ -199,7 +199,7 @@
   // True only while handling a gesture tap event on this view.
   bool has_been_tapped_ = false;
 
-  base::ScopedObservation<AccessibilityControllerImpl, AccessibilityObserver>
+  base::ScopedObservation<AccessibilityController, AccessibilityObserver>
       accessibility_observation_{this};
 
   // Helps to update the current a11y override window. It will be the native
diff --git a/ash/capture_mode/capture_mode_camera_unittests.cc b/ash/capture_mode/capture_mode_camera_unittests.cc
index 72bf65df..aa89f0f 100644
--- a/ash/capture_mode/capture_mode_camera_unittests.cc
+++ b/ash/capture_mode/capture_mode_camera_unittests.cc
@@ -4024,7 +4024,7 @@
   auto* event_generator = GetEventGenerator();
 
   for (const bool switch_access_enabled : {false, true}) {
-    AccessibilityControllerImpl* a11y_controller =
+    AccessibilityController* a11y_controller =
         Shell::Get()->accessibility_controller();
     a11y_controller->switch_access().SetEnabled(switch_access_enabled);
     EXPECT_EQ(switch_access_enabled, a11y_controller->IsSwitchAccessRunning());
@@ -4088,7 +4088,7 @@
   auto* event_generator = GetEventGenerator();
 
   for (const bool switch_access_enabled : {false, true}) {
-    AccessibilityControllerImpl* a11y_controller =
+    AccessibilityController* a11y_controller =
         Shell::Get()->accessibility_controller();
     a11y_controller->switch_access().SetEnabled(switch_access_enabled);
     EXPECT_EQ(switch_access_enabled, a11y_controller->IsSwitchAccessRunning());
diff --git a/ash/capture_mode/capture_mode_util.cc b/ash/capture_mode/capture_mode_util.cc
index 250ffb9d2..4d381a7 100644
--- a/ash/capture_mode/capture_mode_util.cc
+++ b/ash/capture_mode/capture_mode_util.cc
@@ -238,7 +238,7 @@
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE,
       base::BindOnce(
-          &AccessibilityControllerImpl::TriggerAccessibilityAlertWithMessage,
+          &AccessibilityController::TriggerAccessibilityAlertWithMessage,
           Shell::Get()->accessibility_controller()->GetWeakPtr(), message));
 }
 
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index fbb2f894a..f059c33 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -289,6 +289,9 @@
              "CrosBatterySaverAlwaysOn",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Controls enabling/disabling the birch feature.
+BASE_FEATURE(kBirchFeature, "BirchFeature", base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables or disables the usage of fixed Bluetooth A2DP packet size to improve
 // audio performance in noisy environment.
 BASE_FEATURE(kBluetoothFixA2dpPacketSize,
@@ -1762,13 +1765,6 @@
              "MicMuteNotifications",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Enable migration of the owner key from the public to the private slot. This
-// experiment represents the second stage of `kStoreOwnerKeyInPrivateSlot` and
-// is only respected if kStoreOwnerKeyInPrivateSlot is enabled.
-BASE_FEATURE(kMigrateOwnerKeyToPrivateSlot,
-             "MigrateOwnerKeyToPrivateSlot",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Controls whether to enable the requirement of a minimum chrome version on the
 // device through the policy DeviceMinimumVersion. If the requirement is
 // not met and the warning time in the policy has expired, the user is
@@ -2521,7 +2517,7 @@
 // Shows live caption in the video conference tray.
 BASE_FEATURE(kShowLiveCaptionInVideoConferenceTray,
              "ShowLiveCaptionInVideoConferenceTray",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Shows the Play Store icon in Demo Mode.
 BASE_FEATURE(kShowPlayInDemoMode,
@@ -2584,11 +2580,6 @@
 // Controls whether the snap group feature is enabled or not.
 BASE_FEATURE(kSnapGroup, "SnapGroup", base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enable storing a newly created owner key in the private slot.
-BASE_FEATURE(kStoreOwnerKeyInPrivateSlot,
-             "StoreOwnerKeyInPrivateSlot",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enables battery indicator for styluses in the palette tray
 BASE_FEATURE(kStylusBatteryStatus,
              "StylusBatteryStatus",
@@ -3172,6 +3163,10 @@
   return base::FeatureList::IsEnabled(kBatterySaverAlwaysOn);
 }
 
+bool IsBirchFeatureEnabled() {
+  return base::FeatureList::IsEnabled(kBirchFeature);
+}
+
 bool IsBluetoothQualityReportEnabled() {
   return base::FeatureList::IsEnabled(kBluetoothQualityReport);
 }
@@ -3608,10 +3603,6 @@
   return base::FeatureList::IsEnabled(kStartAssistantAudioDecoderOnDemand);
 }
 
-bool IsStoreOwnerKeyInPrivateSlotEnabled() {
-  return base::FeatureList::IsEnabled(kStoreOwnerKeyInPrivateSlot);
-}
-
 bool IsImeTrayHideVoiceButtonEnabled() {
   return base::FeatureList::IsEnabled(kImeTrayHideVoiceButton);
 }
@@ -3767,11 +3758,6 @@
   return base::FeatureList::IsEnabled(kMinimumChromeVersion);
 }
 
-bool ShouldMigrateOwnerKeyToPrivateSlot() {
-  return base::FeatureList::IsEnabled(kStoreOwnerKeyInPrivateSlot) &&
-         base::FeatureList::IsEnabled(kMigrateOwnerKeyToPrivateSlot);
-}
-
 bool IsMultiZoneRgbKeyboardEnabled() {
   return base::FeatureList::IsEnabled(kMultiZoneRgbKeyboard);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 8dd64001..53925f1 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -82,6 +82,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::FeatureParam<double> kBatterySaverActivationChargePercent;
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kBatterySaverAlwaysOn);
+COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kBirchFeature);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kBluetoothFixA2dpPacketSize);
 COMPONENT_EXPORT(ASH_CONSTANTS) BASE_DECLARE_FEATURE(kBluetoothQualityReport);
@@ -925,6 +926,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBackgroundBlurEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBatterySaverAvailable();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBatterySaverAlwaysOn();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBirchFeatureEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBluetoothQualityReportEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCaptureModeAudioMixingEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCaptureModeEducationEnabled();
diff --git a/ash/constants/ash_switches.cc b/ash/constants/ash_switches.cc
index 5f6514ac..97026bb 100644
--- a/ash/constants/ash_switches.cc
+++ b/ash/constants/ash_switches.cc
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/command_line.h"
+#include "base/hash/sha1.h"
 #include "base/metrics/field_trial.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
@@ -22,6 +23,11 @@
 constexpr base::TimeDelta kAshContextualNudgesMinInterval = base::Seconds(0);
 constexpr base::TimeDelta kAshContextualNudgesMaxInterval = base::Seconds(60);
 
+// The hash value for the secret key of the birch feature.
+constexpr char kBirchHashKey[] =
+    "\x1a\x93\x5f\x64\x0d\x7f\x0c\x2f\x88\xe8\x80\x9a\x5f\x16\xbb\xd8\x74\x06"
+    "\x8a\xb1";
+
 }  // namespace
 
 // Please keep the order of these switches synchronized with the header file
@@ -295,6 +301,9 @@
 // instead of displaying an interactive animation.
 const char kAuraLegacyPowerButton[] = "aura-legacy-power-button";
 
+// Supply secret key for the Birch feature.
+const char kBirchFeatureKey[] = "birch-feature-key";
+
 // If this flag is set, it indicates that this device is a "Cellular First"
 // device. Cellular First devices use cellular telephone data networks as
 // their primary means of connecting to the internet.
@@ -1305,5 +1314,21 @@
       kAllowDefaultShelfPinLayoutIgnoringSync);
 }
 
+bool IsBirchSecretKeyMatched() {
+  // Commandline looks like:
+  //  out/Default/chrome --user-data-dir=/tmp/tmp123
+  //  --birch-feature-key="INSERT KEY HERE" --enable-features=BirchFeature
+  const std::string provided_key_hash = base::SHA1HashString(
+      base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+          switches::kBirchFeatureKey));
+
+  bool birch_key_matched = (provided_key_hash == kBirchHashKey);
+  if (!birch_key_matched) {
+    LOG(ERROR) << "Provided secret key does not match with the expected one.";
+  }
+
+  return birch_key_matched;
+}
+
 }  // namespace switches
 }  // namespace ash
diff --git a/ash/constants/ash_switches.h b/ash/constants/ash_switches.h
index d3c2a34..7c006bd 100644
--- a/ash/constants/ash_switches.h
+++ b/ash/constants/ash_switches.h
@@ -95,6 +95,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kAshUiModeClamshell[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kAshUiModeTablet[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kAuraLegacyPowerButton[];
+COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kBirchFeatureKey[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kCellularFirst[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kChildWallpaperLarge[];
 COMPONENT_EXPORT(ASH_CONSTANTS) extern const char kChildWallpaperSmall[];
@@ -495,6 +496,9 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool ShouldAllowDefaultShelfPinLayoutIgnoringSync();
 
+COMPONENT_EXPORT(ASH_CONSTANTS)
+bool IsBirchSecretKeyMatched();
+
 }  // namespace ash::switches
 
 #endif  // ASH_CONSTANTS_ASH_SWITCHES_H_
diff --git a/ash/events/accessibility_event_rewriter.cc b/ash/events/accessibility_event_rewriter.cc
index 7752d21..6fd032ce 100644
--- a/ash/events/accessibility_event_rewriter.cc
+++ b/ash/events/accessibility_event_rewriter.cc
@@ -212,7 +212,7 @@
   }
 
   if (key_event->type() == ui::ET_KEY_PRESSED) {
-    AccessibilityControllerImpl* accessibility_controller =
+    AccessibilityController* accessibility_controller =
         Shell::Get()->accessibility_controller();
 
     if (accessibility_controller->IsPointScanEnabled()) {
@@ -301,7 +301,7 @@
 void AccessibilityEventRewriter::MaybeSendMouseEvent(const ui::Event& event) {
   // Mouse moves are the only pertinent event for accessibility component
   // extensions.
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   if (send_mouse_events_ &&
       (event.type() == ui::ET_MOUSE_MOVED ||
diff --git a/ash/events/accessibility_event_rewriter_unittest.cc b/ash/events/accessibility_event_rewriter_unittest.cc
index 5743cf9..07a9aaff 100644
--- a/ash/events/accessibility_event_rewriter_unittest.cc
+++ b/ash/events/accessibility_event_rewriter_unittest.cc
@@ -247,7 +247,7 @@
 
 // The delegate should not intercept events when spoken feedback is disabled.
 TEST_F(ChromeVoxAccessibilityEventRewriterTest, EventsNotConsumedWhenDisabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->spoken_feedback().enabled());
 
@@ -269,7 +269,7 @@
 
 // The delegate should intercept key events when spoken feedback is enabled.
 TEST_F(ChromeVoxAccessibilityEventRewriterTest, KeyEventsConsumedWhenEnabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
   EXPECT_TRUE(controller->spoken_feedback().enabled());
@@ -316,7 +316,7 @@
 
 TEST_F(ChromeVoxAccessibilityEventRewriterTest,
        KeysNotEatenWithChromeVoxDisabled) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->spoken_feedback().enabled());
 
@@ -347,7 +347,7 @@
 }
 
 TEST_F(ChromeVoxAccessibilityEventRewriterTest, KeyEventsCaptured) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
   EXPECT_TRUE(controller->spoken_feedback().enabled());
@@ -392,7 +392,7 @@
 
 TEST_F(ChromeVoxAccessibilityEventRewriterTest,
        KeyEventsCapturedWithModifierRemapping) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
   EXPECT_TRUE(controller->spoken_feedback().enabled());
@@ -450,7 +450,7 @@
 
 TEST_F(ChromeVoxAccessibilityEventRewriterTest,
        PositionalInputMethodKeysMightBeRewritten) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetSpokenFeedbackEnabled(true, A11Y_NOTIFICATION_NONE);
   EXPECT_TRUE(controller->spoken_feedback().enabled());
@@ -677,7 +677,7 @@
  protected:
   raw_ptr<ui::test::EventGenerator, ExperimentalAsh> generator_ = nullptr;
   EventCapturer event_capturer_;
-  raw_ptr<AccessibilityControllerImpl, ExperimentalAsh> controller_ = nullptr;
+  raw_ptr<AccessibilityController, ExperimentalAsh> controller_ = nullptr;
   std::unique_ptr<SwitchAccessTestDelegate> delegate_;
   input_method::FakeImeKeyboard fake_ime_keyboard_;
   std::unique_ptr<AccessibilityEventRewriter> accessibility_event_rewriter_;
@@ -999,7 +999,7 @@
  protected:
   raw_ptr<ui::test::EventGenerator, ExperimentalAsh> generator_ = nullptr;
   EventCapturer event_capturer_;
-  raw_ptr<AccessibilityControllerImpl, ExperimentalAsh> controller_ = nullptr;
+  raw_ptr<AccessibilityController, ExperimentalAsh> controller_ = nullptr;
   std::unique_ptr<MagnifierTestDelegate> delegate_;
   input_method::FakeImeKeyboard fake_ime_keyboard_;
   std::unique_ptr<AccessibilityEventRewriter> accessibility_event_rewriter_;
diff --git a/ash/events/select_to_speak_event_handler_unittest.cc b/ash/events/select_to_speak_event_handler_unittest.cc
index 1d38700..5aebee5 100644
--- a/ash/events/select_to_speak_event_handler_unittest.cc
+++ b/ash/events/select_to_speak_event_handler_unittest.cc
@@ -145,7 +145,7 @@
  protected:
   raw_ptr<ui::test::EventGenerator, ExperimentalAsh> generator_ = nullptr;
   EventCapturer event_capturer_;
-  raw_ptr<AccessibilityControllerImpl, ExperimentalAsh> controller_ = nullptr;
+  raw_ptr<AccessibilityController, ExperimentalAsh> controller_ = nullptr;
   std::unique_ptr<TestDelegate> delegate_;
 };
 
diff --git a/ash/keyboard/virtual_keyboard_controller_unittest.cc b/ash/keyboard/virtual_keyboard_controller_unittest.cc
index 5115f45..f715ee4 100644
--- a/ash/keyboard/virtual_keyboard_controller_unittest.cc
+++ b/ash/keyboard/virtual_keyboard_controller_unittest.cc
@@ -92,7 +92,7 @@
 
 TEST_F(VirtualKeyboardControllerTest,
        ForceToShowKeyboardWithKeysetWhenAccessibilityKeyboardIsEnabled) {
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   accessibility_controller->virtual_keyboard().SetEnabled(true);
   ASSERT_TRUE(accessibility_controller->virtual_keyboard().enabled());
diff --git a/ash/login/ui/local_authentication_request_view.cc b/ash/login/ui/local_authentication_request_view.cc
index 73dda64..3702727 100644
--- a/ash/login/ui/local_authentication_request_view.cc
+++ b/ash/login/ui/local_authentication_request_view.cc
@@ -9,7 +9,6 @@
 #include "ash/login/ui/arrow_button_view.h"
 #include "ash/login/ui/local_authentication_request_widget.h"
 #include "ash/login/ui/non_accessible_view.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "ash/public/cpp/login/local_authentication_request_controller.h"
 #include "ash/public/cpp/login/login_utils.h"
 #include "ash/public/cpp/session/user_info.h"
diff --git a/ash/login/ui/pin_request_view_unittest.cc b/ash/login/ui/pin_request_view_unittest.cc
index 7fe7045..901a363 100644
--- a/ash/login/ui/pin_request_view_unittest.cc
+++ b/ash/login/ui/pin_request_view_unittest.cc
@@ -689,7 +689,7 @@
 TEST_F(PinRequestWidgetTest, SpokenFeedbackKeyCombo) {
   ShowWidget();
 
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   EXPECT_FALSE(controller->spoken_feedback().enabled());
 
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index fcf87d4..0d05cca 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -18,8 +18,6 @@
     "accelerators.h",
     "accelerators_util.cc",
     "accelerators_util.h",
-    "accessibility_controller.cc",
-    "accessibility_controller.h",
     "accessibility_controller_client.h",
     "accessibility_controller_enums.h",
     "accessibility_event_rewriter_delegate.h",
diff --git a/ash/public/cpp/accessibility_controller.cc b/ash/public/cpp/accessibility_controller.cc
deleted file mode 100644
index 07f16cf8..0000000
--- a/ash/public/cpp/accessibility_controller.cc
+++ /dev/null
@@ -1,29 +0,0 @@
-// 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.
-
-#include "ash/public/cpp/accessibility_controller.h"
-
-#include "base/check_op.h"
-
-namespace ash {
-
-namespace {
-AccessibilityController* g_instance = nullptr;
-}
-
-AccessibilityController* AccessibilityController::Get() {
-  return g_instance;
-}
-
-AccessibilityController::AccessibilityController() {
-  DCHECK_EQ(nullptr, g_instance);
-  g_instance = this;
-}
-
-AccessibilityController::~AccessibilityController() {
-  DCHECK_EQ(this, g_instance);
-  g_instance = nullptr;
-}
-
-}  // namespace ash
diff --git a/ash/public/cpp/accessibility_controller.h b/ash/public/cpp/accessibility_controller.h
deleted file mode 100644
index 4ef2cb9..0000000
--- a/ash/public/cpp/accessibility_controller.h
+++ /dev/null
@@ -1,223 +0,0 @@
-// 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.
-
-#ifndef ASH_PUBLIC_CPP_ACCESSIBILITY_CONTROLLER_H_
-#define ASH_PUBLIC_CPP_ACCESSIBILITY_CONTROLLER_H_
-
-#include <optional>
-#include <string>
-#include <vector>
-
-#include "ash/public/cpp/accelerators.h"
-#include "ash/public/cpp/accessibility_controller_enums.h"
-#include "ash/public/cpp/ash_public_export.h"
-#include "base/functional/callback.h"
-
-namespace gfx {
-class Rect;
-}  // namespace gfx
-
-namespace ash {
-
-class AccessibilityControllerClient;
-enum class AccessibilityPanelState;
-enum class DictationToggleSource;
-enum class DictationBubbleHintType;
-enum class DictationBubbleIconType;
-enum class DictationNotificationType;
-class SelectToSpeakEventHandlerDelegate;
-enum class SelectToSpeakState;
-
-// Interface for ash client (e.g. Chrome) to control and query accessibility
-// features.
-class ASH_PUBLIC_EXPORT AccessibilityController {
- public:
-  static AccessibilityController* Get();
-
-  AccessibilityController(const AccessibilityController&) = delete;
-  AccessibilityController& operator=(const AccessibilityController&) = delete;
-
-  // Sets the client interface.
-  virtual void SetClient(AccessibilityControllerClient* client) = 0;
-
-  // Starts or stops darkening the screen (e.g. to allow chrome a11y extensions
-  // to darken the screen).
-  virtual void SetDarkenScreen(bool darken) = 0;
-
-  // Called when braille display state is changed.
-  virtual void BrailleDisplayStateChanged(bool connected) = 0;
-
-  // Sets the focus highlight rect using |bounds_in_screen|. Called when focus
-  // changed in page and a11y focus highlight feature is enabled.
-  virtual void SetFocusHighlightRect(const gfx::Rect& bounds_in_screen) = 0;
-
-  // Sets the text input caret bounds used to draw the caret highlight effect.
-  // For effciency, only sent when the caret highlight feature is enabled.
-  // Setting off-screen or empty bounds suppresses the highlight.
-  virtual void SetCaretBounds(const gfx::Rect& bounds_in_screen) = 0;
-
-  // Sets whether the accessibility panel should always be visible, regardless
-  // of whether the window is fullscreen.
-  virtual void SetAccessibilityPanelAlwaysVisible(bool always_visible) = 0;
-
-  // Sets the bounds for the accessibility panel. Overrides current
-  // configuration (i.e. fullscreen, full-width).
-  virtual void SetAccessibilityPanelBounds(const gfx::Rect& bounds,
-                                           AccessibilityPanelState state) = 0;
-
-  // Sets the current Select-to-Speak state. This should be used by the Select-
-  // to-Speak extension to inform ash of its updated state.
-  virtual void SetSelectToSpeakState(SelectToSpeakState state) = 0;
-
-  // Set the delegate used by the Select-to-Speak event handler.
-  virtual void SetSelectToSpeakEventHandlerDelegate(
-      SelectToSpeakEventHandlerDelegate* delegate) = 0;
-
-  // Displays the Select-to-Speak panel.
-  virtual void ShowSelectToSpeakPanel(const gfx::Rect& anchor,
-                                      bool is_paused,
-                                      double speech_rate) = 0;
-
-  // Hides the Select-to-Speak panel.
-  virtual void HideSelectToSpeakPanel() = 0;
-
-  // Dispatches event to notify Select-to-speak that a panel action occurred,
-  // with an optional value.
-  virtual void OnSelectToSpeakPanelAction(SelectToSpeakPanelAction action,
-                                          double value) = 0;
-
-  // Hides the Switch Access back button.
-  virtual void HideSwitchAccessBackButton() = 0;
-
-  // Hides the Switch Access menu.
-  virtual void HideSwitchAccessMenu() = 0;
-
-  // Show the Switch Access back button next to the specified rectangle.
-  virtual void ShowSwitchAccessBackButton(const gfx::Rect& bounds) = 0;
-
-  // Show the Switch Access menu with the specified actions.
-  virtual void ShowSwitchAccessMenu(
-      const gfx::Rect& bounds,
-      std::vector<std::string> actions_to_show) = 0;
-
-  // Starts point scanning in Switch Access.
-  virtual void StartPointScan() = 0;
-
-  // Stops point scanning in Switch Access.
-  virtual void StopPointScan() = 0;
-
-  // Sets point scanning speed in Switch Access.
-  virtual void SetPointScanSpeedDipsPerSecond(
-      int point_scan_speed_dips_per_second) = 0;
-
-  // Set whether dictation is active.
-  virtual void SetDictationActive(bool is_active) = 0;
-
-  // Starts or stops dictation. Records metrics for toggling via SwitchAccess.
-  virtual void ToggleDictationFromSource(DictationToggleSource source) = 0;
-
-  // Enables Dictation if the feature is currently disabled. Toggles (starts or
-  // stops) Dictation if the feature is currently enabled. Note: this behavior
-  // is currently behind a feature flag - if the feature flag is off, then this
-  // method behaves like ToggleDictationFromSource.
-  virtual void EnableOrToggleDictationFromSource(
-      DictationToggleSource source) = 0;
-
-  // Shows a nudge explaining that a user's dictation language was upgraded to
-  // work offline.
-  virtual void ShowDictationLanguageUpgradedNudge(
-      const std::string& dictation_locale,
-      const std::string& application_locale) = 0;
-
-  // Called when the Automatic Clicks extension finds scrollable bounds.
-  virtual void HandleAutoclickScrollableBoundsFound(
-      const gfx::Rect& bounds_in_screen) = 0;
-
-  // Retrieves a string description of the current battery status.
-  virtual std::u16string GetBatteryDescription() const = 0;
-
-  // Shows or hides the virtual keyboard.
-  virtual void SetVirtualKeyboardVisible(bool is_visible) = 0;
-
-  // Performs the given accelerator action.
-  virtual void PerformAcceleratorAction(
-      AcceleratorAction accelerator_action) = 0;
-
-  // Notify observers that the accessibility status has changed. This is part of
-  // the public interface because a11y features like screen magnifier are
-  // managed outside of this accessibility controller.
-  virtual void NotifyAccessibilityStatusChanged() = 0;
-
-  // Returns true if the |path| pref is being controlled by a policy which
-  // enforces turning it on or its not being controlled by any type of policy
-  // and false otherwise.
-  virtual bool IsAccessibilityFeatureVisibleInTrayMenu(
-      const std::string& path) = 0;
-
-  // Disables restoring of recommended policy values.
-  virtual void DisablePolicyRecommendationRestorerForTesting() {}
-
-  // Disables the dialog shown when Switch Access is turned off.
-  // Used in tests.
-  virtual void DisableSwitchAccessDisableConfirmationDialogTesting() = 0;
-
-  // Disables the dialog shown when Switch Access is turned on.
-  // Used in tests.
-  virtual void DisableSwitchAccessEnableNotificationTesting() = 0;
-
-  // Shows floating accessibility menu if it was enabled by policy.
-  virtual void ShowFloatingMenuIfEnabled() {}
-
-  // Suspends (or resumes) key handling for Switch Access.
-  virtual void SuspendSwitchAccessKeyHandling(bool suspend) {}
-
-  // Enables ChromeVox's volume slide gesture.
-  virtual void EnableChromeVoxVolumeSlideGesture() {}
-
-  // Shows a confirmation dialog with the given text, description,
-  // and cancel button name, and calls the relevant callback when the
-  // dialog is confirmed, canceled or closed.
-  virtual void ShowConfirmationDialog(const std::u16string& title,
-                                      const std::u16string& description,
-                                      const std::u16string& cancel_name,
-                                      base::OnceClosure on_accept_callback,
-                                      base::OnceClosure on_cancel_callback,
-                                      base::OnceClosure on_close_callback) {}
-
-  // Updates the enabled state, tooltip, and progress ring of the dictation
-  // button in the status tray when speech recognition file download state
-  // changes. `download_progress` indicates SODA download progress and is
-  // guaranteed to be between 0 and 100 (inclusive).
-  virtual void UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
-      int download_progress) = 0;
-
-  // Shows a notification card in the message center informing the user that
-  // speech recognition files have either downloaded successfully or failed.
-  // Specific to the Dictation feature.
-  virtual void ShowNotificationForDictation(
-      DictationNotificationType type,
-      const std::u16string& display_language) = 0;
-
-  // Updates the Dictation UI bubble. `text` is optional to allow clients to
-  // clear the bubble's text.
-  virtual void UpdateDictationBubble(
-      bool visible,
-      DictationBubbleIconType icon,
-      const std::optional<std::u16string>& text,
-      const std::optional<std::vector<DictationBubbleHintType>>& hints) = 0;
-
-  // Cancels all of spoken feedback's current and queued speech immediately.
-  virtual void SilenceSpokenFeedback() = 0;
-
-  // Shows an accessibility-related toast.
-  virtual void ShowToast(AccessibilityToastType type) = 0;
-
- protected:
-  AccessibilityController();
-  virtual ~AccessibilityController();
-};
-
-}  // namespace ash
-
-#endif  // ASH_PUBLIC_CPP_ACCESSIBILITY_CONTROLLER_H_
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_view.cc b/ash/public/cpp/external_arc/message_center/arc_notification_view.cc
index 2dfe0ac..1b710f1 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_view.cc
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_view.cc
@@ -9,7 +9,7 @@
 #include "ash/public/cpp/external_arc/message_center/arc_notification_content_view.h"
 #include "ash/public/cpp/external_arc/message_center/arc_notification_item.h"
 #include "ash/public/cpp/message_center/arc_notification_constants.h"
-#include "ash/style/ash_color_provider.h"
+#include "ash/public/cpp/style/color_provider.h"
 #include "ash/system/message_center/message_center_constants.h"
 #include "base/time/time.h"
 #include "chromeos/constants/chromeos_features.h"
@@ -71,12 +71,6 @@
 
   AddChildView(content_view_.get());
 
-  if (content_view_->background()) {
-    background()->SetNativeControlColor(
-        AshColorProvider::Get()->GetBaseLayerColor(
-            AshColorProvider::BaseLayerType::kTransparent80));
-  }
-
   if (shown_in_popup) {
     layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
     layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
@@ -147,20 +141,11 @@
     return;
   }
 
-  const auto* ash_color_provider = AshColorProvider::Get();
   const auto* color_provider = GetColorProvider();
-
   const SkColor color_in_popup =
-      chromeos::features::IsJellyEnabled()
-          ? color_provider->GetColor(cros_tokens::kCrosSysSystemBaseElevated)
-          : ash_color_provider->GetBaseLayerColor(
-                AshColorProvider::BaseLayerType::kTransparent80);
+      color_provider->GetColor(cros_tokens::kCrosSysSystemBaseElevated);
   const SkColor color_in_message_center =
-      chromeos::features::IsJellyEnabled()
-          ? color_provider->GetColor(cros_tokens::kCrosSysSystemOnBase)
-          : ash_color_provider->GetControlsLayerColor(
-                AshColorProvider::ControlsLayerType::
-                    kControlBackgroundColorInactive);
+      color_provider->GetColor(cros_tokens::kCrosSysSystemOnBase);
   SetBackground(views::CreateBackgroundFromPainter(
       std::make_unique<message_center::NotificationBackgroundPainter>(
           top_radius(), bottom_radius(),
@@ -313,6 +298,15 @@
   return false;
 }
 
+void ArcNotificationView::OnThemeChanged() {
+  message_center::MessageView::OnThemeChanged();
+
+  if (content_view_->background()) {
+    background()->SetNativeControlColor(
+        GetColorProvider()->GetColor(cros_tokens::kCrosSysSystemBaseElevated));
+  }
+}
+
 void ArcNotificationView::OnItemDestroying() {
   DCHECK(item_);
   item_->RemoveObserver(this);
diff --git a/ash/public/cpp/external_arc/message_center/arc_notification_view.h b/ash/public/cpp/external_arc/message_center/arc_notification_view.h
index 19a09755..872f94e 100644
--- a/ash/public/cpp/external_arc/message_center/arc_notification_view.h
+++ b/ash/public/cpp/external_arc/message_center/arc_notification_view.h
@@ -77,6 +77,7 @@
   bool OnKeyPressed(const ui::KeyEvent& event) override;
   void ChildPreferredSizeChanged(View* child) override;
   bool HandleAccessibleAction(const ui::AXActionData& action) override;
+  void OnThemeChanged() override;
 
   // ArcNotificationItem::Observer
   void OnItemDestroying() override;
diff --git a/ash/public/cpp/style/color_provider.h b/ash/public/cpp/style/color_provider.h
index 3092732..4ee0847 100644
--- a/ash/public/cpp/style/color_provider.h
+++ b/ash/public/cpp/style/color_provider.h
@@ -14,17 +14,6 @@
 // An interface implemented by Ash that provides colors for the system UI.
 class ASH_PUBLIC_EXPORT ColorProvider {
  public:
-  // Types of Shield layer. Number at the end of each type indicates the alpha
-  // value.
-  enum class ShieldLayerType {
-    kShield20 = 0,
-    kShield40,
-    kShield60,
-    kShield80,
-    kShield90,
-    kShield95,
-  };
-
   // Blur sigma for system UI layers.
   static constexpr float kBackgroundBlurSigma = 30.f;
 
@@ -32,21 +21,6 @@
   // improves performance.
   static constexpr float kBackgroundBlurQuality = 0.33f;
 
-  // Types of Base layer.
-  enum class BaseLayerType {
-    // Number at the end of each transparent type indicates the alpha value.
-    kTransparent20 = 0,
-    kTransparent40,
-    kTransparent60,
-    kTransparent80,
-    kInvertedTransparent80,
-    kTransparent90,
-    kTransparent95,
-
-    // Base layer is opaque.
-    kOpaque,
-  };
-
   // Types of Controls layer.
   enum class ControlsLayerType {
     kHairlineBorderColor,
@@ -146,7 +120,6 @@
 
   // Gets the color of |type| of the corresponding layer based on the current
   // color mode.
-  virtual SkColor GetBaseLayerColor(BaseLayerType type) const = 0;
   virtual SkColor GetControlsLayerColor(ControlsLayerType type) const = 0;
   virtual SkColor GetContentLayerColor(ContentLayerType type) const = 0;
 
diff --git a/ash/shelf/shelf_config.cc b/ash/shelf/shelf_config.cc
index adc994d..e6112ab 100644
--- a/ash/shelf/shelf_config.cc
+++ b/ash/shelf/shelf_config.cc
@@ -73,7 +73,7 @@
  private:
   base::RepeatingClosure accessibility_state_changed_callback_;
 
-  base::ScopedObservation<AccessibilityControllerImpl, AccessibilityObserver>
+  base::ScopedObservation<AccessibilityController, AccessibilityObserver>
       observation_{this};
 };
 
diff --git a/ash/shell.cc b/ash/shell.cc
index ff434aa0..33c3c2a3 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -1249,7 +1249,7 @@
   accessibility_focus_ring_controller_ =
       std::make_unique<AccessibilityFocusRingControllerImpl>();
   accessibility_delegate_.reset(shell_delegate_->CreateAccessibilityDelegate());
-  accessibility_controller_ = std::make_unique<AccessibilityControllerImpl>();
+  accessibility_controller_ = std::make_unique<AccessibilityController>();
   toast_manager_ = std::make_unique<ToastManagerImpl>();
   anchored_nudge_manager_ = std::make_unique<AnchoredNudgeManagerImpl>();
   system_nudge_pause_manager_ = std::make_unique<SystemNudgePauseManagerImpl>();
diff --git a/ash/shell.h b/ash/shell.h
index 1174167e..dc4b81df 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -96,7 +96,7 @@
 class AcceleratorKeycodeLookupCache;
 class AcceleratorPrefs;
 class AcceleratorTracker;
-class AccessibilityControllerImpl;
+class AccessibilityController;
 class AccessibilityDelegate;
 class AccessibilityEventHandlerManager;
 class AccessibilityFocusRingControllerImpl;
@@ -407,7 +407,7 @@
   AcceleratorTracker* accelerator_tracker() {
     return accelerator_tracker_.get();
   }
-  AccessibilityControllerImpl* accessibility_controller() {
+  AccessibilityController* accessibility_controller() {
     return accessibility_controller_.get();
   }
   AccessibilityDelegate* accessibility_delegate() {
@@ -971,7 +971,7 @@
   std::unique_ptr<AcceleratorControllerImpl> accelerator_controller_;
   std::unique_ptr<AcceleratorKeycodeLookupCache>
       accelerator_keycode_lookup_cache_;
-  std::unique_ptr<AccessibilityControllerImpl> accessibility_controller_;
+  std::unique_ptr<AccessibilityController> accessibility_controller_;
   std::unique_ptr<AccessibilityDelegate> accessibility_delegate_;
   std::unique_ptr<AccessibilityFocusRingControllerImpl>
       accessibility_focus_ring_controller_;
diff --git a/ash/style/ash_color_provider.cc b/ash/style/ash_color_provider.cc
index 07db324..9b06cc2 100644
--- a/ash/style/ash_color_provider.cc
+++ b/ash/style/ash_color_provider.cc
@@ -45,31 +45,6 @@
   return g_instance;
 }
 
-SkColor AshColorProvider::GetBaseLayerColor(BaseLayerType type) const {
-  // TODO(crbug.com/1350510): Delete this function after all clients migrate.
-  auto* color_provider = GetColorProvider();
-  DCHECK(color_provider);
-
-  switch (type) {
-    case BaseLayerType::kTransparent20:
-      return color_provider->GetColor(kColorAshShieldAndBase20);
-    case BaseLayerType::kTransparent40:
-      return color_provider->GetColor(kColorAshShieldAndBase40);
-    case BaseLayerType::kTransparent60:
-      return color_provider->GetColor(kColorAshShieldAndBase60);
-    case BaseLayerType::kTransparent80:
-      return color_provider->GetColor(kColorAshShieldAndBase80);
-    case BaseLayerType::kInvertedTransparent80:
-      return color_provider->GetColor(kColorAshInvertedShieldAndBase80);
-    case BaseLayerType::kTransparent90:
-      return color_provider->GetColor(kColorAshShieldAndBase90);
-    case BaseLayerType::kTransparent95:
-      return color_provider->GetColor(kColorAshShieldAndBase95);
-    case BaseLayerType::kOpaque:
-      return color_provider->GetColor(kColorAshShieldAndBaseOpaque);
-  }
-}
-
 SkColor AshColorProvider::GetControlsLayerColor(ControlsLayerType type) const {
   // TODO(crbug.com/1292244): Delete this function after all callers migrate.
   auto* color_provider = GetColorProvider();
diff --git a/ash/style/ash_color_provider.h b/ash/style/ash_color_provider.h
index 168496a..f46531c 100644
--- a/ash/style/ash_color_provider.h
+++ b/ash/style/ash_color_provider.h
@@ -38,7 +38,6 @@
   static AshColorProvider* Get();
 
   // ColorProvider:
-  SkColor GetBaseLayerColor(BaseLayerType type) const override;
   SkColor GetControlsLayerColor(ControlsLayerType type) const override;
   SkColor GetContentLayerColor(ContentLayerType type) const override;
   std::pair<SkColor, float> GetInkDropBaseColorAndOpacity(
diff --git a/ash/style/ash_color_provider_unittest.cc b/ash/style/ash_color_provider_unittest.cc
index 2bc6faf0..71f1addf 100644
--- a/ash/style/ash_color_provider_unittest.cc
+++ b/ash/style/ash_color_provider_unittest.cc
@@ -83,61 +83,6 @@
       color_provider_;
 };
 
-using AshColorProviderBaseLayerTest =
-    AshColorProviderBase<ColorProvider::BaseLayerType>;
-
-TEST_P(AshColorProviderBaseLayerTest, TestBaseColors) {
-  const auto& test_case = GetParam();
-  bool dark = test_case.color_mode == ColorMode::kDark;
-  DarkLightModeController::Get()->SetDarkModeEnabledForTest(dark);
-  EXPECT_EQ(test_case.expected_color,
-            color_provider_->GetBaseLayerColor(test_case.type))
-      << "Colors do not match. Expected " << test_case << " Actual: "
-      << ColorToString(color_provider_->GetBaseLayerColor(test_case.type));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-    AshColorProviderTests,
-    AshColorProviderBaseLayerTest,
-    testing::ValuesIn<ColorsTestCase<ColorProvider::BaseLayerType>>(
-        {// Light mode values
-         {ColorMode::kLight, ColorProvider::BaseLayerType::kTransparent20,
-          SkColorSetARGB(0x33, 0xF8, 0xF8, 0xF8)},
-         {ColorMode::kLight, ColorProvider::BaseLayerType::kTransparent40,
-          SkColorSetARGB(0x66, 0xF8, 0xF8, 0xF8)},
-         {ColorMode::kLight, ColorProvider::BaseLayerType::kTransparent60,
-          SkColorSetARGB(0x99, 0xF8, 0xF8, 0xF8)},
-         {ColorMode::kLight, ColorProvider::BaseLayerType::kTransparent80,
-          SkColorSetARGB(0xCC, 0xF8, 0xF8, 0xF8)},
-         {ColorMode::kLight,
-          ColorProvider::BaseLayerType::kInvertedTransparent80,
-          SkColorSetARGB(0xCC, 0x07, 0x07, 0x07)},
-         {ColorMode::kLight, ColorProvider::BaseLayerType::kTransparent90,
-          SkColorSetARGB(0xE5, 0xF8, 0xF8, 0xF8)},
-         {ColorMode::kLight, ColorProvider::BaseLayerType::kTransparent95,
-          SkColorSetARGB(0xF2, 0xF8, 0xF8, 0xF8)},
-         {ColorMode::kLight, ColorProvider::BaseLayerType::kOpaque,
-          SkColorSetARGB(0xFF, 0xF8, 0xF8, 0xF8)},
-
-         // Dark mode values
-         {ColorMode::kDark, ColorProvider::BaseLayerType::kTransparent20,
-          SkColorSetARGB(0x33, 0x5A, 0x5A, 0x5A)},
-         {ColorMode::kDark, ColorProvider::BaseLayerType::kTransparent40,
-          SkColorSetARGB(0x66, 0x5A, 0x5A, 0x5A)},
-         {ColorMode::kDark, ColorProvider::BaseLayerType::kTransparent60,
-          SkColorSetARGB(0x99, 0x5A, 0x5A, 0x5A)},
-         {ColorMode::kDark, ColorProvider::BaseLayerType::kTransparent80,
-          SkColorSetARGB(0xCC, 0x5A, 0x5A, 0x5A)},
-         {ColorMode::kDark,
-          ColorProvider::BaseLayerType::kInvertedTransparent80,
-          SkColorSetARGB(0xCC, 0xA5, 0xA5, 0xA5)},
-         {ColorMode::kDark, ColorProvider::BaseLayerType::kTransparent90,
-          SkColorSetARGB(0xE5, 0x5A, 0x5A, 0x5A)},
-         {ColorMode::kDark, ColorProvider::BaseLayerType::kTransparent95,
-          SkColorSetARGB(0xF2, 0x5A, 0x5A, 0x5A)},
-         {ColorMode::kDark, ColorProvider::BaseLayerType::kOpaque,
-          SkColorSetARGB(0xFF, 0x5A, 0x5A, 0x5A)}}));
-
 using AshColorProviderControlsLayerTest =
     AshColorProviderBase<ColorProvider::ControlsLayerType>;
 
diff --git a/ash/system/accessibility/accessibility_detailed_view.cc b/ash/system/accessibility/accessibility_detailed_view.cc
index 30d47ac..8e54745a 100644
--- a/ash/system/accessibility/accessibility_detailed_view.cc
+++ b/ash/system/accessibility/accessibility_detailed_view.cc
@@ -67,7 +67,7 @@
 }
 
 bool IsSodaFeatureEnabled(SodaFeature feature) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   switch (feature) {
     case SodaFeature::kDictation:
@@ -155,7 +155,7 @@
 
 void AccessibilityDetailedView::OnAccessibilityStatusChanged() {
   AccessibilityDelegate* delegate = Shell::Get()->accessibility_delegate();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   if (controller->IsSpokenFeedbackSettingVisibleInTray()) {
@@ -277,7 +277,7 @@
 }
 
 void AccessibilityDetailedView::AddEnabledFeatures(views::View* container) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   if (controller->IsSpokenFeedbackSettingVisibleInTray() &&
@@ -356,7 +356,7 @@
 }
 
 void AccessibilityDetailedView::AddAllFeatures(views::View* container) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   if (controller->IsSpokenFeedbackSettingVisibleInTray()) {
@@ -676,7 +676,7 @@
 
 void AccessibilityDetailedView::HandleViewClicked(views::View* view) {
   AccessibilityDelegate* delegate = Shell::Get()->accessibility_delegate();
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   using base::RecordAction;
   using base::UserMetricsAction;
@@ -905,7 +905,7 @@
 }
 
 bool AccessibilityDetailedView::IsSodaFeatureInTray(SodaFeature feature) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   switch (feature) {
     case SodaFeature::kDictation:
diff --git a/ash/system/accessibility/accessibility_detailed_view_unittest.cc b/ash/system/accessibility/accessibility_detailed_view_unittest.cc
index fd1a06d..805b8d5d 100644
--- a/ash/system/accessibility/accessibility_detailed_view_unittest.cc
+++ b/ash/system/accessibility/accessibility_detailed_view_unittest.cc
@@ -476,7 +476,7 @@
     return detailed_menu_->GetClassName();
   }
 
-  AccessibilityControllerImpl* controller() { return controller_; }
+  AccessibilityController* controller() { return controller_; }
   AccessibilityDetailedView* detailed_menu() { return detailed_menu_; }
   views::View* scroll_content() { return detailed_menu_->scroll_content(); }
 
@@ -601,7 +601,7 @@
     }
   }
 
-  raw_ptr<AccessibilityControllerImpl, ExperimentalAsh> controller_ = nullptr;
+  raw_ptr<AccessibilityController, ExperimentalAsh> controller_ = nullptr;
   std::unique_ptr<views::Widget> widget_;
   std::unique_ptr<DetailedViewDelegate> delegate_;
   raw_ptr<AccessibilityDetailedView, DanglingUntriaged | ExperimentalAsh>
@@ -1039,7 +1039,7 @@
 }
 
 TEST_F(AccessibilityDetailedViewTest, ClickDetailMenu) {
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   // Confirms that the check item toggles the spoken feedback.
   EXPECT_FALSE(accessibility_controller->spoken_feedback().enabled());
diff --git a/ash/system/accessibility/accessibility_feature_pod_controller.cc b/ash/system/accessibility/accessibility_feature_pod_controller.cc
index ab6f873..608a629 100644
--- a/ash/system/accessibility/accessibility_feature_pod_controller.cc
+++ b/ash/system/accessibility/accessibility_feature_pod_controller.cc
@@ -31,7 +31,7 @@
 namespace {
 
 std::u16string GenerateSublabelText(
-    std::vector<AccessibilityControllerImpl::Feature*> enabled_features,
+    std::vector<AccessibilityController::Feature*> enabled_features,
     int max_width,
     gfx::FontList font_list) {
   CHECK(!enabled_features.empty());
@@ -61,7 +61,7 @@
 }
 
 std::u16string GenerateTooltipText(
-    std::vector<AccessibilityControllerImpl::Feature*> enabled_features) {
+    std::vector<AccessibilityController::Feature*> enabled_features) {
   if (enabled_features.empty()) {
     return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ACCESSIBILITY_TOOLTIP);
   }
diff --git a/ash/system/accessibility/accessibility_feature_pod_controller_unittest.cc b/ash/system/accessibility/accessibility_feature_pod_controller_unittest.cc
index d2431d1c..ec1bf252 100644
--- a/ash/system/accessibility/accessibility_feature_pod_controller_unittest.cc
+++ b/ash/system/accessibility/accessibility_feature_pod_controller_unittest.cc
@@ -50,7 +50,7 @@
     tile_ = controller_->CreateTile();
   }
 
-  AccessibilityControllerImpl* GetAccessibilityController() {
+  AccessibilityController* GetAccessibilityController() {
     return Shell::Get()->accessibility_controller();
   }
 
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc b/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
index 734a19e..9c7de5b9 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller_unittest.cc
@@ -117,7 +117,7 @@
 }
 
 TEST_F(AutoclickMenuBubbleControllerTest, CanSelectAutoclickTypeFromBubble) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Set to a different event type than the first event in kTestCases.
   controller->SetAutoclickEventType(AutoclickEventType::kRightClick);
@@ -152,7 +152,7 @@
 }
 
 TEST_F(AutoclickMenuBubbleControllerTest, UnpausesWhenPauseAlreadySelected) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   views::View* pause_button =
       GetMenuButton(AutoclickMenuView::ButtonId::kPause);
@@ -181,7 +181,7 @@
 }
 
 TEST_F(AutoclickMenuBubbleControllerTest, CanChangePosition) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
 
   // Set to a known position for than the first event in kTestCases.
@@ -247,7 +247,7 @@
 }
 
 TEST_F(AutoclickMenuBubbleControllerTest, ScrollBubbleShowsAndCloses) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetAutoclickEventType(AutoclickEventType::kLeftClick);
   // No scroll view yet.
@@ -268,7 +268,7 @@
 }
 
 TEST_F(AutoclickMenuBubbleControllerTest, ScrollBubbleDefaultPositioning) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetAutoclickEventType(AutoclickEventType::kScroll);
 
@@ -314,7 +314,7 @@
 
 TEST_F(AutoclickMenuBubbleControllerTest,
        ScrollBubbleManualPositioningLargeScrollBounds) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetAutoclickEventType(AutoclickEventType::kScroll);
 
@@ -388,7 +388,7 @@
 TEST_F(AutoclickMenuBubbleControllerTest,
        ScrollBubbleManualPositioningSmallScrollBounds) {
   UpdateDisplay("1200x1000");
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetAutoclickEventType(AutoclickEventType::kScroll);
 
diff --git a/ash/system/accessibility/dictation_button_tray_unittest.cc b/ash/system/accessibility/dictation_button_tray_unittest.cc
index c70ec8f1..61eab445 100644
--- a/ash/system/accessibility/dictation_button_tray_unittest.cc
+++ b/ash/system/accessibility/dictation_button_tray_unittest.cc
@@ -142,7 +142,7 @@
 // Ensures that creation doesn't cause any crashes and adds the image icon.
 // Also checks that the tray is visible.
 TEST_F(DictationButtonTrayTest, BasicConstruction) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->dictation().SetEnabled(true);
   EXPECT_TRUE(GetImageView(GetTray()));
@@ -151,7 +151,7 @@
 
 // Test that clicking the button activates dictation.
 TEST_F(DictationButtonTrayTest, ButtonActivatesDictation) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityControllerClient client;
   controller->dictation().SetEnabled(true);
@@ -166,7 +166,7 @@
 
 // Test that activating dictation causes the button to activate.
 TEST_F(DictationButtonTrayTest, ActivatingDictationActivatesButton) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->dictation().SetEnabled(true);
   Shell::Get()->OnDictationStarted();
@@ -179,7 +179,7 @@
 // Tests that the tray only renders as active while dictation is listening. Any
 // termination of dictation clears the active state.
 TEST_F(DictationButtonTrayTest, ActiveStateOnlyDuringDictation) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityControllerClient client;
   controller->dictation().SetEnabled(true);
@@ -201,7 +201,7 @@
 }
 
 TEST_F(DictationButtonTrayTest, ImageIcons) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityControllerClient client;
   controller->dictation().SetEnabled(true);
@@ -236,7 +236,7 @@
 TEST_F(DictationButtonTrayTest, DisabledWhenNoInputFocused) {
   DetachTextInputClient();
 
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->dictation().SetEnabled(true);
   DictationButtonTray* tray = GetTray();
@@ -318,7 +318,7 @@
 
 // Tests the behavior of the UpdateOnSpeechRecognitionDownloadChanged() method.
 TEST_F(DictationButtonTraySodaTest, UpdateOnSpeechRecognitionDownloadChanged) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->dictation().SetEnabled(true);
   DictationButtonTray* tray = GetTray();
diff --git a/ash/system/accessibility/floating_accessibility_controller.cc b/ash/system/accessibility/floating_accessibility_controller.cc
index 3e58ca9..1ac5b86 100644
--- a/ash/system/accessibility/floating_accessibility_controller.cc
+++ b/ash/system/accessibility/floating_accessibility_controller.cc
@@ -54,7 +54,7 @@
 }  // namespace
 
 FloatingAccessibilityController::FloatingAccessibilityController(
-    AccessibilityControllerImpl* accessibility_controller)
+    AccessibilityController* accessibility_controller)
     : accessibility_controller_(accessibility_controller) {
   Shell::Get()->locale_update_controller()->AddObserver(this);
   accessibility_controller_->AddObserver(this);
diff --git a/ash/system/accessibility/floating_accessibility_controller.h b/ash/system/accessibility/floating_accessibility_controller.h
index 0be2523..c98f072 100644
--- a/ash/system/accessibility/floating_accessibility_controller.h
+++ b/ash/system/accessibility/floating_accessibility_controller.h
@@ -18,7 +18,7 @@
 
 namespace ash {
 
-class AccessibilityControllerImpl;
+class AccessibilityController;
 class FloatingAccessibilityView;
 
 // Controls the floating accessibility menu.
@@ -31,7 +31,7 @@
       public display::DisplayObserver {
  public:
   explicit FloatingAccessibilityController(
-      AccessibilityControllerImpl* accessibility_controller);
+      AccessibilityController* accessibility_controller);
   FloatingAccessibilityController(const FloatingAccessibilityController&) =
       delete;
   FloatingAccessibilityController& operator=(
@@ -88,7 +88,7 @@
 
   display::ScopedDisplayObserver display_observer_{this};
 
-  const raw_ptr<AccessibilityControllerImpl, ExperimentalAsh>
+  const raw_ptr<AccessibilityController, ExperimentalAsh>
       accessibility_controller_;  // Owns us.
 };
 
diff --git a/ash/system/accessibility/floating_accessibility_controller_unittest.cc b/ash/system/accessibility/floating_accessibility_controller_unittest.cc
index 19b0115..2b6c071 100644
--- a/ash/system/accessibility/floating_accessibility_controller_unittest.cc
+++ b/ash/system/accessibility/floating_accessibility_controller_unittest.cc
@@ -45,7 +45,7 @@
     SetTwoAvailableImes();
   }
 
-  AccessibilityControllerImpl* accessibility_controller() {
+  AccessibilityController* accessibility_controller() {
     return Shell::Get()->accessibility_controller();
   }
 
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_menu_bubble_controller_unittest.cc b/ash/system/accessibility/select_to_speak/select_to_speak_menu_bubble_controller_unittest.cc
index 498e53f..931e3a62 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_menu_bubble_controller_unittest.cc
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_menu_bubble_controller_unittest.cc
@@ -38,7 +38,7 @@
 
   void TearDown() override { AshTestBase::TearDown(); }
 
-  AccessibilityControllerImpl* GetAccessibilitController() {
+  AccessibilityController* GetAccessibilitController() {
     return Shell::Get()->accessibility_controller();
   }
 
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_speed_bubble_controller_unittest.cc b/ash/system/accessibility/select_to_speak/select_to_speak_speed_bubble_controller_unittest.cc
index eb0bab81..096644a3 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_speed_bubble_controller_unittest.cc
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_speed_bubble_controller_unittest.cc
@@ -45,7 +45,7 @@
     AshTestBase::TearDown();
   }
 
-  AccessibilityControllerImpl* GetAccessibilitController() {
+  AccessibilityController* GetAccessibilitController() {
     return Shell::Get()->accessibility_controller();
   }
 
diff --git a/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc b/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
index 24fbe32..98c1aaa6 100644
--- a/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
+++ b/ash/system/accessibility/select_to_speak/select_to_speak_tray_unittest.cc
@@ -121,7 +121,7 @@
 // Test that changing the SelectToSpeakState in the AccessibilityController
 // results in a change of icon and activation in the tray.
 TEST_F(SelectToSpeakTrayTest, SelectToSpeakStateImpactsImageAndActivation) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetSelectToSpeakState(
       SelectToSpeakState::kSelectToSpeakStateSelecting);
@@ -161,7 +161,7 @@
   feature_list.InitAndEnableFeature(
       ::features::kAccessibilitySelectToSpeakHoverTextImprovements);
 
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetSelectToSpeakState(
       SelectToSpeakState::kSelectToSpeakStateSelecting);
@@ -194,7 +194,7 @@
   feature_list.InitAndDisableFeature(
       ::features::kAccessibilitySelectToSpeakHoverTextImprovements);
 
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->SetSelectToSpeakState(
       SelectToSpeakState::kSelectToSpeakStateSelecting);
diff --git a/ash/system/audio/audio_detailed_view.cc b/ash/system/audio/audio_detailed_view.cc
index 300de2a..3cc4039 100644
--- a/ash/system/audio/audio_detailed_view.cc
+++ b/ash/system/audio/audio_detailed_view.cc
@@ -254,7 +254,7 @@
 }
 
 void AudioDetailedView::OnAccessibilityStatusChanged() {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // The live caption state has been updated.
   UpdateLiveCaptionView(controller->live_caption().enabled());
@@ -582,7 +582,7 @@
 
 void AudioDetailedView::MaybeShowSodaMessage(speech::LanguageCode language_code,
                                              std::u16string message) {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   const bool is_live_caption_enabled = controller->live_caption().enabled();
   // Only show updates for this feature if the language code applies to the SODA
@@ -615,7 +615,7 @@
 }
 
 void AudioDetailedView::ToggleLiveCaptionState() {
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   // Updates the enable state for live caption.
   controller->live_caption().SetEnabled(!controller->live_caption().enabled());
diff --git a/ash/system/audio/audio_effects_controller.cc b/ash/system/audio/audio_effects_controller.cc
index af18b3d..881d034 100644
--- a/ash/system/audio/audio_effects_controller.cc
+++ b/ash/system/audio/audio_effects_controller.cc
@@ -94,7 +94,7 @@
     }
     case VcEffectId::kLiveCaption: {
       // Toggle live caption.
-      AccessibilityControllerImpl* controller =
+      AccessibilityController* controller =
           Shell::Get()->accessibility_controller();
       controller->live_caption().SetEnabled(
           !controller->live_caption().enabled());
diff --git a/ash/system/audio/audio_effects_controller_unittest.cc b/ash/system/audio/audio_effects_controller_unittest.cc
index cf657d07..0a59cc4 100644
--- a/ash/system/audio/audio_effects_controller_unittest.cc
+++ b/ash/system/audio/audio_effects_controller_unittest.cc
@@ -518,7 +518,7 @@
   SimulateUserLogin("testuser1@gmail.com");
 
   // Explicitly disable live caption, confirm that it is disabled.
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->live_caption().SetEnabled(false);
   EXPECT_FALSE(controller->live_caption().enabled());
@@ -541,7 +541,7 @@
   SimulateUserLogin("testuser1@gmail.com");
 
   // Explicitly enable live caption, confirm that it is enabled.
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->live_caption().SetEnabled(true);
   EXPECT_TRUE(controller->live_caption().enabled());
@@ -564,7 +564,7 @@
   SimulateUserLogin("testuser1@gmail.com");
 
   // Explicitly enable live caption, confirm that it is enabled.
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->live_caption().SetEnabled(true);
   EXPECT_TRUE(controller->live_caption().enabled());
@@ -588,7 +588,7 @@
   SimulateUserLogin("testuser1@gmail.com");
 
   // Explicitly disable live caption, confirm that it is disabled.
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   controller->live_caption().SetEnabled(false);
   EXPECT_FALSE(controller->live_caption().enabled());
diff --git a/ash/system/audio/unified_volume_view.cc b/ash/system/audio/unified_volume_view.cc
index cf404c2..26de3e8 100644
--- a/ash/system/audio/unified_volume_view.cc
+++ b/ash/system/audio/unified_volume_view.cc
@@ -47,7 +47,6 @@
           &kQuickSettingsRightArrowIcon,
           IDS_ASH_STATUS_TRAY_AUDIO))),
       is_active_output_node_(is_active_output_node),
-      a11y_controller_(Shell::Get()->accessibility_controller()),
       device_id_(CrasAudioHandler::Get()->GetPrimaryActiveOutputNode()) {
   CrasAudioHandler::Get()->AddAudioObserver(this);
 
@@ -62,8 +61,9 @@
   more_button_->SetIconColor(cros_tokens::kCrosSysSecondary);
   // TODO(b/257151067): Update the a11y name id.
   // Adds the live caption button before `more_button_`.
-  a11y_controller_->AddObserver(this);
-  const bool enabled = a11y_controller_->live_caption().enabled();
+  Shell::Get()->accessibility_controller()->AddObserver(this);
+  const bool enabled =
+      Shell::Get()->accessibility_controller()->live_caption().enabled();
   live_caption_button_ = AddChildViewAt(
       std::make_unique<IconButton>(
           base::BindRepeating(&UnifiedVolumeView::OnLiveCaptionButtonPressed,
@@ -112,7 +112,6 @@
                         Style::kRadioActive),
       more_button_(nullptr),
       is_active_output_node_(is_active_output_node),
-      a11y_controller_(Shell::Get()->accessibility_controller()),
       device_id_(device_id) {
   CrasAudioHandler::Get()->AddAudioObserver(this);
 
@@ -131,7 +130,7 @@
 
 UnifiedVolumeView::~UnifiedVolumeView() {
   CrasAudioHandler::Get()->RemoveAudioObserver(this);
-  a11y_controller_->RemoveObserver(this);
+  Shell::Get()->accessibility_controller()->RemoveObserver(this);
 }
 
 void UnifiedVolumeView::Update(bool by_user) {
@@ -221,8 +220,8 @@
 }
 
 void UnifiedVolumeView::OnLiveCaptionButtonPressed() {
-  a11y_controller_->live_caption().SetEnabled(
-      !a11y_controller_->live_caption().enabled());
+  Shell::Get()->accessibility_controller()->live_caption().SetEnabled(
+      !Shell::Get()->accessibility_controller()->live_caption().enabled());
 }
 
 void UnifiedVolumeView::OnOutputNodeVolumeChanged(uint64_t node_id,
@@ -253,7 +252,8 @@
 }
 
 void UnifiedVolumeView::OnAccessibilityStatusChanged() {
-  const bool enabled = a11y_controller_->live_caption().enabled();
+  const bool enabled =
+      Shell::Get()->accessibility_controller()->live_caption().enabled();
 
   // Sets `live_caption_button_` toggle state to update its icon, icon color,
   // and background color.
diff --git a/ash/system/audio/unified_volume_view.h b/ash/system/audio/unified_volume_view.h
index 74ef813..dac3f2f7 100644
--- a/ash/system/audio/unified_volume_view.h
+++ b/ash/system/audio/unified_volume_view.h
@@ -91,7 +91,6 @@
   // Whether this `UnifiedVolumeView` is the view for the active output node.
   bool const is_active_output_node_;
 
-  const raw_ptr<AccessibilityControllerImpl, ExperimentalAsh> a11y_controller_;
   uint64_t device_id_ = 0;
   // Owned by the views hierarchy.
   raw_ptr<IconButton, ExperimentalAsh> live_caption_button_ = nullptr;
diff --git a/ash/system/focus_mode/focus_mode_chip_carousel.cc b/ash/system/focus_mode/focus_mode_chip_carousel.cc
index 2a7e2be6..20491c9 100644
--- a/ash/system/focus_mode/focus_mode_chip_carousel.cc
+++ b/ash/system/focus_mode/focus_mode_chip_carousel.cc
@@ -7,6 +7,7 @@
 #include "ash/api/tasks/tasks_types.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "base/containers/adapters.h"
+#include "base/i18n/rtl.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/compositor/layer.h"
@@ -139,7 +140,6 @@
   right_overflow_icon_->SetBoundsRect(
       gfx::Rect(contents_bounds.right() - kOverflowButtonWidth, y,
                 kOverflowButtonWidth, h));
-  scroll_view_->SetBoundsRect(contents_bounds);
 
   UpdateGradient();
 }
@@ -202,8 +202,9 @@
   const float gradient_end_position =
       (chevron_space + kGradientWidth) / scroll_view_->bounds().width();
 
-  // Left fade in section.
-  if (show_left_gradient) {
+  // Left fade in section. Gradients don't account for RTL like other `Views`
+  // coordinates do, so we need to flip to account for RTL ourselves.
+  if (base::i18n::IsRTL() ? show_right_gradient : show_left_gradient) {
     gradient_mask.AddStep(/*fraction=*/0, /*alpha=*/0);
     if (hovered) {
       gradient_mask.AddStep(gradient_start_position, 0);
@@ -212,7 +213,7 @@
   }
 
   // Right fade out section.
-  if (show_right_gradient) {
+  if (base::i18n::IsRTL() ? show_left_gradient : show_right_gradient) {
     gradient_mask.AddStep(/*fraction=*/(1 - gradient_end_position),
                           /*alpha=*/255);
     if (hovered) {
diff --git a/ash/system/focus_mode/focus_mode_chip_carousel_unittest.cc b/ash/system/focus_mode/focus_mode_chip_carousel_unittest.cc
index 9d92f57..471b99f 100644
--- a/ash/system/focus_mode/focus_mode_chip_carousel_unittest.cc
+++ b/ash/system/focus_mode/focus_mode_chip_carousel_unittest.cc
@@ -7,6 +7,7 @@
 #include "ash/api/tasks/tasks_types.h"
 #include "ash/constants/ash_features.h"
 #include "ash/test/ash_test_base.h"
+#include "base/i18n/rtl.h"
 #include "base/test/scoped_feature_list.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "ui/compositor/layer.h"
@@ -25,6 +26,9 @@
                                             "Podcast interview Script",
                                             "Book a flight to Seoul"};
 
+constexpr int kWidgetWidth = 320;
+constexpr float kGradientWidth = 16;
+
 }  // namespace
 
 namespace ash {
@@ -38,7 +42,7 @@
   void SetUp() override {
     AshTestBase::SetUp();
     widget_ = CreateFramelessTestWidget();
-    widget_->SetBounds(gfx::Rect(/*width=*/320, /*height=*/48));
+    widget_->SetBounds(gfx::Rect(/*width=*/kWidgetWidth, /*height=*/48));
 
     focus_mode_chip_carousel_ = widget_->SetContentsView(
         std::make_unique<FocusModeChipCarousel>(base::DoNothing()));
@@ -185,7 +189,8 @@
   EXPECT_TRUE(GetRightOverflowIcon()->GetVisible());
 
   // Both overflow icons should be shown on top of the scroll view.
-  EXPECT_EQ(gfx::Size(320, 32), GetScrollView()->GetBoundsInScreen().size());
+  EXPECT_EQ(gfx::Size(kWidgetWidth, 32),
+            GetScrollView()->GetBoundsInScreen().size());
   EXPECT_EQ(gfx::Size(28, 32),
             GetLeftOverflowIcon()->GetBoundsInScreen().size());
   EXPECT_EQ(gfx::Size(28, 32),
@@ -200,4 +205,27 @@
           GetScrollView()->GetVisibleRect())));
 }
 
+// Tests that the gradient shows up on the correct side in RTL.
+TEST_F(FocusModeChipCarouselTest, GradientInRTL) {
+  base::i18n::SetRTLForTesting(true);
+
+  auto tasks = MakeTasks(kTestTaskTitles);
+  focus_mode_chip_carousel()->SetTasks(GetTaskPtrs(tasks));
+  views::test::RunScheduledLayout(focus_mode_chip_carousel());
+  EXPECT_TRUE(GetScrollView()->layer()->HasGradientMask());
+
+  // In RTL the carousel starts on the right side, so we can only scroll to the
+  // left and not to the right. Because of this the gradient should only be
+  // shown on the left side.
+  ASSERT_EQ(2u, GetScrollView()->layer()->gradient_mask().step_count());
+  auto steps = GetScrollView()->layer()->gradient_mask().steps();
+  const float allowed_difference = 0.0001f;
+
+  EXPECT_FLOAT_EQ(0.0f, steps.front().fraction);
+  EXPECT_EQ(0u, steps.front().alpha);
+  EXPECT_NEAR(kGradientWidth / kWidgetWidth, steps[1].fraction,
+              allowed_difference);
+  EXPECT_EQ(255u, steps[1].alpha);
+}
+
 }  // namespace ash
diff --git a/ash/system/phonehub/camera_roll_thumbnail_unittest.cc b/ash/system/phonehub/camera_roll_thumbnail_unittest.cc
index a94531e..d5b2e94 100644
--- a/ash/system/phonehub/camera_roll_thumbnail_unittest.cc
+++ b/ash/system/phonehub/camera_roll_thumbnail_unittest.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_id.h"
 #include "ash/style/ash_color_provider.h"
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
@@ -134,9 +135,9 @@
     if (is_video) {
       cc::PaintFlags flags;
       flags.setAntiAlias(true);
-      flags.setColor(provider->GetBaseLayerColor(
-          AshColorProvider::BaseLayerType::kTransparent80));
       flags.setStyle(cc::PaintFlags::kFill_Style);
+      flags.setColor(camera_roll_thumbnail()->GetColorProvider()->GetColor(
+          kColorAshShieldAndBase80));
       expected.DrawCircle(kExpectedCameraRollThumbnailVideoCircleOrigin,
                           kExpectedCameraRollThumbnailVideoCircleRadius, flags);
       expected.DrawImageInt(
diff --git a/ash/system/tray/tray_background_view_unittest.cc b/ash/system/tray/tray_background_view_unittest.cc
index 217b08ab..a6b4055 100644
--- a/ash/system/tray/tray_background_view_unittest.cc
+++ b/ash/system/tray/tray_background_view_unittest.cc
@@ -158,7 +158,7 @@
                 std::unique_ptr<TrayBackgroundView>(std::move(tmp))));
 
     // Set Dictation button to be visible.
-    AccessibilityControllerImpl* controller =
+    AccessibilityController* controller =
         Shell::Get()->accessibility_controller();
     controller->dictation().SetEnabled(true);
   }
diff --git a/ash/system/tray/tray_bubble_view.cc b/ash/system/tray/tray_bubble_view.cc
index 6189613f..6d9a557 100644
--- a/ash/system/tray/tray_bubble_view.cc
+++ b/ash/system/tray/tray_bubble_view.cc
@@ -272,7 +272,7 @@
   DCHECK((init_params.anchor_mode != TrayBubbleView::AnchorMode::kView) ||
          anchor_widget());
   set_parent_window(params_.parent_window);
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   SetCanActivate(controller->spoken_feedback().enabled() ||
                  controller->dictation().enabled());
diff --git a/ash/user_education/welcome_tour/welcome_tour_controller.h b/ash/user_education/welcome_tour/welcome_tour_controller.h
index 5739c22..a9b95620 100644
--- a/ash/user_education/welcome_tour/welcome_tour_controller.h
+++ b/ash/user_education/welcome_tour/welcome_tour_controller.h
@@ -27,7 +27,7 @@
 
 namespace ash {
 
-class AccessibilityControllerImpl;
+class AccessibilityController;
 class ScopedNudgePause;
 class ScopedToastPause;
 class SessionController;
@@ -141,7 +141,7 @@
 
   // The accessibility controller is observed only while the Welcome Tour is in
   // progress, and will trigger an abort of the tour if ChromeVox is enabled.
-  base::ScopedObservation<AccessibilityControllerImpl, AccessibilityObserver>
+  base::ScopedObservation<AccessibilityController, AccessibilityObserver>
       accessibility_observation_{this};
 
   // Sessions are observed only until the primary user session is activated for
diff --git a/ash/webui/common/backend/accessibility_features.cc b/ash/webui/common/backend/accessibility_features.cc
index 147103c..bab5e918 100644
--- a/ash/webui/common/backend/accessibility_features.cc
+++ b/ash/webui/common/backend/accessibility_features.cc
@@ -20,7 +20,7 @@
 namespace {
 
 bool ShouldForceHiddenElementsVisible() {
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   if (!accessibility_controller) {
     return false;
@@ -34,7 +34,7 @@
 }  // namespace
 
 AccessibilityFeatures::AccessibilityFeatures() {
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   if (!accessibility_controller) {
     return;
@@ -47,7 +47,7 @@
 }
 
 AccessibilityFeatures::~AccessibilityFeatures() {
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   if (!accessibility_controller) {
     return;
diff --git a/ash/webui/common/backend/accessibility_features_unittest.cc b/ash/webui/common/backend/accessibility_features_unittest.cc
index 29ffcf4..deeace1 100644
--- a/ash/webui/common/backend/accessibility_features_unittest.cc
+++ b/ash/webui/common/backend/accessibility_features_unittest.cc
@@ -105,7 +105,7 @@
   // Verify the observer is initialized with |force_visible| as false.
   ASSERT_FALSE(fake_observer_.force_visible());
 
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
 
   // Spoken feedback.
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
index d49e03a..23e90f7 100644
--- a/ash/wm/desks/desks_controller.cc
+++ b/ash/wm/desks/desks_controller.cc
@@ -11,7 +11,6 @@
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/notifier_catalogs.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/shelf_prefs.h"
 #include "ash/public/cpp/shelf_types.h"
@@ -1984,7 +1983,7 @@
 
   // We should only announce desks are being merged if we are combining desks.
   // Otherwise, we tell the user that the desk has closed with its windows.
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       shell->accessibility_controller();
   if (close_type == DeskCloseType::kCombineDesks) {
     accessibility_controller->TriggerAccessibilityAlertWithMessage(
diff --git a/ash/wm/desks/templates/saved_desk_dialog_controller.cc b/ash/wm/desks/templates/saved_desk_dialog_controller.cc
index 10caca6..640e3aa 100644
--- a/ash/wm/desks/templates/saved_desk_dialog_controller.cc
+++ b/ash/wm/desks/templates/saved_desk_dialog_controller.cc
@@ -221,7 +221,7 @@
   dialog_widget_observation_.Observe(dialog_widget_.get());
 
   // Ensure that if ChromeVox is enabled, it focuses on the dialog.
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   if (accessibility_controller->spoken_feedback().enabled()) {
     accessibility_controller->SetA11yOverrideWindow(
diff --git a/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc b/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
index 8c971b4..aabe4beb 100644
--- a/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
+++ b/ash/wm/desks/templates/saved_desk_save_desk_button_container.cc
@@ -137,7 +137,7 @@
  private:
   base::RepeatingClosure accessibility_state_changed_callback_;
 
-  base::ScopedObservation<AccessibilityControllerImpl, AccessibilityObserver>
+  base::ScopedObservation<AccessibilityController, AccessibilityObserver>
       observation_{this};
 };
 
diff --git a/ash/wm/desks/templates/saved_desk_unittest.cc b/ash/wm/desks/templates/saved_desk_unittest.cc
index 01d50c6..f7b8a92 100644
--- a/ash/wm/desks/templates/saved_desk_unittest.cc
+++ b/ash/wm/desks/templates/saved_desk_unittest.cc
@@ -4508,7 +4508,7 @@
   ui::ScopedAnimationDurationScaleMode animation_scale(
       ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
 
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
   accessibility_controller->spoken_feedback().SetEnabled(true);
   EXPECT_TRUE(accessibility_controller->spoken_feedback().enabled());
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index c5f2209..c1fc472 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -1107,7 +1107,7 @@
 TEST_P(OverviewSessionTest, DesksWidgetBoundsChangeWhenDisableChromeVox) {
   std::unique_ptr<aura::Window> window1 = CreateTestWindow();
 
-  AccessibilityControllerImpl* accessibility_controller =
+  AccessibilityController* accessibility_controller =
       Shell::Get()->accessibility_controller();
 
   // Enable ChromeVox.
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index 9819be1..46c705a 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -13,13 +13,11 @@
 
 #include "ash/accelerometer/accelerometer_reader.h"
 #include "ash/accelerometer/accelerometer_types.h"
-#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/accessibility/test_accessibility_controller_client.h"
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/constants/app_types.h"
 #include "ash/constants/ash_switches.h"
 #include "ash/display/screen_orientation_controller.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "ash/public/cpp/test/shell_test_api.h"
diff --git a/ash/wm/workspace/workspace_layout_manager_unittest.cc b/ash/wm/workspace/workspace_layout_manager_unittest.cc
index 49532b8..23ab272e 100644
--- a/ash/wm/workspace/workspace_layout_manager_unittest.cc
+++ b/ash/wm/workspace/workspace_layout_manager_unittest.cc
@@ -171,8 +171,7 @@
   }
 
  private:
-  raw_ptr<AccessibilityControllerImpl, ExperimentalAsh>
-      accessibility_controller_;
+  raw_ptr<AccessibilityController, ExperimentalAsh> accessibility_controller_;
   const bool enabled_;
 };
 
@@ -1972,7 +1971,7 @@
 TEST_F(WorkspaceLayoutManagerBackdropTest, SpokenFeedbackFullscreenBackground) {
   WorkspaceController* wc = ShellTestApi().workspace_controller();
   WorkspaceControllerTestApi test_helper(wc);
-  AccessibilityControllerImpl* controller =
+  AccessibilityController* controller =
       Shell::Get()->accessibility_controller();
   TestAccessibilityControllerClient client;
 
diff --git a/base/process/process_mac.cc b/base/process/process_mac.cc
index b8659de..9331a05 100644
--- a/base/process/process_mac.cc
+++ b/base/process/process_mac.cc
@@ -28,7 +28,7 @@
 // TASK_DEFAULT_APPLICATION.
 BASE_FEATURE(kMacSetDefaultTaskRole,
              "MacSetDefaultTaskRole",
-             FEATURE_DISABLED_BY_DEFAULT);
+             FEATURE_ENABLED_BY_DEFAULT);
 
 // Returns the `task_role_t` of the process whose task port is `task_port`.
 absl::optional<task_role_t> GetTaskCategoryPolicyRole(mach_port_t task_port) {
diff --git a/build/android/pylib/gtest/gtest_test_instance.py b/build/android/pylib/gtest/gtest_test_instance.py
index f1a150d..7d41f7e 100644
--- a/build/android/pylib/gtest/gtest_test_instance.py
+++ b/build/android/pylib/gtest/gtest_test_instance.py
@@ -110,6 +110,10 @@
 _RE_DISABLED = re.compile(r'DISABLED_')
 _RE_FLAKY = re.compile(r'FLAKY_')
 
+# Regex that matches the printout when there are test failures.
+# matches "[  FAILED  ] 1 test, listed below:"
+_RE_ANY_TESTS_FAILED = re.compile(r'\[ +FAILED +\].*listed below')
+
 # Detect stack line in stdout.
 _STACK_LINE_RE = re.compile(r'\s*#\d+')
 
@@ -229,6 +233,9 @@
       else:
         log.append(l)
 
+    if _RE_ANY_TESTS_FAILED.match(l):
+      break
+
     if result_type and test_name:
       # Don't bother symbolizing output if the test passed.
       if result_type == base_test_result.ResultType.PASS:
@@ -238,7 +245,10 @@
           log=symbolize_stack_and_merge_with_log()))
       test_name = None
 
-  handle_possibly_unknown_test()
+  else:
+    # Executing this after tests have finished with a failure causes a
+    # duplicate test entry to be added to results. crbug/1380825
+    handle_possibly_unknown_test()
 
   return results
 
diff --git a/build/rust/cargo_crate.gni b/build/rust/cargo_crate.gni
index 61ecfe2..cf16a68 100644
--- a/build/rust/cargo_crate.gni
+++ b/build/rust/cargo_crate.gni
@@ -195,6 +195,11 @@
     }
   }
 
+  _testonly = false
+  if (defined(invoker.testonly)) {
+    _testonly = invoker.testonly
+  }
+
   # The main target, either a Rust source set or an executable.
   target(_target_type, target_name) {
     forward_variables_from(invoker,
@@ -214,7 +219,11 @@
                                  "rustenv",
                                  "dev_deps",
                                ])
-    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
+
+    testonly = _testonly
+    if (defined(invoker.visibility)) {
+      visibility = invoker.visibility
+    }
     if (defined(crate_type) && crate_type == "cdylib") {
       # See comments above about cdylib.
       crate_type = "rlib"
@@ -327,6 +336,10 @@
       script = rebase_path("//build/rust/run_build_script.py")
       build_script_target = ":${_build_script_name}($rust_macro_toolchain)"
       deps = [ build_script_target ]
+      testonly = _testonly
+      if (defined(invoker.visibility)) {
+        visibility = invoker.visibility
+      }
 
       # The build script may be built with a different toolchain when
       # cross-compiling (the host toolchain) so we must find the path relative
@@ -391,6 +404,10 @@
       rust_executable(_build_script_name) {
         sources = invoker.build_sources
         crate_root = invoker.build_root
+        testonly = _testonly
+        if (defined(invoker.visibility)) {
+          visibility = invoker.visibility
+        }
         if (defined(invoker.build_deps)) {
           deps = invoker.build_deps
         }
diff --git a/build/rust/tests/BUILD.gn b/build/rust/tests/BUILD.gn
index 6514fdac..9e9ec8e 100644
--- a/build/rust/tests/BUILD.gn
+++ b/build/rust/tests/BUILD.gn
@@ -30,6 +30,7 @@
       "test_rlib_crate:target1",
       "test_rlib_crate:target2",
       "test_rlib_crate:test_rlib_crate_associated_bin",
+      "test_rlib_crate_testonly:testonly_target",
       "test_rust_metadata:test_rust_metadata_cc_exe",
       "test_rust_metadata:test_rust_metadata_exe",
       "test_rust_multiple_dep_versions_exe",
diff --git a/build/rust/tests/test_rlib_crate_testonly/BUILD.gn b/build/rust/tests/test_rlib_crate_testonly/BUILD.gn
new file mode 100644
index 0000000..d246702
--- /dev/null
+++ b/build/rust/tests/test_rlib_crate_testonly/BUILD.gn
@@ -0,0 +1,22 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/rust/cargo_crate.gni")
+
+cargo_crate("testonly_target") {
+  testonly = true
+  crate_root = "crate/src/main.rs"
+  crate_type = "bin"
+  sources = [ "crate/src/main.rs" ]
+  build_sources = [ "crate/build.rs" ]
+  build_root = "crate/build.rs"
+  build_deps = [ ":testonly_build_dep" ]
+}
+
+cargo_crate("testonly_build_dep") {
+  testonly = true
+  crate_name = "test_only_build_dep"
+  crate_root = "crate/src/lib.rs"
+  sources = [ "crate/src/lib.rs" ]
+}
diff --git a/build/rust/tests/test_rlib_crate_testonly/crate/build.rs b/build/rust/tests/test_rlib_crate_testonly/crate/build.rs
new file mode 100644
index 0000000..6d03310
--- /dev/null
+++ b/build/rust/tests/test_rlib_crate_testonly/crate/build.rs
@@ -0,0 +1,5 @@
+// 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.
+
+fn main() {}
diff --git a/build/rust/tests/test_rlib_crate_testonly/crate/src/lib.rs b/build/rust/tests/test_rlib_crate_testonly/crate/src/lib.rs
new file mode 100644
index 0000000..2c54a522
--- /dev/null
+++ b/build/rust/tests/test_rlib_crate_testonly/crate/src/lib.rs
@@ -0,0 +1,3 @@
+// 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.
diff --git a/build/rust/tests/test_rlib_crate_testonly/crate/src/main.rs b/build/rust/tests/test_rlib_crate_testonly/crate/src/main.rs
new file mode 100644
index 0000000..9d78366
--- /dev/null
+++ b/build/rust/tests/test_rlib_crate_testonly/crate/src/main.rs
@@ -0,0 +1,5 @@
+// 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.
+
+pub fn main() {}
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
index 1775ea5c..7bf4e528 100644
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -410,7 +410,6 @@
   "java/res/drawable/qr_code.xml",
   "java/res/drawable/reading_list_empty_state_illustration.xml",
   "java/res/drawable/safety_check.xml",
-  "java/res/drawable/screenshot.xml",
   "java/res/drawable/section_tab_background.xml",
   "java/res/drawable/send_tab.xml",
   "java/res/drawable/shared_clipboard_zero_state_dark.xml",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 887aeeda..dff1413a 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -151,6 +151,7 @@
   "javatests/src/org/chromium/chrome/browser/customtabs/CustomTabDeferredStartupTest.java",
   "javatests/src/org/chromium/chrome/browser/customtabs/CustomTabExternalNavigationTest.java",
   "javatests/src/org/chromium/chrome/browser/customtabs/CustomTabFromChromeExternalNavigationTest.java",
+  "javatests/src/org/chromium/chrome/browser/customtabs/CustomTabModalDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPostMessageTest.java",
   "javatests/src/org/chromium/chrome/browser/customtabs/CustomTabPrivacySandboxDialogTest.java",
   "javatests/src/org/chromium/chrome/browser/customtabs/CustomTabTabPersistenceIntegrationTest.java",
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphTest.java
index 3ff9e92e..857495c 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridIphTest.java
@@ -55,6 +55,7 @@
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
@@ -65,6 +66,8 @@
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
+import org.chromium.components.feature_engagement.FeatureConstants;
+import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
@@ -93,6 +96,7 @@
 @DisableFeatures({ChromeFeatureList.ARCHIVE_TAB_SERVICE, ChromeFeatureList.START_SURFACE_ANDROID})
 public class TabGridIphTest {
     private ModalDialogManager mModalDialogManager;
+    private Tracker mTracker;
 
     @Rule
     public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@@ -113,6 +117,18 @@
         mModalDialogManager =
                 TestThreadUtils.runOnUiThreadBlockingNoException(
                         mActivityTestRule.getActivity()::getModalDialogManager);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mTracker =
+                            TrackerFactory.getTrackerForProfile(
+                                    mActivityTestRule.getProfile(false));
+                });
+        CriteriaHelper.pollUiThread(mTracker::isInitialized);
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    return mTracker.wouldTriggerHelpUI(
+                            FeatureConstants.TAB_GROUPS_DRAG_AND_DROP_FEATURE);
+                });
     }
 
     @After
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
index 222e889..5b7dba6 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
@@ -879,10 +879,6 @@
     @UseMethodParameter(RefactorTestParams.class)
     @EnableFeatures({ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"})
     @CommandLineFlags.Add({BASE_PARAMS})
-    @DisableIf.Build(
-            message = "Flaky on Android P, see https://crbug.com/1063991",
-            sdk_is_greater_than = VERSION_CODES.O_MR1,
-            sdk_is_less_than = VERSION_CODES.Q)
     public void testIncognitoToggle_tabCount(boolean isStartSurfaceRefactorEnabled)
             throws InterruptedException {
         mActivityTestRule.loadUrl(mUrl);
diff --git a/chrome/android/java/res/drawable/screenshot.xml b/chrome/android/java/res/drawable/screenshot.xml
deleted file mode 100644
index 2ac9efbf9..0000000
--- a/chrome/android/java/res/drawable/screenshot.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2020 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="@macro/default_icon_color">
-    <path
-        android:pathData="m8.6,2h-4.192c-0.7776,0 -1.408,0.6304 -1.408,1.408v4.192"
-        android:strokeWidth="1.8"
-        android:fillColor="#00000000"
-        android:strokeColor="@android:color/white"
-        android:strokeLineCap="square"/>
-    <path
-        android:pathData="m8.6,18.8l-4.192,-0c-0.7776,-0 -1.408,-0.6304 -1.408,-1.408l0,-4.192"
-        android:strokeWidth="1.8"
-        android:fillColor="#00000000"
-        android:strokeColor="@android:color/white"
-        android:strokeLineCap="square"/>
-    <path
-        android:pathData="m14.2,2l4.192,0c0.7776,0 1.408,0.6304 1.408,1.408l-0,4.192"
-        android:strokeWidth="1.8"
-        android:fillColor="#00000000"
-        android:strokeColor="@android:color/white"
-        android:strokeLineCap="square"/>
-    <path
-        android:pathData="m19.8,12.5v9.8"
-        android:strokeWidth="1.8"
-        android:fillColor="#00000000"
-        android:strokeColor="@android:color/white"
-        android:strokeLineCap="square"/>
-    <path
-        android:pathData="m13.5,18.8l9.8,-0"
-        android:strokeWidth="1.8"
-        android:fillColor="#00000000"
-        android:strokeColor="@android:color/white"
-        android:strokeLineCap="square"/>
-</vector>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 486221c..6183dac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -128,7 +128,6 @@
 import org.chromium.chrome.browser.metrics.MainIntentBehaviorMetrics;
 import org.chromium.chrome.browser.metrics.SimpleStartupForegroundSessionDetector;
 import org.chromium.chrome.browser.modaldialog.ChromeTabModalPresenter;
-import org.chromium.chrome.browser.modaldialog.TabModalLifetimeHandler;
 import org.chromium.chrome.browser.multiwindow.MultiInstanceChromeTabbedActivity;
 import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
@@ -241,7 +240,6 @@
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.dragdrop.DragAndDropDelegate;
 import org.chromium.ui.dragdrop.DragAndDropDelegateImpl;
-import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.widget.Toast;
 import org.chromium.url.GURL;
 
@@ -331,7 +329,6 @@
     private HistoricalTabModelObserver mHistoricalTabModelObserver;
 
     private BrowserControlsVisibilityDelegate mVrBrowserControlsVisibilityDelegate;
-    private TabModalLifetimeHandler mTabModalHandler;
 
     private boolean mUIWithNativeInitialized;
 
@@ -2851,7 +2848,7 @@
     }
 
     private void onOmniboxFocusChanged(boolean hasFocus) {
-        mTabModalHandler.onOmniboxFocusChanged(hasFocus);
+        getTabModalLifetimeHandler().onOmniboxFocusChanged(hasFocus);
     }
 
     private void recordLauncherShortcutAction(boolean isIncognito) {
@@ -2876,7 +2873,7 @@
             return true;
         }
 
-        if (mTabModalHandler.onBackPressed()) {
+        if (getTabModalLifetimeHandler().onBackPressed()) {
             BackPressManager.record(BackPressHandler.Type.TAB_MODAL_HANDLER);
             return true;
         }
@@ -3443,6 +3440,11 @@
     }
 
     @Override
+    protected boolean supportsTabModalDialogs() {
+        return true;
+    }
+
+    @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         Boolean result =
                 KeyboardShortcuts.dispatchKeyEvent(
@@ -3540,29 +3542,7 @@
     }
 
     private ComposedBrowserControlsVisibilityDelegate getAppBrowserControlsVisibilityDelegate() {
-        // TODO(jinsukkim): Move this to RootUiCoordinator.
-        return ((TabbedRootUiCoordinator) mRootUiCoordinator)
-                .getAppBrowserControlsVisibilityDelegate();
-    }
-
-    @Override
-    protected ModalDialogManager createModalDialogManager() {
-        ModalDialogManager manager = super.createModalDialogManager();
-        // TODO(crbug.com/1157310): Transition this::method refs to dedicated suppliers.
-        mTabModalHandler =
-                new TabModalLifetimeHandler(
-                        this,
-                        getLifecycleDispatcher(),
-                        manager,
-                        this::getAppBrowserControlsVisibilityDelegate,
-                        this::getTabObscuringHandler,
-                        this::getToolbarManager,
-                        getContextualSearchManagerSupplier(),
-                        getTabModelSelectorSupplier(),
-                        this::getBrowserControlsManager,
-                        this::getFullscreenManager,
-                        mBackPressManager);
-        return manager;
+        return mRootUiCoordinator.getAppBrowserControlsVisibilityDelegate();
     }
 
     // App Menu related code -----------------------------------------------------------------------
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index d20186ca..39988c451 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -129,6 +129,7 @@
 import org.chromium.chrome.browser.metrics.ActivityTabStartupMetricsTracker;
 import org.chromium.chrome.browser.metrics.LaunchMetrics;
 import org.chromium.chrome.browser.metrics.UmaSessionStats;
+import org.chromium.chrome.browser.modaldialog.TabModalLifetimeHandler;
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.night_mode.SystemNightModeMonitor;
 import org.chromium.chrome.browser.night_mode.WebContentsDarkModeController;
@@ -402,6 +403,8 @@
     private boolean mIsRecreatingForTabletModeChange;
     // This is only used on automotive.
     private @Nullable MissingDeviceLockLauncher mMissingDeviceLockLauncher;
+    // Handling the dismissal of tab modal dialog.
+    private TabModalLifetimeHandler mTabModalLifetimeHandler;
 
     protected ChromeActivity() {
         mManualFillingComponentSupplier.set(ManualFillingComponentFactory.createComponent());
@@ -1785,8 +1788,40 @@
 
     @Override
     protected ModalDialogManager createModalDialogManager() {
-        return new ModalDialogManager(
-                new AppModalPresenter(this), ModalDialogManager.ModalDialogType.APP);
+        var dialogManager =
+                new ModalDialogManager(
+                        new AppModalPresenter(this), ModalDialogManager.ModalDialogType.APP);
+        // TODO(crbug.com/1157310): Transition this::method refs to dedicated suppliers.
+        if (supportsTabModalDialogs()) {
+            mTabModalLifetimeHandler =
+                    new TabModalLifetimeHandler(
+                            this,
+                            getLifecycleDispatcher(),
+                            dialogManager,
+                            () -> mRootUiCoordinator.getAppBrowserControlsVisibilityDelegate(),
+                            this::getTabObscuringHandler,
+                            this::getToolbarManager,
+                            getContextualSearchManagerSupplier(),
+                            getTabModelSelectorSupplier(),
+                            this::getBrowserControlsManager,
+                            this::getFullscreenManager,
+                            mBackPressManager);
+        }
+        return dialogManager;
+    }
+
+    /**
+     * Whether tab modal dialog is supported. If not, a dialog will be shown as a App modal dialog.
+     *
+     * @return True if tab modal dialog is supported.
+     */
+    protected boolean supportsTabModalDialogs() {
+        return false;
+    }
+
+    @Nullable
+    protected TabModalLifetimeHandler getTabModalLifetimeHandler() {
+        return mTabModalLifetimeHandler;
     }
 
     protected Drawable getBackgroundDrawable() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index 8ece752..dc07728 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -571,6 +571,12 @@
 
     @Override
     protected boolean handleBackPressed() {
+        if (!BackPressManager.isEnabled()
+                && getTabModalLifetimeHandler() != null
+                && getTabModalLifetimeHandler().onBackPressed()) {
+            BackPressManager.record(BackPressHandler.Type.TAB_MODAL_HANDLER);
+            return true;
+        }
         if (BackPressManager.correctTabNavigationOnFallback()) {
             if (getToolbarManager() != null && getToolbarManager().back()) {
                 return true;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 03464dfd..a763b26 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -241,6 +241,11 @@
         }
         mTabController = tabController;
         mMinimizeDelegateSupplier = minimizeDelegateSupplier;
+        // TODO(https://crbug.com/1509163): move this RootUiCoordinator once this flag is removed.
+        if (ChromeFeatureList.sCctTabModalDialog.isEnabled()) {
+            getAppBrowserControlsVisibilityDelegate()
+                    .addDelegate(browserControlsManager.getBrowserVisibilityDelegate());
+        }
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index e2cd01f2..04ae8322 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -377,6 +377,11 @@
         return new CustomTabLaunchCauseMetrics(this);
     }
 
+    @Override
+    protected boolean supportsTabModalDialogs() {
+        return ChromeFeatureList.sCctTabModalDialog.isEnabled();
+    }
+
     public NightModeStateProvider getNightModeStateProviderForTesting() {
         return super.getNightModeStateProvider();
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserver.java
index b9108d2c5..aac2454b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserver.java
@@ -4,10 +4,8 @@
 
 package org.chromium.chrome.browser.customtabs.content;
 
-import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.NONE;
 import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.ON_SCROLL_END;
 
-import android.graphics.Point;
 import android.os.Bundle;
 import android.os.SystemClock;
 
@@ -21,8 +19,6 @@
 import org.chromium.base.ResettersForTesting;
 import org.chromium.base.UserData;
 import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency;
-import org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.EnumType;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver;
 import org.chromium.chrome.browser.customtabs.features.TabInteractionRecorder;
@@ -62,13 +58,9 @@
     @VisibleForTesting protected static final String REAL_VALUES = "real_values";
     private static final int STUB_PERCENT = 0;
 
-    // Feature param for the time after the scroll-end a scroll update is allowed.
-    @VisibleForTesting
-    protected static final String TIME_CAN_UPDATE_AFTER_END = "time_can_update_after_end";
-
     // This value was chosen based on experiment data. 300ms covers about 98% of the scrolls while
     // trying to increase coverage further would require an unreasonably high threshold.
-    private static final int DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS = 300;
+    @VisibleForTesting static final int DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS = 300;
 
     private static final String TIME_SCROLL_UPDATE_RECEIVED_AFTER_SCROLL_END =
             "CustomTabs.TimeScrollUpdateReceivedAfterScrollEnd";
@@ -84,8 +76,7 @@
     @Nullable private GestureStateListener mGestureStateListener;
     @Nullable private WebContentsObserver mEngagementSignalWebContentsObserver;
     @Nullable private ScrollState mScrollState;
-    private @RootScrollOffsetUpdateFrequency.EnumType int mScrollOffsetUpdateFrequency;
-    private int mAfterScrollEndThresholdMs;
+
     // Tracks the user interaction state across multiple tabs and WebContents.
     private boolean mDidGetUserInteraction;
     // Prevents sending Engagement Signals temporarily.
@@ -114,16 +105,6 @@
         mTabObserverRegistrar = tabObserverRegistrar;
         mCallback = callback;
 
-        mScrollOffsetUpdateFrequency =
-                ChromeFeatureList.isEnabled(
-                                ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL)
-                        ? ON_SCROLL_END
-                        : NONE;
-        mAfterScrollEndThresholdMs =
-                ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
-                        ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL,
-                        TIME_CAN_UPDATE_AFTER_END,
-                        DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS);
         mShouldSendRealValues = shouldSendRealValues();
 
         mPendingInitialUpdate = hadScrollDown;
@@ -229,7 +210,6 @@
 
         mWebContents = tab.getWebContents();
         mScrollState = ScrollState.from(tab);
-        mScrollState.setParams(mScrollOffsetUpdateFrequency, mAfterScrollEndThresholdMs);
 
         mGestureStateListener =
                 new GestureStateListener() {
@@ -245,26 +225,8 @@
                     }
 
                     @Override
-                    public void onScrollUpdateGestureConsumed(@Nullable Point rootScrollOffset) {
-                        if (mScrollOffsetUpdateFrequency == ON_SCROLL_END) return;
-
-                        if (rootScrollOffset != null) {
-                            RenderCoordinates renderCoordinates =
-                                    RenderCoordinates.fromWebContents(tab.getWebContents());
-                            // We don't care about the return value of #onScrollUpdate here because
-                            // this method will always be called before #onScrollEnded.
-                            mScrollState.onScrollUpdate(
-                                    rootScrollOffset.y,
-                                    renderCoordinates.getMaxVerticalScrollPixInt(),
-                                    false);
-                        }
-                    }
-
-                    @Override
                     public void onScrollOffsetOrExtentChanged(
                             int scrollOffsetY, int scrollExtentY) {
-                        if (mScrollOffsetUpdateFrequency == NONE && !mPendingInitialUpdate) return;
-
                         assert tab != null;
                         RenderCoordinates renderCoordinates =
                                 RenderCoordinates.fromWebContents(tab.getWebContents());
@@ -330,7 +292,7 @@
         GestureListenerManager gestureListenerManager =
                 GestureListenerManager.fromWebContents(mWebContents);
         if (!gestureListenerManager.hasListener(mGestureStateListener)) {
-            gestureListenerManager.addListener(mGestureStateListener, mScrollOffsetUpdateFrequency);
+            gestureListenerManager.addListener(mGestureStateListener, ON_SCROLL_END);
         }
         mWebContents.addObserver(mEngagementSignalWebContentsObserver);
     }
@@ -428,23 +390,10 @@
         boolean mIsDirectionUp;
         int mMaxScrollPercentage;
         int mMaxReportedScrollPercentage;
-        @RootScrollOffsetUpdateFrequency.EnumType int mScrollOffsetUpdateFrequency = NONE;
-        int mAfterScrollEndThresholdMs = DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS;
         Long mTimeLastOnScrollEnded;
         boolean mHadFirstDownScroll;
 
         /**
-         * @param frequency The {@link RootScrollOffsetUpdateFrequency.EnumType}, can be |NONE| or
-         *                  |ON_SCROLL_END|.
-         * @param afterScrollEndThreshold The after scroll-end threshold in ms, ignored if the
-         *                                frequency isn't |ON_SCROLL_END|.
-         */
-        void setParams(@EnumType int frequency, int afterScrollEndThreshold) {
-            mScrollOffsetUpdateFrequency = frequency;
-            mAfterScrollEndThresholdMs = afterScrollEndThreshold;
-        }
-
-        /**
          * @param isDirectionUp Whether the scroll direction is up.
          * @return Whether there has been a down scroll.
          */
@@ -516,7 +465,7 @@
                 mMaxReportedScrollPercentage = maxScrollPercentageFivesMultiple;
                 reportedPercentage = mMaxReportedScrollPercentage;
             }
-            if (mScrollOffsetUpdateFrequency == ON_SCROLL_END && allowUpdateAfter) {
+            if (allowUpdateAfter) {
                 mTimeLastOnScrollEnded = SystemClock.elapsedRealtime();
             }
             mIsScrollActive = false;
@@ -543,7 +492,7 @@
         private boolean isValidUpdateAfterScrollEnd() {
             return !mIsScrollActive
                     && mTimeLastOnScrollEnded != null
-                    && timeSinceLastOnScrollEndedMillis() <= mAfterScrollEndThresholdMs;
+                    && timeSinceLastOnScrollEndedMillis() <= DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS;
         }
 
         private long timeSinceLastOnScrollEndedMillis() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index 5d736b26..52e84bb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -946,9 +946,7 @@
 
     @Override
     public CaptureReadinessResult isReadyForTextureCapture() {
-        if (ToolbarFeatures.shouldBlockCapturesForAblation()) {
-            return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.SCROLL_ABLATION);
-        } else if (ToolbarFeatures.shouldSuppressCaptures()) {
+        if (ToolbarFeatures.shouldSuppressCaptures()) {
             CustomTabCaptureStateToken currentToken = generateCaptureStateToken();
             final @ToolbarSnapshotDifference int difference =
                     currentToken.getAnyDifference(mLastCustomTabCaptureStateToken);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
index 79e61da7..b3c701c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabbed_mode/TabbedRootUiCoordinator.java
@@ -119,7 +119,6 @@
 import org.chromium.chrome.features.start_surface.StartSurfaceUserData;
 import org.chromium.components.browser_ui.accessibility.PageZoomCoordinator;
 import org.chromium.components.browser_ui.bottomsheet.EmptyBottomSheetObserver;
-import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
 import org.chromium.components.browser_ui.widget.InsetObserver;
 import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
 import org.chromium.components.browser_ui.widget.TouchEventObserver;
@@ -159,7 +158,6 @@
     private NotificationPermissionController mNotificationPermissionController;
     private HistoryNavigationCoordinator mHistoryNavigationCoordinator;
     private NavigationSheet mNavigationSheet;
-    private ComposedBrowserControlsVisibilityDelegate mAppBrowserControlsVisibilityDelegate;
     private LayoutManagerImpl mLayoutManager;
     private CommerceSubscriptionsService mCommerceSubscriptionsService;
     private UndoGroupSnackbarController mUndoGroupSnackbarController;
@@ -1070,16 +1068,6 @@
         return EdgeToEdgeControllerFactory.isSupportedConfiguration(mActivity);
     }
 
-    /**
-     * @return {@link ComposedBrowserControlsVisibilityDelegate} object for tabbed activity.
-     */
-    public ComposedBrowserControlsVisibilityDelegate getAppBrowserControlsVisibilityDelegate() {
-        if (mAppBrowserControlsVisibilityDelegate == null) {
-            mAppBrowserControlsVisibilityDelegate = new ComposedBrowserControlsVisibilityDelegate();
-        }
-        return mAppBrowserControlsVisibilityDelegate;
-    }
-
     public StatusIndicatorCoordinator getStatusIndicatorCoordinatorForTesting() {
         return mStatusIndicatorCoordinator;
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index 364389c..15ee19d39 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -164,6 +164,7 @@
 import org.chromium.components.browser_ui.bottomsheet.ManagedBottomSheetController;
 import org.chromium.components.browser_ui.device_lock.DeviceLockActivityLauncher;
 import org.chromium.components.browser_ui.device_lock.DeviceLockActivityLauncherSupplier;
+import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
 import org.chromium.components.browser_ui.widget.MenuOrKeyboardActionController;
 import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
 import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
@@ -331,6 +332,7 @@
     private FoldTransitionController mFoldTransitionController;
     private RestoreTabsFeatureHelper mRestoreTabsFeatureHelper;
     private @Nullable EdgeToEdgeController mE2eController;
+    private ComposedBrowserControlsVisibilityDelegate mAppBrowserControlsVisibilityDelegate;
     private @Nullable BoardingPassController mBoardingPassController;
 
     /**
@@ -1789,6 +1791,16 @@
     }
 
     /**
+     * @return {@link ComposedBrowserControlsVisibilityDelegate} object for tabbed activity.
+     */
+    public ComposedBrowserControlsVisibilityDelegate getAppBrowserControlsVisibilityDelegate() {
+        if (mAppBrowserControlsVisibilityDelegate == null) {
+            mAppBrowserControlsVisibilityDelegate = new ComposedBrowserControlsVisibilityDelegate();
+        }
+        return mAppBrowserControlsVisibilityDelegate;
+    }
+
+    /**
      * Gets the browser controls manager, creates it unless already created.
      * @deprecated Instead, inject this directly to your constructor. If that's not possible, then
      *         use {@link BrowserControlsManagerSupplier}.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabModalDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabModalDialogTest.java
new file mode 100644
index 0000000..3737c71
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabModalDialogTest.java
@@ -0,0 +1,287 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.customtabs;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.hamcrest.Matchers.is;
+
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.Criteria;
+import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.HistogramWatcher;
+import org.chromium.cc.input.BrowserControlsState;
+import org.chromium.chrome.browser.back_press.BackPressManager;
+import org.chromium.chrome.browser.firstrun.FirstRunStatus;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
+import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
+import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.content_public.browser.test.util.UiUtils;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.ui.modaldialog.DialogDismissalCause;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
+
+@Batch(Batch.PER_CLASS)
+@RunWith(ChromeJUnit4ClassRunner.class)
+public class CustomTabModalDialogTest {
+
+    @Rule
+    public CustomTabActivityTestRule mCustomTabActivityTestRule = new CustomTabActivityTestRule();
+
+    private static final String TEST_PAGE = "/chrome/test/data/android/google.html";
+    private static final String TEST_PAGE_2 = "/chrome/test/data/android/test.html";
+    private String mTestPage;
+    private String mTestPage2;
+    private EmbeddedTestServer mTestServer;
+
+    @Before
+    public void setUp() throws Exception {
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(true));
+        Context appContext = getInstrumentation().getTargetContext().getApplicationContext();
+        mTestServer = EmbeddedTestServer.createAndStartServer(appContext);
+        mTestPage = mTestServer.getURL(TEST_PAGE);
+        mTestPage2 = mTestServer.getURL(TEST_PAGE_2);
+        LibraryLoader.getInstance().ensureInitialized();
+    }
+
+    @After
+    public void tearDown() {
+        TestThreadUtils.runOnUiThreadBlocking(() -> FirstRunStatus.setFirstRunFlowComplete(false));
+
+        // finish() is called on a non-UI thread by the testing harness. Must hide the menu
+        // first, otherwise the UI is manipulated on a non-UI thread.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    if (getActivity() == null) return;
+                    AppMenuCoordinator coordinator =
+                            mCustomTabActivityTestRule.getAppMenuCoordinator();
+                    // CCT doesn't always have a menu (ex. in the media viewer).
+                    if (coordinator == null) return;
+                    AppMenuHandler handler = coordinator.getAppMenuHandler();
+                    if (handler != null) handler.hideAppMenu();
+                });
+    }
+
+    private CustomTabActivity getActivity() {
+        return mCustomTabActivityTestRule.getActivity();
+    }
+
+    @Test
+    @SmallTest
+    @Features.EnableFeatures(ChromeFeatureList.CCT_TAB_MODAL_DIALOG)
+    public void testShowAndDismissTabModalDialog() throws InterruptedException {
+        Context context = getInstrumentation().getTargetContext().getApplicationContext();
+        Intent intent = CustomTabsIntentTestUtils.createMinimalCustomTabIntent(context, mTestPage);
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
+
+        var visibilityDelegate =
+                mCustomTabActivityTestRule
+                        .getActivity()
+                        .getRootUiCoordinatorForTesting()
+                        .getAppBrowserControlsVisibilityDelegate();
+
+        ModalDialogManager dialogManager =
+                mCustomTabActivityTestRule.getActivity().getModalDialogManagerSupplier().get();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    PropertyModel dialog =
+                            new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
+                                    .with(ModalDialogProperties.TITLE, "test")
+                                    .with(
+                                            ModalDialogProperties.POSITIVE_BUTTON_TEXT,
+                                            context.getString(
+                                                    org.chromium.chrome.test.R.string.delete))
+                                    .with(
+                                            ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
+                                            context.getString(
+                                                    org.chromium.chrome.test.R.string.cancel))
+                                    .with(
+                                            ModalDialogProperties.CONTROLLER,
+                                            new ModalDialogProperties.Controller() {
+                                                @Override
+                                                public void onClick(
+                                                        PropertyModel model, int buttonType) {}
+
+                                                @Override
+                                                public void onDismiss(
+                                                        PropertyModel model, int dismissalCause) {}
+                                            })
+                                    .build();
+
+                    dialogManager.showDialog(dialog, ModalDialogManager.ModalDialogType.TAB);
+                });
+
+        Assert.assertNotNull(visibilityDelegate.get());
+        Assert.assertEquals(
+                "Browser Control should be SHOWN when dialog is being displayed.",
+                BrowserControlsState.SHOWN,
+                (int) visibilityDelegate.get());
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> dialogManager.dismissAllDialogs(DialogDismissalCause.DISMISSED_BY_NATIVE));
+
+        UiUtils.settleDownUI(InstrumentationRegistry.getInstrumentation());
+        Assert.assertEquals(
+                "Browser Control State should be BOTH when dialog becomes hidden.",
+                BrowserControlsState.BOTH,
+                (int) visibilityDelegate.get());
+    }
+
+    @Test
+    @SmallTest
+    public void testNavigationDismissTabModalDialog() {
+        Context context = getInstrumentation().getTargetContext().getApplicationContext();
+        Intent intent = CustomTabsIntentTestUtils.createMinimalCustomTabIntent(context, mTestPage);
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
+        final Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
+
+        ModalDialogManager dialogManager =
+                mCustomTabActivityTestRule.getActivity().getModalDialogManagerSupplier().get();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    PropertyModel dialog =
+                            new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
+                                    .with(ModalDialogProperties.TITLE, "test")
+                                    .with(
+                                            ModalDialogProperties.POSITIVE_BUTTON_TEXT,
+                                            context.getString(
+                                                    org.chromium.chrome.test.R.string.delete))
+                                    .with(
+                                            ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
+                                            context.getString(
+                                                    org.chromium.chrome.test.R.string.cancel))
+                                    .with(
+                                            ModalDialogProperties.CONTROLLER,
+                                            new ModalDialogProperties.Controller() {
+                                                @Override
+                                                public void onClick(
+                                                        PropertyModel model, int buttonType) {}
+
+                                                @Override
+                                                public void onDismiss(
+                                                        PropertyModel model, int dismissalCause) {}
+                                            })
+                                    .build();
+
+                    dialogManager.showDialog(dialog, ModalDialogManager.ModalDialogType.TAB);
+                });
+
+        CriteriaHelper.pollUiThread(() -> dialogManager.isShowing());
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) () -> tab.loadUrl(new LoadUrlParams(mTestPage2)));
+        ChromeTabUtils.waitForTabPageLoaded(tab, mTestPage2);
+
+        Assert.assertTrue(tab.canGoBack());
+        Assert.assertFalse(tab.canGoForward());
+
+        CriteriaHelper.pollUiThread(() -> !dialogManager.isShowing());
+    }
+
+    @Test
+    @SmallTest
+    @Features.DisableFeatures(ChromeFeatureList.BACK_GESTURE_REFACTOR)
+    public void testBackPressDismissTabModalDialog() {
+        Context context = getInstrumentation().getTargetContext().getApplicationContext();
+        Intent intent = CustomTabsIntentTestUtils.createMinimalCustomTabIntent(context, mTestPage);
+        mCustomTabActivityTestRule.startCustomTabActivityWithIntent(intent);
+        final Tab tab = mCustomTabActivityTestRule.getActivity().getActivityTab();
+
+        ModalDialogManager dialogManager =
+                mCustomTabActivityTestRule.getActivity().getModalDialogManagerSupplier().get();
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                (Runnable) () -> tab.loadUrl(new LoadUrlParams(mTestPage2)));
+        ChromeTabUtils.waitForTabPageLoaded(tab, mTestPage2);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    PropertyModel dialog =
+                            new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
+                                    .with(ModalDialogProperties.TITLE, "test")
+                                    .with(
+                                            ModalDialogProperties.POSITIVE_BUTTON_TEXT,
+                                            context.getString(
+                                                    org.chromium.chrome.test.R.string.delete))
+                                    .with(
+                                            ModalDialogProperties.NEGATIVE_BUTTON_TEXT,
+                                            context.getString(
+                                                    org.chromium.chrome.test.R.string.cancel))
+                                    .with(
+                                            ModalDialogProperties.CONTROLLER,
+                                            new ModalDialogProperties.Controller() {
+                                                @Override
+                                                public void onClick(
+                                                        PropertyModel model, int buttonType) {}
+
+                                                @Override
+                                                public void onDismiss(
+                                                        PropertyModel model, int dismissalCause) {}
+                                            })
+                                    .build();
+
+                    dialogManager.showDialog(dialog, ModalDialogManager.ModalDialogType.TAB);
+                });
+        CriteriaHelper.pollUiThread(() -> dialogManager.isShowing(), "Dialog should be displayed");
+
+        HistogramWatcher histogramWatcher =
+                HistogramWatcher.newSingleRecordWatcher(
+                        "Android.BackPress.Intercept",
+                        BackPressManager.getHistogramValueForTesting(
+                                BackPressHandler.Type.TAB_MODAL_HANDLER));
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mCustomTabActivityTestRule
+                            .getActivity()
+                            .getOnBackPressedDispatcher()
+                            .onBackPressed();
+                });
+
+        Assert.assertTrue("Should be able to navigate back after navigation", tab.canGoBack());
+        Assert.assertFalse("Should be unable to navigate forward", tab.canGoForward());
+        CriteriaHelper.pollInstrumentationThread(
+                () -> {
+                    Criteria.checkThat(
+                            "Tab should not be navigated when dialog is dismissed",
+                            ChromeTabUtils.getUrlStringOnUiThread(getActivity().getActivityTab()),
+                            is(mTestPage2));
+                });
+
+        histogramWatcher.assertExpected("Dialog should be dismissed by back press");
+        CriteriaHelper.pollUiThread(
+                () -> !dialogManager.isShowing(), "Dialog should be dismissed by back press");
+    }
+
+    @Test
+    @SmallTest
+    @Features.EnableFeatures(ChromeFeatureList.BACK_GESTURE_REFACTOR)
+    public void testBackPressDismissTabModalDialog_BackGestureRefactor() {
+        testBackPressDismissTabModalDialog();
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/OWNERS
index c8486c8..cc0884a9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/OWNERS
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/OWNERS
@@ -1,2 +1,3 @@
 file://chrome/android/java/src/org/chromium/chrome/browser/customtabs/OWNERS
 per-file CustomTabPrivacySandboxDialogTest.java=file://components/privacy_sandbox/OWNERS
+per-file CustomTabModalDialogTest.java=file://ui/android/java/src/org/chromium/ui/modaldialog/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/IdentityDiscControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/IdentityDiscControllerTest.java
index 121d533d..df2ad3b6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/IdentityDiscControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/identity_disc/IdentityDiscControllerTest.java
@@ -35,7 +35,6 @@
 import org.mockito.junit.MockitoRule;
 import org.mockito.quality.Strictness;
 
-import org.chromium.base.Callback;
 import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
@@ -74,21 +73,6 @@
     private static final String EMAIL = "email@gmail.com";
     private static final String NAME = "Email Emailson";
     private static final String FULL_NAME = NAME + ".full";
-    private static final ObservableSupplier<Profile> EMPTY_PROFILE_SUPPLIER =
-            new ObservableSupplier<>() {
-                @Override
-                public Profile addObserver(Callback<Profile> obs) {
-                    return null;
-                }
-
-                @Override
-                public void removeObserver(Callback<Profile> obs) {}
-
-                @Override
-                public Profile get() {
-                    return null;
-                }
-            };
 
     private final ChromeTabbedActivityTestRule mActivityTestRule =
             new ChromeTabbedActivityTestRule();
@@ -373,7 +357,7 @@
         TrackerFactory.setTrackerForTests(mTracker);
         IdentityDiscController identityDiscController =
                 new IdentityDiscController(
-                        mActivityTestRule.getActivity(), mDispatcher, EMPTY_PROFILE_SUPPLIER);
+                        mActivityTestRule.getActivity(), mDispatcher, mProfileSupplier);
 
         // If the button is tapped before the profile is set, the click shouldn't be recorded.
         identityDiscController.onClick();
@@ -397,7 +381,7 @@
             ButtonDataProvider.ButtonDataObserver observer) {
         IdentityDiscController controller =
                 new IdentityDiscController(
-                        mActivityTestRule.getActivity(), mDispatcher, EMPTY_PROFILE_SUPPLIER);
+                        mActivityTestRule.getActivity(), mDispatcher, mProfileSupplier);
         controller.addObserver(observer);
 
         return controller;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
index f194eba..84f4a1c9 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/page_info/PageInfoViewTest.java
@@ -632,6 +632,7 @@
     @Test
     @MediumTest
     @Feature({"RenderTest"})
+    @DisabledTest(message = "https://crbug.com/1510968")
     public void testShowCookiesSubpageUserBypassOn() throws IOException {
         setThirdPartyCookieBlocking(CookieControlsMode.BLOCK_THIRD_PARTY);
         loadUrlAndOpenPageInfo(mTestServerRule.getServer().getURL(sSimpleHtml));
@@ -658,6 +659,7 @@
     @Test
     @MediumTest
     @Feature({"RenderTest"})
+    @DisabledTest(message = "https://crbug.com/1510968")
     public void testShowCookiesSubpageTrackingProtection() throws IOException {
         enableTrackingProtection();
         setThirdPartyCookieBlocking(CookieControlsMode.BLOCK_THIRD_PARTY);
@@ -685,6 +687,7 @@
     @Test
     @MediumTest
     @Feature({"RenderTest"})
+    @DisabledTest(message = "https://crbug.com/1510968")
     public void testShowCookiesSubpageTrackingProtectionBlockAll() throws IOException {
         enableTrackingProtection();
         blockAll3PC();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
index 327dac1..a3a18cd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
@@ -50,8 +50,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import org.chromium.base.Callback;
-import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.params.ParameterAnnotations;
 import org.chromium.base.test.params.ParameterizedRunner;
@@ -1126,21 +1125,7 @@
     private static class TestControlsVisibilityDelegate
             extends BrowserStateBrowserControlsVisibilityDelegate {
         public TestControlsVisibilityDelegate() {
-            super(
-                    new ObservableSupplier<Boolean>() {
-                        @Override
-                        public Boolean addObserver(Callback<Boolean> obs) {
-                            return false;
-                        }
-
-                        @Override
-                        public void removeObserver(Callback<Boolean> obs) {}
-
-                        @Override
-                        public Boolean get() {
-                            return false;
-                        }
-                    });
+            super(new ObservableSupplierImpl<>(false));
         }
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTest.java
index 74ba777..f9f4e2aa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkActivityTest.java
@@ -14,6 +14,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.lifecycle.Stage;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -22,6 +23,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.test.util.ApplicationTestUtils;
 import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.cc.input.BrowserControlsState;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -34,9 +36,14 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeTabUtils;
 import org.chromium.chrome.test.util.browser.webapps.WebApkIntentDataProviderBuilder;
+import org.chromium.components.permissions.PermissionDialogController;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.webapk.lib.common.WebApkConstants;
 
+import java.util.concurrent.TimeoutException;
+
 /** Tests for WebAPK {@link WebappActivity}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@@ -124,6 +131,26 @@
         ChromeActivityTestRule.waitFor(WebappActivity.class);
     }
 
+    /** Test a permission dialog can be correctly presented. */
+    @Test
+    @LargeTest
+    public void testShowPermissionPrompt() throws TimeoutException {
+        EmbeddedTestServer server = mActivityTestRule.getEmbeddedTestServerRule().getServer();
+        String url = server.getURL("/content/test/data/android/permission_navigation.html");
+        String baseUrl = server.getURL("/content/test/data/android/");
+        WebappActivity activity =
+                mActivityTestRule.startWebApkActivity(createIntentDataProvider(url, baseUrl));
+        mActivityTestRule.runJavaScriptCodeInCurrentTab("requestGeolocationPermission()");
+        CriteriaHelper.pollUiThread(
+                () -> PermissionDialogController.getInstance().isDialogShownForTest(),
+                "Permission prompt did not appear in allotted time");
+        Assert.assertEquals(
+                "Only App modal dialog is supported on web apk",
+                activity.getModalDialogManager()
+                        .getPresenterForTest(ModalDialogManager.ModalDialogType.APP),
+                activity.getModalDialogManager().getCurrentPresenterForTest());
+    }
+
     private BrowserServicesIntentDataProvider createIntentDataProvider(
             String startUrl, String scopeUrl) {
         WebApkIntentDataProviderBuilder intentDataProviderBuilder =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
index 62cc2f7..4852f47 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebappNavigationTest.java
@@ -22,6 +22,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.hamcrest.Matchers;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -62,14 +63,19 @@
 import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils;
 import org.chromium.chrome.test.util.browser.webapps.WebappTestPage;
 import org.chromium.components.browser_ui.styles.ChromeColors;
+import org.chromium.components.permissions.PermissionDialogController;
 import org.chromium.content_public.browser.test.NativeLibraryTestUtils;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.common.ContentSwitches;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.base.PageTransition;
+import org.chromium.ui.modaldialog.ModalDialogManager;
 import org.chromium.ui.test.util.UiRestriction;
 
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
 /** Tests web navigations originating from a WebappActivity. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @DoNotBatch(reason = "tests run on startup.")
@@ -419,6 +425,50 @@
         ChromeTabUtils.waitForTabPageLoaded(activity.getActivityTab(), initialInScopeUrl);
     }
 
+    /** Test a permission dialog can be correctly presented and dismissed by navigation. */
+    @Test
+    @LargeTest
+    @Feature({"Webapps"})
+    public void testShowPermissionPrompt() throws TimeoutException, ExecutionException {
+        Intent launchIntent = mActivityTestRule.createIntent();
+        mActivityTestRule.addTwaExtrasToIntent(launchIntent);
+
+        WebappActivity activity =
+                runWebappActivityAndWaitForIdleWithUrl(
+                        launchIntent,
+                        mActivityTestRule
+                                .getTestServer()
+                                .getURL("/content/test/data/android/permission_navigation.html"));
+        mActivityTestRule.runJavaScriptCodeInCurrentTab("requestGeolocationPermission()");
+        CriteriaHelper.pollUiThread(
+                () -> PermissionDialogController.getInstance().isDialogShownForTest(),
+                "Permission prompt did not appear in allotted time");
+        Assert.assertEquals(
+                "Only App modal dialog is supported on web apk",
+                activity.getModalDialogManager()
+                        .getPresenterForTest(ModalDialogManager.ModalDialogType.APP),
+                activity.getModalDialogManager().getCurrentPresenterForTest());
+        // Launch a new page, which should be in CCT
+        mActivityTestRule.runJavaScriptCodeInCurrentTab("navigate()");
+        CriteriaHelper.pollUiThread(
+                () -> !PermissionDialogController.getInstance().isDialogShownForTest(),
+                "Permission prompt is not dismissed.");
+
+        // Toolbar with the close button should be visible.
+        WebappActivityTestRule.assertToolbarShownMaybeHideable(activity);
+
+        // Navigate back to in-scope through a close button.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () ->
+                        activity.getToolbarManager()
+                                .getToolbarLayoutForTesting()
+                                .findViewById(R.id.close_button)
+                                .callOnClick());
+        CriteriaHelper.pollUiThread(
+                () -> !PermissionDialogController.getInstance().isDialogShownForTest(),
+                "Permission prompt is not dismissed.");
+    }
+
     private WebappActivity runWebappActivityAndWaitForIdle(Intent intent) {
         return runWebappActivityAndWaitForIdleWithUrl(
                 intent, WebappTestPage.getServiceWorkerUrl(mActivityTestRule.getTestServer()));
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsHandlerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsHandlerUnitTest.java
index 5c290fa..f683cbe1 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsHandlerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsHandlerUnitTest.java
@@ -31,16 +31,13 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.customtabs.CustomTabsConnection;
 import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager.Observer;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 
 /** Unit tests for {@link EngagementSignalsHandler}. */
 @RunWith(BaseRobolectricTestRunner.class)
-@EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
 public class EngagementSignalsHandlerUnitTest {
     @Rule public Features.JUnitProcessor processor = new Features.JUnitProcessor();
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsInitialScrollObserverUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsInitialScrollObserverUnitTest.java
index 11251a1..59ac8462 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsInitialScrollObserverUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsInitialScrollObserverUnitTest.java
@@ -40,10 +40,7 @@
 /** Unit tests for {@link EngagementSignalsInitialScrollObserver}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
-@EnableFeatures({
-    ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS,
-    ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL
-})
+@EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS})
 public class EngagementSignalsInitialScrollObserverUnitTest {
     private static final int SCROLL_EXTENT = 100;
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserverUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserverUnitTest.java
index 8d11db5..6cfd2a5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserverUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserverUnitTest.java
@@ -20,10 +20,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.NONE;
 import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.ON_SCROLL_END;
+import static org.chromium.chrome.browser.customtabs.content.RealtimeEngagementSignalObserver.DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS;
 import static org.chromium.chrome.browser.customtabs.content.RealtimeEngagementSignalObserver.REAL_VALUES;
-import static org.chromium.chrome.browser.customtabs.content.RealtimeEngagementSignalObserver.TIME_CAN_UPDATE_AFTER_END;
 
 import android.graphics.Point;
 import android.os.Bundle;
@@ -57,7 +56,6 @@
 import org.chromium.chrome.browser.tab.TabHidingType;
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content.browser.GestureListenerManagerImpl;
 import org.chromium.content.browser.RenderCoordinatesImpl;
@@ -73,10 +71,7 @@
 /** Unit test for {@link RealtimeEngagementSignalObserver}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(shadows = {ShadowSystemClock.class})
-@EnableFeatures({
-    ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS,
-    ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL
-})
+@EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS})
 public class RealtimeEngagementSignalObserverUnitTest {
     @Rule
     public final CustomTabActivityContentTestEnvironment env =
@@ -124,16 +119,8 @@
     }
 
     @Test
-    @DisableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
-    public void addsListenersForSignalsIfFeatureIsEnabled() {
-        initializeTabForTest();
-
-        verify(mGestureListenerManagerImpl).addListener(any(GestureStateListener.class), eq(NONE));
-    }
-
-    @Test
     public void addsListenersForSignalsIfFeatureIsEnabled_alternativeImpl() {
-        setFeatureParams(null, 100);
+        setFeatureParams(null);
         initializeTabForTest();
 
         verify(mGestureListenerManagerImpl)
@@ -559,7 +546,7 @@
 
     @Test
     public void sendsFalseForScrollDirectionIfSendingFakeValues() {
-        setFeatureParams(false, null);
+        setFeatureParams(false);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener();
 
@@ -583,7 +570,7 @@
 
     @Test
     public void sendsZeroForMaxScrollSignalsIfSendingFakeValues() {
-        setFeatureParams(false, null);
+        setFeatureParams(false);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener();
 
@@ -625,7 +612,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void sendsSignalWithAlternativeImpl_updateBeforeEnd() {
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
@@ -643,9 +629,8 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void sendsSignalWithAlternativeImpl_updateAfterEnd() {
-        setFeatureParams(null, 25);
+        setFeatureParams(null);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
 
@@ -668,28 +653,8 @@
     }
 
     @Test
-    @DisableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
-    public void doesNotSendSignalUpdateAfterEndWithAlternativeImplDisabled() {
-        initializeTabForTest();
-        GestureStateListener listener = captureGestureStateListener(NONE);
-
-        // Start by scrolling down.
-        listener.onScrollStarted(0, SCROLL_EXTENT, false);
-        // End scrolling.
-        listener.onScrollEnded(0, SCROLL_EXTENT);
-        // Send the signal for 24% 10ms after the scroll ended.
-        advanceTime(10);
-        when(mRenderCoordinatesImpl.getScrollYPixInt()).thenReturn(24);
-        listener.onScrollOffsetOrExtentChanged(24, SCROLL_EXTENT);
-        // We shouldn't make any call.
-        verify(mEngagementSignalsCallback, never())
-                .onGreatestScrollPercentageIncreased(anyInt(), any(Bundle.class));
-    }
-
-    @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void doesNotSendLowerPercentWithAlternativeImpl() {
-        setFeatureParams(null, 20);
+        setFeatureParams(null);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
 
@@ -716,9 +681,8 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void doNotSendSignalWithAlternativeImplAfterThreshold() {
-        setFeatureParams(null, 10);
+        setFeatureParams(null);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
 
@@ -726,8 +690,8 @@
         listener.onScrollStarted(59, SCROLL_EXTENT, false);
         // End scrolling.
         listener.onScrollEnded(59, SCROLL_EXTENT);
-        // Send the signal for 59% 18ms after the scroll ended.
-        advanceTime(18);
+        // Send the signal for 59% 18ms outside the threshold.
+        advanceTime(DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS + 18);
         when(mRenderCoordinatesImpl.getScrollYPixInt()).thenReturn(59);
         listener.onScrollOffsetOrExtentChanged(59, SCROLL_EXTENT);
         // We shouldn't make a call since the call was outside the threshold.
@@ -736,9 +700,8 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void doNotSendSignalWithAlternativeImplIfScrollStartReceived() {
-        setFeatureParams(null, 25);
+        setFeatureParams(null);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
 
@@ -758,7 +721,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void sendOnSessionEnded_HadInteraction() {
         initializeTabForTest();
         doReturn(false).when(mTabInteractionRecorder).didGetUserInteraction();
@@ -778,7 +740,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void sendOnSessionEnded_HadNoInteraction() {
         initializeTabForTest();
         doReturn(false).when(mTabInteractionRecorder).didGetUserInteraction();
@@ -796,7 +757,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void doNotSendOnSessionEndedWhenSuspended() {
         initializeTabForTest();
         mEngagementSignalObserver.suppressNextSessionEndedCall();
@@ -818,7 +778,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void pauseAndUnpauseSignalsOnPageWithTextFragment() {
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
@@ -858,7 +817,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void doesNotSendSignalsBeforeDownScroll() {
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
@@ -885,7 +843,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void doesNotSendSignalsBeforeDownScroll_AfterNavigation() {
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
@@ -919,24 +876,6 @@
     }
 
     @Test
-    @DisableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
-    public void sendInitialOffsetUpdate_AltImplDisabled() {
-        initializeTabForTest(/* hadScrollDown= */ true);
-        // When the alternative impl flag is enabled, the listener should be added with `NONE`.
-        var listener = captureGestureStateListener(NONE);
-
-        // Simulate renderer sending the offset update.
-        when(mRenderCoordinatesImpl.getScrollYPixInt()).thenReturn(42);
-        listener.onScrollOffsetOrExtentChanged(42, SCROLL_EXTENT);
-
-        // We should get a notification since we initialized the observer class with true for
-        // hadScrollDown.
-        verify(mEngagementSignalsCallback)
-                .onGreatestScrollPercentageIncreased(eq(40), any(Bundle.class));
-    }
-
-    @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL})
     public void sendInitialOffsetUpdate_AltImplEnabled() {
         initializeTabForTest(/* hadScrollDown= */ true);
         // When the alternative impl flag is enabled, the listener should be added with
@@ -959,27 +898,16 @@
 
     /**
      * @param realValues CCT_REAL_TIME_ENGAGEMENT_SIGNALS real_values.
-     * @param threshold CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL time_can_update_after_end.
      */
-    private void setFeatureParams(Boolean realValues, Integer threshold) {
-        if (realValues == null && threshold == null) return;
+    private void setFeatureParams(Boolean realValues) {
+        if (realValues == null) return;
 
         TestValues testValues = new TestValues();
         testValues.addFeatureFlagOverride(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS, true);
-        testValues.addFeatureFlagOverride(
-                ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL, true);
-        if (realValues != null) {
-            testValues.addFieldTrialParamOverride(
-                    ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS,
-                    REAL_VALUES,
-                    realValues.toString());
-        }
-        if (threshold != null) {
-            testValues.addFieldTrialParamOverride(
-                    ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL,
-                    TIME_CAN_UPDATE_AFTER_END,
-                    Integer.toString(threshold));
-        }
+        testValues.addFieldTrialParamOverride(
+                ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS,
+                REAL_VALUES,
+                realValues.toString());
         FeatureList.setTestValues(testValues);
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/messages/ChromeMessageQueueMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/messages/ChromeMessageQueueMediatorTest.java
index 63b98b9..2b5f248f 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/messages/ChromeMessageQueueMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/messages/ChromeMessageQueueMediatorTest.java
@@ -29,7 +29,6 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.UserDataHost;
-import org.chromium.base.supplier.ObservableSupplier;
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.OneshotSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -170,20 +169,7 @@
         when(mBrowserControlsManager.getBrowserVisibilityDelegate())
                 .thenReturn(
                         new BrowserStateBrowserControlsVisibilityDelegate(
-                                new ObservableSupplier<Boolean>() {
-                                    @Override
-                                    public Boolean addObserver(Callback<Boolean> obs) {
-                                        return null;
-                                    }
-
-                                    @Override
-                                    public void removeObserver(Callback<Boolean> obs) {}
-
-                                    @Override
-                                    public Boolean get() {
-                                        return false;
-                                    }
-                                }));
+                                new ObservableSupplierImpl<>(false)));
         mMediator =
                 new ChromeMessageQueueMediator(
                         mBrowserControlsManager,
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 9e1f814..2720fab 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9101,7 +9101,7 @@
         Right-click on the tab group name to edit this group or click to collapse
       </message>
       <message name="IDS_TAB_ORGANIZATION_SUCCESS_IPH_SCREENREADER" desc="The screen reader text of the IPH describing how to interact with a tab group resulting from tab organization." translateable="false">
-        Select the tab group and activate the context menu to edit, or select the tab group and press enter to collapse
+        Select the tab group and activate the context menu to edit
       </message>
 
       <!-- Strings for intent picker -->
@@ -10695,7 +10695,7 @@
         Show me how
       </message>
       <message name="IDS_TAB_ORGANIZATION_LEARN_MORE" desc="The actionable text in the tab organization UI results state for getting more information" translateable="false">
-        Learn more
+        Learn more about using AI tools
       </message>
 
       <!-- Strings for Window Titles in Menus -->
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index ef8ae3be..3212322 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -2820,6 +2820,9 @@
   <message name="IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_DESCRIPTION" desc="Description of dialog that allows the user to input shortcuts that will be used as the action on a button.">
     Press 1-4 modifier keys (ctrl, alt, shift, search, or launcher) and 1 more key. You can also select a single key.
   </message>
+  <message name="IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CHANGE" desc="Label of change button on dialogs in customize device buttons pages.">
+    Change
+  </message>
   <message name="IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CANCEL" desc="Label of cancel button on dialogs in customize device buttons pages.">
     Cancel
   </message>
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CHANGE.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CHANGE.png.sha1
new file mode 100644
index 0000000..68714e9
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CHANGE.png.sha1
@@ -0,0 +1 @@
+ad2e40375a43a66d949ac408a58956103309f535
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 599460f..926a9b6 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -317,6 +317,8 @@
     "component_updater/mei_preload_component_installer.h",
     "component_updater/network_quality_observer.cc",
     "component_updater/network_quality_observer.h",
+    "component_updater/optimization_guide_on_device_model_installer.cc",
+    "component_updater/optimization_guide_on_device_model_installer.h",
     "component_updater/payload_test_component_installer.cc",
     "component_updater/payload_test_component_installer.h",
     "component_updater/pki_metadata_component_installer.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index f24e762..0ab4fe45 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -138,7 +138,6 @@
 #include "components/performance_manager/public/features.h"
 #include "components/permissions/features.h"
 #include "components/policy/core/common/features.h"
-#include "components/power_bookmarks/core/flag_descriptions.h"
 #include "components/power_bookmarks/core/power_bookmark_features.h"
 #include "components/privacy_sandbox/privacy_sandbox_features.h"
 #include "components/query_tiles/switches.h"
@@ -574,26 +573,6 @@
      {"Send fake values", kCCTRealTimeEngagementSignalsParamFakeValues,
       std::size(kCCTRealTimeEngagementSignalsParamFakeValues), nullptr}};
 
-const FeatureEntry::FeatureParam
-    kCCTRealTimeEngagementSignalsAlternativeImplParam300[] = {
-        {"time_can_update_after_end", "300"}  // 300ms
-};
-const FeatureEntry::FeatureParam
-    kCCTRealTimeEngagementSignalsAlternativeImplParam100[] = {
-        {"time_can_update_after_end", "100"}  // 100ms
-};
-
-const FeatureEntry::FeatureVariation
-    kCCTRealTimeEngagementSignalsAlternativeImplVariations[] = {
-        {"Allow 300ms for scroll updates after scroll-end",
-         kCCTRealTimeEngagementSignalsAlternativeImplParam300,
-         std::size(kCCTRealTimeEngagementSignalsAlternativeImplParam300),
-         nullptr},
-        {"Allow 100ms for scroll updates after scroll-end",
-         kCCTRealTimeEngagementSignalsAlternativeImplParam100,
-         std::size(kCCTRealTimeEngagementSignalsAlternativeImplParam100),
-         nullptr}};
-
 const FeatureEntry::Choice kReaderModeHeuristicsChoices[] = {
     {flags_ui::kGenericExperimentChoiceDefault, "", ""},
     {flag_descriptions::kReaderModeHeuristicsMarkup,
@@ -5325,10 +5304,6 @@
      flag_descriptions::kShareSheetMigrationAndroidName,
      flag_descriptions::kShareSheetMigrationAndroidDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kShareSheetMigrationAndroid)},
-    {"share-sheet-custom-actions-polish",
-     flag_descriptions::kShareSheetCustomActionsPolishName,
-     flag_descriptions::kShareSheetCustomActionsPolishDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(chrome::android::kShareSheetCustomActionsPolish)},
 
 #endif  // BUILDFLAG(IS_ANDROID)
     {"disallow-doc-written-script-loads",
@@ -7388,14 +7363,6 @@
          chrome::android::kCCTRealTimeEngagementSignals,
          kCCTRealTimeEngagementSignalsVariations,
          "CCTRealTimeEngagementSignals")},
-    {"cct-real-time-engagement-signals-alternative-impl",
-     flag_descriptions::kCCTRealTimeEngagementSignalsAlternativeImplName,
-     flag_descriptions::kCCTRealTimeEngagementSignalsAlternativeImplDescription,
-     kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         chrome::android::kCCTRealTimeEngagementSignalsAlternativeImpl,
-         kCCTRealTimeEngagementSignalsAlternativeImplVariations,
-         "CCTRealTimeEngagementSignalsAlternativeImpl")},
 #endif
 
 #if BUILDFLAG(IS_ANDROID)
@@ -7912,16 +7879,19 @@
      flag_descriptions::kEnableFencedFramesDescription, kOsAll,
      FEATURE_VALUE_TYPE(blink::features::kFencedFrames)},
 
+    {"enable-fenced-frames-cross-origin-automatic-beacons",
+     flag_descriptions::kEnableFencedFramesCrossOriginAutomaticBeaconsName,
+     flag_descriptions::
+         kEnableFencedFramesCrossOriginAutomaticBeaconsDescription,
+     kOsAll,
+     FEATURE_VALUE_TYPE(
+         blink::features::kFencedFramesCrossOriginAutomaticBeacons)},
+
     {"enable-fenced-frames-developer-mode",
      flag_descriptions::kEnableFencedFramesDeveloperModeName,
      flag_descriptions::kEnableFencedFramesDeveloperModeDescription, kOsAll,
      FEATURE_VALUE_TYPE(blink::features::kFencedFramesDefaultMode)},
 
-    {"enable-fenced-frames-M120-features",
-     flag_descriptions::kEnableFencedFramesM120FeaturesName,
-     flag_descriptions::kEnableFencedFramesM120FeaturesDescription, kOsAll,
-     FEATURE_VALUE_TYPE(blink::features::kFencedFramesM120FeaturesPart2)},
-
     {"enable-fenced-frames-reporting-attestations-changes",
      flag_descriptions::kEnableFencedFramesReportingAttestationsChangeName,
      flag_descriptions::
@@ -10223,14 +10193,6 @@
      FEATURE_VALUE_TYPE(ash::features::kEnablePerDeskZOrder)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if !BUILDFLAG(IS_ANDROID)
-    {"simplified-bookmark-save-flow",
-     power_bookmarks::flag_descriptions::kSimplifiedBookmarkSaveFlowName,
-     power_bookmarks::flag_descriptions::kSimplifiedBookmarkSaveFlowName,
-     kOsDesktop,
-     FEATURE_VALUE_TYPE(power_bookmarks::kSimplifiedBookmarkSaveFlow)},
-#endif
-
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     {"gallery-app-pdf-edit-notification",
      flag_descriptions::kGalleryAppPdfEditNotificationName,
diff --git a/chrome/browser/accessibility/DEPS b/chrome/browser/accessibility/DEPS
index 8ae5b94..81b2219 100644
--- a/chrome/browser/accessibility/DEPS
+++ b/chrome/browser/accessibility/DEPS
@@ -10,5 +10,6 @@
 specific_include_rules = {
   'accessibility_extension_api_ash.cc': [
     "+services/accessibility/public/mojom/assistive_technology_type.mojom.h",
+    "+ash/accessibility/accessibility_controller_impl.h",
   ]
 }
\ No newline at end of file
diff --git a/chrome/browser/accessibility/accessibility_extension_api_ash.cc b/chrome/browser/accessibility/accessibility_extension_api_ash.cc
index 43d928d15..a4354378 100644
--- a/chrome/browser/accessibility/accessibility_extension_api_ash.cc
+++ b/chrome/browser/accessibility/accessibility_extension_api_ash.cc
@@ -9,7 +9,7 @@
 #include <set>
 #include <vector>
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/public/cpp/accessibility_focus_ring_info.h"
 #include "ash/public/cpp/event_rewriter_controller.h"
diff --git a/chrome/browser/ash/accessibility/accessibility_manager.cc b/chrome/browser/ash/accessibility/accessibility_manager.cc
index fb838aac..bf5bc271 100644
--- a/chrome/browser/ash/accessibility/accessibility_manager.cc
+++ b/chrome/browser/ash/accessibility/accessibility_manager.cc
@@ -9,6 +9,7 @@
 
 #include <utility>
 
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/accessibility/autoclick/autoclick_controller.h"
 #include "ash/accessibility/sticky_keys/sticky_keys_controller.h"
 #include "ash/color_enhancement/color_enhancement_controller.h"
@@ -16,7 +17,6 @@
 #include "ash/constants/ash_pref_names.h"
 #include "ash/constants/ash_switches.h"
 #include "ash/public/cpp/accelerators.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/public/cpp/accessibility_focus_ring_controller.h"
 #include "ash/public/cpp/accessibility_focus_ring_info.h"
diff --git a/chrome/browser/ash/accessibility/chromevox_panel.cc b/chrome/browser/ash/accessibility/chromevox_panel.cc
index 544a67f..2ecb342 100644
--- a/chrome/browser/ash/accessibility/chromevox_panel.cc
+++ b/chrome/browser/ash/accessibility/chromevox_panel.cc
@@ -6,7 +6,7 @@
 
 #include <memory>
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
diff --git a/chrome/browser/ash/accessibility/select_to_speak_event_handler_delegate_impl.cc b/chrome/browser/ash/accessibility/select_to_speak_event_handler_delegate_impl.cc
index 41442e1..00745d5 100644
--- a/chrome/browser/ash/accessibility/select_to_speak_event_handler_delegate_impl.cc
+++ b/chrome/browser/ash/accessibility/select_to_speak_event_handler_delegate_impl.cc
@@ -6,7 +6,7 @@
 
 #include <memory>
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "chrome/browser/ash/accessibility/event_handler_common.h"
 #include "chrome/common/extensions/extension_constants.h"
diff --git a/chrome/browser/ash/accessibility/service/autoclick_client_impl.cc b/chrome/browser/ash/accessibility/service/autoclick_client_impl.cc
index e17e617..73462c5 100644
--- a/chrome/browser/ash/accessibility/service/autoclick_client_impl.cc
+++ b/chrome/browser/ash/accessibility/service/autoclick_client_impl.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ash/accessibility/service/autoclick_client_impl.h"
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "base/debug/stack_trace.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
diff --git a/chrome/browser/ash/accessibility/service/user_interface_impl.cc b/chrome/browser/ash/accessibility/service/user_interface_impl.cc
index 5d73bb65..1c5f403 100644
--- a/chrome/browser/ash/accessibility/service/user_interface_impl.cc
+++ b/chrome/browser/ash/accessibility/service/user_interface_impl.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ash/accessibility/service/user_interface_impl.h"
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/public/cpp/accessibility_focus_ring_info.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "content/public/common/color_parser.h"
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
index dec55888..4229de2 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
@@ -12,7 +12,6 @@
 #include "ash/constants/ash_switches.h"
 #include "ash/display/display_configuration_controller.h"
 #include "ash/public/cpp/accelerators.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "ash/public/cpp/event_rewriter_controller.h"
 #include "ash/public/cpp/screen_backlight.h"
 #include "ash/public/cpp/shelf_model.h"
diff --git a/chrome/browser/ash/app_mode/kiosk_system_session.cc b/chrome/browser/ash/app_mode/kiosk_system_session.cc
index 75960ea..2e8b9bb 100644
--- a/chrome/browser/ash/app_mode/kiosk_system_session.cc
+++ b/chrome/browser/ash/app_mode/kiosk_system_session.cc
@@ -6,7 +6,7 @@
 #include <memory>
 #include <optional>
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "base/memory/weak_ptr.h"
 #include "base/notreached.h"
 #include "base/scoped_observation.h"
diff --git a/chrome/browser/ash/crosapi/embedded_accessibility_helper_client_ash.cc b/chrome/browser/ash/crosapi/embedded_accessibility_helper_client_ash.cc
index b54ca7ba..6763e13 100644
--- a/chrome/browser/ash/crosapi/embedded_accessibility_helper_client_ash.cc
+++ b/chrome/browser/ash/crosapi/embedded_accessibility_helper_client_ash.cc
@@ -6,7 +6,7 @@
 
 #include <string>
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
 #include "chromeos/crosapi/mojom/embedded_accessibility_helper.mojom.h"
 
diff --git a/chrome/browser/ash/crosapi/test_controller_ash.cc b/chrome/browser/ash/crosapi/test_controller_ash.cc
index 5789db1..8504dd6 100644
--- a/chrome/browser/ash/crosapi/test_controller_ash.cc
+++ b/chrome/browser/ash/crosapi/test_controller_ash.cc
@@ -11,7 +11,6 @@
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/constants/ash_pref_names.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "ash/public/cpp/shelf_item_delegate.h"
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/public/cpp/tablet_mode.h"
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
index cac4413..9e553b08 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
@@ -12,6 +12,7 @@
 #include <sstream>
 #include <utility>
 
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/app_list/app_list_public_test_util.h"
 #include "ash/components/arc/arc_prefs.h"
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
@@ -25,7 +26,6 @@
 #include "ash/constants/ash_pref_names.h"
 #include "ash/constants/ash_switches.h"
 #include "ash/public/cpp/accelerators.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "ash/public/cpp/ambient/ambient_prefs.h"
 #include "ash/public/cpp/ambient/ambient_ui_model.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
diff --git a/chrome/browser/ash/login/lock/screen_locker_unittest.cc b/chrome/browser/ash/login/lock/screen_locker_unittest.cc
index 3fcb2ba..69250893 100644
--- a/chrome/browser/ash/login/lock/screen_locker_unittest.cc
+++ b/chrome/browser/ash/login/lock/screen_locker_unittest.cc
@@ -21,7 +21,6 @@
 #include "chrome/browser/certificate_provider/certificate_provider_service.h"
 #include "chrome/browser/certificate_provider/certificate_provider_service_factory.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h"
 #include "chrome/browser/ui/ash/assistant/assistant_browser_delegate_impl.h"
 #include "chrome/browser/ui/ash/login_screen_client_impl.h"
 #include "chrome/browser/ui/ash/session_controller_client_impl.h"
@@ -183,8 +182,6 @@
   ScopedStubInstallAttributes test_install_attributes_;
 
   // ScreenLocker dependencies:
-  // * AccessibilityManager dependencies:
-  FakeAccessibilityController fake_accessibility_controller_;
   // * LoginScreenClientImpl dependencies:
   session_manager::SessionManager session_manager_;
   TestLoginScreen test_login_screen_;
diff --git a/chrome/browser/ash/ownership/owner_key_loader.cc b/chrome/browser/ash/ownership/owner_key_loader.cc
index 5b95033..7a32321 100644
--- a/chrome/browser/ash/ownership/owner_key_loader.cc
+++ b/chrome/browser/ash/ownership/owner_key_loader.cc
@@ -7,8 +7,8 @@
 #include <string>
 #include <utility>
 
-#include "ash/constants/ash_features.h"
 #include "base/check_is_test.h"
+#include "base/feature_list.h"
 #include "base/task/thread_pool.h"
 #include "chrome/browser/ash/ownership/ownership_histograms.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
@@ -17,15 +17,52 @@
 #include "chrome/browser/net/nss_service.h"
 #include "chrome/browser/net/nss_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/channel_info.h"
 #include "components/ownership/owner_key_util.h"
 #include "components/policy/proto/device_management_backend.pb.h"
 #include "components/user_manager/user_manager.h"
+#include "components/version_info/channel.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "net/cert/nss_cert_database.h"
 
 namespace ash {
 
+// Enable storing a newly created owner key in the private slot.
+BASE_FEATURE(kStoreOwnerKeyInPrivateSlot,
+             "StoreOwnerKeyInPrivateSlot",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
+// Enable migration of the owner key from the public to the private slot. This
+// experiment represents the second stage of `kStoreOwnerKeyInPrivateSlot` and
+// is only respected if kStoreOwnerKeyInPrivateSlot is enabled.
+BASE_FEATURE(kMigrateOwnerKeyToPrivateSlot,
+             "MigrateOwnerKeyToPrivateSlot",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
+bool IsStoreOwnerKeyInPrivateSlotEnabled() {
+  if (base::FeatureList::GetInstance()->IsFeatureOverridden(
+          kStoreOwnerKeyInPrivateSlot.name)) {
+    // Return the value if it was overridden by Finch, command line, etc.
+    return base::FeatureList::IsEnabled(kStoreOwnerKeyInPrivateSlot);
+  }
+
+  version_info::Channel channel = chrome::GetChannel();
+  if (channel == version_info::Channel::STABLE ||
+      channel == version_info::Channel::BETA) {
+    // TODO(b/264397430): Disable on beta and stable channels for now, remove
+    // the condition when the new code is more reliable.
+    return false;
+  }
+
+  return base::FeatureList::IsEnabled(kStoreOwnerKeyInPrivateSlot);
+}
+
+bool ShouldMigrateOwnerKeyToPrivateSlot() {
+  return IsStoreOwnerKeyInPrivateSlotEnabled() &&
+         base::FeatureList::IsEnabled(kMigrateOwnerKeyToPrivateSlot);
+}
+
 namespace {
 
 // Max number of attempts to generate a new owner key.
@@ -85,7 +122,7 @@
     crypto::ScopedPK11Slot public_slot,
     crypto::ScopedPK11Slot private_slot) {
   crypto::ScopedSECKEYPrivateKey sec_priv_key;
-  if (private_slot && features::IsStoreOwnerKeyInPrivateSlotEnabled()) {
+  if (private_slot && IsStoreOwnerKeyInPrivateSlotEnabled()) {
     sec_priv_key = owner_key_util->GenerateKeyPair(private_slot.get());
     RecordOwnerKeyEvent(OwnerKeyEvent::kPrivateSlotKeyGeneration,
                         bool(sec_priv_key));
@@ -299,8 +336,7 @@
     RecordOwnerKeyEvent(OwnerKeyEvent::kOwnerKeyInPublicSlot,
                         /*success=*/found_in_public_slot);
 
-    if (features::ShouldMigrateOwnerKeyToPrivateSlot() &&
-        found_in_public_slot) {
+    if (ShouldMigrateOwnerKeyToPrivateSlot() && found_in_public_slot) {
       // If the key was found in the public slot and the migration is enabled,
       // then replace it by generating a new one in the private slot. The old
       // key will be deleted by OwnerSettingsServiceAsh when the new key is
@@ -314,9 +350,8 @@
       return;
     }
 
-    if (!features::ShouldMigrateOwnerKeyToPrivateSlot() &&
-        !features::IsStoreOwnerKeyInPrivateSlotEnabled() &&
-        !found_in_public_slot) {
+    if (!ShouldMigrateOwnerKeyToPrivateSlot() &&
+        !IsStoreOwnerKeyInPrivateSlotEnabled() && !found_in_public_slot) {
       // If all experiments are disabled but the key is in the private slot, it
       // means they were reverted and it's probably better to migrate the owner
       // key back to the public slot.
diff --git a/chrome/browser/ash/ownership/owner_key_loader.h b/chrome/browser/ash/ownership/owner_key_loader.h
index 4cc18c1..d60e78f 100644
--- a/chrome/browser/ash/ownership/owner_key_loader.h
+++ b/chrome/browser/ash/ownership/owner_key_loader.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_ASH_OWNERSHIP_OWNER_KEY_LOADER_H_
 #define CHROME_BROWSER_ASH_OWNERSHIP_OWNER_KEY_LOADER_H_
 
+#include "base/feature_list.h"
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
@@ -21,6 +22,9 @@
 
 class DeviceSettingsService;
 
+BASE_DECLARE_FEATURE(kStoreOwnerKeyInPrivateSlot);
+BASE_DECLARE_FEATURE(kMigrateOwnerKeyToPrivateSlot);
+
 // A helper single-use class to load the owner key.
 // Determines whether the current user is the owner or not.
 // For the non-owner just loads the public owner key (which can be used to
diff --git a/chrome/browser/ash/ownership/owner_key_loader_unittest.cc b/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
index 0dd1e98..43665c1 100644
--- a/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
+++ b/chrome/browser/ash/ownership/owner_key_loader_unittest.cc
@@ -139,8 +139,8 @@
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
       /*enabled_features=*/{},
-      /*disabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot,
-                             ash::features::kMigrateOwnerKeyToPrivateSlot});
+      /*disabled_features=*/{kStoreOwnerKeyInPrivateSlot,
+                             kMigrateOwnerKeyToPrivateSlot});
 
   // In real code DeviceSettingsService must call this for the first user.
   device_settings_service_.MarkWillEstablishConsumerOwnership();
@@ -168,8 +168,8 @@
 TEST_F(RegularOwnerKeyLoaderTest, FirstUserGeneratesOwnerKeyInPrivateSlot) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot},
-      /*disabled_features=*/{ash::features::kMigrateOwnerKeyToPrivateSlot});
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+      /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
   // In real code DeviceSettingsService must call this for the first user.
   device_settings_service_.MarkWillEstablishConsumerOwnership();
@@ -244,8 +244,8 @@
 TEST_F(RegularOwnerKeyLoaderTest, OwnerUserLoadsExistingKeyFromPublicSlot) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot},
-      /*disabled_features=*/{ash::features::kMigrateOwnerKeyToPrivateSlot});
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+      /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
   // Configure existing device policies and the owner key.
   auto signing_key = ConfigureExistingPolicies(profile_->GetProfileUserName());
@@ -273,8 +273,8 @@
 TEST_F(RegularOwnerKeyLoaderTest, OwnerUserLoadsExistingKeyFromPrivateSlot) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot},
-      /*disabled_features=*/{ash::features::kMigrateOwnerKeyToPrivateSlot});
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+      /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
   // Configure existing device policies and the owner key.
   auto signing_key = ConfigureExistingPolicies(profile_->GetProfileUserName());
@@ -304,8 +304,8 @@
        OwnerUserLoadsExistingKeyFromPublicSlotWithoutPolicies) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot},
-      /*disabled_features=*/{ash::features::kMigrateOwnerKeyToPrivateSlot});
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+      /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
   policy::DevicePolicyBuilder policy_builder;
   auto signing_key = policy_builder.GetSigningKey();
@@ -334,8 +334,8 @@
        OwnerUserLoadsExistingKeyFromPrivateSlotWithoutPolicies) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot},
-      /*disabled_features=*/{ash::features::kMigrateOwnerKeyToPrivateSlot});
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+      /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
   policy::DevicePolicyBuilder policy_builder;
   auto signing_key = policy_builder.GetSigningKey();
@@ -511,8 +511,8 @@
 TEST_F(RegularOwnerKeyLoaderTest, MigrateFromPublicToPrivateSlot) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot,
-                            ash::features::kMigrateOwnerKeyToPrivateSlot},
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot,
+                            kMigrateOwnerKeyToPrivateSlot},
       /*disabled_features=*/{});
 
   // Configure existing device policies and the owner key.
@@ -550,8 +550,8 @@
   // With this config Chrome should generate new keys in the private slot, but
   // not migrate existing keys from the public slot.
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot},
-      /*disabled_features=*/{ash::features::kMigrateOwnerKeyToPrivateSlot});
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+      /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
   // Configure existing device policies and the owner key.
   auto signing_key = ConfigureExistingPolicies(profile_->GetProfileUserName());
@@ -585,8 +585,8 @@
   // With this config Chrome should generate new keys in the private slot, but
   // not migrate existing keys from the public slot.
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot,
-                            ash::features::kMigrateOwnerKeyToPrivateSlot},
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot,
+                            kMigrateOwnerKeyToPrivateSlot},
       /*disabled_features=*/{});
 
   // Configure existing device policies and the owner key.
@@ -622,8 +622,8 @@
   // not migrate existing keys from the public slot.
   feature_list.InitWithFeatures(
       /*enabled_features=*/{},
-      /*disabled_features=*/{ash::features::kStoreOwnerKeyInPrivateSlot,
-                             ash::features::kMigrateOwnerKeyToPrivateSlot});
+      /*disabled_features=*/{kStoreOwnerKeyInPrivateSlot,
+                             kMigrateOwnerKeyToPrivateSlot});
 
   // Configure existing device policies and the owner key.
   auto signing_key = ConfigureExistingPolicies(profile_->GetProfileUserName());
diff --git a/chrome/browser/ash/ownership/owner_settings_service_ash_unittest.cc b/chrome/browser/ash/ownership/owner_settings_service_ash_unittest.cc
index 92ce583..33d96ad 100644
--- a/chrome/browser/ash/ownership/owner_settings_service_ash_unittest.cc
+++ b/chrome/browser/ash/ownership/owner_settings_service_ash_unittest.cc
@@ -7,7 +7,6 @@
 #include <memory>
 #include <utility>
 
-#include "ash/constants/ash_features.h"
 #include "base/containers/contains.h"
 #include "base/containers/queue.h"
 #include "base/functional/bind.h"
@@ -18,6 +17,7 @@
 #include "base/test/scoped_path_override.h"
 #include "base/test/test_future.h"
 #include "base/values.h"
+#include "chrome/browser/ash/ownership/owner_key_loader.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
 #include "chrome/browser/ash/ownership/ownership_histograms.h"
 #include "chrome/browser/ash/settings/device_settings_provider.h"
@@ -114,8 +114,8 @@
     // By default disable the migration, so the imported key doesn't get
     // replaced.
     feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kStoreOwnerKeyInPrivateSlot},
-        /*disabled_features=*/{features::kMigrateOwnerKeyToPrivateSlot});
+        /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+        /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
     provider_ = std::make_unique<DeviceSettingsProvider>(
         base::BindRepeating(&OnPrefChanged), device_settings_service_.get(),
@@ -344,8 +344,8 @@
     // By default disable the migration, so the imported key doesn't get
     // replaced.
     feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kStoreOwnerKeyInPrivateSlot},
-        /*disabled_features=*/{features::kMigrateOwnerKeyToPrivateSlot});
+        /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+        /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
     provider_ = std::make_unique<DeviceSettingsProvider>(
         base::BindRepeating(&OnPrefChanged), device_settings_service_.get(),
@@ -438,8 +438,8 @@
   base::HistogramTester histogram_tester;
   base::test::ScopedFeatureList feature_list;
   feature_list.InitWithFeatures(
-      /*enabled_features=*/{features::kStoreOwnerKeyInPrivateSlot,
-                            features::kMigrateOwnerKeyToPrivateSlot},
+      /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot,
+                            kMigrateOwnerKeyToPrivateSlot},
       /*disabled_features=*/{});
 
   FakeNssService* nss_service = FakeNssService::InitializeForBrowserContext(
diff --git a/chrome/browser/ash/policy/login/login_screen_accessibility_policy_browsertest.cc b/chrome/browser/ash/policy/login/login_screen_accessibility_policy_browsertest.cc
index a3480b33..b91c3c3 100644
--- a/chrome/browser/ash/policy/login/login_screen_accessibility_policy_browsertest.cc
+++ b/chrome/browser/ash/policy/login/login_screen_accessibility_policy_browsertest.cc
@@ -4,9 +4,9 @@
 
 #include <string>
 
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/constants/ash_switches.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "base/command_line.h"
 #include "base/compiler_specific.h"
 #include "base/functional/bind.h"
diff --git a/chrome/browser/ash/power/ml/smart_dim/smart_dim_integration_test.cc b/chrome/browser/ash/power/ml/smart_dim/smart_dim_integration_test.cc
index 58b3d75f..6e52760 100644
--- a/chrome/browser/ash/power/ml/smart_dim/smart_dim_integration_test.cc
+++ b/chrome/browser/ash/power/ml/smart_dim/smart_dim_integration_test.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 "base/cpu.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -17,6 +18,7 @@
 #include "chromeos/ash/components/standalone_browser/standalone_browser_features.h"
 #include "components/component_updater/component_updater_service.h"
 #include "net/dns/mock_host_resolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/env.h"
 #include "ui/aura/env_observer.h"
 #include "ui/aura/window_observer.h"
@@ -189,9 +191,13 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-// Disabled due to failure on chromeos-betty-pi-arc-chrome.
-// TODO(http://b/308674133): Enable after fix.
-IN_PROC_BROWSER_TEST_F(SmartDimLacrosIntegrationTest, DISABLED_SmartDim) {
+IN_PROC_BROWSER_TEST_F(SmartDimLacrosIntegrationTest, SmartDim) {
+  // Lacros fails to start up correctly on VM tryservers like
+  // chromeos-amd64-generic (Lacros restarts in a loop). b/303359438
+  if (base::CPU().is_running_in_vm()) {
+    GTEST_SKIP();
+  }
+
   ASSERT_TRUE(crosapi::browser_util::IsLacrosEnabled());
 
   // The test opens a Lacros window, so ensure the Wayland server is running and
diff --git a/chrome/browser/ash/settings/cros_settings_unittest.cc b/chrome/browser/ash/settings/cros_settings_unittest.cc
index 320c18de..08e8d08c 100644
--- a/chrome/browser/ash/settings/cros_settings_unittest.cc
+++ b/chrome/browser/ash/settings/cros_settings_unittest.cc
@@ -16,6 +16,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ash/ownership/owner_key_loader.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
 #include "chrome/browser/ash/policy/core/device_policy_builder.h"
@@ -67,8 +68,8 @@
   void SetUp() override {
     // Disable owner key migration.
     feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kStoreOwnerKeyInPrivateSlot},
-        /*disabled_features=*/{features::kMigrateOwnerKeyToPrivateSlot});
+        /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+        /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
     device_policy_.Build();
 
diff --git a/chrome/browser/ash/settings/device_settings_provider_unittest.cc b/chrome/browser/ash/settings/device_settings_provider_unittest.cc
index 255b798..3dd9571 100644
--- a/chrome/browser/ash/settings/device_settings_provider_unittest.cc
+++ b/chrome/browser/ash/settings/device_settings_provider_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/scoped_path_override.h"
 #include "base/values.h"
+#include "chrome/browser/ash/ownership/owner_key_loader.h"
 #include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/ash/settings/device_settings_test_helper.h"
 #include "chrome/common/chrome_paths.h"
@@ -64,8 +65,8 @@
 
     // Disable owner key migration.
     feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kStoreOwnerKeyInPrivateSlot},
-        /*disabled_features=*/{features::kMigrateOwnerKeyToPrivateSlot});
+        /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+        /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
     EXPECT_CALL(*this, SettingChanged(_)).Times(AnyNumber());
     provider_ = std::make_unique<DeviceSettingsProvider>(
diff --git a/chrome/browser/ash/settings/device_settings_service_unittest.cc b/chrome/browser/ash/settings/device_settings_service_unittest.cc
index 83978060..0f63ec1 100644
--- a/chrome/browser/ash/settings/device_settings_service_unittest.cc
+++ b/chrome/browser/ash/settings/device_settings_service_unittest.cc
@@ -6,13 +6,13 @@
 
 #include <stdint.h>
 
-#include "ash/constants/ash_features.h"
 #include "base/compiler_specific.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/run_loop.h"
 #include "base/time/time.h"
+#include "chrome/browser/ash/ownership/owner_key_loader.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash.h"
 #include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
 #include "chrome/browser/ash/settings/device_settings_test_helper.h"
@@ -75,8 +75,8 @@
 
     // Disable owner key migration.
     feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kStoreOwnerKeyInPrivateSlot},
-        /*disabled_features=*/{features::kMigrateOwnerKeyToPrivateSlot});
+        /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
+        /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});
 
     device_policy_->payload()
         .mutable_device_policy_refresh_rate()
diff --git a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserStateBrowserControlsVisibilityDelegate.java b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserStateBrowserControlsVisibilityDelegate.java
index 1e63263..4862b71 100644
--- a/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserStateBrowserControlsVisibilityDelegate.java
+++ b/chrome/browser/browser_controls/android/java/src/org/chromium/chrome/browser/browser_controls/BrowserStateBrowserControlsVisibilityDelegate.java
@@ -131,9 +131,6 @@
             return BrowserControlsState.HIDDEN;
         } else if (mTokenHolder.hasTokens() && !sDisableOverridesForTesting) {
             return BrowserControlsState.SHOWN;
-        } else if (FeatureList.isNativeInitialized()
-                && ChromeFeatureList.isEnabled(ChromeFeatureList.TOOLBAR_SCROLL_ABLATION_ANDROID)) {
-            return BrowserControlsState.SHOWN;
         }
         return BrowserControlsState.BOTH;
     }
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index bccd339..dfd870e 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -330,6 +330,7 @@
     "//ui/base/ime",
     "//ui/chromeos/strings",
     "//ui/chromeos/styles:cros_styles_views",
+    "//ui/chromeos/styles:cros_tokens_color_mappings",
     "//ui/color:color_headers",
     "//ui/compositor",
     "//ui/events:event_constants",
diff --git a/chrome/browser/chromeos/policy/dlp/clipboard_bubble.cc b/chrome/browser/chromeos/policy/dlp/clipboard_bubble.cc
index 2f2ce6f..ff4c867 100644
--- a/chrome/browser/chromeos/policy/dlp/clipboard_bubble.cc
+++ b/chrome/browser/chromeos/policy/dlp/clipboard_bubble.cc
@@ -21,9 +21,9 @@
 #include "ui/views/controls/styled_label.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/new_window_delegate.h"
 #include "ash/public/cpp/style/color_provider.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -158,22 +158,18 @@
 
 ClipboardBubbleView::ClipboardBubbleView(const std::u16string& text) {
   SetPaintToLayer(ui::LAYER_SOLID_COLOR);
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  ash::ColorProvider* color_provider = ash::ColorProvider::Get();
-  SkColor background_color = color_provider->GetBaseLayerColor(
-      ash::ColorProvider::BaseLayerType::kTransparent80);
-  layer()->SetColor(background_color);
-#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
   // TODO(crbug.com/1311180) Replace color retrieval with more long term
   // solution.
   layer()->SetColor(RetrieveColor(cros_styles::ColorName::kBgColor));
   layer()->SetBackgroundBlur(kBubbleBlurRadius);
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
   layer()->SetBackgroundBlur(kBubbleBlurRadius);
   layer()->SetRoundedCornerRadius(kCornerRadii);
 
   // Add the managed icon.
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+  ash::ColorProvider* color_provider = ash::ColorProvider::Get();
   const SkColor icon_color = color_provider->GetContentLayerColor(
       ash::ColorProvider::ContentLayerType::kIconColorPrimary);
 #elif BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -211,7 +207,6 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   message_style.override_color = color_provider->GetContentLayerColor(
       ash::ColorProvider::ContentLayerType::kTextColorPrimary);
-  label_->SetDisplayedOnBackgroundColor(background_color);
 #elif BUILDFLAG(IS_CHROMEOS_LACROS)
   // TODO(crbug.com/1311180) Replace color retrieval with more long term
   // solution.
@@ -261,6 +256,16 @@
 
 ClipboardBubbleView::~ClipboardBubbleView() = default;
 
+void ClipboardBubbleView::OnThemeChanged() {
+  views::View::OnThemeChanged();
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  const SkColor background_color =
+      GetColorProvider()->GetColor(cros_tokens::kCrosSysSystemBaseElevated);
+  layer()->SetColor(background_color);
+  label_->SetDisplayedOnBackgroundColor(background_color);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+}
+
 void ClipboardBubbleView::UpdateBorderSize(const gfx::Size& size) {
   border_->SetSize(size);
 }
diff --git a/chrome/browser/chromeos/policy/dlp/clipboard_bubble.h b/chrome/browser/chromeos/policy/dlp/clipboard_bubble.h
index a1918b3..ecf1cf7 100644
--- a/chrome/browser/chromeos/policy/dlp/clipboard_bubble.h
+++ b/chrome/browser/chromeos/policy/dlp/clipboard_bubble.h
@@ -33,6 +33,9 @@
   virtual gfx::Size GetBubbleSize() const = 0;
 
  protected:
+  // views::View:
+  void OnThemeChanged() override;
+
   // This function should get called if the view got updated e.g. AddChildView.
   void UpdateBorderSize(const gfx::Size& size);
 
diff --git a/chrome/browser/component_updater/optimization_guide_on_device_model_installer.cc b/chrome/browser/component_updater/optimization_guide_on_device_model_installer.cc
new file mode 100644
index 0000000..8a2eaaa
--- /dev/null
+++ b/chrome/browser/component_updater/optimization_guide_on_device_model_installer.cc
@@ -0,0 +1,134 @@
+// 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/component_updater/optimization_guide_on_device_model_installer.h"
+
+#include <cstdint>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "chrome/browser/browser_process.h"
+#include "components/optimization_guide/core/model_execution/on_device_model_component.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/sha2.h"
+
+using ::optimization_guide::OnDeviceModelComponentStateManager;
+
+namespace component_updater {
+
+namespace {
+
+// Extension id is fklghjjljmnfjoepjmlobpekiapffcja.
+constexpr char kManifestName[] = "Optimization Guide On Device Model";
+constexpr base::FilePath::CharType kInstallationRelativePath[] =
+    FILE_PATH_LITERAL("OptGuideOnDeviceModel");
+constexpr uint8_t kPublicKeySHA256[32] = {
+    0x90, 0x1a, 0xaa, 0x21, 0xb7, 0xa5, 0x9c, 0xce, 0x48, 0x4a, 0x5e,
+    0x32, 0xff, 0x00, 0xa6, 0x2b, 0x4c, 0xb9, 0x89, 0xfc, 0x0b, 0xce,
+    0x6a, 0xf9, 0x93, 0x49, 0xb6, 0x51, 0x32, 0x82, 0x2c, 0xad,
+};
+static_assert(std::size(kPublicKeySHA256) == crypto::kSHA256Length);
+
+}  // namespace
+
+OptimizationGuideOnDeviceModelInstallerPolicy::
+    OptimizationGuideOnDeviceModelInstallerPolicy(
+        scoped_refptr<optimization_guide::OnDeviceModelComponentStateManager>
+            state_manager)
+    : state_manager_(state_manager) {}
+
+OptimizationGuideOnDeviceModelInstallerPolicy::
+    ~OptimizationGuideOnDeviceModelInstallerPolicy() = default;
+
+bool OptimizationGuideOnDeviceModelInstallerPolicy::VerifyInstallation(
+    const base::Value::Dict& manifest,
+    const base::FilePath& install_dir) const {
+  return state_manager_->VerifyInstallation(install_dir, manifest);
+}
+
+bool OptimizationGuideOnDeviceModelInstallerPolicy::
+    SupportsGroupPolicyEnabledComponentUpdates() const {
+  return true;
+}
+
+bool OptimizationGuideOnDeviceModelInstallerPolicy::RequiresNetworkEncryption()
+    const {
+  // This installer is only registered for users who use certain features, and
+  // we do not want to expose that they are users of those features.
+  return true;
+}
+
+update_client::CrxInstaller::Result
+OptimizationGuideOnDeviceModelInstallerPolicy::OnCustomInstall(
+    const base::Value::Dict& manifest,
+    const base::FilePath& install_dir) {
+  return update_client::CrxInstaller::Result(update_client::InstallError::NONE);
+}
+
+void OptimizationGuideOnDeviceModelInstallerPolicy::OnCustomUninstall() {
+  state_manager_->UninstallComplete();
+}
+
+void OptimizationGuideOnDeviceModelInstallerPolicy::ComponentReady(
+    const base::Version& version,
+    const base::FilePath& install_dir,
+    base::Value::Dict manifest) {
+  state_manager_->SetReady(version, install_dir, manifest);
+}
+
+base::FilePath
+OptimizationGuideOnDeviceModelInstallerPolicy::GetRelativeInstallDir() const {
+  return base::FilePath(kInstallationRelativePath);
+}
+
+void OptimizationGuideOnDeviceModelInstallerPolicy::GetHash(
+    std::vector<uint8_t>* hash) const {
+  hash->assign(std::begin(kPublicKeySHA256), std::end(kPublicKeySHA256));
+}
+
+std::string OptimizationGuideOnDeviceModelInstallerPolicy::GetName() const {
+  return kManifestName;
+}
+
+update_client::InstallerAttributes
+OptimizationGuideOnDeviceModelInstallerPolicy::GetInstallerAttributes() const {
+  return {
+      // TODO(b/310740288): Decide on attributes for model variant.
+  };
+}
+
+bool OptimizationGuideOnDeviceModelInstallerPolicy::AllowCachedCopies() const {
+  return false;
+}
+
+void RegisterOptimizationGuideOnDeviceModelComponent(
+    ComponentUpdateService* cus,
+    scoped_refptr<OnDeviceModelComponentStateManager> state_manager) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  base::MakeRefCounted<ComponentInstaller>(
+      std::make_unique<OptimizationGuideOnDeviceModelInstallerPolicy>(
+          state_manager))
+      ->Register(cus, base::DoNothing());
+}
+
+void UninstallOptimizationGuideOnDeviceModelComponent(
+    scoped_refptr<OnDeviceModelComponentStateManager> state_manager) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  base::MakeRefCounted<ComponentInstaller>(
+      std::make_unique<OptimizationGuideOnDeviceModelInstallerPolicy>(
+          state_manager))
+      ->Uninstall();
+}
+
+}  // namespace component_updater
diff --git a/chrome/browser/component_updater/optimization_guide_on_device_model_installer.h b/chrome/browser/component_updater/optimization_guide_on_device_model_installer.h
new file mode 100644
index 0000000..39cddfd3
--- /dev/null
+++ b/chrome/browser/component_updater/optimization_guide_on_device_model_installer.h
@@ -0,0 +1,59 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_COMPONENT_UPDATER_OPTIMIZATION_GUIDE_ON_DEVICE_MODEL_INSTALLER_H_
+#define CHROME_BROWSER_COMPONENT_UPDATER_OPTIMIZATION_GUIDE_ON_DEVICE_MODEL_INSTALLER_H_
+
+#include "base/memory/scoped_refptr.h"
+#include "components/component_updater/component_installer.h"
+
+namespace optimization_guide {
+class OnDeviceModelComponentStateManager;
+}  // namespace optimization_guide
+
+namespace component_updater {
+
+class OptimizationGuideOnDeviceModelInstallerPolicy
+    : public ComponentInstallerPolicy {
+ public:
+  explicit OptimizationGuideOnDeviceModelInstallerPolicy(
+      scoped_refptr<optimization_guide::OnDeviceModelComponentStateManager>
+          state_manager);
+  ~OptimizationGuideOnDeviceModelInstallerPolicy() override;
+
+  // Overrides for ComponentInstallerPolicy.
+  bool VerifyInstallation(const base::Value::Dict& manifest,
+                          const base::FilePath& install_dir) const override;
+  bool SupportsGroupPolicyEnabledComponentUpdates() const override;
+  bool RequiresNetworkEncryption() const override;
+  update_client::CrxInstaller::Result OnCustomInstall(
+      const base::Value::Dict& manifest,
+      const base::FilePath& install_dir) override;
+  void OnCustomUninstall() override;
+  void ComponentReady(const base::Version& version,
+                      const base::FilePath& install_dir,
+                      base::Value::Dict manifest) override;
+  base::FilePath GetRelativeInstallDir() const override;
+  void GetHash(std::vector<uint8_t>* hash) const override;
+  std::string GetName() const override;
+  update_client::InstallerAttributes GetInstallerAttributes() const override;
+  bool AllowCachedCopies() const override;
+
+ private:
+  scoped_refptr<optimization_guide::OnDeviceModelComponentStateManager>
+      state_manager_;
+};
+
+void RegisterOptimizationGuideOnDeviceModelComponent(
+    ComponentUpdateService* cus,
+    scoped_refptr<optimization_guide::OnDeviceModelComponentStateManager>
+        state_manager);
+
+void UninstallOptimizationGuideOnDeviceModelComponent(
+    scoped_refptr<optimization_guide::OnDeviceModelComponentStateManager>
+        state_manager);
+
+}  // namespace component_updater
+
+#endif  // CHROME_BROWSER_COMPONENT_UPDATER_OPTIMIZATION_GUIDE_ON_DEVICE_MODEL_INSTALLER_H_
diff --git a/chrome/browser/compose/compose_enabling_unittest.cc b/chrome/browser/compose/compose_enabling_unittest.cc
index 0e5ae97..2414cdd4 100644
--- a/chrome/browser/compose/compose_enabling_unittest.cc
+++ b/chrome/browser/compose/compose_enabling_unittest.cc
@@ -60,10 +60,7 @@
 class CustomMockOptimizationGuideKeyedService
     : public MockOptimizationGuideKeyedService {
  public:
-  explicit CustomMockOptimizationGuideKeyedService(
-      content::BrowserContext* browser_context)
-      : MockOptimizationGuideKeyedService(browser_context) {}
-
+  CustomMockOptimizationGuideKeyedService() = default;
   ~CustomMockOptimizationGuideKeyedService() override = default;
 
   MOCK_METHOD(void,
@@ -100,8 +97,7 @@
       context, base::BindRepeating([](content::BrowserContext* context)
                                        -> std::unique_ptr<KeyedService> {
         return std::make_unique<
-            testing::NiceMock<CustomMockOptimizationGuideKeyedService>>(
-            context);
+            testing::NiceMock<CustomMockOptimizationGuideKeyedService>>();
       }));
 }
 
diff --git a/chrome/browser/compose/compose_text_usage_logger_unittest.cc b/chrome/browser/compose/compose_text_usage_logger_unittest.cc
index 9eb8b94..bb21bfe 100644
--- a/chrome/browser/compose/compose_text_usage_logger_unittest.cc
+++ b/chrome/browser/compose/compose_text_usage_logger_unittest.cc
@@ -389,5 +389,105 @@
                       })));
 }
 
+TEST_F(ComposeTextUsageLoggerTest, ContentEditableEntry) {
+  FormData form_data = CreateForm(FormControlType::kContentEditable);
+  autofill_manager()->AddSeenFormStructure(
+      std::make_unique<autofill::FormStructure>(form_data));
+  SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
+                 u"Some text");
+
+  DeleteContents();
+
+  EXPECT_THAT(LoggedTextUsage(),
+              testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
+                  ukm_source_id_,
+                  {
+                      {"AutofillFormControlType",
+                       static_cast<int64_t>(FormControlType::kContentEditable)},
+                      {"IsAutofillFieldType", 0},
+                      {"TypedCharacterCount", 8},
+                      {"TypedWordCount", 2},
+                  })));
+}
+
+TEST_F(ComposeTextUsageLoggerTest, ContentEditableFormNotFound) {
+  // Not calling AddSeenFormStructure(), so the form won't be found.
+  FormData form_data = CreateForm(FormControlType::kContentEditable);
+  SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
+                 u"Some text");
+
+  DeleteContents();
+
+  EXPECT_THAT(LoggedTextUsage(),
+              testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
+                  ukm_source_id_, {
+                                      {"AutofillFormControlType", -1},
+                                      {"IsAutofillFieldType", 0},
+                                      {"TypedCharacterCount", 8},
+                                      {"TypedWordCount", 2},
+                                  })));
+}
+
+TEST_F(ComposeTextUsageLoggerTest, TwoTypesOfFormsModified) {
+  FormData form_data = CreateForm(FormControlType::kTextArea);
+  autofill_manager()->AddSeenFormStructure(
+      std::make_unique<autofill::FormStructure>(form_data));
+  SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
+                 u"Some text");
+  SimulateTyping(form_data.global_id(), form_data.fields[1].global_id(),
+                 u"One two three four");
+  FormData content_editable_form_data =
+      CreateForm(FormControlType::kContentEditable);
+  autofill_manager()->AddSeenFormStructure(
+      std::make_unique<autofill::FormStructure>(content_editable_form_data));
+  SimulateTyping(content_editable_form_data.global_id(),
+                 content_editable_form_data.fields[0].global_id(),
+                 u"Some text");
+  SimulateTyping(content_editable_form_data.global_id(),
+                 content_editable_form_data.fields[1].global_id(),
+                 u"One two three four");
+  DeleteContents();
+
+  EXPECT_THAT(
+      LoggedTextUsage(),
+      testing::UnorderedElementsAre(
+          ukm::TestUkmRecorder::HumanReadableUkmEntry(
+              ukm_source_id_,
+              {
+                  {"AutofillFormControlType",
+                   static_cast<int64_t>(FormControlType::kTextArea)},
+                  {"IsAutofillFieldType", 0},
+                  {"TypedCharacterCount", 8 /*9 rounded down*/},
+                  {"TypedWordCount", 2},
+              }),
+          ukm::TestUkmRecorder::HumanReadableUkmEntry(
+              ukm_source_id_,
+              {
+                  {"AutofillFormControlType",
+                   static_cast<int64_t>(FormControlType::kTextArea)},
+                  {"IsAutofillFieldType", 0},
+                  {"TypedCharacterCount", 16 /*18 rounded down*/},
+                  {"TypedWordCount", 4},
+              }),
+
+          ukm::TestUkmRecorder::HumanReadableUkmEntry(
+              ukm_source_id_,
+              {
+                  {"AutofillFormControlType",
+                   static_cast<int64_t>(FormControlType::kContentEditable)},
+                  {"IsAutofillFieldType", 0},
+                  {"TypedCharacterCount", 8 /*9 rounded down*/},
+                  {"TypedWordCount", 2},
+              }),
+          ukm::TestUkmRecorder::HumanReadableUkmEntry(
+              ukm_source_id_,
+              {
+                  {"AutofillFormControlType",
+                   static_cast<int64_t>(FormControlType::kContentEditable)},
+                  {"IsAutofillFieldType", 0},
+                  {"TypedCharacterCount", 16 /*18 rounded down*/},
+                  {"TypedWordCount", 4},
+              })));
+}
 }  // namespace
 }  // namespace compose
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 0f58a70..5583ae9 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2283,8 +2283,8 @@
   },
   {
     "name": "enable-core-location-backend",
-    "owners": [ "jameshollyer@google.com" ],
-    "expiry_milestone": 100
+    "owners": [ "deviceapi-team@google.com" ],
+    "expiry_milestone": 135
   },
   {
     "name": "enable-critical-persisted-tab-data",
@@ -2733,6 +2733,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "enable-fenced-frames-cross-origin-automatic-beacons",
+    "owners": [ "lbrady@google.com", "shivanisha@chromium.org", "chrome-fenced-frames-core@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "enable-fenced-frames-developer-mode",
     "owners": [ "dom@chromium.org", "shivanisha@chromium.org", "chrome-fenced-frames@google.com" ],
     "expiry_milestone": 130
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 850d297..4f640db 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1164,6 +1164,14 @@
     "#privacy-sandbox-ads-apis to also be enabled. See "
     "https://github.com/shivanigithub/fenced-frame";
 
+const char kEnableFencedFramesCrossOriginAutomaticBeaconsName[] =
+    "Enable automatic beacons from cross-origin subframes.";
+const char kEnableFencedFramesCrossOriginAutomaticBeaconsDescription[] =
+    "Allows documents that are cross-origin to an ad frame root to send "
+    "automatic beacons, if the document and the data are both opted in to "
+    "being used in cross-origin beacons. The data must still be set in a "
+    "document that is same-origin to the ad frame root.";
+
 const char kEnableFencedFramesDeveloperModeName[] =
     "Enable the `FencedFrameConfig` constructor.";
 const char kEnableFencedFramesDeveloperModeDescription[] =
@@ -3971,11 +3979,6 @@
 const char kCCTRealTimeEngagementSignalsDescription[] =
     "Enables sending real-time engagement signals (e.g. scroll) through "
     "CustomTabsCallback.";
-const char kCCTRealTimeEngagementSignalsAlternativeImplName[] =
-    "Enable alternative implementation for CCT real-time engagement signals.";
-const char kCCTRealTimeEngagementSignalsAlternativeImplDescription[] =
-    "Enables an alternative implementation for sending real-time engagement "
-    "signals (e.g. scroll) through CustomTabsCallback.";
 
 const char kCCTTextFragmentLookupApiEnabledName[] =
     "Enable CCT API to lookup text fragments";
@@ -4457,12 +4460,6 @@
     "When enabled, sets the market URL for use in testing the update menu "
     "item.";
 
-const char kShareSheetCustomActionsPolishName[] =
-    "Share sheet custom actions polish";
-const char kShareSheetCustomActionsPolishDescription[] =
-    "Polish Chrome provided custom actions for share sheet including dropping "
-    "low engagement actions, and shuffle the ordering. Android only.";
-
 const char kShareSheetMigrationAndroidName[] = "Share sheet refactor Android";
 const char kShareSheetMigrationAndroidDescription[] =
     "When enabled, use the Android OS share sheet.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 6789f8c..ec795d5 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -774,6 +774,9 @@
 extern const char kEnableFencedFramesName[];
 extern const char kEnableFencedFramesDescription[];
 
+extern const char kEnableFencedFramesCrossOriginAutomaticBeaconsName[];
+extern const char kEnableFencedFramesCrossOriginAutomaticBeaconsDescription[];
+
 extern const char kEnableFencedFramesDeveloperModeName[];
 extern const char kEnableFencedFramesDeveloperModeDescription[];
 
@@ -2299,8 +2302,6 @@
 
 extern const char kCCTRealTimeEngagementSignalsName[];
 extern const char kCCTRealTimeEngagementSignalsDescription[];
-extern const char kCCTRealTimeEngagementSignalsAlternativeImplName[];
-extern const char kCCTRealTimeEngagementSignalsAlternativeImplDescription[];
 
 extern const char kCCTTextFragmentLookupApiEnabledName[];
 extern const char kCCTTextFragmentLookupApiEnabledDescription[];
@@ -2606,9 +2607,6 @@
 extern const char kSetMarketUrlForTestingName[];
 extern const char kSetMarketUrlForTestingDescription[];
 
-extern const char kShareSheetCustomActionsPolishName[];
-extern const char kShareSheetCustomActionsPolishDescription[];
-
 extern const char kShareSheetMigrationAndroidName[];
 extern const char kShareSheetMigrationAndroidDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index cca4c06..b9482ec6 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -204,7 +204,6 @@
     &kCCTPostMessageAPI,
     &kCCTPrefetchDelayShowOnStart,
     &kCCTRealTimeEngagementSignals,
-    &kCCTRealTimeEngagementSignalsAlternativeImpl,
     &kCCTRedirectPreconnect,
     &kCCTRemoveRemoteViewIds,
     &kCCTReportParallelRequestStatus,
@@ -212,6 +211,7 @@
     &kCCTResizableSideSheet,
     &kCCTResizableSideSheetForThirdParties,
     &kCCTResourcePrefetch,
+    &kCCTTabModalDialog,
     &kCCTTextFragmentLookupApiEnabled,
     &kCCTToolbarCustomizations,
     &kDontAutoHideBrowserControls,
@@ -302,7 +302,6 @@
     &kFeedPositionAndroid,
     &kSearchResumptionModuleAndroid,
     &kShareSheetMigrationAndroid,
-    &kShareSheetCustomActionsPolish,
     &kSpecialLocaleWrapper,
     &kSpecialUserDecision,
     &kSuppressToolbarCaptures,
@@ -318,7 +317,6 @@
     &kTestDefaultEnabled,
     &kThumbnailPlaceholder,
     &kToolbarMicIphAndroid,
-    &kToolbarScrollAblationAndroid,
     &kTrustedWebActivityPostMessage,
     &kSpareTab,
     &kStartSurfaceAndroid,
@@ -598,10 +596,6 @@
              "CCTRealTimeEngagementSignals",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-BASE_FEATURE(kCCTRealTimeEngagementSignalsAlternativeImpl,
-             "CCTRealTimeEngagementSignalsAlternativeImpl",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kCCTRedirectPreconnect,
              "CCTRedirectPreconnect",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -630,6 +624,10 @@
              "CCTResourcePrefetch",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kCCTTabModalDialog,
+             "CCTTabModalDialog",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 BASE_FEATURE(kCCTTextFragmentLookupApiEnabled,
              "CCTTextFragmentLookupApiEnabled",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -986,10 +984,6 @@
              "ShowScrollableMVTOnNtpPhoneAndroid",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kShareSheetCustomActionsPolish,
-             "ShareSheetCustomActionsPolish",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kShareSheetMigrationAndroid,
              "ShareSheetMigrationAndroid",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -1056,10 +1050,6 @@
              "ToolbarMicIphAndroid",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kToolbarScrollAblationAndroid,
-             "ToolbarScrollAblationAndroid",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kTrustedWebActivityPostMessage,
              "TrustedWebActivityPostMessage",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 059e8d4f..b1b1510 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -60,7 +60,6 @@
 BASE_DECLARE_FEATURE(kCCTPostMessageAPI);
 BASE_DECLARE_FEATURE(kCCTPrefetchDelayShowOnStart);
 BASE_DECLARE_FEATURE(kCCTRealTimeEngagementSignals);
-BASE_DECLARE_FEATURE(kCCTRealTimeEngagementSignalsAlternativeImpl);
 BASE_DECLARE_FEATURE(kCCTRedirectPreconnect);
 BASE_DECLARE_FEATURE(kCCTRemoveRemoteViewIds);
 BASE_DECLARE_FEATURE(kCCTReportParallelRequestStatus);
@@ -69,6 +68,7 @@
 BASE_DECLARE_FEATURE(kCCTResizableSideSheetForThirdParties);
 BASE_DECLARE_FEATURE(kCCTResourcePrefetch);
 BASE_DECLARE_FEATURE(kCCTRetainingStateInMemory);
+BASE_DECLARE_FEATURE(kCCTTabModalDialog);
 BASE_DECLARE_FEATURE(kCCTTextFragmentLookupApiEnabled);
 BASE_DECLARE_FEATURE(kCCTToolbarCustomizations);
 BASE_DECLARE_FEATURE(kDontAutoHideBrowserControls);
@@ -172,7 +172,6 @@
 BASE_DECLARE_FEATURE(kFeedPositionAndroid);
 BASE_DECLARE_FEATURE(kScrollToTLDOptimization);
 BASE_DECLARE_FEATURE(kSearchResumptionModuleAndroid);
-BASE_DECLARE_FEATURE(kShareSheetCustomActionsPolish);
 BASE_DECLARE_FEATURE(kShareSheetMigrationAndroid);
 BASE_DECLARE_FEATURE(kSpecialLocaleWrapper);
 BASE_DECLARE_FEATURE(kSpecialUserDecision);
@@ -189,7 +188,6 @@
 BASE_DECLARE_FEATURE(kTestDefaultEnabled);
 BASE_DECLARE_FEATURE(kThumbnailPlaceholder);
 BASE_DECLARE_FEATURE(kToolbarMicIphAndroid);
-BASE_DECLARE_FEATURE(kToolbarScrollAblationAndroid);
 BASE_DECLARE_FEATURE(kToolbarUseHardwareBitmapDraw);
 BASE_DECLARE_FEATURE(kTrustedWebActivityPostMessage);
 BASE_DECLARE_FEATURE(kSpareTab);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index c0a47b7..69a0b92 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -192,8 +192,6 @@
     public static final String CCT_POST_MESSAGE_API = "CCTPostMessageAPI";
     public static final String CCT_PREFETCH_DELAY_SHOW_ON_START = "CCTPrefetchDelayShowOnStart";
     public static final String CCT_REAL_TIME_ENGAGEMENT_SIGNALS = "CCTRealTimeEngagementSignals";
-    public static final String CCT_REAL_TIME_ENGAGEMENT_SIGNALS_ALTERNATIVE_IMPL =
-            "CCTRealTimeEngagementSignalsAlternativeImpl";
     public static final String CCT_REDIRECT_PRECONNECT = "CCTRedirectPreconnect";
     public static final String CCT_REMOVE_REMOTE_VIEW_IDS = "CCTRemoveRemoteViewIds";
     public static final String CCT_REPORT_PARALLEL_REQUEST_STATUS =
@@ -203,6 +201,7 @@
     public static final String CCT_RESIZABLE_SIDE_SHEET_FOR_THIRD_PARTIES =
             "CCTResizableSideSheetForThirdParties";
     public static final String CCT_RESOURCE_PREFETCH = "CCTResourcePrefetch";
+    public static final String CCT_TAB_MODAL_DIALOG = "CCTTabModalDialog";
     public static final String CCT_TEXT_FRAGMENT_LOOKUP_API_ENABLED =
             "CCTTextFragmentLookupApiEnabled";
     public static final String CCT_TOOLBAR_CUSTOMIZATIONS = "CCTToolbarCustomizations";
@@ -422,7 +421,6 @@
             "SearchReadyOmniboxAllowQueryEdit";
     public static final String SEARCH_RESUMPTION_MODULE_ANDROID = "SearchResumptionModuleAndroid";
     public static final String SEED_ACCOUNTS_REVAMP = "SeedAccountsRevamp";
-    public static final String SHARE_SHEET_CUSTOM_ACTIONS_POLISH = "ShareSheetCustomActionsPolish";
     public static final String SHARE_SHEET_MIGRATION_ANDROID = "ShareSheetMigrationAndroid";
     public static final String SEND_TAB_TO_SELF_V2 = "SendTabToSelfV2";
     public static final String SHOPPING_LIST = "ShoppingList";
@@ -466,7 +464,6 @@
     public static final String TEST_DEFAULT_DISABLED = "TestDefaultDisabled";
     public static final String TEST_DEFAULT_ENABLED = "TestDefaultEnabled";
     public static final String THUMBNAIL_PLACEHOLDER = "ThumbnailPlaceholder";
-    public static final String TOOLBAR_SCROLL_ABLATION_ANDROID = "ToolbarScrollAblationAndroid";
     public static final String TOOLBAR_USE_HARDWARE_BITMAP_DRAW = "ToolbarUseHardwareBitmapDraw";
     public static final String TRACKING_PROTECTION_SETTINGS_PAGE_ROLLBACK_NOTICE =
             "TrackingProtectionSettingsPageRollbackNotice";
@@ -541,6 +538,7 @@
             new CachedFlag(CCT_RESIZABLE_SIDE_SHEET, true);
     public static final CachedFlag sCctResizableSideSheetForThirdParties =
             new CachedFlag(CCT_RESIZABLE_SIDE_SHEET_FOR_THIRD_PARTIES, true);
+    public static final CachedFlag sCctTabModalDialog = new CachedFlag(CCT_TAB_MODAL_DIALOG, true);
     public static final CachedFlag sCctToolbarCustomizations =
             new CachedFlag(CCT_TOOLBAR_CUSTOMIZATIONS, true);
     public static final CachedFlag sCloseTabSaveTabList =
@@ -668,6 +666,7 @@
                     sCctResizableForThirdParties,
                     sCctResizableSideSheet,
                     sCctResizableSideSheetForThirdParties,
+                    sCctTabModalDialog,
                     sCctToolbarCustomizations,
                     sCloseTabSaveTabList,
                     sCollectAndroidFrameTimelineMetrics,
@@ -786,8 +785,6 @@
             newMutableFlagWithSafeDefault(SUPPRESS_TOOLBAR_CAPTURES, false);
     public static final MutableFlagWithSafeDefault sThumbnailPlaceholder =
             newMutableFlagWithSafeDefault(THUMBNAIL_PLACEHOLDER, true);
-    public static final MutableFlagWithSafeDefault sToolbarScrollAblation =
-            newMutableFlagWithSafeDefault(TOOLBAR_SCROLL_ABLATION_ANDROID, false);
     public static final MutableFlagWithSafeDefault sTouchDownTriggerForPrefetch =
             newMutableFlagWithSafeDefault(OMNIBOX_TOUCH_DOWN_TRIGGER_FOR_PREFETCH, false);
     public static final MutableFlagWithSafeDefault sVisibleUrlTruncation =
diff --git a/chrome/browser/lacros/clipboard_lacros_browsertest.cc b/chrome/browser/lacros/clipboard_lacros_browsertest.cc
index 606c49d2..e2debc4f 100644
--- a/chrome/browser/lacros/clipboard_lacros_browsertest.cc
+++ b/chrome/browser/lacros/clipboard_lacros_browsertest.cc
@@ -67,6 +67,9 @@
   aura::Window* window = BrowserView::GetBrowserViewForBrowser(browser())
                              ->frame()
                              ->GetNativeWindow();
+  std::string id =
+      lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(id));
   ASSERT_TRUE(
       browser_test_util::SendAndWaitForMouseClick(window->GetRootWindow()));
 
diff --git a/chrome/browser/lacros/download_status_updater_lacros_browsertest.cc b/chrome/browser/lacros/download_status_updater_lacros_browsertest.cc
index 212234a..7a2a8ae2 100644
--- a/chrome/browser/lacros/download_status_updater_lacros_browsertest.cc
+++ b/chrome/browser/lacros/download_status_updater_lacros_browsertest.cc
@@ -284,6 +284,19 @@
     item_ = nullptr;
   }
 
+  Browser* CreateAndWaitForBrowser(Profile* profile, bool otr = false) {
+    Browser* browser =
+        otr ? CreateIncognitoBrowser(profile) : CreateBrowser(profile);
+    std::string browser_window_id =
+        lacros_window_utility::GetRootWindowUniqueId(
+            BrowserView::GetBrowserViewForBrowser(browser)
+                ->frame()
+                ->GetNativeWindow()
+                ->GetRootWindow());
+    EXPECT_TRUE(browser_test_util::WaitForWindowCreation(browser_window_id));
+    return browser;
+  }
+
   void SetUpBrowserForTest(Browser* browser) {
     download_button(browser)->DisableAutoCloseTimerForTesting();
     download_button(browser)->DisableDownloadStartedAnimationForTesting();
@@ -536,7 +549,7 @@
     DownloadStatusUpdaterBrowserTest,
     ShowInBrowser_NormalDownload_PickMostRecentActiveBrowser) {
   // Open a different browser window and activate it.
-  Browser* browser2 = CreateBrowser(browser()->profile());
+  Browser* browser2 = CreateAndWaitForBrowser(browser()->profile());
   ActivateBrowser(browser2);
 
   DownloadStatusUpdaterClientAsyncWaiter client(
@@ -563,7 +576,7 @@
     DownloadStatusUpdaterBrowserTest,
     ShowInBrowser_DangerousDownload_PickMostRecentActiveBrowser) {
   // Open a different browser window and activate it.
-  Browser* browser2 = CreateBrowser(browser()->profile());
+  Browser* browser2 = CreateAndWaitForBrowser(browser()->profile());
   ActivateBrowser(browser2);
 
   DownloadStatusUpdaterClientAsyncWaiter client(
@@ -693,7 +706,8 @@
     DownloadStatusUpdaterBrowserTest,
     ShowInBrowser_NormalDownload_MatchBrowserForExactProfile) {
   // Open an incognito browser window.
-  Browser* otr_browser = CreateIncognitoBrowser(browser()->profile());
+  Browser* otr_browser =
+      CreateAndWaitForBrowser(browser()->profile(), /*otr=*/true);
 
   // Make the incognito window the last active browser. It should not be picked
   // even though it is the most recent active browser, because the profile is
@@ -724,7 +738,8 @@
     DownloadStatusUpdaterBrowserTest,
     ShowInBrowser_DangerousDownload_MatchBrowserForExactProfile) {
   // Open an incognito browser window.
-  Browser* otr_browser = CreateIncognitoBrowser(browser()->profile());
+  Browser* otr_browser =
+      CreateAndWaitForBrowser(browser()->profile(), /*otr=*/true);
 
   // Make the incognito window the last active browser. It should not be picked
   // even though it is the most recent active browser, because the profile is
@@ -754,7 +769,8 @@
 IN_PROC_BROWSER_TEST_F(DownloadStatusUpdaterBrowserTest,
                        ShowInBrowser_NormalDownload_IncognitoBrowser) {
   // Open an incognito browser window.
-  Browser* otr_browser = CreateIncognitoBrowser(browser()->profile());
+  Browser* otr_browser =
+      CreateAndWaitForBrowser(browser()->profile(), /*otr=*/true);
 
   // Make `browser()` the last active browser. It should not be picked even
   // though it is the most recent active browser, because the profile is
@@ -785,7 +801,8 @@
 IN_PROC_BROWSER_TEST_F(DownloadStatusUpdaterBrowserTest,
                        ShowInBrowser_DangerousDownload_IncognitoBrowser) {
   // Open an incognito browser window.
-  Browser* otr_browser = CreateIncognitoBrowser(browser()->profile());
+  Browser* otr_browser =
+      CreateAndWaitForBrowser(browser()->profile(), /*otr=*/true);
 
   // Make `browser()` the last active browser. It should not be picked even
   // though it is the most recent active browser, because the profile is
diff --git a/chrome/browser/lacros/input_method_lacros_browsertest.cc b/chrome/browser/lacros/input_method_lacros_browsertest.cc
index fe41a922..df04496 100644
--- a/chrome/browser/lacros/input_method_lacros_browsertest.cc
+++ b/chrome/browser/lacros/input_method_lacros_browsertest.cc
@@ -586,13 +586,7 @@
   if (!input_method.is_bound()) {
     GTEST_SKIP() << "Unsupported ash version";
   }
-
-  // Create a browser without omnibox since we don't want to deal with the
-  // automatic omnibox focus.
-  // TODO(b/315079554): Find a cleaner way.
-  auto* browser2 = CreateBrowserForApp("browser2", browser()->profile());
-
-  const std::string id = RenderAutofocusedInputFieldInLacros(browser2);
+  const std::string id = RenderAutofocusedInputFieldInLacros(browser());
   InputMethodTestInterfaceAsyncWaiter input_method_async_waiter(
       input_method.get());
   input_method_async_waiter.WaitForFocus();
@@ -888,13 +882,7 @@
   if (!input_method.is_bound()) {
     GTEST_SKIP() << "Unsupported ash version";
   }
-
-  // Create a browser without omnibox since we don't want to deal with the
-  // automatic omnibox focus.
-  // TODO(b/315079554): Find a cleaner way.
-  auto* browser2 = CreateBrowserForApp("browser2", browser()->profile());
-
-  const std::string id = RenderAutofocusedInputFieldInLacros(browser2);
+  const std::string id = RenderAutofocusedInputFieldInLacros(browser());
   InputMethodTestInterfaceAsyncWaiter input_method_async_waiter(
       input_method.get());
   input_method_async_waiter.WaitForFocus();
diff --git a/chrome/browser/lacros/overview_lacros_browsertest.cc b/chrome/browser/lacros/overview_lacros_browsertest.cc
index f0913c7..8551369 100644
--- a/chrome/browser/lacros/overview_lacros_browsertest.cc
+++ b/chrome/browser/lacros/overview_lacros_browsertest.cc
@@ -42,6 +42,12 @@
   if (!IsServiceAvailable())
     return;
 
+  // Wait for the window to be visible.
+  aura::Window* window = browser()->window()->GetNativeWindow();
+  std::string id =
+      lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(id));
+
   // Enter overview mode.
   auto& test_controller = chromeos::LacrosService::Get()
                               ->GetRemote<crosapi::mojom::TestController>();
@@ -51,8 +57,6 @@
 
   // Close the window by closing all tabs and wait for it to stop existing in
   // ash.
-  std::string id = lacros_window_utility::GetRootWindowUniqueId(
-      browser()->window()->GetNativeWindow()->GetRootWindow());
   browser()->tab_strip_model()->CloseAllTabs();
   ASSERT_TRUE(browser_test_util::WaitForWindowDestruction(id));
 }
@@ -65,11 +69,22 @@
   if (!IsServiceAvailable())
     return;
 
+  // Wait for the window to be visible.
+  aura::Window* main_window = browser()->window()->GetNativeWindow();
+  std::string main_id = lacros_window_utility::GetRootWindowUniqueId(
+      main_window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(main_id));
+
   // Create an incognito window and make it visible.
   Browser* incognito_browser = Browser::Create(Browser::CreateParams(
       browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
       true));
   AddBlankTabAndShow(incognito_browser);
+  aura::Window* incognito_window =
+      incognito_browser->window()->GetNativeWindow();
+  std::string incognito_id = lacros_window_utility::GetRootWindowUniqueId(
+      incognito_window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(incognito_id));
 
   // Enter overview mode.
   auto& test_controller = chromeos::LacrosService::Get()
@@ -81,8 +96,6 @@
 
   // Close the incognito window by closing all tabs and wait for it to stop
   // existing in ash.
-  std::string incognito_id = lacros_window_utility::GetRootWindowUniqueId(
-      incognito_browser->window()->GetNativeWindow()->GetRootWindow());
   incognito_browser->tab_strip_model()->CloseAllTabs();
   ASSERT_TRUE(browser_test_util::WaitForWindowDestruction(incognito_id));
 
diff --git a/chrome/browser/lacros/popup_lacros_browsertest.cc b/chrome/browser/lacros/popup_lacros_browsertest.cc
index 5b9e456..6d338bb 100644
--- a/chrome/browser/lacros/popup_lacros_browsertest.cc
+++ b/chrome/browser/lacros/popup_lacros_browsertest.cc
@@ -87,12 +87,16 @@
   // near the top of the screen.
   browser()->window()->Maximize();
 
+  // Wait for the window to be created.
+  aura::Window* window = browser()->window()->GetNativeWindow();
+  std::string window_id =
+      lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(window_id));
+
   // Wait for the window to be globally positioned at 0,0. It will eventually
   // have this position because it is maximized. We cannot assert the position
   // lacros-side because Wayland clients do not know the position of their
   // windows on the display.
-  std::string window_id = lacros_window_utility::GetRootWindowUniqueId(
-      browser()->window()->GetNativeWindow()->GetRootWindow());
   WaitForWindowPositionInScreen(window_id, gfx::Point(0, 0));
 
   // Precondition: The browser is the only open widget.
diff --git a/chrome/browser/lacros/tab_scrubber_lacros_browsertest.cc b/chrome/browser/lacros/tab_scrubber_lacros_browsertest.cc
index 0edd225e..fc2c6ca 100644
--- a/chrome/browser/lacros/tab_scrubber_lacros_browsertest.cc
+++ b/chrome/browser/lacros/tab_scrubber_lacros_browsertest.cc
@@ -55,7 +55,14 @@
                            kTriggerTabScrubbingMinVersion)) {
     return;
   }
-  // Add further 5 blank tabs to the initial browser.
+
+  // Wait for the window to be created.
+  aura::Window* window = browser()->window()->GetNativeWindow();
+  std::string window_id =
+      lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(window_id));
+
+  // Add further 5 blank tabs.
   for (int i = 0; i < 5; ++i)
     AddBlankTab(browser());
 
diff --git a/chrome/browser/lacros/tablet_mode_lacros_browsertest.cc b/chrome/browser/lacros/tablet_mode_lacros_browsertest.cc
index be320d63..f2e4d749 100644
--- a/chrome/browser/lacros/tablet_mode_lacros_browsertest.cc
+++ b/chrome/browser/lacros/tablet_mode_lacros_browsertest.cc
@@ -34,11 +34,22 @@
     return;
   }
 
+  // Wait for the window to be visible.
+  aura::Window* main_window = browser()->window()->GetNativeWindow();
+  std::string main_id = lacros_window_utility::GetRootWindowUniqueId(
+      main_window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(main_id));
+
   // Create an incognito window and make it visible.
   Browser* incognito_browser = Browser::Create(Browser::CreateParams(
       browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
       true));
   AddBlankTabAndShow(incognito_browser);
+  aura::Window* incognito_window =
+      incognito_browser->window()->GetNativeWindow();
+  std::string incognito_id = lacros_window_utility::GetRootWindowUniqueId(
+      incognito_window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(incognito_id));
 
   auto& test_controller =
       lacros_service->GetRemote<crosapi::mojom::TestController>();
@@ -51,8 +62,6 @@
 
   // Close the incognito window by closing all tabs and wait for it to stop
   // existing in ash.
-  std::string incognito_id = lacros_window_utility::GetRootWindowUniqueId(
-      incognito_browser->window()->GetNativeWindow()->GetRootWindow());
   incognito_browser->tab_strip_model()->CloseAllTabs();
   ASSERT_TRUE(browser_test_util::WaitForWindowDestruction(incognito_id));
 
diff --git a/chrome/browser/lacros/web_contents_can_go_back_observer_browsertest.cc b/chrome/browser/lacros/web_contents_can_go_back_observer_browsertest.cc
index 73eb7cbd..e1860a0 100644
--- a/chrome/browser/lacros/web_contents_can_go_back_observer_browsertest.cc
+++ b/chrome/browser/lacros/web_contents_can_go_back_observer_browsertest.cc
@@ -74,6 +74,13 @@
   ASSERT_TRUE(lacros_service);
   ASSERT_TRUE(lacros_service->IsAvailable<crosapi::mojom::TestController>());
 
+  aura::Window* window = BrowserView::GetBrowserViewForBrowser(browser())
+                             ->frame()
+                             ->GetNativeWindow();
+  std::string id =
+      lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(id));
+
   EXPECT_FALSE(chrome::CanGoBack(browser()));
   EXPECT_FALSE(chrome::CanGoForward(browser()));
 
@@ -82,12 +89,9 @@
                                WindowOpenDisposition::CURRENT_TAB,
                                ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
 
-  std::string window_id = lacros_window_utility::GetRootWindowUniqueId(
-      browser()->window()->GetNativeWindow()->GetRootWindow());
-
   EXPECT_TRUE(chrome::CanGoBack(browser()));
   EXPECT_FALSE(chrome::CanGoForward(browser()));
-  CheckCanGoBackOnServer(window_id, true /* expected_value */);
+  CheckCanGoBackOnServer(id, true /* expected_value */);
 
   // Tweak the back/forward list.
   chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
@@ -96,7 +100,7 @@
 
   EXPECT_FALSE(chrome::CanGoBack(browser()));
   EXPECT_TRUE(chrome::CanGoForward(browser()));
-  CheckCanGoBackOnServer(window_id, false /* expected_value */);
+  CheckCanGoBackOnServer(id, false /* expected_value */);
 }
 
 IN_PROC_BROWSER_TEST_F(WebContentsCanGoBackObserverTest,
@@ -105,6 +109,13 @@
   ASSERT_TRUE(lacros_service);
   ASSERT_TRUE(lacros_service->IsAvailable<crosapi::mojom::TestController>());
 
+  aura::Window* window = BrowserView::GetBrowserViewForBrowser(browser())
+                             ->frame()
+                             ->GetNativeWindow();
+  std::string id =
+      lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
+  ASSERT_TRUE(browser_test_util::WaitForWindowCreation(id));
+
   EXPECT_FALSE(chrome::CanGoBack(browser()));
   EXPECT_FALSE(chrome::CanGoForward(browser()));
 
@@ -113,12 +124,9 @@
                                WindowOpenDisposition::CURRENT_TAB,
                                ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
 
-  std::string window_id = lacros_window_utility::GetRootWindowUniqueId(
-      browser()->window()->GetNativeWindow()->GetRootWindow());
-
   EXPECT_TRUE(chrome::CanGoBack(browser()));
   EXPECT_FALSE(chrome::CanGoForward(browser()));
-  CheckCanGoBackOnServer(window_id, true /* expected_value */);
+  CheckCanGoBackOnServer(id, true /* expected_value */);
 
   NavigateToURLWithDisposition(browser(), GURL(chrome::kChromeUIVersionURL),
                                WindowOpenDisposition::NEW_FOREGROUND_TAB,
@@ -127,7 +135,7 @@
   EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
   EXPECT_FALSE(chrome::CanGoBack(browser()));
   EXPECT_FALSE(chrome::CanGoForward(browser()));
-  CheckCanGoBackOnServer(window_id, false /* expected_value */);
+  CheckCanGoBackOnServer(id, false /* expected_value */);
 
   // Navigate the current (second) tab to a different URL, so we can test
   // back/forward later.
@@ -136,7 +144,7 @@
                                ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
   EXPECT_TRUE(chrome::CanGoBack(browser()));
   EXPECT_FALSE(chrome::CanGoForward(browser()));
-  CheckCanGoBackOnServer(window_id, true /* expected_value */);
+  CheckCanGoBackOnServer(id, true /* expected_value */);
 
   // Tweak the back/forward list of the 2nd tab, and verify.
   chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
@@ -145,7 +153,7 @@
 
   EXPECT_FALSE(chrome::CanGoBack(browser()));
   EXPECT_TRUE(chrome::CanGoForward(browser()));
-  CheckCanGoBackOnServer(window_id, false /* expected_value */);
+  CheckCanGoBackOnServer(id, false /* expected_value */);
 
   // Switch to a different tab, and verify whether the `can go back` property
   // updates accordingly.
@@ -156,5 +164,5 @@
 
   EXPECT_TRUE(chrome::CanGoBack(browser()));
   EXPECT_FALSE(chrome::CanGoForward(browser()));
-  CheckCanGoBackOnServer(window_id, true /* expected_value */);
+  CheckCanGoBackOnServer(id, true /* expected_value */);
 }
diff --git a/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc b/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc
index 2d1ca32..32ee550 100644
--- a/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc
+++ b/chrome/browser/media/webrtc/webrtc_video_display_perf_browsertest.cc
@@ -118,6 +118,59 @@
   }
 }
 
+content::WebContents* OpenWebrtcInternalsTab(Browser* browser) {
+  chrome::AddTabAt(browser, GURL(url::kAboutBlankURL), -1, true);
+  EXPECT_TRUE(
+      ui_test_utils::NavigateToURL(browser, GURL("chrome://webrtc-internals")));
+  return browser->tab_strip_model()->GetActiveWebContents();
+}
+
+std::vector<double> ParseGoogMaxDecodeFromWebrtcInternalsTab(
+    const std::string& webrtc_internals_stats_json) {
+  std::vector<double> goog_decode_ms;
+
+  absl::optional<base::Value> parsed_json =
+      base::JSONReader::Read(webrtc_internals_stats_json);
+  if (!parsed_json || !parsed_json->is_dict())
+    return goog_decode_ms;
+  const base::Value::Dict& dictionary = parsed_json->GetDict();
+
+  // |dictionary| should have exactly two entries, one per ssrc.
+  if (dictionary.size() != 2u)
+    return goog_decode_ms;
+
+  // Only a given |dictionary| entry will have a "stats" entry that has a key
+  // that ends with "recv-googMaxDecodeMs" inside (it will start with the ssrc
+  // id, but we don't care about that). Then collect the string of "values" out
+  // of that key and convert those into the |goog_decode_ms| vector of doubles.
+  for (auto dictionary_entry : dictionary) {
+    for (auto ssrc_entry : dictionary_entry.second.GetDict()) {
+      if (ssrc_entry.first != "stats")
+        continue;
+
+      for (auto stat_entry : ssrc_entry.second.GetDict()) {
+        if (!base::EndsWith(stat_entry.first, "recv-googMaxDecodeMs",
+                            base::CompareCase::SENSITIVE)) {
+          continue;
+        }
+        const std::string* values_entry =
+            stat_entry.second.GetDict().FindString("values");
+        if (!values_entry) {
+          continue;
+        }
+        base::StringTokenizer values_tokenizer(*values_entry, "[,]");
+        while (values_tokenizer.GetNext()) {
+          if (values_tokenizer.token_is_delim())
+            continue;
+          goog_decode_ms.push_back(atof(values_tokenizer.token().c_str()) *
+                                   base::Time::kMicrosecondsPerMillisecond);
+        }
+      }
+    }
+  }
+  return goog_decode_ms;
+}
+
 }  // anonymous namespace
 
 // Tests the performance of Chrome displaying remote video.
@@ -162,6 +215,13 @@
 
   void TestVideoDisplayPerf(const std::string& video_codec) {
     ASSERT_TRUE(embedded_test_server()->Start());
+    // chrome:webrtc-internals doesn't start tracing anything until the
+    // connection(s) are up.
+    content::WebContents* webrtc_internals_tab =
+        OpenWebrtcInternalsTab(browser());
+    EXPECT_TRUE(
+        content::ExecJs(webrtc_internals_tab,
+                        "currentGetStatsMethod = OPTION_GETSTATS_LEGACY"));
 
     content::WebContents* left_tab =
         OpenPageAndGetUserMediaInNewTabWithConstraints(
@@ -204,6 +264,12 @@
     // Run the connection for 5 seconds to collect metrics.
     test::SleepInJavascript(left_tab, 5000);
 
+    const std::string webrtc_internals_stats_json = ExecuteJavascript(
+        "JSON.stringify(peerConnectionDataStore);", webrtc_internals_tab);
+    webrtc_decode_latencies_ =
+        ParseGoogMaxDecodeFromWebrtcInternalsTab(webrtc_internals_stats_json);
+    chrome::CloseWebContents(browser(), webrtc_internals_tab, false);
+
     std::string json_events;
     ASSERT_TRUE(tracing::EndTracing(&json_events));
     std::unique_ptr<trace_analyzer::TraceAnalyzer> analyzer(
@@ -373,6 +439,8 @@
 
     PrintMeanAndMax("Post-decode-to-raster latency", name_modifier,
                     video_frame_submmitter_latencies_);
+    PrintMeanAndMax("WebRTC decode latency", name_modifier,
+                    webrtc_decode_latencies_);
   }
 
   VideoDisplayPerfTestConfig test_config_;
@@ -389,6 +457,7 @@
   // These two put together represent the whole delay from encoded video frames
   // to OS swap buffers call (or callback, depending on the platform).
   std::vector<double> video_frame_submmitter_latencies_;
+  std::vector<double> webrtc_decode_latencies_;
 };
 
 INSTANTIATE_TEST_SUITE_P(WebRtcVideoDisplayPerfBrowserTests,
@@ -399,14 +468,15 @@
                                           testing::Values(30, 60),
                                           testing::Bool()));
 
+// TODO(crbug.com/1509755): Rewrite these tests to not use legacy GetStats API.
 IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
-                       TestVideoDisplayPerfVP9) {
+                       DISABLED_TestVideoDisplayPerfVP9) {
   TestVideoDisplayPerf("VP9");
 }
 
 #if BUILDFLAG(RTC_USE_H264)
 IN_PROC_BROWSER_TEST_P(WebRtcVideoDisplayPerfBrowserTest,
-                       TestVideoDisplayPerfH264) {
+                       DISABLED_TestVideoDisplayPerfH264) {
   if (!base::FeatureList::IsEnabled(
           blink::features::kWebRtcH264WithOpenH264FFmpeg)) {
     LOG(WARNING) << "Run-time feature WebRTC-H264WithOpenH264FFmpeg disabled. "
diff --git a/chrome/browser/optimization_guide/android/optimization_guide_bridge_unittest.cc b/chrome/browser/optimization_guide/android/optimization_guide_bridge_unittest.cc
index 5ad8170..67aee69 100644
--- a/chrome/browser/optimization_guide/android/optimization_guide_bridge_unittest.cc
+++ b/chrome/browser/optimization_guide/android/optimization_guide_bridge_unittest.cc
@@ -53,16 +53,15 @@
     pref_service_ = std::make_unique<TestingPrefServiceSimple>();
     optimization_guide::prefs::RegisterProfilePrefs(pref_service_->registry());
 
-    optimization_guide_keyed_service_ =
-        static_cast<MockOptimizationGuideKeyedService*>(
-            OptimizationGuideKeyedServiceFactory::GetInstance()
-                ->SetTestingFactoryAndUse(
-                    profile_,
-                    base::BindRepeating([](content::BrowserContext* context)
-                                            -> std::unique_ptr<KeyedService> {
-                      return std::make_unique<
-                          MockOptimizationGuideKeyedService>(context);
-                    })));
+    optimization_guide_keyed_service_ = static_cast<
+        MockOptimizationGuideKeyedService*>(
+        OptimizationGuideKeyedServiceFactory::GetInstance()
+            ->SetTestingFactoryAndUse(
+                profile_,
+                base::BindRepeating([](content::BrowserContext* context)
+                                        -> std::unique_ptr<KeyedService> {
+                  return std::make_unique<MockOptimizationGuideKeyedService>();
+                })));
   }
 
   void RegisterOptimizationTypes() {
diff --git a/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.cc
index 4b235e8..f9ae2b7 100644
--- a/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.cc
+++ b/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.cc
@@ -49,9 +49,10 @@
   }
 }
 
-MockOptimizationGuideKeyedService::MockOptimizationGuideKeyedService(
-    content::BrowserContext* browser_context)
-    : OptimizationGuideKeyedService(browser_context) {}
+MockOptimizationGuideKeyedService::MockOptimizationGuideKeyedService()
+    : OptimizationGuideKeyedService(nullptr) {}
 
 MockOptimizationGuideKeyedService::~MockOptimizationGuideKeyedService() =
     default;
+
+void MockOptimizationGuideKeyedService::Shutdown() {}
diff --git a/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h
index f86356fd..0c384f3 100644
--- a/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/mock_optimization_guide_keyed_service.h
@@ -25,10 +25,11 @@
   static void TearDown();
   static void ResetForTesting();
 
-  explicit MockOptimizationGuideKeyedService(
-      content::BrowserContext* browser_context);
+  MockOptimizationGuideKeyedService();
   ~MockOptimizationGuideKeyedService() override;
 
+  void Shutdown() override;
+
   MOCK_METHOD(void,
               RegisterOptimizationTypes,
               (const std::vector<optimization_guide::proto::OptimizationType>&),
@@ -62,6 +63,24 @@
   MOCK_METHOD(void,
               UploadModelQualityLogs,
               (std::unique_ptr<optimization_guide::ModelQualityLogEntry>));
+  MOCK_METHOD(void,
+              AddObserverForOptimizationTargetModel,
+              (optimization_guide::proto::OptimizationTarget,
+               const absl::optional<optimization_guide::proto::Any>&,
+               optimization_guide::OptimizationTargetModelObserver*),
+              (override));
+  MOCK_METHOD(void,
+              RemoveObserverForOptimizationTargetModel,
+              (optimization_guide::proto::OptimizationTarget,
+               optimization_guide::OptimizationTargetModelObserver*),
+              (override));
+
+  MOCK_METHOD(void,
+              OnNavigationStartOrRedirect,
+              (OptimizationGuideNavigationData*),
+              (override));
+
+  MOCK_METHOD(void, OnNavigationFinish, (const std::vector<GURL>&), (override));
 };
 
 #endif  // CHROME_BROWSER_OPTIMIZATION_GUIDE_MOCK_OPTIMIZATION_GUIDE_KEYED_SERVICE_H_
diff --git a/chrome/browser/optimization_guide/model_execution/chrome_on_device_model_service_controller.cc b/chrome/browser/optimization_guide/model_execution/chrome_on_device_model_service_controller.cc
index 1e7d5aad..2faa10b 100644
--- a/chrome/browser/optimization_guide/model_execution/chrome_on_device_model_service_controller.cc
+++ b/chrome/browser/optimization_guide/model_execution/chrome_on_device_model_service_controller.cc
@@ -17,10 +17,13 @@
 
 }  // namespace
 
-ChromeOnDeviceModelServiceController::ChromeOnDeviceModelServiceController()
+ChromeOnDeviceModelServiceController::ChromeOnDeviceModelServiceController(
+    base::WeakPtr<OnDeviceModelComponentStateManager>
+        on_device_component_state_manager)
     : OnDeviceModelServiceController(
           std::make_unique<OnDeviceModelAccessController>(
-              *g_browser_process->local_state())) {
+              *g_browser_process->local_state()),
+          std::move(on_device_component_state_manager)) {
   CHECK_EQ(nullptr, g_instance);
   g_instance = this;
 }
diff --git a/chrome/browser/optimization_guide/model_execution/chrome_on_device_model_service_controller.h b/chrome/browser/optimization_guide/model_execution/chrome_on_device_model_service_controller.h
index f3cbc0f..e0b23cb 100644
--- a/chrome/browser/optimization_guide/model_execution/chrome_on_device_model_service_controller.h
+++ b/chrome/browser/optimization_guide/model_execution/chrome_on_device_model_service_controller.h
@@ -7,7 +7,10 @@
 
 #include "components/optimization_guide/core/model_execution/on_device_model_service_controller.h"
 
+#include "base/memory/scoped_refptr.h"
+
 namespace optimization_guide {
+class OnDeviceModelComponentStateManager;
 
 // Chrome uses a single instance of OnDeviceModelServiceController. This is done
 // for two reasons:
@@ -19,7 +22,9 @@
 class ChromeOnDeviceModelServiceController
     : public OnDeviceModelServiceController {
  public:
-  ChromeOnDeviceModelServiceController();
+  explicit ChromeOnDeviceModelServiceController(
+      base::WeakPtr<OnDeviceModelComponentStateManager>
+          on_device_component_state_manager);
 
   // Returns the OnDeviceModelServiceController, null if it one hasn't been
   // created yet.
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
index ba91646..76a0202 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.cc
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/component_updater/optimization_guide_on_device_model_installer.h"
 #include "chrome/browser/download/background_download_service_factory.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/optimization_guide/chrome_hints_manager.h"
@@ -33,6 +34,7 @@
 #include "components/optimization_guide/core/hints_processing_util.h"
 #include "components/optimization_guide/core/model_execution/model_execution_features_controller.h"
 #include "components/optimization_guide/core/model_execution/model_execution_manager.h"
+#include "components/optimization_guide/core/model_execution/on_device_model_component.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_service_controller.h"
 #include "components/optimization_guide/core/model_quality/model_quality_log_entry.h"
 #include "components/optimization_guide/core/model_quality/model_quality_logs_uploader_service.h"
@@ -41,6 +43,7 @@
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_logger.h"
 #include "components/optimization_guide/core/optimization_guide_navigation_data.h"
+#include "components/optimization_guide/core/optimization_guide_prefs.h"
 #include "components/optimization_guide/core/optimization_guide_store.h"
 #include "components/optimization_guide/core/optimization_guide_util.h"
 #include "components/optimization_guide/core/prediction_manager.h"
@@ -67,6 +70,7 @@
 
 namespace {
 
+using ::optimization_guide::OnDeviceModelComponentStateManager;
 using ::optimization_guide::OnDeviceModelPerformanceClass;
 
 // Deletes old store paths that were written in incorrect locations.
@@ -159,18 +163,41 @@
 }
 
 scoped_refptr<optimization_guide::OnDeviceModelServiceController>
-GetOnDeviceModelServiceController() {
+GetOnDeviceModelServiceController(
+    base::WeakPtr<optimization_guide::OnDeviceModelComponentStateManager>
+        on_device_component_manager) {
   scoped_refptr<optimization_guide::OnDeviceModelServiceController>
       service_controller = optimization_guide::
           ChromeOnDeviceModelServiceController::GetSingleInstanceMayBeNull();
   if (!service_controller) {
     service_controller = base::MakeRefCounted<
-        optimization_guide::ChromeOnDeviceModelServiceController>();
+        optimization_guide::ChromeOnDeviceModelServiceController>(
+        std::move(on_device_component_manager));
     service_controller->Init();
   }
   return service_controller;
 }
 
+class OnDeviceModelComponentStateManagerDelegate
+    : public OnDeviceModelComponentStateManager::Delegate {
+ public:
+  ~OnDeviceModelComponentStateManagerDelegate() override = default;
+  void RegisterInstaller(scoped_refptr<OnDeviceModelComponentStateManager>
+                             state_manager) override {
+    if (!g_browser_process) {
+      return;
+    }
+    component_updater::RegisterOptimizationGuideOnDeviceModelComponent(
+        g_browser_process->component_updater(), state_manager);
+  }
+
+  void Uninstall(scoped_refptr<OnDeviceModelComponentStateManager>
+                     state_manager) override {
+    component_updater::UninstallOptimizationGuideOnDeviceModelComponent(
+        state_manager);
+  }
+};
+
 }  // namespace
 
 // static
@@ -200,11 +227,18 @@
 }
 
 // static
-void OptimizationGuideKeyedService::LogOnDeviceMetrics() {
-  auto controller = GetOnDeviceModelServiceController();
+// We're using a weakptr here for testing purposes. We need to allow
+// OnDeviceModelComponentStateManager to be destroyed along with a test harness.
+void OptimizationGuideKeyedService::DeterminePerformanceClass(
+    base::WeakPtr<optimization_guide::OnDeviceModelComponentStateManager>
+        on_device_component_state_manager) {
+  auto controller =
+      GetOnDeviceModelServiceController(on_device_component_state_manager);
   controller->GetEstimatedPerformanceClass(base::BindOnce(
       [](scoped_refptr<optimization_guide::OnDeviceModelServiceController>
              controller,
+         base::WeakPtr<optimization_guide::OnDeviceModelComponentStateManager>
+             on_device_component_state_manager,
          std::optional<on_device_model::mojom::PerformanceClass>
              performance_class) {
         auto optimization_guide_performance_class =
@@ -212,7 +246,10 @@
         base::UmaHistogramEnumeration(
             "OptimizationGuide.ModelExecution.OnDeviceModelPerformanceClass",
             optimization_guide_performance_class);
-
+        if (on_device_component_state_manager) {
+          on_device_component_state_manager->DevicePerformanceClassChanged(
+              optimization_guide_performance_class);
+        }
         ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(
             "SyntheticOnDeviceModelPerformanceClass",
             OnDeviceModelPerformanceClassToString(
@@ -220,14 +257,16 @@
             variations::SyntheticTrialAnnotationMode::kCurrentLog);
         controller->ShutdownServiceIfNoModelLoaded();
       },
-      controller));
+      controller, on_device_component_state_manager));
 }
 
 OptimizationGuideKeyedService::OptimizationGuideKeyedService(
     content::BrowserContext* browser_context)
     : browser_context_(browser_context) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  Initialize();
+  if (browser_context_) {  // Null in MockOptimizationGuideKeyedService.
+    Initialize();
+  }
 }
 
 OptimizationGuideKeyedService::~OptimizationGuideKeyedService() {
@@ -361,28 +400,37 @@
           // |this| owns |prediction_manager_|.
           base::Unretained(this)));
 
-  if (!profile->IsOffTheRecord() &&
-      base::FeatureList::IsEnabled(
-          optimization_guide::features::kLogOnDeviceMetricsOnStartup)) {
-    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-        FROM_HERE,
-        base::BindOnce(&OptimizationGuideKeyedService::LogOnDeviceMetrics),
-        optimization_guide::features::GetOnDeviceStartupMetricDelay());
-  }
-
-  if (!profile->IsOffTheRecord() &&
-      base::FeatureList::IsEnabled(
-          optimization_guide::features::kOptimizationGuideModelExecution)) {
-    scoped_refptr<optimization_guide::OnDeviceModelServiceController>
-        service_controller;
+  if (!profile->IsOffTheRecord()) {
+    on_device_component_manager_ =
+        optimization_guide::OnDeviceModelComponentStateManager::CreateOrGet(
+            g_browser_process->local_state(),
+            std::make_unique<OnDeviceModelComponentStateManagerDelegate>());
+    on_device_component_manager_->OnStartup();
     if (base::FeatureList::IsEnabled(
-            optimization_guide::features::kOptimizationGuideOnDeviceModel)) {
-      service_controller = GetOnDeviceModelServiceController();
+            optimization_guide::features::kLogOnDeviceMetricsOnStartup)) {
+      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+          FROM_HERE,
+          base::BindOnce(
+              &OptimizationGuideKeyedService::DeterminePerformanceClass,
+              on_device_component_manager_->GetWeakPtr()),
+          optimization_guide::features::GetOnDeviceStartupMetricDelay());
     }
-    model_execution_manager_ =
-        std::make_unique<optimization_guide::ModelExecutionManager>(
-            url_loader_factory, IdentityManagerFactory::GetForProfile(profile),
-            std::move(service_controller), optimization_guide_logger_.get());
+
+    if (base::FeatureList::IsEnabled(
+            optimization_guide::features::kOptimizationGuideModelExecution)) {
+      scoped_refptr<optimization_guide::OnDeviceModelServiceController>
+          service_controller;
+      if (base::FeatureList::IsEnabled(
+              optimization_guide::features::kOptimizationGuideOnDeviceModel)) {
+        service_controller = GetOnDeviceModelServiceController(
+            on_device_component_manager_->GetWeakPtr());
+      }
+      model_execution_manager_ =
+          std::make_unique<optimization_guide::ModelExecutionManager>(
+              url_loader_factory,
+              IdentityManagerFactory::GetForProfile(profile),
+              std::move(service_controller), optimization_guide_logger_.get());
+    }
   }
 
   if (!profile->IsOffTheRecord() &&
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
index d9a23b33..b1ff92b 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service.h
@@ -44,6 +44,7 @@
 class ModelInfo;
 class ModelQualityLogEntry;
 class ModelQualityLogsUploaderService;
+class OnDeviceModelComponentStateManager;
 class OptimizationGuideStore;
 class PredictionManager;
 class PredictionManagerBrowserTestBase;
@@ -192,8 +193,10 @@
   friend class PersonalizedHintsFetcherBrowserTest;
   friend class settings::SettingsUI;
 
-  // Logs metrics from the OnDeviceModelService.
-  static void LogOnDeviceMetrics();
+  // Evaluates and logs the device performance class.
+  static void DeterminePerformanceClass(
+      base::WeakPtr<optimization_guide::OnDeviceModelComponentStateManager>
+          on_device_component_state_manager);
 
   // Initializes |this|.
   void Initialize();
@@ -210,13 +213,14 @@
   }
 
   // Notifies |hints_manager_| that the navigation associated with
-  // |navigation_data| has started or redirected.
-  void OnNavigationStartOrRedirect(
+  // |navigation_data| has started or redirected. Virtual for testing.
+  virtual void OnNavigationStartOrRedirect(
       OptimizationGuideNavigationData* navigation_data);
 
   // Notifies |hints_manager_| that the navigation associated with
-  // |navigation_redirect_chain| has finished.
-  void OnNavigationFinish(const std::vector<GURL>& navigation_redirect_chain);
+  // |navigation_redirect_chain| has finished. Virtual for testing.
+  virtual void OnNavigationFinish(
+      const std::vector<GURL>& navigation_redirect_chain);
 
   // Clears data specific to the user.
   void ClearData();
@@ -254,6 +258,10 @@
   // internals page. Must outlive `prediction_manager_` and `hints_manager_`.
   std::unique_ptr<OptimizationGuideLogger> optimization_guide_logger_;
 
+  // Keep a reference to this so it stays alive.
+  scoped_refptr<optimization_guide::OnDeviceModelComponentStateManager>
+      on_device_component_manager_;
+
   // Manages the storing, loading, and fetching of hints.
   std::unique_ptr<optimization_guide::ChromeHintsManager> hints_manager_;
 
diff --git a/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc b/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
index cee813c..e2dc1fa 100644
--- a/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
+++ b/chrome/browser/optimization_guide/optimization_guide_keyed_service_browsertest.cc
@@ -9,8 +9,11 @@
 #include "base/logging.h"
 #include "base/run_loop.h"
 #include "base/strings/escape.h"
+#include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/run_until.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
@@ -26,6 +29,7 @@
 #include "components/optimization_guide/core/command_line_top_host_provider.h"
 #include "components/optimization_guide/core/model_execution/model_execution_features.h"
 #include "components/optimization_guide/core/model_execution/model_execution_features_controller.h"
+#include "components/optimization_guide/core/model_execution/on_device_model_component.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_prefs.h"
@@ -51,6 +55,7 @@
 
 namespace {
 
+using optimization_guide::OnDeviceModelComponentStateManager;
 using optimization_guide::proto::OptimizationType;
 
 // A WebContentsObserver that asks whether an optimization type can be applied.
@@ -163,6 +168,10 @@
          {optimization_guide::features::kOptimizationGuideModelExecution, {}},
          {optimization_guide::features::internal::kComposeSettingsVisibility,
           {}},
+         {optimization_guide::features::kLogOnDeviceMetricsOnStartup,
+          {
+              {"on_device_startup_metric_delay", "0"},
+          }},
          {optimization_guide::features::internal::
               kTabOrganizationSettingsVisibility,
           {{"allow_unsigned_user", "true"}}}},
@@ -1155,6 +1164,25 @@
           MODEL_EXECUTION_FEATURE_WALLPAPER_SEARCH));
 }
 
+IN_PROC_BROWSER_TEST_F(OptimizationGuideKeyedServiceBrowserTest,
+                       LogOnDeviceMetricsAfterStart) {
+  OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile());
+  OnDeviceModelComponentStateManager* on_device_component_state_manager =
+      OnDeviceModelComponentStateManager::GetInstanceForTesting();
+  ASSERT_TRUE(on_device_component_state_manager);
+
+  EXPECT_TRUE(base::test::RunUntil([&]() {
+    return histogram_tester()
+               ->GetAllSamples(
+                   "OptimizationGuide.ModelExecution."
+                   "OnDeviceModelPerformanceClass")
+               .size() > 0;
+  }));
+
+  histogram_tester()->ExpectTotalCount(
+      "OptimizationGuide.ModelExecution.OnDeviceModelPerformanceClass", 1);
+}
+
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
 // CreateGuestBrowser() is not supported for Android or ChromeOS out of the box.
 IN_PROC_BROWSER_TEST_F(OptimizationGuideKeyedServiceBrowserTest,
diff --git a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewTest.java b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewTest.java
index d8a2822..1e187f7 100644
--- a/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewTest.java
+++ b/chrome/browser/paint_preview/android/javatests/src/org/chromium/chrome/browser/paint_preview/TabbedPaintPreviewTest.java
@@ -24,7 +24,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.UnguessableToken;
-import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
@@ -311,21 +311,7 @@
         private int mLastToken = TokenHolder.INVALID_TOKEN;
 
         public TestControlsVisibilityDelegate() {
-            super(
-                    new ObservableSupplier<Boolean>() {
-                        @Override
-                        public Boolean addObserver(Callback<Boolean> obs) {
-                            return false;
-                        }
-
-                        @Override
-                        public void removeObserver(Callback<Boolean> obs) {}
-
-                        @Override
-                        public Boolean get() {
-                            return false;
-                        }
-                    });
+            super(new ObservableSupplierImpl<>(false));
         }
 
         public boolean isPersistent() {
diff --git a/chrome/browser/permissions/permission_manager_browsertest.cc b/chrome/browser/permissions/permission_manager_browsertest.cc
index 0515a244..95c1338 100644
--- a/chrome/browser/permissions/permission_manager_browsertest.cc
+++ b/chrome/browser/permissions/permission_manager_browsertest.cc
@@ -52,7 +52,7 @@
     callback_ = std::move(callback);
   }
 
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
@@ -60,7 +60,7 @@
       base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback)
       override {
     SubscriptionId result =
-        permissions::PermissionManager::SubscribePermissionStatusChange(
+        permissions::PermissionManager::SubscribeToPermissionStatusChange(
             permission, render_process_host, render_frame_host,
             requesting_origin, callback);
     std::move(callback_).Run();
diff --git a/chrome/browser/policy/profile_policy_connector.cc b/chrome/browser/policy/profile_policy_connector.cc
index a01f6c89..d4d10fa8 100644
--- a/chrome/browser/policy/profile_policy_connector.cc
+++ b/chrome/browser/policy/profile_policy_connector.cc
@@ -255,6 +255,7 @@
 
   void AddInfobarForActiveLocalTestPolicies(
       content::WebContents* web_contents) {
+    infobars::ContentInfoBarManager::CreateForWebContents(web_contents);
     CreateSimpleAlertInfoBar(
         infobars::ContentInfoBarManager::FromWebContents(web_contents),
         infobars::InfoBarDelegate::LOCAL_TEST_POLICIES_APPLIED_INFOBAR, nullptr,
@@ -293,6 +294,7 @@
 
   void DismissInfobarForActiveLocalTestPolicies(
       content::WebContents* web_contents) {
+    infobars::ContentInfoBarManager::CreateForWebContents(web_contents);
     auto* infobar_manager =
         infobars::ContentInfoBarManager::FromWebContents(web_contents);
     const auto it = base::ranges::find(
diff --git a/chrome/browser/predictors/loading_predictor_tab_helper_unittest.cc b/chrome/browser/predictors/loading_predictor_tab_helper_unittest.cc
index 3ec25f6..9dcdd45 100644
--- a/chrome/browser/predictors/loading_predictor_tab_helper_unittest.cc
+++ b/chrome/browser/predictors/loading_predictor_tab_helper_unittest.cc
@@ -94,7 +94,7 @@
                   base::BindRepeating([](content::BrowserContext* context)
                                           -> std::unique_ptr<KeyedService> {
                     return std::make_unique<
-                        NiceMock<MockOptimizationGuideKeyedService>>(context);
+                        NiceMock<MockOptimizationGuideKeyedService>>();
                   })));
   LoadingPredictorTabHelper::CreateForWebContents(web_contents());
   tab_helper_ = LoadingPredictorTabHelper::FromWebContents(web_contents());
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index da3656d..78cf778 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -994,6 +994,11 @@
 constexpr char kWebAndAppActivityEnabledForShopping[] =
     "web_and_app_activity_enabled_for_shopping";
 
+// Deprecated 12/2023.
+#if BUILDFLAG(IS_ANDROID)
+const char kTemplatesRandomOrder[] = "content_creation.notes.random_order";
+#endif
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -1413,6 +1418,11 @@
 
   // Deprecated 11/2023.
   registry->RegisterBooleanPref(kWebAndAppActivityEnabledForShopping, true);
+
+  // Deprecated 12/2023.
+#if BUILDFLAG(IS_ANDROID)
+  registry->RegisterListPref(kTemplatesRandomOrder);
+#endif
 }
 
 void ClearSyncRequestedPrefAndMaybeMigrate(PrefService* profile_prefs) {
@@ -1850,7 +1860,6 @@
 
 #if BUILDFLAG(IS_ANDROID)
   cdm::MediaDrmStorageImpl::RegisterProfilePrefs(registry);
-  content_creation::prefs::RegisterProfilePrefs(registry);
   KnownInterceptionDisclosureInfoBarDelegate::RegisterProfilePrefs(registry);
   MediaDrmOriginIdManager::RegisterProfilePrefs(registry);
   NotificationChannelsProviderAndroid::RegisterProfilePrefs(registry);
@@ -2667,6 +2676,11 @@
       profile_prefs);
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+#if BUILDFLAG(IS_ANDROID)
+  // Added 12/2023.
+  profile_prefs->ClearPref(kTemplatesRandomOrder);
+#endif
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_PROFILE_PREFS
 
diff --git a/chrome/browser/readaloud/android/BUILD.gn b/chrome/browser/readaloud/android/BUILD.gn
index 3c186f7..86e618d9 100644
--- a/chrome/browser/readaloud/android/BUILD.gn
+++ b/chrome/browser/readaloud/android/BUILD.gn
@@ -8,6 +8,7 @@
 android_library("java") {
   sources = [
     "java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java",
+    "java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java",
     "java/src/org/chromium/chrome/browser/readaloud/ReadAloudMiniPlayerSceneLayer.java",
     "java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefs.java",
     "java/src/org/chromium/chrome/browser/readaloud/ReadAloudToolbarButtonController.java",
@@ -156,6 +157,7 @@
   testonly = true
   sources = [
     "java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java",
+    "java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetricsUnitTest.java",
     "java/src/org/chromium/chrome/browser/readaloud/ReadAloudMiniPlayerSceneLayerUnitTest.java",
     "java/src/org/chromium/chrome/browser/readaloud/ReadAloudPrefsUnitTest.java",
     "java/src/org/chromium/chrome/browser/readaloud/ReadAloudToolbarButtonControllerUnitTest.java",
@@ -196,6 +198,7 @@
     "//third_party/androidx:androidx_appcompat_appcompat_java",
     "//third_party/androidx:androidx_test_core_java",
     "//third_party/androidx:androidx_test_ext_junit_java",
+    "//third_party/androidx:androidx_test_runner_java",
     "//third_party/jni_zero:jni_zero_java",
     "//third_party/junit",
     "//third_party/mockito:mockito_java",
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
index c8dd760d..c10bc8f9 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
@@ -196,6 +196,7 @@
                 @Override
                 public void onSuccess(String url, boolean isReadable, boolean timepointsSupported) {
                     Log.d(TAG, "onSuccess called for %s", url);
+                    ReadAloudMetrics.recordIsPageReadable(isReadable);
                     mReadabilityMap.put(url, isReadable);
                     mTimepointsSupportedMap.put(url, timepointsSupported);
                     mPendingRequests.remove(url);
@@ -205,6 +206,7 @@
                 @Override
                 public void onFailure(String url, Throwable t) {
                     Log.d(TAG, "onFailure called for %s because %s", url, t);
+                    ReadAloudMetrics.recordIsPageReadable(false);
                     mPendingRequests.remove(url);
                 }
             };
@@ -280,6 +282,7 @@
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     public void maybeCheckReadability(GURL url) {
         if (!isURLReadAloudSupported(url)) {
+            ReadAloudMetrics.recordIsPageReadable(false);
             return;
         }
 
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
index b4c5d2da..09909dd4d 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
@@ -39,6 +39,7 @@
 
 import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsSizer;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
@@ -1080,6 +1081,28 @@
         assertEquals(mController.getReadabilitySupplier().get(), testUrl);
     }
 
+    @Test
+    public void testMetricRecorded_readability() {
+        final String histogramName = ReadAloudMetrics.READABILITY_SUCCESS;
+
+        var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true);
+        mController.maybeCheckReadability(sTestGURL);
+        verify(mHooksImpl, times(1))
+                .isPageReadable(eq(sTestGURL.getSpec()), mCallbackCaptor.capture());
+        mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), true, false);
+        histogram.assertExpected();
+
+        histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false);
+        mCallbackCaptor.getValue().onSuccess(sTestGURL.getSpec(), false, false);
+        histogram.assertExpected();
+
+        histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false);
+        mCallbackCaptor
+                .getValue()
+                .onFailure(sTestGURL.getSpec(), new Throwable("Something went wrong"));
+        histogram.assertExpected();
+    }
+
     private void onPlaybackSuccess(Playback playback) {
         mPlaybackCallbackCaptor.getValue().onSuccess(playback);
         resolvePromises();
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java
new file mode 100644
index 0000000..55e96ec
--- /dev/null
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetrics.java
@@ -0,0 +1,17 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.readaloud;
+
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.base.metrics.RecordHistogram;
+
+public class ReadAloudMetrics {
+    @VisibleForTesting public static String READABILITY_SUCCESS = "ReadAloud.IsPageReadable";
+
+    public static void recordIsPageReadable(boolean successful) {
+        RecordHistogram.recordBooleanHistogram(READABILITY_SUCCESS, successful);
+    }
+}
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetricsUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetricsUnitTest.java
new file mode 100644
index 0000000..39fe82c4
--- /dev/null
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudMetricsUnitTest.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.readaloud;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.HistogramWatcher;
+
+/** Unit tests for {@link ReadAloudMetrics}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class ReadAloudMetricsUnitTest {
+    @Test
+    @SmallTest
+    public void testRecordIsPageReadable() {
+        final String histogramName = ReadAloudMetrics.READABILITY_SUCCESS;
+
+        var histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, true);
+        ReadAloudMetrics.recordIsPageReadable(true);
+        histogram.assertExpected();
+
+        histogram = HistogramWatcher.newSingleRecordWatcher(histogramName, false);
+        ReadAloudMetrics.recordIsPageReadable(false);
+        histogram.assertExpected();
+    }
+}
diff --git a/chrome/browser/resources/ash/settings/device_page/input_device_settings_shared.css b/chrome/browser/resources/ash/settings/device_page/input_device_settings_shared.css
index 7bbde9b..e7893c3 100644
--- a/chrome/browser/resources/ash/settings/device_page/input_device_settings_shared.css
+++ b/chrome/browser/resources/ash/settings/device_page/input_device_settings_shared.css
@@ -44,9 +44,9 @@
 }
 
 cr-dialog [slot='button-container'] {
-  padding: 0 32px 28px 32px;
   display: flex;
-  gap: 8px;
+  justify-content: space-between;
+  padding: 0 32px 28px 32px;
 }
 
 cr-dialog [slot='body'] {
@@ -57,7 +57,8 @@
   padding: 32px 32px 0 32px;
 }
 
-cr-dialog #cancelButton {
+cr-dialog #cancelButton,
+cr-dialog #editButton {
   background-color: var(--cros-bg-color);
   border: solid 1px var(--cros-button-stroke-color-secondary);
 }
diff --git a/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.html b/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.html
index cf2e3ebf..778e1bb4 100644
--- a/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.html
+++ b/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.html
@@ -20,6 +20,11 @@
     text-align: center;
     padding-bottom: 24px;
   }
+
+  .right-button-group {
+    display: flex;
+    gap: 8px;
+  }
 </style>
 <cr-dialog id="keyCombinationInputDialog">
   <div slot="title">$i18n{keyCombinationDialogTitle}</div>
@@ -32,24 +37,23 @@
           shortcut-input-provider="[[getShortcutProvider()]]"
           show-separator="true">
       </shortcut-input>
-      <template is="dom-if" if="[[shouldShowEditButton_(isCapturing)]]">
-        <div class="edit-icon-container">
-          <cr-icon-button class="edit-button"
-              iron-icon="os-settings:edit"
-              on-click="onEditButtonClicked_">
-          </cr-icon-button>
-        </div>
-      </template>
     </div>
   </div>
   <div slot="button-container">
-    <div>
+    <div class="left-button-group">
+      <template is="dom-if" if="[[shouldShowEditButton_(isCapturing)]]">
+        <cr-button class="edit-button"
+            id="editButton"
+            on-click="onEditButtonClicked_">
+          $i18n{buttonRemappingDialogChangeLabel}
+        </cr-button>
+      </template>
+    </div>
+    <div class="right-button-group">
       <cr-button id="cancelButton"
           on-click="cancelDialogClicked_">
         $i18n{buttonRemappingDialogCancelLabel}
       </cr-button>
-    </div>
-    <div>
       <cr-button id="saveButton" class="action-button"
           on-click="saveDialogClicked_"
           disabled$="[[shouldDisableSaveButton_(inputKeyEvent)]]">
diff --git a/chrome/browser/resources/ash/settings/os_people_page/account_manager_settings_card.html b/chrome/browser/resources/ash/settings/os_people_page/account_manager_settings_card.html
index 9ed0255..df70dc5 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/account_manager_settings_card.html
+++ b/chrome/browser/resources/ash/settings/os_people_page/account_manager_settings_card.html
@@ -101,7 +101,7 @@
       </template>
       <localized-link
           localized-string=
-            "[[getManagementDescription_(isChildUser_, deviceAccount_)]]"
+            "[[getManagementDescription_(isChildUser_, deviceAccount)]]"
           link-url="$i18nRaw{accountManagerChromeUIManagementURL}">
       </localized-link>
     </div>
@@ -113,7 +113,7 @@
       aria-describedby="deviceAccountEmail">
     <div class="profile-icon device-account-icon"
         aria-hidden="true"
-        style="background-image: [[getIconImageSet_(deviceAccount_.pic)]]">
+        style="background-image: [[getIconImageSet_(deviceAccount)]]">
       <template is="dom-if"
           if="[[shouldShowManagedBadge_(isDeviceAccountManaged_,
                 isChildUser_)]]">
@@ -123,10 +123,10 @@
       </template>
     </div>
     <span id="deviceAccountFullName" class="primary" aria-hidden="true">
-      [[deviceAccount_.fullName]]
+      [[deviceAccount.fullName]]
     </span>
     <span id="deviceAccountEmail" class="secondary" aria-hidden="true">
-      [[deviceAccount_.email]]
+      [[deviceAccount.email]]
     </span>
   </div>
 </settings-card>
diff --git a/chrome/browser/resources/ash/settings/os_people_page/account_manager_settings_card.ts b/chrome/browser/resources/ash/settings/os_people_page/account_manager_settings_card.ts
index e828bd4..8c9bac4 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/account_manager_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/account_manager_settings_card.ts
@@ -24,7 +24,7 @@
 import {isChild} from '../common/load_time_booleans.js';
 import {ParentalControlsBrowserProxyImpl} from '../parental_controls_page/parental_controls_browser_proxy.js';
 
-import {Account, AccountManagerBrowserProxy, AccountManagerBrowserProxyImpl} from './account_manager_browser_proxy.js';
+import {Account} from './account_manager_browser_proxy.js';
 import {getTemplate} from './account_manager_settings_card.html.js';
 
 const AccountManagerSettingsCardElementBase =
@@ -45,7 +45,7 @@
       /**
        * Primary / Device account.
        */
-      deviceAccount_: Object,
+      deviceAccount: Object,
 
       isChildUser_: {
         type: Boolean,
@@ -87,40 +87,11 @@
     };
   }
 
-  private browserProxy_: AccountManagerBrowserProxy;
-  private deviceAccount_: Account|null;
+  deviceAccount: Account|null;
   private isChildUser_: boolean;
   private isDeviceAccountManaged_: boolean;
   private isSecondaryGoogleAccountSigninAllowed_: boolean;
 
-  constructor() {
-    super();
-
-    this.browserProxy_ = AccountManagerBrowserProxyImpl.getInstance();
-  }
-
-  override connectedCallback(): void {
-    super.connectedCallback();
-
-    this.addWebUiListener('accounts-changed', this.refreshAccounts_.bind(this));
-  }
-
-  override ready(): void {
-    super.ready();
-    this.refreshAccounts_();
-  }
-
-  private async refreshAccounts_(): Promise<void> {
-    const accounts = await this.browserProxy_.getAccounts();
-    this.set('accounts_', accounts);
-    const deviceAccount = accounts.find(account => account.isDeviceAccount);
-    if (!deviceAccount) {
-      console.error('Cannot find device account.');
-      return;
-    }
-    this.deviceAccount_ = deviceAccount;
-  }
-
   private onManagedIconClick_(): void {
     if (this.isChildUser_) {
       ParentalControlsBrowserProxyImpl.getInstance().launchFamilyLinkSettings();
@@ -138,11 +109,11 @@
     if (this.isChildUser_) {
       return this.i18nAdvanced('accountManagerManagementDescription');
     }
-    if (!this.deviceAccount_) {
+    if (!this.deviceAccount) {
       return '';
     }
-    assertExists(this.deviceAccount_.organization);
-    if (!this.deviceAccount_.organization) {
+    assertExists(this.deviceAccount.organization);
+    if (!this.deviceAccount.organization) {
       if (this.isDeviceAccountManaged_) {
         console.error(
             'The device account is managed, but the organization is not set.');
@@ -154,7 +125,7 @@
     // Where href will be set by <localized-link>.
     return this.i18nAdvanced('accountManagerManagementDescription', {
       substitutions: [
-        this.deviceAccount_.organization,
+        this.deviceAccount.organization,
       ],
     });
   }
@@ -170,8 +141,11 @@
   /**
    * @return a CSS image-set for multiple scale factors.
    */
-  private getIconImageSet_(iconUrl: string): string {
-    return getImage(iconUrl);
+  private getIconImageSet_(): string {
+    if (!this.deviceAccount) {
+      return '';
+    }
+    return getImage(this.deviceAccount.pic);
   }
 }
 
diff --git a/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.html b/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.html
index 5c20e68..a45c2b5 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.html
+++ b/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.html
@@ -137,7 +137,7 @@
 
   <!-- Secondary Accounts list -->
   <template is="dom-repeat" id="accountList"
-      items="[[getSecondaryAccounts_(accounts_)]]">
+      items="[[getSecondaryAccounts_(accounts)]]">
     <div class="settings-box" role="listitem">
       <div class="profile-icon"
           style="background-image: [[getIconImageSet_(item.pic)]]">
diff --git a/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.ts b/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.ts
index e9b841f..ea9003f 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/additional_accounts_settings_card.ts
@@ -59,7 +59,7 @@
 
   static get properties() {
     return {
-      accounts_: {
+      accounts: {
         type: Array,
         value() {
           return [];
@@ -67,11 +67,6 @@
       },
 
       /**
-       * Primary / Device account.
-       */
-      deviceAccount_: Object,
-
-      /**
        * The targeted account for menu operations.
        */
       actionMenuAccount_: Object,
@@ -129,10 +124,9 @@
     };
   }
 
-  private accounts_: Account[];
+  accounts: Account[];
   private actionMenuAccount_: Account|null;
   private browserProxy_: AccountManagerBrowserProxy;
-  private deviceAccount_: Account|null;
   private isArcAccountRestrictionsEnabled_: boolean;
   private isChildUser_: boolean;
   private isDeviceAccountManaged_: boolean;
@@ -144,17 +138,6 @@
     this.browserProxy_ = AccountManagerBrowserProxyImpl.getInstance();
   }
 
-  override connectedCallback(): void {
-    super.connectedCallback();
-
-    this.addWebUiListener('accounts-changed', this.refreshAccounts_.bind(this));
-  }
-
-  override ready(): void {
-    super.ready();
-    this.refreshAccounts_();
-  }
-
   override currentRouteChanged(newRoute: Route): void {
     if (newRoute !== routes.OS_PEOPLE) {
       return;
@@ -163,18 +146,6 @@
     this.attemptDeepLink();
   }
 
-  private async refreshAccounts_(): Promise<void> {
-    const accounts = await this.browserProxy_.getAccounts();
-    this.set('accounts_', accounts);
-    const deviceAccount = accounts.find(account => account.isDeviceAccount);
-
-    if (!deviceAccount) {
-      console.error('Cannot find device account.');
-      return;
-    }
-    this.deviceAccount_ = deviceAccount;
-  }
-
   /**
    * @return accounts list header (e.g. 'Secondary accounts' for
    * regular users or 'School accounts' for child users).
@@ -204,7 +175,7 @@
 
   private addAccount_(): void {
     recordSettingChange(
-        Setting.kAddAccount, {intValue: this.accounts_.length + 1});
+        Setting.kAddAccount, {intValue: this.accounts.length + 1});
     this.browserProxy_.addAccount();
   }
 
@@ -249,12 +220,11 @@
   }
 
   private shouldShowSecondaryAccountsList_(): boolean {
-    return this.accounts_.filter(account => !account.isDeviceAccount).length ===
-        0;
+    return this.getSecondaryAccounts_().length === 0;
   }
 
   private getSecondaryAccounts_(): Account[] {
-    return this.accounts_.filter(account => !account.isDeviceAccount);
+    return this.accounts.filter(account => !account.isDeviceAccount);
   }
 
   private getAddAccountLabel_(): string {
diff --git a/chrome/browser/resources/ash/settings/os_people_page/os_people_page.html b/chrome/browser/resources/ash/settings/os_people_page/os_people_page.html
index 134cd01..9c5de7f 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/os_people_page.html
+++ b/chrome/browser/resources/ash/settings/os_people_page/os_people_page.html
@@ -46,14 +46,15 @@
 </style>
 
 <os-settings-animated-pages id="pages" section="[[section_]]">
-  <!-- Show settings card is RavampWayfinding is enabled. -->
   <template is="dom-if" if="[[isRevampWayfindingEnabled_]]">
     <div route-path="default">
       <account-manager-settings-card
-          prefs="{{prefs}}">
+          prefs="{{prefs}}"
+          device-account="[[deviceAccount_]]">
       </account-manager-settings-card>
       <additional-accounts-settings-card
-          prefs="{{prefs}}">
+          prefs="{{prefs}}"
+          accounts="[[accounts_]]">
       </additional-accounts-settings-card>
       <template is="dom-if" if="[[showParentalControls_]]">
         <parental-controls-settings-card
diff --git a/chrome/browser/resources/ash/settings/os_people_page/os_people_page.ts b/chrome/browser/resources/ash/settings/os_people_page/os_people_page.ts
index 5df1b32..a0c8a52 100644
--- a/chrome/browser/resources/ash/settings/os_people_page/os_people_page.ts
+++ b/chrome/browser/resources/ash/settings/os_people_page/os_people_page.ts
@@ -25,6 +25,7 @@
 import {ProfileInfo, ProfileInfoBrowserProxyImpl} from '/shared/settings/people_page/profile_info_browser_proxy.js';
 import {SyncBrowserProxy, SyncBrowserProxyImpl, SyncStatus} from '/shared/settings/people_page/sync_browser_proxy.js';
 import {convertImageSequenceToPng} from 'chrome://resources/ash/common/cr_picture/png.js';
+import {assert} from 'chrome://resources/js/assert.js';
 import {sendWithPromise} from 'chrome://resources/js/cr.js';
 import {getImage} from 'chrome://resources/js/icon.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
@@ -72,6 +73,20 @@
        */
       syncStatus: Object,
 
+      accounts_: {
+        type: Array,
+        value() {
+          return [];
+        },
+      },
+
+      deviceAccount_: {
+        type: Object,
+        value() {
+          return null;
+        },
+      },
+
       authTokenInfo_: {
         type: Object,
         observer: 'onAuthTokenChanged_',
@@ -81,6 +96,7 @@
        * The current profile icon URL. Usually a data:image/png URL.
        */
       profileIconUrl_: String,
+
       profileName_: String,
 
       profileEmail_: String,
@@ -155,6 +171,8 @@
   }
 
   syncStatus: SyncStatus;
+  private accounts_: Account[];
+  private deviceAccount_: Account|null;
   private authTokenInfo_: chrome.quickUnlockPrivate.TokenInfo|undefined;
   private profileIconUrl_: string;
   private profileName_: string;
@@ -162,7 +180,7 @@
   private profileLabel_: string;
   private fingerprintUnlockEnabled_: boolean;
   private isAccountManagerEnabled_: boolean;
-  private isRevampWayfindingEnabled_: boolean;
+  private readonly isRevampWayfindingEnabled_: boolean;
   private showParentalControls_: boolean;
   private section_: Section;
   private showPasswordPromptDialog_: boolean;
@@ -353,27 +371,30 @@
   private async updateAccounts_(): Promise<void> {
     const accounts =
         await AccountManagerBrowserProxyImpl.getInstance().getAccounts();
+    this.accounts_ = accounts;
+
     // The user might not have any GAIA accounts (e.g. guest mode or Active
     // Directory). In these cases the profile row is hidden, so there's nothing
     // to do.
     if (accounts.length === 0) {
       return;
     }
-    this.profileName_ = accounts[0].fullName;
-    this.profileEmail_ = accounts[0].email;
-    this.profileIconUrl_ = accounts[0].pic;
 
-    await this.setProfileLabel(accounts);
-  }
+    // First account is always the device account.
+    assert(
+        accounts[0].isDeviceAccount,
+        'The device account should always be first.');
+    this.deviceAccount_ = accounts[0];
+    this.profileName_ = this.deviceAccount_.fullName;
+    this.profileEmail_ = this.deviceAccount_.email;
+    this.profileIconUrl_ = this.deviceAccount_.pic;
 
-  private async setProfileLabel(accounts: Account[]): Promise<void> {
     // Template: "$1 Google accounts" with correct plural of "account".
     const labelTemplate = await sendWithPromise(
-        'getPluralString', 'profileLabel', accounts.length);
-
+        'getPluralString', 'profileLabel', this.accounts_.length);
     // Final output: "X Google accounts"
     this.profileLabel_ = loadTimeData.substituteString(
-        labelTemplate, accounts[0].email, accounts.length);
+        labelTemplate, this.profileEmail_, this.accounts_.length);
   }
 
   /**
diff --git a/chrome/browser/resources/chromeos/accessibility/definitions/tabs.d.ts b/chrome/browser/resources/chromeos/accessibility/definitions/tabs.d.ts
index 3e1e12e3..4c59fe7 100644
--- a/chrome/browser/resources/chromeos/accessibility/definitions/tabs.d.ts
+++ b/chrome/browser/resources/chromeos/accessibility/definitions/tabs.d.ts
@@ -129,24 +129,26 @@
       export function duplicate(tabId: number, callback: (tab: Tab) => void):
           void;
 
-      export function query(queryInfo: {
-        active?: boolean,
-        pinned?: boolean,
-        audible?: boolean,
-        muted?: boolean,
-        highlighted?: boolean,
-        discarded?: boolean,
-        autoDiscardable?: boolean,
-        currentWindow?: boolean,
-        lastFocusedWindow?: boolean,
-        status?: TabStatus,
-        title?: string,
-        url?: string|string[],
-        groupId?: number,
-        windowId?: number,
-        windowType?: WindowType,
-        index?: number,
-      }): Promise<Tab[]>;
+      export function query(
+          queryInfo: {
+            active?: boolean,
+            pinned?: boolean,
+            audible?: boolean,
+            muted?: boolean,
+            highlighted?: boolean,
+            discarded?: boolean,
+            autoDiscardable?: boolean,
+            currentWindow?: boolean,
+            lastFocusedWindow?: boolean,
+            status?: TabStatus,
+            title?: string,
+            url?: string|string[],
+            groupId?: number,
+            windowId?: number,
+            windowType?: WindowType,
+            index?: number,
+          },
+          callback: (tabs: Tab[]) => void): void;
 
       export function highlight(highlightInfo: {
         windowId?: number, tabs: number[]|number,
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
index 646a340..840bca6 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
@@ -34,6 +34,7 @@
   "input_handler.ts",
   "ui_manager.ts",
   "prefs_manager.ts",
+  "select_to_speak.ts",
 ]
 
 # Add TS files needed from ../common/ here.
@@ -43,9 +44,6 @@
   "tree_walker.ts",
 ]
 
-# JS files required for ts build.
-js_deps = [ "select_to_speak.js" ]
-
 # Root dir must be the parent directory so it can reach common.
 ts_library("ts_build") {
   root_dir = "../"
@@ -54,6 +52,7 @@
   definitions = [
     "../definitions/tts.d.ts",
     "//tools/typescript/definitions/metrics_private.d.ts",
+    "//tools/typescript/definitions/context_menus.d.ts",
     "../definitions/automation.d.ts",
     "../definitions/extensions.d.ts",
     "../definitions/runtime.d.ts",
@@ -68,9 +67,6 @@
   ]
 
   in_files = []
-  foreach(_js_file, js_deps) {
-    in_files += [ "select_to_speak/" + _js_file ]
-  }
   foreach(_ts_file, ts_modules) {
     in_files += [ "select_to_speak/" + _ts_file ]
   }
@@ -92,7 +88,6 @@
     "checked.png",
     "earcons/null_selection.ogg",
     "select_to_speak-2x.svg",
-    "select_to_speak.js",
     "sts-icon-128.png",
     "sts-icon-16.png",
     "sts-icon-48.png",
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.ts
index cda202e6..917c956 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.ts
@@ -136,11 +136,11 @@
     document.addEventListener('copy', evt => this.onClipboardCopy_(evt));
     chrome.accessibilityPrivate.onSelectToSpeakKeysPressedChanged.addListener(
         (keysPressed) => {
-          this.onKeysPressedChanged_(new Set(keysPressed));
+          this.onKeysPressedChanged(new Set(keysPressed));
         });
     chrome.accessibilityPrivate.onSelectToSpeakMouseChanged.addListener(
         (eventType, mouseX, mouseY) => {
-          this.onMouseEvent_(eventType, mouseX, mouseY);
+          this.onMouseEvent(eventType, mouseX, mouseY);
         });
   }
 
@@ -180,8 +180,9 @@
    *     coordinates.
    * @param mouseY The mouse y coordinate in global screen
    *     coordinates.
+   * Visible for testing.
    */
-  private onMouseEvent_(
+  onMouseEvent(
       type: chrome.accessibilityPrivate.SyntheticMouseEventType, mouseX: number,
       mouseY: number): void {
     if (type === chrome.accessibilityPrivate.SyntheticMouseEventType.PRESS) {
@@ -272,7 +273,7 @@
   /**
    * Visible for testing.
    */
-  private onKeysPressedChanged_(keysCurrentlyPressed: Set<number>) : void {
+  onKeysPressedChanged(keysCurrentlyPressed: Set<number>): void {
     if (keysCurrentlyPressed.size > this.keysCurrentlyDown_.size) {
       // If a key was pressed.
       for (const key of keysCurrentlyPressed) {
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.ts
similarity index 83%
rename from chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
rename to chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.ts
index 7ebc9db3..a6a9bd5 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/select_to_speak.ts
@@ -15,14 +15,13 @@
 import {PrefsManager} from './prefs_manager.js';
 import {SelectToSpeakConstants} from './select_to_speak_constants.js';
 import {TtsManager} from './tts_manager.js';
-import {UiManager} from './ui_manager.js';
+import {SelectToSpeakUiListener, UiManager} from './ui_manager.js';
 
-const AutomationNode = chrome.automation.AutomationNode;
-const AutomationEvent = chrome.automation.AutomationEvent;
-const EventType = chrome.automation.EventType;
-const RoleType = chrome.automation.RoleType;
-const AccessibilityFeature = chrome.accessibilityPrivate.AccessibilityFeature;
-const SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState;
+import AutomationNode = chrome.automation.AutomationNode;
+import AutomationEvent = chrome.automation.AutomationEvent;
+import EventType = chrome.automation.EventType;
+import RoleType = chrome.automation.RoleType;
+import SelectToSpeakState = chrome.accessibilityPrivate.SelectToSpeakState;
 
 // Matches one of the known GSuite apps which need the clipboard to find and
 // read selected text. Includes sandbox and non-sandbox versions.
@@ -35,11 +34,11 @@
  * pages are included, because some are not known to have a problem with
  * selection: Forms is not included since it's relatively similar to any HTML
  * page, for example.
- * @param {AutomationNode=}  node The node to check
- * @return {?AutomationNode} The root node of the GSuite app, or null if none is
- *     found.
+ * @param node The node to check
+ * @return The root node of the GSuite app, or null if none is found.
  */
-export function getGSuiteAppRoot(node) {
+export function getGSuiteAppRoot(node: AutomationNode|
+                                 undefined): AutomationNode|null {
   while (node !== undefined && node.root !== undefined) {
     if (node.root.url !== undefined && GSUITE_APP_REGEXP.exec(node.root.url)) {
       return node.root;
@@ -51,22 +50,41 @@
 
 /**
  * Select-to-speak component extension controller.
- * @implements {SelectToSpeakUiListener}
  */
-export class SelectToSpeak {
+export class SelectToSpeak implements SelectToSpeakUiListener {
+  private currentCharIndex_: number;
+  private currentNodeGroupIndex_: number;
+  // TODO(b/314203187): In many places we've added a currentNodeGroupItem_!,
+  // determine if this is correct or if a check should be added.
+  private currentNodeGroupItem_: ParagraphUtils.NodeGroupItem|null;
+  private currentNodeGroupItemIndex_: number;
+  private currentNodeGroups_: ParagraphUtils.NodeGroup[];
+  private currentNodeWord_: {start: number, end: number}|null;
+  private desktop_: chrome.automation.AutomationNode|undefined;
+  private inputHandler_: InputHandler|null;
+  private intervalId_: number|undefined;
+  private nullSelectionTone_: HTMLAudioElement;
+  private onStateChangeRequestedCallbackForTest_: (() => void)|null;
+  private prefsManager_: PrefsManager;
+  private scrollToSpokenNode_: boolean;
+  private speechRateMultiplier_: number;
+  private state_: SelectToSpeakState;
+  private supportsNavigationPanel_: boolean;
+  private ttsManager_: TtsManager;
+  private uiManager_: UiManager;
+  private onLoadDesktopCallbackForTest_: (() => void)|null;
+
   /** Please keep fields in alphabetical order. */
   constructor() {
     /**
      * The start char index of the word to be spoken. The index is relative
      * to the text content of the current node group.
-     * @private {number}
      */
     this.currentCharIndex_ = -1;
 
     /**
      * The index for the node group currently being spoken in
      * |this.currentNodeGroups_|.
-     * @private {number}
      */
     this.currentNodeGroupIndex_ = -1;
 
@@ -75,7 +93,6 @@
      * representation of the original input nodes, but may not be the same. For
      * example, an input inline text node will be represented by its static text
      * node in the node group item.
-     * @private {?ParagraphUtils.NodeGroupItem}
      */
     this.currentNodeGroupItem_ = null;
 
@@ -86,7 +103,6 @@
      * |this.currentNodeGroupItemIndex_| can be used to get
      * |this.currentNodeGroupItem_| from the current node group. However, in
      * Gsuite, we will have node group items outside of a node group.
-     * @private {number}
      */
     this.currentNodeGroupItemIndex_ = -1;
 
@@ -95,82 +111,61 @@
      * pass one node group at a time to the TTS engine. Note that we do not use
      * node groups for user-selected text in Gsuite. See more details in
      * readNodesBetweenPositions_.
-     * @private {!Array<!ParagraphUtils.NodeGroup>}
      */
     this.currentNodeGroups_ = [];
 
     /**
      * The indexes within the current node group item representing the word
      * currently being spoken. Only updated if word highlighting is enabled.
-     * @private {?{start: number, end: number}}
      */
     this.currentNodeWord_ = null;
 
-    /** @private {chrome.automation.AutomationNode} */
     this.desktop_;
 
-    /**
-     * Feature flag controlling STS language detection integration.
-     * @private {boolean}
-     */
-    this.enableLanguageDetectionIntegration_ = false;
-
-    /** @private {InputHandler} */
     this.inputHandler_ = null;
 
     /**
      * The interval ID from a call to setInterval, which is set whenever
      * speech is in progress.
-     * @private {number|undefined}
      */
     this.intervalId_;
 
-    /** @private {Audio} */
-    this.null_selection_tone_ = new Audio('earcons/null_selection.ogg');
+    this.nullSelectionTone_ = new Audio('earcons/null_selection.ogg');
 
     /**
      * Function to be called when a state change request is received from the
      * accessibilityPrivate API.
-     * @protected {?function()}
      */
     this.onStateChangeRequestedCallbackForTest_ = null;
 
-    /** @private {PrefsManager} */
     this.prefsManager_ = new PrefsManager();
 
-    /** @private {boolean} */
     this.scrollToSpokenNode_ = false;
 
-    /** @private {number} Speech rate multiplier. */
+    /** Speech rate multiplier. */
     this.speechRateMultiplier_ = 1.0;
 
     /**
      * The current state of the SelectToSpeak extension, from
      * SelectToSpeakState.
-     * @private {!chrome.accessibilityPrivate.SelectToSpeakState}
      */
     this.state_ = SelectToSpeakState.INACTIVE;
 
     /**
      * Whether the current nodes support use of the navigation panel.
-     * @private {boolean}
      */
     this.supportsNavigationPanel_ = true;
 
-    /** @private {!TtsManager} */
     this.ttsManager_ = new TtsManager();
 
-    /** @private {!UiManager} */
-    this.uiManager_ = new UiManager(this.prefsManager_, this /* listener */);
+    this.uiManager_ = new UiManager(this.prefsManager_, /*listener=*/ this);
 
-    /** @private {?function()} */
     this.onLoadDesktopCallbackForTest_ = null;
 
     this.init_();
   }
 
-  /** @private */
-  init_() {
+  private init_(): void {
     chrome.automation.getDesktop(desktop => {
       this.desktop_ = desktop;
 
@@ -195,7 +190,7 @@
     chrome.contextMenus.create({
       title: chrome.i18n.getMessage(
           'select_to_speak_listen_context_menu_option_text'),
-      contexts: ['selection'],
+      contexts: [chrome.contextMenus.ContextType.SELECTION],
       onclick: () => {
         this.getFocusedNodeAndSpeakSelectedText_();
       },
@@ -208,9 +203,8 @@
 
   /**
    * Gets the node group currently being spoken.
-   * @return {!ParagraphUtils.NodeGroup|undefined}
    */
-  getCurrentNodeGroup_() {
+  private getCurrentNodeGroup_(): ParagraphUtils.NodeGroup|undefined {
     if (this.currentNodeGroups_.length === 0) {
       return undefined;
     }
@@ -218,23 +212,11 @@
   }
 
   /**
-   * Gets the last node group from current selection.
-   * @return {!ParagraphUtils.NodeGroup|undefined}
-   */
-  getLastNodeGroup_() {
-    if (this.currentNodeGroups_.length === 0) {
-      return undefined;
-    }
-    return this.currentNodeGroups_[this.currentNodeGroups_.length - 1];
-  }
-
-  /**
    * Determines if navigation controls should be shown (and other related
    * functionality, such as auto-dismiss and click-to-navigate to sentence,
    * should be activated) based on feature flag and user setting.
-   * @private
    */
-  shouldShowNavigationControls_() {
+  private shouldShowNavigationControls_(): boolean {
     return this.prefsManager_.navigationControlsEnabled() &&
         this.supportsNavigationPanel_;
   }
@@ -243,10 +225,9 @@
    * Called in response to our hit test after the mouse is released,
    * when the user is in a mode where Select-to-speak is capturing
    * mouse events (for example holding down Search).
-   * @param {!AutomationEvent} evt The automation event.
-   * @private
+   * @param evt The automation event from the hit test.
    */
-  onAutomationHitTest_(evt) {
+  private onAutomationHitTest_(evt: AutomationEvent): void {
     // Walk up to the nearest window, web area, toolbar, or dialog that the
     // hit node is contained inside. Only speak objects within that
     // container. In the future we might include other container-like
@@ -261,8 +242,8 @@
       root = root.parent;
     }
 
-    var rect = this.inputHandler_.getMouseRect();
-    var nodes = [];
+    var rect = this.inputHandler_!.getMouseRect();
+    var nodes: chrome.automation.AutomationNode[] = [];
     chrome.automation.getFocus(focusedNode => {
       // In some cases, e.g. ARC++, the window received in the hit test request,
       // which is computed based on which window is the event handler for the
@@ -272,7 +253,7 @@
       // so, look for classname exoshell on the root or root parent to confirm
       // that a node is in ARC++.
       if (!NodeUtils.findAllMatching(root, rect, nodes) && focusedNode &&
-          focusedNode.root.role !== RoleType.DESKTOP) {
+          focusedNode.root!.role !== RoleType.DESKTOP) {
         NodeUtils.findAllMatching(focusedNode.root, rect, nodes);
       }
       if (nodes.length === 1 && UiManager.isTrayButton(nodes[0])) {
@@ -288,14 +269,15 @@
         // expand to entire paragraph.
         nodes = NodeUtils.getAllNodesInParagraph(nodes[0]);
       }
-      this.startSpeechQueue_(nodes, {clearFocusRing: true});
+      this.startSpeechQueue_(nodes, {
+        clearFocusRing: true,
+      });
       MetricsUtils.recordStartEvent(
           MetricsUtils.StartSpeechMethod.MOUSE, this.prefsManager_);
     });
   }
 
-  /** @private */
-  getFocusedNodeAndSpeakSelectedText_() {
+  private getFocusedNodeAndSpeakSelectedText_(): void {
     chrome.automation.getFocus(
         focusedNode => this.requestSpeakSelectedText_(
             MetricsUtils.StartSpeechMethod.CONTEXT_MENU, focusedNode));
@@ -304,11 +286,12 @@
   /**
    * Queues up selected text for reading by finding the Position objects
    * representing the selection.
-   * @private
-   * @param {MetricsUtils.StartSpeechMethod} method the method that
+   * @param method the method that
    *     caused the text to speak.
    */
-  requestSpeakSelectedText_(method, focusedNode) {
+  private requestSpeakSelectedText_(
+      method: MetricsUtils.StartSpeechMethod,
+      focusedNode: chrome.automation.AutomationNode): void {
     // If nothing is selected, return early. Check if the focused node has
     // textSelStart and textSelEnd. For native UI like the omnibox, the root
     // might not have a selectionStartObject and selectionEndObject. Therefore
@@ -327,9 +310,9 @@
     }
 
     let startObject;
-    let startOffset;
+    let startOffset = 0;
     let endObject;
-    let endOffset;
+    let endOffset = 0;
     // Use selectionStartObject/selectionEndObject if available. Otherwise,
     // use textSelStart/textSelEnd to get the selection offset.
     if (hasSelectionObjects) {
@@ -398,22 +381,22 @@
 
   /**
    * Reads nodes between positions.
-   * @param {NodeUtils.Position} firstPosition The first position at which to
-   *     start reading.
-   * @param {NodeUtils.Position} lastPosition The last position at which to
-   *     stop reading.
-   * @param {MetricsUtils.StartSpeechMethod | null} method the method used to
+   * @param firstPosition The first position at which to start reading.
+   * @param lastPosition The last position at which to stop reading.
+   * @param method the method used to
    *     activate the speech, null if not actived by user.
-   * @param {AutomationNode=} focusedNode The node with user focus.
-   * @private
+   * @param focusedNode The node with user focus.
    */
-  readNodesBetweenPositions_(firstPosition, lastPosition, method, focusedNode) {
+  private readNodesBetweenPositions_(
+      firstPosition: NodeUtils.Position, lastPosition: NodeUtils.Position,
+      method: MetricsUtils.StartSpeechMethod|null,
+      focusedNode: AutomationNode|undefined): void {
     const nodes = [];
     let selectedNode = firstPosition.node;
     // If the method is set, a user requested the speech.
     const userRequested = method !== null;
     /**@type {number} */
-    const methodNumber = method !== null ? method : -1;
+    const methodNumber: number = method !== null ? method : -1;
     // Certain nodes such as omnibox store text value in the value property,
     // instead of the name property. The getNodeName method in ParagraphUtils
     // does handle this case properly, so use this static method to get text
@@ -495,7 +478,7 @@
         if (!gsuiteAppRootNode || gsuiteAppRootNode.url === undefined) {
           return;
         }
-        this.inputHandler_.onRequestReadClipboardData();
+        this.inputHandler_!.onRequestReadClipboardData();
         this.currentNodeGroupItem_ =
             new ParagraphUtils.NodeGroupItem(gsuiteAppRootNode, 0, false);
         if (tabs.length > 0 && tabs[0].url === gsuiteAppRootNode.url) {
@@ -521,15 +504,15 @@
   /**
    * Gets ready to cancel future scrolling to offscreen nodes as soon as
    * a user-initiated scroll is done.
-   * @param {AutomationNode=} root The root node to listen for events on.
-   * @private
+   * @param root The root node to listen for events on.
    */
-  initializeScrollingToOffscreenNodes_(root) {
+  private initializeScrollingToOffscreenNodes_(root: AutomationNode|
+                                               undefined): void {
     if (!root) {
       return;
     }
     this.scrollToSpokenNode_ = true;
-    const listener = event => {
+    const listener = (event: chrome.automation.AutomationEvent): void => {
       if (event.eventFrom !== 'action') {
         // User initiated event. Cancel all future scrolling to spoken nodes.
         // If the user wants a certain scroll position we will respect that.
@@ -556,11 +539,10 @@
   /**
    * Plays a tone to let the user know they did the correct
    * keystroke but nothing was selected.
-   * @private
    */
-  onNullSelection_() {
+  private onNullSelection_(): void {
     if (!this.shouldShowNavigationControls_()) {
-      this.null_selection_tone_.play();
+      this.nullSelectionTone_.play();
       return;
     }
 
@@ -572,27 +554,23 @@
    * false and |this.state_| is SPEAKING.
    * TODO(leileilei): use two SelectToSpeak states to differentiate speaking and
    * pausing with panel.
-   * @private
    */
-  isPaused_() {
+  private isPaused_(): boolean {
     return !this.ttsManager_.isSpeaking() &&
         this.state_ === SelectToSpeakState.SPEAKING;
   }
 
   /**
    * Pause the TTS.
-   * @return {!Promise}
-   * @private
    */
-  pause_() {
+  private pause_(): Promise<any> {
     return this.ttsManager_.pause();
   }
 
   /**
    * Resume the TTS.
-   * @private
    */
-  resume_() {
+  private resume_(): void {
     // If TTS is not paused, return early.
     if (!this.isPaused_()) {
       return;
@@ -610,9 +588,8 @@
   /**
    * If resume is successful, a resume event will be sent. We use this event to
    * update node state.
-   * @param {!chrome.tts.TtsEvent} event
    */
-  onTtsResumeSucceedEvent_(event) {
+  private onTtsResumeSucceedEvent_(event: chrome.tts.TtsEvent): void {
     // If the node group is invalid, ignore the resume event. This is not
     // expected.
     const currentNodeGroup = this.getCurrentNodeGroup_();
@@ -628,9 +605,8 @@
    * is no remaining user-selected content, STS will read from the current
    * position to the end of the current paragraph. If there is no content left
    * in this paragraph, we navigate to the next paragraph.
-   * @param {!chrome.tts.TtsEvent} event
    */
-  onTtsResumeErrorEvent_(event) {
+  private onTtsResumeErrorEvent_(_event: chrome.tts.TtsEvent): void {
     // If the node group is invalid, ignore the error event. This is not
     // expected.
     const currentNodeGroup = this.getCurrentNodeGroup_();
@@ -660,8 +636,10 @@
       return;
     }
 
-    this.startSpeechQueue_(
-        remainingNodes, {clearFocusRing: false, startCharIndex: offset});
+    this.startSpeechQueue_(remainingNodes, {
+      clearFocusRing: false,
+      startCharIndex: offset,
+    });
   }
 
   /**
@@ -671,9 +649,8 @@
    * If speech was not in progress, i.e. if the user was drawing
    * a focus ring on the screen, this still clears the visual
    * focus ring.
-   * @private
    */
-  stopAll_() {
+  private stopAll_(): void {
     this.ttsManager_.stop();
     this.uiManager_.clear();
     this.onStateChanged_(SelectToSpeakState.INACTIVE);
@@ -682,9 +659,8 @@
   /**
    * Clears the current focus ring and node, but does
    * not stop the speech.
-   * @private
    */
-  clearFocusRingAndNode_() {
+  private clearFocusRingAndNode_(): void {
     this.uiManager_.clear();
     // Clear the node and also stop the interval testing.
     this.resetNodes_();
@@ -698,9 +674,8 @@
 
   /**
    * Resets the instance variables for nodes and node groups.
-   * @private
    */
-  resetNodes_() {
+  private resetNodes_(): void {
     this.currentNodeGroups_ = [];
     this.currentNodeGroupIndex_ = -1;
     this.currentNodeGroupItem_ = null;
@@ -716,9 +691,8 @@
    * tabs already opened will be checked.
    * This should be kept in sync with the "content_scripts" section in
    * the Select-to-Speak manifest.
-   * @private
    */
-  runContentScripts_() {
+  private runContentScripts_(): void {
     const scripts = chrome.runtime.getManifest()['content_scripts'][0]['js'];
 
     // We only ever expect one content script.
@@ -745,9 +719,8 @@
 
   /**
    * Set up event listeners user input.
-   * @private
    */
-  setUpEventListeners_() {
+  private setUpEventListeners_(): void {
     this.inputHandler_ = new InputHandler({
       // canStartSelecting: Whether mouse selection can begin.
       canStartSelecting: () => {
@@ -760,7 +733,7 @@
           // Fire a hit test event on click to warm up the cache, and cancel
           // if speaking.
           this.cancelIfSpeaking_(false /* don't clear the focus ring */);
-          this.desktop_.hitTest(x, y, EventType.MOUSE_PRESSED);
+          this.desktop_!.hitTest(x, y, EventType.MOUSE_PRESSED);
         } else {
           this.onStateChanged_(SelectToSpeakState.INACTIVE);
           // Do a hit test at the center of the area the user dragged over.
@@ -768,7 +741,7 @@
           // tree. The hit test will result in a EventType.MOUSE_RELEASED
           // event being fired on the result of that hit test, which will
           // trigger onAutomationHitTest_.
-          this.desktop_.hitTest(x, y, EventType.MOUSE_RELEASED);
+          this.desktop_!.hitTest(x, y, EventType.MOUSE_RELEASED);
         }
       },
       // onSelectionChanged: Mouse selection rect changed.
@@ -799,7 +772,7 @@
   /**
    * Called when Chrome OS is requesting Select-to-Speak to switch states.
    */
-  onStateChangeRequested() {
+  onStateChangeRequested(): void {
     // Switch Select-to-Speak states on request.
     // We will need to track the current state and toggle from one state to
     // the next when this function is called, and then call
@@ -807,7 +780,7 @@
     switch (this.state_) {
       case SelectToSpeakState.INACTIVE:
         // Start selection.
-        this.inputHandler_.setTrackingMouse(true);
+        this.inputHandler_!.setTrackingMouse(true);
         this.onStateChanged_(SelectToSpeakState.SELECTING);
         MetricsUtils.recordSelectToSpeakStateChangeEvent(
             MetricsUtils.StateChangeEvent.START_SELECTION);
@@ -821,7 +794,7 @@
         break;
       case SelectToSpeakState.SELECTING:
         // Cancelled selection.
-        this.inputHandler_.setTrackingMouse(false);
+        this.inputHandler_!.setTrackingMouse(false);
         this.onStateChanged_(SelectToSpeakState.INACTIVE);
         MetricsUtils.recordSelectToSpeakStateChangeEvent(
             MetricsUtils.StateChangeEvent.CANCEL_SELECTION);
@@ -831,40 +804,40 @@
   }
 
   /** Handles user request to navigate to next paragraph. */
-  onNextParagraphRequested() {
+  onNextParagraphRequested(): void {
     this.navigateToNextParagraph_(constants.Dir.FORWARD);
   }
 
   /** Handles user request to navigate to previous paragraph. */
-  onPreviousParagraphRequested() {
+  onPreviousParagraphRequested(): void {
     this.navigateToNextParagraph_(constants.Dir.BACKWARD);
   }
 
   /** Handles user request to navigate to next sentence. */
-  onNextSentenceRequested() {
+  onNextSentenceRequested(): void {
     this.navigateToNextSentence_(constants.Dir.FORWARD);
   }
 
   /** Handles user request to navigate to previous sentence. */
-  onPreviousSentenceRequested() {
+  onPreviousSentenceRequested(): void {
     this.navigateToNextSentence_(constants.Dir.BACKWARD);
   }
 
   /** Handles user request to navigate to exit STS. */
-  onExitRequested() {
+  onExitRequested(): void {
     // User manually requested, so log cancel metric.
     MetricsUtils.recordCancelIfSpeaking();
     this.stopAll_();
   }
 
   /** Handles user request to pause TTS. */
-  onPauseRequested() {
+  onPauseRequested(): void {
     MetricsUtils.recordPauseEvent();
     this.pause_();
   }
 
   /** Handles user request to resume TTS. */
-  onResumeRequested() {
+  onResumeRequested(): void {
     if (this.isPaused_()) {
       MetricsUtils.recordResumeEvent();
       this.resume_();
@@ -873,10 +846,8 @@
 
   /**
    * Handles user request to adjust reading speed.
-   * @param {number} rateMultiplier
-   * @private
    */
-  onChangeSpeedRequested(rateMultiplier) {
+  onChangeSpeedRequested(rateMultiplier: number): void {
     this.speechRateMultiplier_ = rateMultiplier;
     // If currently playing, stop TTS, then resume from current spot.
     if (!this.isPaused_()) {
@@ -888,13 +859,13 @@
 
   /**
    * Navigates to the next sentence.
-   * @param {constants.Dir} direction Direction to search for the next sentence.
+   * @param direction Direction to search for the next sentence.
    *     If set to forward, we look for the sentence start after the current
    *     position. Otherwise, we look for the sentence start before the current
    *     position.
-   * @private
    */
-  async navigateToNextSentence_(direction) {
+  private async navigateToNextSentence_(direction: constants.Dir):
+      Promise<void> {
     if (!this.isPaused_()) {
       await this.pause_();
     }
@@ -907,15 +878,16 @@
     // Ensure the first node in the paragraph is visible.
     nodes[0].makeVisible();
 
-    this.startSpeechQueue_(nodes, {startCharIndex: offset});
+    this.startSpeechQueue_(nodes, {
+      startCharIndex: offset,
+    });
   }
 
   /**
    * Navigates to the next text block in the given direction.
-   * @param {constants.Dir} direction
-   * @private
    */
-  async navigateToNextParagraph_(direction) {
+  private async navigateToNextParagraph_(direction: constants.Dir):
+      Promise<void> {
     if (!this.isPaused_()) {
       // Stop TTS if it is currently playing.
       await this.pause_();
@@ -938,11 +910,9 @@
   /**
    * A predicate for paragraph selection and navigation. The current
    * implementation filters out paragraph that belongs to the panel.
-   * @param {Array<!AutomationNode>} nodes
-   * @return {boolean} Whether the paragraph made of the |nodes| is valid
-   * @private
+   * @return Whether the paragraph made of the |nodes| is valid
    */
-  skipPanel_(nodes) {
+  private skipPanel_(nodes: AutomationNode[]): boolean {
     return !AutomationUtil.getAncestors(nodes[0]).find(
         n => UiManager.isPanel(n));
   }
@@ -951,10 +921,9 @@
    * Enqueue speech for the single given string. The string is not associated
    * with any particular nodes, so this does not do any work around drawing
    * focus rings, unlike startSpeechQueue_ below.
-   * @param {string} text The text to speak.
-   * @private
+   * @param text The text to speak.
    */
-  startSpeech_(text) {
+  private startSpeech_(text: string): void {
     this.prepareForSpeech_(true /* clearFocusRing */);
     this.maybeShowEnhancedVoicesDialog_(() => {
       const options = this.prefsManager_.getSpeechOptions(null);
@@ -985,10 +954,8 @@
    * Enqueue nodes to TTS queue and start TTS. This function can be used for
    * adding nodes, either from user selection (e.g., mouse selection) or
    * navigation control (e.g., next paragraph).
-   * @param {!Array<AutomationNode>} nodes The nodes to speak.
-   * @param {!{clearFocusRing: (boolean|undefined),
-   *          startCharIndex: (number|undefined),
-   *          endCharIndex: (number|undefined)}=} opt_params
+   * @param  nodes The nodes to speak.
+   * @param optParams:
    *    clearFocusRing: Whether to clear the focus ring or not. For example, we
    * need to clear the focus ring when starting from scratch but we do not need
    * to clear the focus ring when resuming from a previous pause. If this is not
@@ -997,11 +964,14 @@
    * speaking. If this is not passed, will start at 0.
    *    endCharIndex: The index into the last node's text at which to end
    * speech. If this is not passed, will stop at the end.
-   * @private
    */
-  startSpeechQueue_(nodes, opt_params) {
+  private startSpeechQueue_(nodes: AutomationNode[], optParams?: {
+    clearFocusRing?: boolean,
+    startCharIndex?: number,
+    endCharIndex?: number,
+  }): void {
     this.maybeShowEnhancedVoicesDialog_(() => {
-      const params = opt_params || {};
+      const params = optParams || {};
       const clearFocusRing = params.clearFocusRing || false;
       let startCharIndex = params.startCharIndex;
       let endCharIndex = params.endCharIndex;
@@ -1039,14 +1009,15 @@
    * Updates the node groups to be spoken. Converts |nodes|, |startCharIndex|,
    * and |endCharIndex| into node groups, and updates |this.currentNodeGroups_|
    * and |this.currentNodeGroupIndex_|.
-   * @param {!Array<AutomationNode>} nodes The nodes to speak.
-   * @param {number=} startCharIndex The index into the first node's text at
+   * @param nodes The nodes to speak.
+   * @param startCharIndex The index into the first node's text at
    *     which to start speaking. If this is not passed, will start at 0.
-   * @param {number=} endCharIndex The index into the last node's text at which
+   * @param endCharIndex The index into the last node's text at which
    *     to end speech. If this is not passed, will stop at the end.
-   * @private
    */
-  updateNodeGroups_(nodes, startCharIndex, endCharIndex) {
+  private updateNodeGroups_(
+      nodes: AutomationNode[], startCharIndex?: number,
+      endCharIndex?: number): void {
     this.resetNodes_();
 
     for (let i = 0; i < nodes.length; i++) {
@@ -1128,9 +1099,8 @@
 
   /**
    * Starts reading the current node group.
-   * @private
    */
-  startCurrentNodeGroup_() {
+  private startCurrentNodeGroup_(): void {
     const nodeGroup = this.getCurrentNodeGroup_();
     if (!nodeGroup) {
       return;
@@ -1142,21 +1112,22 @@
     }
 
     const options = this.getTtsOptionsForCurrentNodeGroup_();
-    const voiceName = options['voiceName'] || '';
+    const voiceName = (options && options['voiceName']) || '';
     const fallbackVoiceName = this.prefsManager_.getLocalVoice();
 
     MetricsUtils.recordTtsEngineUsed(voiceName, this.prefsManager_);
     this.ttsManager_.speak(
-        nodeGroup.text, options, this.prefsManager_.isNetworkVoice(voiceName),
+        // TODO(b/314203187): Options may be undefined.
+        nodeGroup.text, options!, this.prefsManager_.isNetworkVoice(voiceName),
         fallbackVoiceName);
   }
 
-  getTtsOptionsForCurrentNodeGroup_() {
+  private getTtsOptionsForCurrentNodeGroup_(): chrome.tts.TtsOptions|undefined {
     const nodeGroup = this.getCurrentNodeGroup_();
     if (!nodeGroup) {
       return;
     }
-    const options = /** @type {!chrome.tts.TtsOptions} */ ({});
+    const options: chrome.tts.TtsOptions = {};
     let language;
     let useVoiceSwitching = false;
     if (this.shouldUseVoiceSwitching_() && nodeGroup.detectedLanguage) {
@@ -1177,7 +1148,7 @@
 
     const nodeGroupText = nodeGroup.text || '';
 
-    options.onEvent = event => {
+    options.onEvent = (event: chrome.tts.TtsEvent) => {
       switch (event.type) {
         case chrome.tts.EventType.START:
           if (nodeGroup.nodes.length <= 0) {
@@ -1197,7 +1168,7 @@
             this.currentNodeWord_ = null;
             // If |this.currentCharIndex_| is not 0, that means we have applied
             // a start offset. Thus, we need to pass startIndexInNodeGroup to
-            // opt_startIndex and overwrite the word boundaries in the original
+            // optStartIndex and overwrite the word boundaries in the original
             // node.
             this.updateNodeHighlight_(
                 nodeGroupText, this.currentCharIndex_,
@@ -1216,6 +1187,7 @@
             this.onTtsResumeErrorEvent_(event);
           }
           break;
+        // @ts-expect-error: Fallthrough on purpose.
         case chrome.tts.EventType.PAUSE:
           // Updates the select to speak state to speaking to keep navigation
           // panel visible, so that the user can click resume from the panel.
@@ -1258,9 +1230,8 @@
    * indicated by the end index. If we have reached the last node group, this
    * function will update STS status depending whether the navigation feature is
    * enabled.
-   * @private
    */
-  onNodeGroupSpeakingCompleted_() {
+  private onNodeGroupSpeakingCompleted_(): void {
     const currentNodeGroup = this.getCurrentNodeGroup_();
 
     // Update the current char index to the end of the node group. If the
@@ -1294,18 +1265,19 @@
   /**
    * Update |this.currentNodeGroupItem_|, the current speaking or the node to be
    * spoken in the node group.
-   * @param {ParagraphUtils.NodeGroup} nodeGroup the current nodeGroup.
-   * @param {number} charIndex the start char index of the word to be spoken.
+   * @param nodeGroup the current nodeGroup.
+   * @param charIndex the start char index of the word to be spoken.
    *    The index is relative to the entire NodeGroup.
-   * @param {number=} opt_startFromNodeGroupIndex the NodeGroupIndex to start
+   * @param optStartFromNodeGroupIndex the NodeGroupIndex to start
    *    with. If undefined, search from 0.
-   * @return {boolean} if the found NodeGroupIndex is different from the
-   *    |opt_startFromNodeGroupIndex|.
+   * @return If the found NodeGroupIndex is different from the
+   *    |optStartFromNodeGroupIndex|.
    */
-  syncCurrentNodeWithCharIndex_(
-      nodeGroup, charIndex, opt_startFromNodeGroupIndex) {
-    if (opt_startFromNodeGroupIndex === undefined) {
-      opt_startFromNodeGroupIndex = 0;
+  private syncCurrentNodeWithCharIndex_(
+      nodeGroup: ParagraphUtils.NodeGroup, charIndex: number,
+      optStartFromNodeGroupIndex?: number): boolean {
+    if (optStartFromNodeGroupIndex === undefined) {
+      optStartFromNodeGroupIndex = 0;
     }
 
     // There is no speaking word, set the NodeGroupItemIndex to 0.
@@ -1313,17 +1285,18 @@
       this.currentNodeGroupItemIndex_ = 0;
       this.currentNodeGroupItem_ =
           nodeGroup.nodes[this.currentNodeGroupItemIndex_];
-      return this.currentNodeGroupItemIndex_ === opt_startFromNodeGroupIndex;
+      return this.currentNodeGroupItemIndex_ === optStartFromNodeGroupIndex;
     }
 
     // Sets the |this.currentNodeGroupItemIndex_| to
-    // |opt_startFromNodeGroupIndex|
-    this.currentNodeGroupItemIndex_ = opt_startFromNodeGroupIndex;
+    // |optStartFromNodeGroupIndex|
+    this.currentNodeGroupItemIndex_ = optStartFromNodeGroupIndex;
     this.currentNodeGroupItem_ =
         nodeGroup.nodes[this.currentNodeGroupItemIndex_];
 
     if (this.currentNodeGroupItemIndex_ + 1 < nodeGroup.nodes.length) {
-      let next = nodeGroup.nodes[this.currentNodeGroupItemIndex_ + 1];
+      let next: ParagraphUtils.NodeGroupItem|null =
+          nodeGroup.nodes[this.currentNodeGroupItemIndex_ + 1];
       let nodeUpdated = false;
       // TODO(katie): For something like a date, the start and end
       // node group nodes can actually be different. Example:
@@ -1345,12 +1318,14 @@
 
   /**
    * Apply start or end offset to the text of the |nodeGroup|.
-   * @param {ParagraphUtils.NodeGroup} nodeGroup the input nodeGroup.
-   * @param {number} offset the size of offset.
-   * @param {boolean} isStartOffset whether to apply a startOffset or an
+   * @param nodeGroup the input nodeGroup.
+   * @param offset the size of offset.
+   * @param isStartOffset whether to apply a startOffset or an
    *     endOffset.
    */
-  applyOffset(nodeGroup, offset, isStartOffset) {
+  applyOffset(
+      nodeGroup: ParagraphUtils.NodeGroup, offset: number,
+      isStartOffset: boolean): void {
     if (isStartOffset) {
       // Applying start offset. Remove all text before the start index so that
       // it is not spoken. Backfill with spaces so that index counting
@@ -1365,10 +1340,9 @@
 
   /**
    * Prepares for speech. Call once before this.ttsManager_.speak is called.
-   * @param {boolean} clearFocusRing Whether to clear the focus ring.
-   * @private
+   * @param clearFocusRing Whether to clear the focus ring.
    */
-  prepareForSpeech_(clearFocusRing) {
+  private prepareForSpeech_(clearFocusRing: boolean): void {
     this.cancelIfSpeaking_(clearFocusRing /* clear the focus ring */);
 
     // Update the UI on an interval, to adapt to automation tree changes.
@@ -1383,12 +1357,12 @@
   /**
    * Uses the 'word' speech event to determine which node is currently beings
    * spoken, and prepares for highlight if enabled.
-   * @param {!chrome.tts.TtsEvent} event The event to use for updates.
-   * @param {ParagraphUtils.NodeGroup} nodeGroup The node group for this
+   * @param event The event to use for updates.
+   * @param nodeGroup The node group for this
    *     utterance.
-   * @private
    */
-  onTtsWordEvent_(event, nodeGroup) {
+  private onTtsWordEvent_(
+      event: chrome.tts.TtsEvent, nodeGroup: ParagraphUtils.NodeGroup): void {
     if (event.charIndex === undefined) {
       return;
     }
@@ -1422,9 +1396,9 @@
     if (this.prefsManager_.wordHighlightingEnabled()) {
       if (hasLength) {
         this.currentNodeWord_ = {
-          'start': event.charIndex - this.currentNodeGroupItem_.startChar,
+          'start': event.charIndex - this.currentNodeGroupItem_!.startChar,
           'end':
-              event.charIndex + length - this.currentNodeGroupItem_.startChar,
+              event.charIndex + length - this.currentNodeGroupItem_!.startChar,
         };
         this.updateUi_();
       } else {
@@ -1438,11 +1412,9 @@
   /**
    * Updates the current node and relevant points to be the next node in the
    * group, then returns the next node in the group after that.
-   * @param {!ParagraphUtils.NodeGroup} nodeGroup
-   * @return {ParagraphUtils.NodeGroupItem}
-   * @private
    */
-  incrementCurrentNodeAndGetNext_(nodeGroup) {
+  private incrementCurrentNodeAndGetNext_(nodeGroup: ParagraphUtils.NodeGroup):
+      ParagraphUtils.NodeGroupItem|null {
     // Move to the next node.
     this.currentNodeGroupItemIndex_ += 1;
     this.currentNodeGroupItem_ =
@@ -1458,10 +1430,9 @@
 
   /**
    * Updates the state.
-   * @param {!chrome.accessibilityPrivate.SelectToSpeakState} state
-   * @private
    */
-  onStateChanged_(state) {
+  private onStateChanged_(
+      state: chrome.accessibilityPrivate.SelectToSpeakState): void {
     if (this.state_ !== state) {
       if (state === SelectToSpeakState.INACTIVE) {
         this.clearFocusRingAndNode_();
@@ -1474,11 +1445,9 @@
 
   /**
    * Cancels the current speech queue.
-   * @param {boolean} clearFocusRing Whether to clear the focus ring
-   *    as well.
-   * @private
+   * @param clearFocusRing Whether to clear the focus ring as well.
    */
-  cancelIfSpeaking_(clearFocusRing) {
+  private cancelIfSpeaking_(clearFocusRing: boolean): void {
     if (clearFocusRing) {
       this.stopAll_();
     } else {
@@ -1488,14 +1457,12 @@
   }
 
   /**
-   * @param {!AutomationNode} node
-   * @return {!Promise<boolean>} Promise that resolves to whether the given node
+   * @return Promise that resolves to whether the given node
    *     should be considered in the foreground or not.
-   * @private
    */
-  isNodeInForeground_(node) {
+  private isNodeInForeground_(node: AutomationNode): Promise<boolean> {
     return new Promise(resolve => {
-      this.desktop_.hitTestWithReply(
+      this.desktop_!.hitTestWithReply(
           node.location.left, node.location.top, nodeAtLocation => {
             chrome.automation.getFocus(focusedNode => {
               const window =
@@ -1535,10 +1502,9 @@
   }
 
   /**
-   * @return {?AutomationNode} Current node that is being spoken.
-   * @private
+   * @return Current node that is being spoken.
    */
-  getCurrentSpokenNode_() {
+  private getCurrentSpokenNode_(): AutomationNode|null {
     if (!this.currentNodeGroupItem_) {
       return null;
     }
@@ -1561,10 +1527,9 @@
 
   /**
    * Updates the UI based on the current STS and node state.
-   * @return {!Promise<void>} Promise that resolves when operation is complete.
-   * @private
+   * @return Promise that resolves when operation is complete.
    */
-  async updateUi_() {
+  private async updateUi_(): Promise<void> {
     if (this.currentNodeGroupItem_ === null) {
       // Nothing to do.
       return;
@@ -1600,7 +1565,7 @@
       return;
     }
 
-    if (this.scrollToSpokenNode_ && spokenNode.state.offscreen) {
+    if (this.scrollToSpokenNode_ && spokenNode.state!['offscreen']) {
       spokenNode.makeVisible();
     }
     const currentWord = this.prefsManager_.wordHighlightingEnabled() ?
@@ -1618,10 +1583,10 @@
    * showing privacy disclaimer and asking if the user wants to turn on enhanced
    * network voices.
    *
-   * @param {function()} callback Called back after user has confirmed or
+   * @param callback Called back after user has confirmed or
    *     canceled in the dialog.
    */
-  maybeShowEnhancedVoicesDialog_(callback) {
+  private maybeShowEnhancedVoicesDialog_(callback: () => any): void {
     if (!this.prefsManager_.enhancedVoicesDialogShown() &&
         this.prefsManager_.enhancedNetworkVoicesAllowed()) {
       // TODO(crbug.com/1230227): Style this dialog to match UX mocks.
@@ -1650,33 +1615,33 @@
   /**
    * Updates the currently highlighted node word based on the current text
    * and the character index of an event.
-   * @param {string} text The current text
-   * @param {number} charIndex The index of a current event in the text.
-   * @param {number=} opt_startIndex The index at which to start the
+   * @param text The current text
+   * @param charIndex The index of a current event in the text.
+   * @param optStartIndex The index at which to start the
    *     highlight. This takes precedence over the charIndex.
-   * @private
    */
-  updateNodeHighlight_(text, charIndex, opt_startIndex) {
+  private updateNodeHighlight_(
+      text: string, charIndex: number, optStartIndex?: number): void {
     if (charIndex >= text.length) {
       // No need to do work if we are at the end of the paragraph.
       return;
     }
     // Get the next word based on the event's charIndex.
-    const nextWordStart =
-        WordUtils.getNextWordStart(text, charIndex, this.currentNodeGroupItem_);
+    const nextWordStart = WordUtils.getNextWordStart(
+        text, charIndex, this.currentNodeGroupItem_!);
     // The |WordUtils.getNextWordEnd| will find the correct end based on the
     // trimmed text, so there is no need to provide additional input like
-    // opt_startIndex.
+    // optStartIndex.
     const nextWordEnd = WordUtils.getNextWordEnd(
-        text, opt_startIndex === undefined ? nextWordStart : opt_startIndex,
-        this.currentNodeGroupItem_);
+        text, optStartIndex === undefined ? nextWordStart : optStartIndex,
+        this.currentNodeGroupItem_!);
     // Map the next word into the node's index from the text.
-    const nodeStart = opt_startIndex === undefined ?
-        nextWordStart - this.currentNodeGroupItem_.startChar :
-        opt_startIndex - this.currentNodeGroupItem_.startChar;
+    const nodeStart = optStartIndex === undefined ?
+        nextWordStart - this.currentNodeGroupItem_!.startChar :
+        optStartIndex - this.currentNodeGroupItem_!.startChar;
     const nodeEnd = Math.min(
-        nextWordEnd - this.currentNodeGroupItem_.startChar,
-        NodeUtils.nameLength(this.currentNodeGroupItem_.node));
+        nextWordEnd - this.currentNodeGroupItem_!.startChar,
+        NodeUtils.nameLength(this.currentNodeGroupItem_!.node));
     if ((this.currentNodeWord_ == null ||
          nodeStart >= this.currentNodeWord_.end) &&
         nodeStart <= nodeEnd) {
@@ -1691,10 +1656,9 @@
   }
 
   /**
-   * @return {number} Current speech rate.
-   * @private
+   * @return Current speech rate.
    */
-  getSpeechRate_() {
+  private getSpeechRate_(): number {
     // Multiply default speech rate with user-selected multiplier.
     const rate = this.prefsManager_.speechRate() * this.speechRateMultiplier_;
     // Then round to the nearest tenth (ex. 1.799999 becomes 1.8).
@@ -1702,11 +1666,9 @@
   }
 
   /**
-   * @param {!Array<!AutomationNode>} nodes
-   * @return {boolean} Whether all given nodes support the navigation panel.
-   * @private
+   * @return Whether all given nodes support the navigation panel.
    */
-  isNavigationPanelSupported_(nodes) {
+  private isNavigationPanelSupported_(nodes: AutomationNode[]): boolean {
     if (nodes.length === 0) {
       return true;
     }
@@ -1728,44 +1690,39 @@
   }
 
   /**
-   * @param {!Array<number>} keysPressed Which keys to pretend are currently
-   *     pressed.
-   * @protected
+   * @param keysPressed Which keys to pretend are currently pressed.
    */
-  sendMockSelectToSpeakKeysPressedChanged(keysPressed) {
-    this.inputHandler_.onKeysPressedChanged_(new Set(keysPressed));
+  protected sendMockSelectToSpeakKeysPressedChanged(keysPressed: number[]):
+      void {
+    this.inputHandler_!.onKeysPressedChanged(new Set(keysPressed));
   }
 
   /**
    * Fires a mock mouse down event for testing.
-   * @param {!chrome.accessibilityPrivate.SyntheticMouseEventType} type The
-   *     event type.
-   * @param {number} mouse_x The mouse x coordinate in global screen
-   *     coordinates.
-   * @param {number} mouse_y The mouse y coordinate in global screen
-   *     coordinates.
-   * @protected
+   * @param type The event type.
+   * @param mouseX The mouse x coordinate in global screen coordinates.
+   * @param mouseY The mouse y coordinate in global screen coordinates.
    */
-  fireMockMouseEvent(type, mouse_x, mouse_y) {
-    this.inputHandler_.onMouseEvent_(type, mouse_x, mouse_y);
+  protected fireMockMouseEvent(
+      type: chrome.accessibilityPrivate.SyntheticMouseEventType, mouseX: number,
+      mouseY: number): void {
+    this.inputHandler_!.onMouseEvent(type, mouseX, mouseY);
   }
 
   /**
    * TODO(crbug.com/950391): Consider adding a metric for when voice switching
    * gets used.
-   * @return {boolean}
-   * @private
    */
-  shouldUseVoiceSwitching_() {
+  private shouldUseVoiceSwitching_(): boolean {
     return this.prefsManager_.voiceSwitchingEnabled();
   }
 
   /**
    * Used by C++ tests to ensure STS load is completed.
-   * @param {!function()} callback Callback for when desktop is loaded from
+   * @param callback Callback for when desktop is loaded from
    * automation.
    */
-  setOnLoadDesktopCallbackForTest(callback) {
+  setOnLoadDesktopCallbackForTest(callback: () => any): void {
     if (!this.desktop_) {
       this.onLoadDesktopCallbackForTest_ = callback;
       return;
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/tsconfig.json b/chrome/browser/resources/chromeos/accessibility/select_to_speak/tsconfig.json
index c699141..dca398a 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/tsconfig.json
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/tsconfig.json
@@ -11,6 +11,7 @@
     "../../../../../../tools/typescript/definitions"
   ],
   "exclude": [
+    "../../../../../../tools/typescript/definitions/runtime.d.ts",
     "../../../../../../tools/typescript/definitions/i18n.d.ts",
     "../../../../../../tools/typescript/definitions/storage.d.ts"
   ]
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.ts b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.ts
index 2917f34..c8508dc 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.ts
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/ui_manager.ts
@@ -63,7 +63,7 @@
    * User requests reading speed adjustment.
    * @param speed rate multiplier.
    */
-  onChangeSpeedRequested: (speed: number) => {};
+  onChangeSpeedRequested: (speed: number) => void;
 
   /** User requests exiting STS. */
   onExitRequested: () => void;
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_e2e_test_base.js b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_e2e_test_base.js
index 1d0f1cf0..cb2fc67 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_e2e_test_base.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_e2e_test_base.js
@@ -16,7 +16,6 @@
 #include "ash/keyboard/ui/keyboard_util.h"
 #include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/constants/ash_pref_names.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
     `);
   }
diff --git a/chrome/browser/resources/settings/metrics_browser_proxy.ts b/chrome/browser/resources/settings/metrics_browser_proxy.ts
index bee4262..51e9e752 100644
--- a/chrome/browser/resources/settings/metrics_browser_proxy.ts
+++ b/chrome/browser/resources/settings/metrics_browser_proxy.ts
@@ -120,7 +120,7 @@
  * numeric values should never be reused.
  *
  * Must be kept in sync with the
- * SafetyChecUnusedSitePermissionsModuleInteractions enum in
+ * SafetyCheckUnusedSitePermissionsModuleInteractions enum in
  * histograms/enums.xml
  */
 export enum SafetyCheckUnusedSitePermissionsModuleInteractions {
@@ -135,6 +135,45 @@
 }
 
 /**
+ * Contains all entry points for Safety Hub page.
+ *
+ * These values are persisted to logs. Entries should not be renumbered and
+ * numeric values should never be reused.
+ *
+ * Must be kept in sync with the SafetyHubEntryPoint enum in
+ * histograms/enums.xml and safety_hub/safety_hub_constants.h.
+ */
+export enum SafetyHubEntryPoint {
+  PRIVACY_SAFE = 0,
+  PRIVACY_WARNING = 1,
+  SITE_SETTINGS = 2,
+  THREE_DOT_MENU = 3,
+  NOTIFICATIONS = 4,
+  // Max value should be updated whenever new entries are added.
+  MAX_VALUE = 5,
+}
+
+/**
+ * Contains all Safety Hub modules.
+ *
+ * These values are persisted to logs. Entries should not be renumbered and
+ * numeric values should never be reused.
+ *
+ * Must be kept in sync with the SafetyHubModuleType enum in
+ * histograms/enums.xml and safety_hub/safety_hub_constants.h.
+ */
+export enum SafetyHubModuleType {
+  PERMISSIONS = 0,
+  NOTIFICATIONS = 1,
+  SAFE_BROWSING = 2,
+  EXTENSIONS = 3,
+  PASSWORDS = 4,
+  VERSION = 5,
+  // Max value should be updated whenever new entries are added.
+  MAX_VALUE = 6,
+}
+
+/**
  * Contains all safe browsing interactions.
  *
  * These values are persisted to logs. Entries should not be renumbered and
@@ -342,6 +381,30 @@
 
   /**
    * Helper function that calls recordHistogram for the
+   * Settings.SafetyHub.EntryPointShown histogram
+   */
+  recordSafetyHubEntryPointShown(page: SafetyHubEntryPoint): void;
+
+  /**
+   * Helper function that calls recordHistogram for the
+   *Settings.SafetyHub.EntryPointClicked histogram
+   */
+  recordSafetyHubEntryPointClicked(page: SafetyHubEntryPoint): void;
+
+  /**
+   * Helper function that calls recordHistogram for the
+   * Settings.SafetyHub.DashboardWarning histogram
+   */
+  recordSafetyHubModuleWarningImpression(module: SafetyHubModuleType): void;
+
+  /**
+   * Helper function that calls recordHistogram for the
+   * Settings.SafetyHub.HasDashboardShowAnyWarning histogram
+   */
+  recordSafetyHubDashboardAnyWarning(visible: boolean): void;
+
+  /**
+   * Helper function that calls recordHistogram for the
    * Settings.SafetyHub.[card_name].StatusOnClick histogram
    */
   recordSafetyHubCardStateClicked(
@@ -484,6 +547,37 @@
         [histogramName, state, SafetyHubCardState.MAX_VALUE]);
   }
 
+  recordSafetyHubEntryPointShown(page: SafetyHubEntryPoint) {
+    chrome.send('metricsHandler:recordInHistogram', [
+      'Settings.SafetyHub.EntryPointShown',
+      page,
+      SafetyHubEntryPoint.MAX_VALUE,
+    ]);
+  }
+
+  recordSafetyHubEntryPointClicked(page: SafetyHubEntryPoint) {
+    chrome.send('metricsHandler:recordInHistogram', [
+      'Settings.SafetyHub.EntryPointClicked',
+      page,
+      SafetyHubEntryPoint.MAX_VALUE,
+    ]);
+  }
+
+  recordSafetyHubModuleWarningImpression(module: SafetyHubModuleType) {
+    chrome.send('metricsHandler:recordInHistogram', [
+      'Settings.SafetyHub.DashboardWarning',
+      module,
+      SafetyHubModuleType.MAX_VALUE,
+    ]);
+  }
+
+  recordSafetyHubDashboardAnyWarning(visible: boolean) {
+    chrome.send('metricsHandler:recordBooleanHistogram', [
+      'Settings.SafetyHub.HasDashboardShowAnyWarning',
+      visible,
+    ]);
+  }
+
   recordSettingsPageHistogram(interaction: PrivacyElementInteractions) {
     chrome.send('metricsHandler:recordInHistogram', [
       'Settings.PrivacyElementInteractions',
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index e3113cd..3abd2dbc 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -155,7 +155,9 @@
         </settings-subpage>
       </template>
 
-      <template is="dom-if" route-path="/adPrivacy">
+      <template is="dom-if" route-path="/adPrivacy"
+          no-search="[[!shouldShowAdPrivacy_(isPrivacySandboxRestricted_,
+                isPrivacySandboxRestrictedNoticeEnabled_)]]">
         <settings-subpage id="privacy-sandbox"
             page-title="$i18n{adPrivacyPageTitle}"
             associated-control="[[$$('#privacySandboxLinkRow')]]"
@@ -166,7 +168,8 @@
         </settings-subpage>
       </template>
 
-      <template is="dom-if" route-path="/adPrivacy/interests">
+      <template is="dom-if" route-path="/adPrivacy/interests"
+          no-search="[[isPrivacySandboxRestricted_]]">
         <settings-subpage id="privacy-sandbox-topics"
             page-title="$i18n{topicsPageTitle}"
             associated-control="[[$$('#privacySandboxLinkRow')]]"
@@ -176,7 +179,8 @@
         </settings-subpage>
       </template>
 
-      <template is="dom-if" route-path="/adPrivacy/sites">
+      <template is="dom-if" route-path="/adPrivacy/sites"
+          no-search="[[isPrivacySandboxRestricted_]]">
         <settings-subpage id="privacy-sandbox-fledge"
             page-title="$i18n{fledgePageTitle}"
             associated-control="[[$$('#privacySandboxLinkRow')]]"
@@ -186,7 +190,9 @@
         </settings-subpage>
       </template>
 
-      <template is="dom-if" route-path="/adPrivacy/measurement">
+      <template is="dom-if" route-path="/adPrivacy/measurement"
+          no-search="[[!shouldShowAdPrivacy_(isPrivacySandboxRestricted_,
+              isPrivacySandboxRestrictedNoticeEnabled_)]]">
         <settings-subpage id="privacy-sandbox-ad-measurement"
             page-title="$i18n{adMeasurementPageTitle}"
             associated-control="[[$$('#privacySandboxLinkRow')]]"
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.ts b/chrome/browser/resources/settings/privacy_page/privacy_page.ts
index 5b69da0..6b201f1 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.ts
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.ts
@@ -38,7 +38,7 @@
 import {FocusConfig} from '../focus_config.js';
 import {HatsBrowserProxyImpl, TrustSafetyInteraction} from '../hats_browser_proxy.js';
 import {loadTimeData} from '../i18n_setup.js';
-import {MetricsBrowserProxy, MetricsBrowserProxyImpl, PrivacyGuideInteractions} from '../metrics_browser_proxy.js';
+import {MetricsBrowserProxy, MetricsBrowserProxyImpl, PrivacyGuideInteractions, SafetyHubEntryPoint} from '../metrics_browser_proxy.js';
 import {routes} from '../route.js';
 import {RouteObserverMixin, Router} from '../router.js';
 import {NotificationPermission, SafetyHubBrowserProxy, SafetyHubBrowserProxyImpl, SafetyHubEvent} from '../safety_hub/safety_hub_browser_proxy.js';
@@ -391,6 +391,15 @@
     this.showPrivacyGuideDialog_ =
         Router.getInstance().getCurrentRoute() === routes.PRIVACY_GUIDE &&
         this.isPrivacyGuideAvailable;
+
+    // Only record the metrics when the user navigates to the notification
+    // settings page that shows the entry point.
+    if (Router.getInstance().getCurrentRoute() ===
+            routes.SITE_SETTINGS_NOTIFICATIONS &&
+        this.showNotificationPermissionsReview_) {
+      this.metricsBrowserProxy_.recordSafetyHubEntryPointShown(
+          SafetyHubEntryPoint.NOTIFICATIONS);
+    }
   }
 
   /**
@@ -587,6 +596,8 @@
   }
 
   private onSafetyHubButtonClick_() {
+    this.metricsBrowserProxy_.recordSafetyHubEntryPointClicked(
+        SafetyHubEntryPoint.NOTIFICATIONS);
     Router.getInstance().navigateTo(routes.SAFETY_HUB);
   }
 }
diff --git a/chrome/browser/resources/settings/safety_hub/safety_hub_card.ts b/chrome/browser/resources/settings/safety_hub/safety_hub_card.ts
index 8a44b9c..45fa280 100644
--- a/chrome/browser/resources/settings/safety_hub/safety_hub_card.ts
+++ b/chrome/browser/resources/settings/safety_hub/safety_hub_card.ts
@@ -46,8 +46,9 @@
     switch (state) {
       case CardState.WARNING:
       case CardState.WEAK:
-      case CardState.INFO:
         return 'cr:error';
+      case CardState.INFO:
+        return 'cr:info';
       case CardState.SAFE:
         return 'cr:check-circle';
       default:
diff --git a/chrome/browser/resources/settings/safety_hub/safety_hub_entry_point.ts b/chrome/browser/resources/settings/safety_hub/safety_hub_entry_point.ts
index 4afef3d9..e4d004a 100644
--- a/chrome/browser/resources/settings/safety_hub/safety_hub_entry_point.ts
+++ b/chrome/browser/resources/settings/safety_hub/safety_hub_entry_point.ts
@@ -12,7 +12,8 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {routes} from '../route.js';
-import {Router} from '../router.js';
+import {Router, RouteObserverMixin} from '../router.js';
+import {MetricsBrowserProxy, MetricsBrowserProxyImpl, SafetyHubEntryPoint} from '../metrics_browser_proxy.js';
 
 import {SafetyHubBrowserProxy, SafetyHubBrowserProxyImpl} from './safety_hub_browser_proxy.js';
 import {getTemplate} from './safety_hub_entry_point.html.js';
@@ -26,7 +27,8 @@
   };
 }
 
-const SettingsSafetyHubEntryPointElementBase = I18nMixin(PolymerElement);
+const SettingsSafetyHubEntryPointElementBase =
+    RouteObserverMixin(I18nMixin(PolymerElement));
 
 export class SettingsSafetyHubEntryPointElement extends
     SettingsSafetyHubEntryPointElementBase {
@@ -65,17 +67,17 @@
     };
   }
 
-  private safetyHubBrowserProxy_: SafetyHubBrowserProxy =
-      SafetyHubBrowserProxyImpl.getInstance();
-
   private buttonClass_: string;
   private hasRecommendations_: boolean;
   private headerString_: string;
   private subheaderString_: string;
   private headerIconColor_: string;
+  private safetyHubBrowserProxy_: SafetyHubBrowserProxy =
+      SafetyHubBrowserProxyImpl.getInstance();
+  private metricsBrowserProxy_: MetricsBrowserProxy =
+      MetricsBrowserProxyImpl.getInstance();
 
   override connectedCallback() {
-    super.connectedCallback();
     this.safetyHubBrowserProxy_.getSafetyHubHasRecommendations().then(
         (hasRecommendations: boolean) => {
           this.hasRecommendations_ = hasRecommendations;
@@ -85,6 +87,24 @@
         (subheader: string) => {
           this.subheaderString_ = subheader;
         });
+    // This should be called after the data for modules are retrieved so that
+    // currentRouteChanged is called afterwards.
+    super.connectedCallback();
+  }
+
+  override currentRouteChanged() {
+    if (Router.getInstance().getCurrentRoute() !== routes.PRIVACY) {
+      return;
+    }
+    // Only record the metrics when the user navigates to the privacy page
+    // that shows the entry point.
+    if (this.hasRecommendations_) {
+      this.metricsBrowserProxy_.recordSafetyHubEntryPointShown(
+          SafetyHubEntryPoint.PRIVACY_WARNING);
+    } else {
+      this.metricsBrowserProxy_.recordSafetyHubEntryPointShown(
+          SafetyHubEntryPoint.PRIVACY_SAFE);
+    }
   }
 
   private computeButtonClass_() {
@@ -101,6 +121,13 @@
   }
 
   private onClick_() {
+    if (this.hasRecommendations_) {
+      this.metricsBrowserProxy_.recordSafetyHubEntryPointClicked(
+          SafetyHubEntryPoint.PRIVACY_WARNING);
+    } else {
+      this.metricsBrowserProxy_.recordSafetyHubEntryPointClicked(
+          SafetyHubEntryPoint.PRIVACY_SAFE);
+    }
     Router.getInstance().navigateTo(routes.SAFETY_HUB);
   }
 }
diff --git a/chrome/browser/resources/settings/safety_hub/safety_hub_module.html b/chrome/browser/resources/settings/safety_hub/safety_hub_module.html
index 9f78869..9a48d25 100644
--- a/chrome/browser/resources/settings/safety_hub/safety_hub_module.html
+++ b/chrome/browser/resources/settings/safety_hub/safety_hub_module.html
@@ -78,7 +78,7 @@
   #line {
     box-sizing: border-box;
     height: var(--separator-line-height);
-    border-bottom: 1px solid var(--google-grey-300);
+    border-bottom: 1px solid var(--cr-separator-color);
     flex: 1;
   }
 
diff --git a/chrome/browser/resources/settings/safety_hub/safety_hub_page.html b/chrome/browser/resources/settings/safety_hub/safety_hub_page.html
index 86e09a8..920fc849 100644
--- a/chrome/browser/resources/settings/safety_hub/safety_hub_page.html
+++ b/chrome/browser/resources/settings/safety_hub/safety_hub_page.html
@@ -30,9 +30,12 @@
   }
 
   .section-header {
+    color: var(--cr-primary-text-color);
     flex: 1;
     /* Should be 13px when html font-size is 16px */
-    font-size: 0.8125rem;
+    font-size: 108%;
+    font-weight: 400;
+    letter-spacing: .25px;
     margin-bottom: 16px;
     margin-top: 30px;
     width: 100%;
@@ -44,9 +47,9 @@
   }
 </style>
 
-<h5 class="section-header cr-secondary-text first">
+<h2 class="section-header cr-secondary-text first">
   $i18n{safetyHubPageCardSectionHeader}
-</h5>
+</h2>
 <div class="card-container">
   <settings-safety-hub-card id="passwords" class="card box"
       data="[[passwordCardData_]]" on-click="onPasswordsClick_"
@@ -61,9 +64,9 @@
       tabindex="0" on-keydown="onSafeBrowsingKeyPress_">
   </settings-safety-hub-card>
 </div>
-<h5 class="section-header cr-secondary-text">
+<h2 class="section-header cr-secondary-text">
   $i18n{safetyHubPageModuleSectionHeader}
-</h5>
+</h2>
 <template is="dom-if" if="[[showNotificationPermissions_]]">
   <settings-safety-hub-notification-permissions-module class="module box">
   </settings-safety-hub-notification-permissions-module>
diff --git a/chrome/browser/resources/settings/safety_hub/safety_hub_page.ts b/chrome/browser/resources/settings/safety_hub/safety_hub_page.ts
index 04e7e2e..3df543c 100644
--- a/chrome/browser/resources/settings/safety_hub/safety_hub_page.ts
+++ b/chrome/browser/resources/settings/safety_hub/safety_hub_page.ts
@@ -17,7 +17,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {PasswordManagerImpl, PasswordManagerPage} from '../autofill_page/password_manager_proxy.js';
-import {MetricsBrowserProxy, MetricsBrowserProxyImpl, SafetyHubCardState, SafetyHubSurfaces} from '../metrics_browser_proxy.js';
+import {MetricsBrowserProxy, MetricsBrowserProxyImpl, SafetyHubCardState, SafetyHubModuleType, SafetyHubSurfaces} from '../metrics_browser_proxy.js';
 import {RelaunchMixin, RestartType} from '../relaunch_mixin.js';
 import {routes} from '../route.js';
 import {RouteObserverMixin, Router} from '../router.js';
@@ -83,16 +83,35 @@
       },
 
       userEducationItemList_: Array,
+
+      // Whether the data for notification permissions is ready.
+      hasDataForNotificationPermissions_: Boolean,
+
+      // Whether the data for unused site permissions is ready.
+      hasDataForUnusedPermissions_: Boolean,
+
+      // Whether the data for extensions is ready.
+      hasDataForExtensions_: Boolean,
     };
   }
 
+  static get observers() {
+    return [
+      'onAllModulesLoaded_(passwordCardData_, versionCardData_, safeBrowsingCardData_, hasDataForUnusedPermissions_, hasDataForNotificationPermissions_, hasDataForExtensions_)',
+    ];
+  }
+
   private passwordCardData_: CardInfo;
   private versionCardData_: CardInfo;
   private safeBrowsingCardData_: CardInfo;
   private showNotificationPermissions_: boolean;
+  private hasDataForNotificationPermissions_: boolean;
   private showUnusedSitePermissions_: boolean;
+  private hasDataForUnusedPermissions_: boolean;
   private showNoRecommendationsState_: boolean;
   private showExtensions_: boolean;
+  private hasDataForExtensions_: boolean;
+  private shouldRecordMetric_: boolean = false;
   private userEducationItemList_: SiteInfo[];
   private browserProxy_: SafetyHubBrowserProxy =
       SafetyHubBrowserProxyImpl.getInstance();
@@ -100,11 +119,11 @@
       MetricsBrowserProxyImpl.getInstance();
 
   override connectedCallback() {
-    super.connectedCallback();
-
     this.initializeCards_();
     this.initializeModules_();
     this.initializeUserEducation_();
+
+    super.connectedCallback();
   }
 
   override currentRouteChanged() {
@@ -119,10 +138,14 @@
         SafetyHubSurfaces.SAFETY_HUB_PAGE);
     this.metricsBrowserProxy_.recordSafetyHubInteraction(
         SafetyHubSurfaces.SAFETY_HUB_PAGE);
+
+    // Only record the metrics when the user navigates to the Safety Hub page.
+    this.shouldRecordMetric_ = true;
+    this.onAllModulesLoaded_();
   }
 
   private initializeCards_() {
-    // TODO(1443466): Add listeners for cards.
+    // TODO(crbug.com/1443466): Add listeners for cards.
     this.browserProxy_.getPasswordCardData().then((data: CardInfo) => {
       this.passwordCardData_ = data;
     });
@@ -243,6 +266,7 @@
     // there is no item on the list but the list was shown before.
     this.showNotificationPermissions_ =
         permissions.length > 0 || this.showNotificationPermissions_;
+    this.hasDataForNotificationPermissions_ = true;
   }
 
   private onUnusedSitePermissionListChanged_(permissions:
@@ -251,6 +275,7 @@
     // there is no item on the list but the list was shown before.
     this.showUnusedSitePermissions_ =
         permissions.length > 0 || this.showUnusedSitePermissions_;
+    this.hasDataForUnusedPermissions_ = true;
   }
 
   private computeShowNoRecommendationsState_(): boolean {
@@ -261,11 +286,73 @@
 
   private onExtensionsChanged_(numberOfExtensions: number) {
     this.showExtensions_ = !!numberOfExtensions;
+    this.hasDataForExtensions_ = true;
   }
 
   private isEnterOrSpaceClicked_(e: KeyboardEvent): boolean {
     return e.key === 'Enter' || e.key === ' ';
   }
+
+  private onAllModulesLoaded_() {
+    // If the metrics are recorded already, don't record again.
+    if (!this.shouldRecordMetric_) {
+      return;
+    }
+
+    // Wait till the data of the cards be ready.
+    if (!this.passwordCardData_ || !this.safeBrowsingCardData_ ||
+        !this.versionCardData_) {
+      return;
+    }
+
+    // Wait till the data of the modules be ready.
+    if (!this.hasDataForUnusedPermissions_ ||
+        !this.hasDataForNotificationPermissions_ ||
+        !this.hasDataForExtensions_) {
+      return;
+    }
+
+    this.shouldRecordMetric_ = false;
+    let hasAnyWarning: boolean = false;
+    // TODO(crbug.com/1443466): Iterate over the cards/modules with for loop.
+    if (this.passwordCardData_.state !== CardState.SAFE) {
+      this.metricsBrowserProxy_.recordSafetyHubModuleWarningImpression(
+          SafetyHubModuleType.PASSWORDS);
+      hasAnyWarning = true;
+    }
+
+    if (this.safeBrowsingCardData_.state !== CardState.SAFE) {
+      this.metricsBrowserProxy_.recordSafetyHubModuleWarningImpression(
+          SafetyHubModuleType.SAFE_BROWSING);
+      hasAnyWarning = true;
+    }
+
+    if (this.versionCardData_.state !== CardState.SAFE) {
+      this.metricsBrowserProxy_.recordSafetyHubModuleWarningImpression(
+          SafetyHubModuleType.VERSION);
+      hasAnyWarning = true;
+    }
+
+    if (this.showNotificationPermissions_) {
+      this.metricsBrowserProxy_.recordSafetyHubModuleWarningImpression(
+          SafetyHubModuleType.NOTIFICATIONS);
+      hasAnyWarning = true;
+    }
+
+    if (this.showUnusedSitePermissions_) {
+      this.metricsBrowserProxy_.recordSafetyHubModuleWarningImpression(
+          SafetyHubModuleType.PERMISSIONS);
+      hasAnyWarning = true;
+    }
+
+    if (this.showExtensions_) {
+      this.metricsBrowserProxy_.recordSafetyHubModuleWarningImpression(
+          SafetyHubModuleType.EXTENSIONS);
+      hasAnyWarning = true;
+    }
+
+    this.metricsBrowserProxy_.recordSafetyHubDashboardAnyWarning(hasAnyWarning);
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/settings/settings.ts b/chrome/browser/resources/settings/settings.ts
index 7d0dc48..8bca5f32 100644
--- a/chrome/browser/resources/settings/settings.ts
+++ b/chrome/browser/resources/settings/settings.ts
@@ -60,7 +60,7 @@
 // </if>
 export {HatsBrowserProxy, HatsBrowserProxyImpl, SafeBrowsingSetting, SecurityPageInteraction, TrustSafetyInteraction} from './hats_browser_proxy.js';
 export {loadTimeData} from './i18n_setup.js';
-export {CvcDeletionUserAction, DeleteBrowsingDataAction, MetricsBrowserProxy, MetricsBrowserProxyImpl, PrivacyElementInteractions, PrivacyGuideInteractions, PrivacyGuideSettingsStates, PrivacyGuideStepsEligibleAndReached, SafeBrowsingInteractions, SafetyCheckInteractions, SafetyCheckNotificationsModuleInteractions, SafetyCheckUnusedSitePermissionsModuleInteractions, SafetyHubCardState, SafetyHubSurfaces} from './metrics_browser_proxy.js';
+export {CvcDeletionUserAction, DeleteBrowsingDataAction, MetricsBrowserProxy, MetricsBrowserProxyImpl, PrivacyElementInteractions, PrivacyGuideInteractions, PrivacyGuideSettingsStates, PrivacyGuideStepsEligibleAndReached, SafeBrowsingInteractions, SafetyCheckInteractions, SafetyCheckNotificationsModuleInteractions, SafetyCheckUnusedSitePermissionsModuleInteractions, SafetyHubCardState, SafetyHubEntryPoint, SafetyHubModuleType, SafetyHubSurfaces} from './metrics_browser_proxy.js';
 export {NtpExtension, OnStartupBrowserProxy, OnStartupBrowserProxyImpl} from './on_startup_page/on_startup_browser_proxy.js';
 export {SettingsOnStartupPageElement} from './on_startup_page/on_startup_page.js';
 export {SettingsStartupUrlDialogElement} from './on_startup_page/startup_url_dialog.js';
diff --git a/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts b/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
index d200fe0..5c2ae188 100644
--- a/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
+++ b/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
@@ -26,8 +26,9 @@
 
 import {FocusConfig} from '../focus_config.js';
 import {loadTimeData} from '../i18n_setup.js';
+import {MetricsBrowserProxy, MetricsBrowserProxyImpl, SafetyHubEntryPoint} from '../metrics_browser_proxy.js';
 import {routes} from '../route.js';
-import {Router} from '../router.js';
+import {RouteObserverMixin, Router} from '../router.js';
 import {SafetyHubBrowserProxy, SafetyHubBrowserProxyImpl, SafetyHubEvent, UnusedSitePermissions} from '../safety_hub/safety_hub_browser_proxy.js';
 import {ContentSettingsTypes} from '../site_settings/constants.js';
 
@@ -385,7 +386,8 @@
   };
 }
 
-const SettingsSiteSettingsPageElementBase = WebUiListenerMixin(PolymerElement);
+const SettingsSiteSettingsPageElementBase =
+    RouteObserverMixin(WebUiListenerMixin(PolymerElement));
 
 export class SettingsSiteSettingsPageElement extends
     SettingsSiteSettingsPageElementBase {
@@ -528,6 +530,8 @@
   private unusedSitePermissionsSubheader_: string;
   private safetyHubBrowserProxy_: SafetyHubBrowserProxy =
       SafetyHubBrowserProxyImpl.getInstance();
+  private metricsBrowserProxy_: MetricsBrowserProxy =
+      MetricsBrowserProxyImpl.getInstance();
 
   private lists_: {
     all: CategoryListItem[],
@@ -537,6 +541,18 @@
     contentAdvanced: CategoryListItem[],
   };
 
+  override currentRouteChanged() {
+    if (Router.getInstance().getCurrentRoute() !== routes.SITE_SETTINGS) {
+      return;
+    }
+    // Only record the metrics when the user navigates to the privacy page
+    // that shows the entry point.
+    if (this.showUnusedSitePermissions_) {
+      this.metricsBrowserProxy_.recordSafetyHubEntryPointShown(
+          SafetyHubEntryPoint.SITE_SETTINGS);
+    }
+  }
+
   private focusConfigChanged_(_newConfig: FocusConfig, oldConfig: FocusConfig) {
     // focusConfig is set only once on the parent, so this observer should
     // only fire once.
@@ -578,6 +594,8 @@
   }
 
   private onSafetyHubButtonClick_() {
+    this.metricsBrowserProxy_.recordSafetyHubEntryPointClicked(
+        SafetyHubEntryPoint.SITE_SETTINGS);
     Router.getInstance().navigateTo(routes.SAFETY_HUB);
   }
 }
diff --git a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html
index 4294cf8..f633aab 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.html
@@ -152,13 +152,38 @@
     width: 100%;
   }
 
-  #descriptorMenuD cr-button {
+  #descriptorMenuD button {
+    appearance: none;
+    display: flex;
+    align-items: center;
+    justify-content: center;
     background-color: var(--color-menu-button-background);
     border-radius: 8px;
-    border-color: var(--color-menu-button-background);
-    height: 40px;
-    min-width: 40px;
-    padding: 0px;
+    border: 0;
+    padding: 0;
+    width: 100%;
+    aspect-ratio: 1 / 1;
+    position: relative;
+    overflow: hidden;
+    cursor: pointer;
+  }
+
+  #descriptorMenuD button:hover::before {
+    display: block;
+    content: '';
+    inset: 0;
+    position: absolute;
+    background-color: var(--cr-hover-background-color);
+  }
+
+  #descriptorMenuD button paper-ripple {
+    --paper-ripple-opacity: 1;
+    color: var(--cr-active-background-color);
+  }
+
+  :host-context(.focus-outline-visible) #descriptorMenuD button:focus {
+    outline: 2px solid var(--cr-focus-outline-color);
+    outline-offset: 2px;
   }
 
   .color-check-mark {
@@ -347,20 +372,22 @@
       </customize-chrome-combobox>
       <cr-grid columns="6" id="descriptorMenuD">
         <template is="dom-repeat" items="[[descriptorD_]]">
-          <cr-button class="default-color" on-click="onDefaultColorClick_"
+          <button class="default-color" on-click="onDefaultColorClick_"
               aria-current$="[[getColorCheckedStatus_(item, selectedDefaultColor_)]]"
               title$="[[getColorLabel_(item)]]">
+            <paper-ripple></paper-ripple>
             <customize-chrome-check-mark-wrapper class="color-check-mark"
                 checked="[[isColorSelected_(item, selectedDefaultColor_)]]"
                 checkmark-border-hidden>
               <span class="descriptor-d" style$="background-color: [[item]];">
               </span>
             </customize-chrome-check-mark-wrapper>
-          </cr-button>
+          </button>
         </template>
-        <cr-button id="customColorContainer" on-click="onCustomColorClick_"
+        <button id="customColorContainer" on-click="onCustomColorClick_"
             aria-current$="[[getCustomColorCheckedStatus_(selectedHue_)]]"
             title="$i18n{colorPickerLabel}">
+          <paper-ripple></paper-ripple>
           <customize-chrome-check-mark-wrapper class="color-check-mark"
               checked="[[selectedHue_]]" checkmark-border-hidden>
             <div class="descriptor-d"
@@ -368,7 +395,7 @@
               <div id="colorPickerIcon"></div>
             </div>
           </customize-chrome-check-mark-wrapper>
-        </cr-button>
+        </button>
       </div>
     </cr-grid>
     <cr-theme-hue-slider-dialog id="hueSlider"
diff --git a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.ts b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.ts
index ac9e098..585822f8 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.ts
+++ b/chrome/browser/resources/side_panel/customize_chrome/wallpaper_search/wallpaper_search.ts
@@ -14,8 +14,10 @@
 import 'chrome://resources/cr_elements/cr_icons.css.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.js';
 import 'chrome://resources/cr_elements/cr_loading_gradient/cr_loading_gradient.js';
+import 'chrome://resources/cr_elements/cr_shared_vars.css.js';
 import 'chrome://resources/cr_elements/icons.html.js';
 import 'chrome://resources/cr_components/theme_color_picker/theme_hue_slider_dialog.js';
+import 'chrome://resources/polymer/v3_0/paper-ripple/paper-ripple.js';
 
 import {SpHeading} from 'chrome://customize-chrome-side-panel.top-chrome/shared/sp_heading.js';
 import {ThemeHueSliderDialogElement} from 'chrome://resources/cr_components/theme_color_picker/theme_hue_slider_dialog.js';
diff --git a/chrome/browser/resources/tab_search/tab_organization_shared_style.css b/chrome/browser/resources/tab_search/tab_organization_shared_style.css
index b5ff5d1..cd2a942 100644
--- a/chrome/browser/resources/tab_search/tab_organization_shared_style.css
+++ b/chrome/browser/resources/tab_search/tab_organization_shared_style.css
@@ -7,9 +7,10 @@
  * #css_wrapper_metadata_end */
 
 .tab-organization-body {
-  color: var(--cr-secondary-text-color);
+  color: var(--color-secondary-foreground);
   font-size: 13px;
   font-weight: 400;
+  line-height: 20px;
 }
 
 .tab-organization-container {
diff --git a/chrome/browser/share/android/BUILD.gn b/chrome/browser/share/android/BUILD.gn
index 4f4db71..41333e7 100644
--- a/chrome/browser/share/android/BUILD.gn
+++ b/chrome/browser/share/android/BUILD.gn
@@ -16,10 +16,8 @@
     "java/res/drawable/preview_icon_border_background.xml",
     "java/res/drawable/qrcode_background.xml",
     "java/res/drawable/save_icon.xml",
-    "java/res/drawable/save_to_device.xml",
     "java/res/drawable/share_icon.xml",
     "java/res/drawable/text_icon.xml",
-    "java/res/drawable/webnote.xml",
     "java/res/layout/qrcode_dialog.xml",
     "java/res/layout/qrcode_share_layout.xml",
     "java/res/layout/screenshot_share_sheet.xml",
diff --git a/chrome/browser/share/android/java/res/drawable/save_to_device.xml b/chrome/browser/share/android/java/res/drawable/save_to_device.xml
deleted file mode 100644
index 67251bf..0000000
--- a/chrome/browser/share/android/java/res/drawable/save_to_device.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2017 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0"
-    android:tint="@macro/default_icon_color">
-
-    <path
-        android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"
-        android:fillColor="@android:color/white"/>
-</vector>
\ No newline at end of file
diff --git a/chrome/browser/share/android/java/res/drawable/webnote.xml b/chrome/browser/share/android/java/res/drawable/webnote.xml
deleted file mode 100644
index 9537abf..0000000
--- a/chrome/browser/share/android/java/res/drawable/webnote.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:autoMirrored="true"
-    android:tint="@macro/default_icon_color">
-  <path
-      android:fillColor="@android:color/white"
-      android:pathData="M4,20h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2zM5,13v-2h14v2L5,13zM15,7v2L5,9L5,7h10zM19,15v2L5,17v-2h14z"/>
-</vector>
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java
index 89922a1a..a81bdac 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/ChromeProvidedSharingOptionsProviderBase.java
@@ -6,8 +6,6 @@
 
 import android.app.Activity;
 import android.net.Uri;
-import android.os.Build;
-import android.os.Build.VERSION;
 import android.text.TextUtils;
 import android.view.View;
 
@@ -17,9 +15,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.content_creation.notes.NoteCreationCoordinator;
-import org.chromium.chrome.browser.content_creation.notes.NoteCreationCoordinatorFactory;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.share.ChromeShareExtras.DetailedContentType;
@@ -31,14 +26,11 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.device_lock.DeviceLockActivityLauncher;
-import org.chromium.components.browser_ui.share.ShareImageFileUtils;
 import org.chromium.components.browser_ui.share.ShareParams;
-import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.ui.base.Clipboard;
 import org.chromium.ui.base.WindowAndroid;
-import org.chromium.ui.widget.Toast;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -51,7 +43,6 @@
 /** Provides a list of Chrome-provided sharing options. */
 public abstract class ChromeProvidedSharingOptionsProviderBase {
     private static final String USER_ACTION_COPY_URL_SELECTED = "SharingHubAndroid.CopyURLSelected";
-    private static final String USER_ACTION_COPY_GIF_SELECTED = "SharingHubAndroid.CopyGifSelected";
     private static final String USER_ACTION_COPY_IMAGE_SELECTED =
             "SharingHubAndroid.CopyImageSelected";
     private static final String USER_ACTION_COPY_SELECTED = "SharingHubAndroid.CopySelected";
@@ -61,11 +52,6 @@
             "SharingHubAndroid.SendTabToSelfSelected";
     private static final String USER_ACTION_QR_CODE_SELECTED = "SharingHubAndroid.QRCodeSelected";
     private static final String USER_ACTION_PRINT_SELECTED = "SharingHubAndroid.PrintSelected";
-    private static final String USER_ACTION_SAVE_IMAGE_SELECTED =
-            "SharingHubAndroid.SaveImageSelected";
-
-    protected static final String USER_ACTION_WEB_STYLE_NOTES_SELECTED =
-            "SharingHubAndroid.WebnotesStylize";
 
     protected final Activity mActivity;
     protected final WindowAndroid mWindowAndroid;
@@ -257,41 +243,20 @@
         return availableOptions;
     }
 
-    protected boolean usePolishedActionOrderedList() {
-        return ChromeFeatureList.isEnabled(ChromeFeatureList.SHARE_SHEET_CUSTOM_ACTIONS_POLISH);
-    }
-
     /**
      * Creates all enabled {@link FirstPartyOption}s and adds them to {@code
      * mOrderedFirstPartyOptions} in the order they should appear. This has to be called by child
      * classes before the provider can function
      */
     protected void initializeFirstPartyOptionsInOrder() {
-        // Only show a limited first party share selection for automotive
-        if (BuildInfo.getInstance().isAutomotive) {
-            maybeAddCopyFirstPartyOption();
-            maybeAddSendTabToSelfFirstPartyOption();
-            maybeAddQrCodeFirstPartyOption();
-            return;
-        }
-        if (usePolishedActionOrderedList()) {
-            maybeAddCopyFirstPartyOption();
+        maybeAddCopyFirstPartyOption();
+        // Only show a limited first party share selection for automotive.
+        if (!BuildInfo.getInstance().isAutomotive) {
             maybeAddLongScreenshotFirstPartyOption();
             maybeAddPrintFirstPartyOption();
-            maybeAddSendTabToSelfFirstPartyOption();
-            maybeAddQrCodeFirstPartyOption();
-        } else {
-            maybeAddWebStyleNotesFirstPartyOption();
-            maybeAddScreenshotFirstPartyOption();
-            maybeAddLongScreenshotFirstPartyOption();
-            // Always show the copy link option as some entries does not offer the change for copy
-            // (e.g. feed card)
-            maybeAddCopyFirstPartyOption();
-            maybeAddSendTabToSelfFirstPartyOption();
-            maybeAddQrCodeFirstPartyOption();
-            maybeAddPrintFirstPartyOption();
-            maybeAddDownloadImageFirstPartyOption();
         }
+        maybeAddSendTabToSelfFirstPartyOption();
+        maybeAddQrCodeFirstPartyOption();
     }
 
     private void maybeAddSendTabToSelfFirstPartyOption() {
@@ -308,13 +273,6 @@
         }
     }
 
-    private void maybeAddScreenshotFirstPartyOption() {
-        FirstPartyOption option = createScreenshotFirstPartyOption();
-        if (option != null) {
-            mOrderedFirstPartyOptions.add(option);
-        }
-    }
-
     private void maybeAddLongScreenshotFirstPartyOption() {
         if (!mTabProvider.hasValue()) {
             return;
@@ -331,40 +289,18 @@
         }
     }
 
-    protected void maybeAddWebStyleNotesFirstPartyOption() {
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.WEBNOTES_STYLIZE)) {
-            mOrderedFirstPartyOptions.add(createWebNotesStylizeFirstPartyOption());
-        }
-    }
-
     protected void maybeAddCopyFirstPartyOption() {
         mOrderedFirstPartyOptions.add(createCopyLinkFirstPartyOption());
-        if (usePolishedActionOrderedList()) {
-            mOrderedFirstPartyOptions.add(createCopyImageFirstPartyOption(false));
-        } else {
-            mOrderedFirstPartyOptions.add(createCopyGifFirstPartyOption());
-            mOrderedFirstPartyOptions.add(createCopyImageFirstPartyOption(true));
-        }
+        mOrderedFirstPartyOptions.add(createCopyImageFirstPartyOption());
         mOrderedFirstPartyOptions.add(createCopyFirstPartyOption());
         mOrderedFirstPartyOptions.add(createCopyTextFirstPartyOption());
     }
 
-    protected void maybeAddDownloadImageFirstPartyOption() {
-        mOrderedFirstPartyOptions.add(createSaveImageFirstPartyOption());
-    }
-
     private FirstPartyOption createCopyLinkFirstPartyOption() {
-        FirstPartyOptionBuilder builder =
-                new FirstPartyOptionBuilder(
-                        ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE);
-        if (usePolishedActionOrderedList()) {
-            builder.setContentTypesToDisableFor(
-                    ContentType.LINK_AND_TEXT, ContentType.IMAGE_AND_LINK);
-        } else {
-            builder.setContentTypesToDisableFor(ContentType.LINK_AND_TEXT);
-        }
-
-        return builder.setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_url)
+        return new FirstPartyOptionBuilder(
+                        ContentType.LINK_PAGE_VISIBLE, ContentType.LINK_PAGE_NOT_VISIBLE)
+                .setContentTypesToDisableFor(ContentType.LINK_AND_TEXT, ContentType.IMAGE_AND_LINK)
+                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_url)
                 .setFeatureNameForMetrics(USER_ACTION_COPY_URL_SELECTED)
                 .setOnClickCallback(
                         (view) -> {
@@ -377,59 +313,24 @@
                 .build();
     }
 
-    private FirstPartyOption createCopyGifFirstPartyOption() {
+    /**
+     * @return The copy first party option.
+     */
+    protected FirstPartyOption createCopyImageFirstPartyOption() {
         return new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
-                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_gif)
-                // Enables only for GIF.
-                .setDetailedContentTypesToDisableFor(
-                        DetailedContentType.IMAGE,
-                        DetailedContentType.WEB_NOTES,
-                        DetailedContentType.NOT_SPECIFIED)
-                .setFeatureNameForMetrics(USER_ACTION_COPY_GIF_SELECTED)
+                .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_image)
+                .setFeatureNameForMetrics(USER_ACTION_COPY_IMAGE_SELECTED)
                 .setOnClickCallback(
                         (view) -> {
                             Uri imageUri = mShareParams.getImageUriToShare();
                             if (imageUri != null) {
-                                Clipboard.getInstance().setImageUri(imageUri);
-                                // TODO(crbug/1448589): Remove copy GIF action.
-                                // This is separate from regular image copy due to the string used
-                                // on the toast. To avoid growing complexity to customize text on
-                                // toast in Clipboard, this is logic guarded by version code.
-                                if (VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
-                                    Toast.makeText(
-                                                    mActivity,
-                                                    R.string.gif_copied,
-                                                    Toast.LENGTH_SHORT)
-                                            .show();
-                                }
+                                Clipboard.getInstance()
+                                        .setImageUri(imageUri, /* notifyOnSuccess= */ true);
                             }
                         })
                 .build();
     }
 
-    /**
-     * @param excludeGif Whether exclude the GIF copy from copy image action.
-     * @return The copy first party option.
-     */
-    protected FirstPartyOption createCopyImageFirstPartyOption(boolean excludeGif) {
-        FirstPartyOptionBuilder builder =
-                new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
-                        .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy_image)
-                        .setFeatureNameForMetrics(USER_ACTION_COPY_IMAGE_SELECTED)
-                        .setOnClickCallback(
-                                (view) -> {
-                                    Uri imageUri = mShareParams.getImageUriToShare();
-                                    if (imageUri != null) {
-                                        Clipboard.getInstance()
-                                                .setImageUri(imageUri, /* notifyOnSuccess= */ true);
-                                    }
-                                });
-        if (excludeGif) {
-            builder.setDetailedContentTypesToDisableFor(DetailedContentType.GIF);
-        }
-        return builder.build();
-    }
-
     private FirstPartyOption createCopyFirstPartyOption() {
         return new FirstPartyOptionBuilder(ContentType.LINK_AND_TEXT)
                 .setIcon(R.drawable.ic_content_copy_black, R.string.sharing_copy)
@@ -516,58 +417,6 @@
                 .build();
     }
 
-    private FirstPartyOption createSaveImageFirstPartyOption() {
-        return new FirstPartyOptionBuilder(ContentType.IMAGE, ContentType.IMAGE_AND_LINK)
-                .setIcon(R.drawable.save_to_device, R.string.sharing_save_image)
-                .setFeatureNameForMetrics(USER_ACTION_SAVE_IMAGE_SELECTED)
-                .setOnClickCallback(
-                        (view) -> {
-                            Uri imageUri = mShareParams.getImageUriToShare();
-                            if (imageUri == null) return;
-                            ShareImageFileUtils.getBitmapFromUriAsync(
-                                    mActivity,
-                                    imageUri,
-                                    (bitmap) -> {
-                                        SaveBitmapDelegate saveBitmapDelegate =
-                                                new SaveBitmapDelegate(
-                                                        mActivity,
-                                                        bitmap,
-                                                        R.string.save_image_filename_prefix,
-                                                        null,
-                                                        mShareParams.getWindow());
-                                        saveBitmapDelegate.save();
-                                    });
-                        })
-                .build();
-    }
-
-    private FirstPartyOption createWebNotesStylizeFirstPartyOption() {
-        String title = mShareParams.getTitle();
-        return new FirstPartyOptionBuilder(ContentType.HIGHLIGHTED_TEXT)
-                .setIcon(R.drawable.webnote, R.string.sharing_webnotes_create_card)
-                .setIconContentDescription(
-                        mActivity.getString(R.string.sharing_webnotes_accessibility_description))
-                .setFeatureNameForMetrics(USER_ACTION_WEB_STYLE_NOTES_SELECTED)
-                .setOnClickCallback(
-                        (view) -> {
-                            mFeatureEngagementTracker.notifyEvent(
-                                    EventConstants.SHARING_HUB_WEBNOTES_STYLIZE_USED);
-                            NoteCreationCoordinator coordinator =
-                                    NoteCreationCoordinatorFactory.create(
-                                            mActivity,
-                                            mShareParams.getWindow(),
-                                            mUrl,
-                                            title,
-                                            mShareParams.getRawText().trim(),
-                                            mChromeOptionShareCallback);
-                            coordinator.showDialog();
-                        })
-                .build();
-    }
-
-    /** Create a {@link FirstPartyOption} used to do screenshot. Return null if not supported. */
-    protected abstract @Nullable FirstPartyOption createScreenshotFirstPartyOption();
-
     /**
      * Create a {@link FirstPartyOption} used to do long screenshot. Return null if not supported.
      */
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidCustomActionProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidCustomActionProvider.java
index 5b8a3b4e..45dab858 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidCustomActionProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidCustomActionProvider.java
@@ -11,10 +11,9 @@
 import android.content.Context;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.Build;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.OptIn;
-import androidx.core.os.BuildCompat;
 
 import org.chromium.base.Callback;
 import org.chromium.base.supplier.Supplier;
@@ -121,12 +120,10 @@
      * @param params The {@link ShareParams} for the current share.
      * @param chromeShareExtras The {@link ChromeShareExtras} for the current share, if exists.
      * @param isMultiWindow Whether the current activity is in multi-window mode.
-     * @return List of custom action used for Android share sheet.
      */
-    @OptIn(markerClass = androidx.core.os.BuildCompat.PrereleaseSdkCheck.class)
     private void initCustomActions(
             ShareParams params, ChromeShareExtras chromeShareExtras, boolean isMultiWindow) {
-        if (!BuildCompat.isAtLeastU()) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
             return;
         }
 
@@ -148,18 +145,6 @@
 
     //  extends ChromeProvidedSharingOptionsProviderBase:
 
-    @Override
-    protected boolean usePolishedActionOrderedList() {
-        // Always use the polished list of actions for Android share sheet.
-        return true;
-    }
-
-    @Nullable
-    @Override
-    protected FirstPartyOption createScreenshotFirstPartyOption() {
-        return null;
-    }
-
     @Nullable
     @Override
     protected FirstPartyOption createLongScreenshotsFirstPartyOption() {
@@ -186,12 +171,6 @@
     }
 
     @Override
-    protected void maybeAddWebStyleNotesFirstPartyOption() {}
-
-    @Override
-    protected void maybeAddDownloadImageFirstPartyOption() {}
-
-    @Override
     protected void maybeAddCopyFirstPartyOption() {
         // getLinkToTextSuccessful is only populated when an link is generated for share.
         if (mShareParams.getLinkToTextSuccessful() != null
@@ -208,7 +187,7 @@
                 && (mChromeShareExtras.getDetailedContentType() == DetailedContentType.WEB_SHARE
                         || mChromeShareExtras.getDetailedContentType()
                                 == DetailedContentType.SCREENSHOT)) {
-            mOrderedFirstPartyOptions.add(createCopyImageFirstPartyOption(false));
+            mOrderedFirstPartyOptions.add(createCopyImageFirstPartyOption());
         }
         mOrderedFirstPartyOptions.add(createCopyImageWithLinkFirstPartyOption());
     }
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
index 59e4e957..5ef20fa 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProvider.java
@@ -18,7 +18,6 @@
 import org.chromium.chrome.browser.share.ShareContentTypeHelper.ContentType;
 import org.chromium.chrome.browser.share.link_to_text.LinkToTextCoordinator.LinkGeneration;
 import org.chromium.chrome.browser.share.long_screenshots.LongScreenshotsCoordinator;
-import org.chromium.chrome.browser.share.screenshot.ScreenshotCoordinator;
 import org.chromium.chrome.browser.share.share_sheet.ShareSheetLinkToggleMetricsHelper.LinkToggleMetricsDetails;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.modules.image_editor.ImageEditorModuleProvider;
@@ -26,7 +25,6 @@
 import org.chromium.components.browser_ui.device_lock.DeviceLockActivityLauncher;
 import org.chromium.components.browser_ui.share.ShareParams;
 import org.chromium.components.feature_engagement.EventConstants;
-import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -40,14 +38,12 @@
     // ComponentName used for Chrome share options in ShareParams.TargetChosenCallback
     public static final ComponentName CHROME_PROVIDED_FEATURE_COMPONENT_NAME =
             new ComponentName("CHROME", "CHROME_FEATURE");
-
-    private static final String USER_ACTION_SCREENSHOT_SELECTED =
-            "SharingHubAndroid.ScreenshotSelected";
     private static final String USER_ACTION_LONG_SCREENSHOT_SELECTED =
             "SharingHubAndroid.LongScreenshotSelected";
 
     private final ShareSheetBottomSheetContent mBottomSheetContent;
     private final long mShareStartTime;
+    // TODO(crbug.com/1448589): Remove the image editor.
     private final ImageEditorModuleProvider mImageEditorModuleProvider;
     private final @LinkGeneration int mLinkGenerationStatusForMetrics;
     private final LinkToggleMetricsDetails mLinkToggleMetricsDetails;
@@ -139,7 +135,6 @@
     }
 
     private PropertyModel getShareSheetModel(FirstPartyOption option) {
-        boolean setShowNewBadge = showNewBadge(option);
         boolean hideBottomSheetContentOnTap = hideBottomSheetContentOnTap(option);
 
         return ShareSheetPropertyModelBuilder.createPropertyModel(
@@ -159,61 +154,11 @@
                     option.onClickCallback.onResult(view);
                     callTargetChosenCallback();
                 },
-                setShowNewBadge);
-    }
-
-    private boolean showNewBadge(FirstPartyOption firstPartyOption) {
-        if (!mFeatureEngagementTracker.isInitialized()) return false;
-
-        if (USER_ACTION_SCREENSHOT_SELECTED.equals(firstPartyOption.featureNameForMetrics)) {
-            return mFeatureEngagementTracker.shouldTriggerHelpUI(
-                    FeatureConstants.IPH_SHARE_SCREENSHOT_FEATURE);
-        }
-        if (USER_ACTION_WEB_STYLE_NOTES_SELECTED.equals(firstPartyOption.featureNameForMetrics)) {
-            return mFeatureEngagementTracker.shouldTriggerHelpUI(
-                    FeatureConstants.SHARING_HUB_WEBNOTES_STYLIZE_FEATURE);
-        }
-        return false;
+                /* showNewBadge= */ false);
     }
 
     private boolean hideBottomSheetContentOnTap(FirstPartyOption firstPartyOption) {
-        if (USER_ACTION_SCREENSHOT_SELECTED.equals(firstPartyOption.featureNameForMetrics)
-                || USER_ACTION_LONG_SCREENSHOT_SELECTED.equals(
-                        firstPartyOption.featureNameForMetrics)) {
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    protected FirstPartyOption createScreenshotFirstPartyOption() {
-        return new FirstPartyOptionBuilder(
-                        ContentType.LINK_PAGE_VISIBLE,
-                        ContentType.TEXT,
-                        ContentType.HIGHLIGHTED_TEXT,
-                        ContentType.IMAGE)
-                .setDetailedContentTypesToDisableFor(DetailedContentType.WEB_NOTES)
-                .setIcon(R.drawable.screenshot, R.string.sharing_screenshot)
-                .setFeatureNameForMetrics(USER_ACTION_SCREENSHOT_SELECTED)
-                .setDisableForMultiWindow(true)
-                .setOnClickCallback(
-                        (view) -> {
-                            mFeatureEngagementTracker.notifyEvent(
-                                    EventConstants.SHARE_SCREENSHOT_SELECTED);
-                            ScreenshotCoordinator coordinator =
-                                    new ScreenshotCoordinator(
-                                            mActivity,
-                                            mShareParams.getWindow(),
-                                            mUrl,
-                                            mChromeOptionShareCallback,
-                                            mBottomSheetController,
-                                            usePolishedActionOrderedList()
-                                                    ? null
-                                                    : mImageEditorModuleProvider);
-                            mBottomSheetController.addObserver(coordinator);
-                            mBottomSheetController.hideContent(mBottomSheetContent, true);
-                        })
-                .build();
+        return !USER_ACTION_LONG_SCREENSHOT_SELECTED.equals(firstPartyOption.featureNameForMetrics);
     }
 
     @Override
@@ -238,9 +183,7 @@
                                             mUrl,
                                             mChromeOptionShareCallback,
                                             mBottomSheetController,
-                                            usePolishedActionOrderedList()
-                                                    ? null
-                                                    : mImageEditorModuleProvider);
+                                            null);
                             mBottomSheetController.addObserver(coordinator);
                             mBottomSheetController.hideContent(mBottomSheetContent, true);
                         })
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java
index c65f865..f7c23977 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/android_share_sheet/AndroidShareSheetControllerUnitTest.java
@@ -599,37 +599,6 @@
     }
 
     @Test
-    @DisableFeatures(ChromeFeatureList.SHARE_SHEET_CUSTOM_ACTIONS_POLISH)
-    @Config(
-            sdk = 34,
-            shadows = {ShadowChooserActionHelper.class})
-    public void ensureNonPolishActionInOrder() {
-        Uri testImageUri = Uri.parse("content://test.image.uri");
-        ShareParams params =
-                new ShareParams.Builder(mWindow, "", "")
-                        .setFileContentType("image/png")
-                        .setSingleImageUri(testImageUri)
-                        .setBypassFixingDomDistillerUrl(true)
-                        .build();
-        ChromeShareExtras chromeShareExtras =
-                new ChromeShareExtras.Builder()
-                        .setDetailedContentType(DetailedContentType.IMAGE)
-                        .setContentUrl(JUnitTestGURLs.GOOGLE_URL)
-                        .setImageSrcUrl(JUnitTestGURLs.GOOGLE_URL_DOGS)
-                        .build();
-
-        mController.showShareSheet(params, chromeShareExtras, 1L);
-
-        // No download option here.
-        Intent intent = Shadows.shadowOf((Activity) mActivity).peekNextStartedActivity();
-        assertCustomActions(
-                intent,
-                R.string.sharing_copy_image_with_link,
-                R.string.sharing_send_tab_to_self,
-                R.string.qr_code_share_icon_label);
-    }
-
-    @Test
     @Config(
             sdk = 34,
             shadows = {ShadowChooserActionHelper.class})
diff --git a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
index 654f11a2a..5a64ebe 100644
--- a/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
+++ b/chrome/browser/share/android/javatests/src/org/chromium/chrome/browser/share/share_sheet/ChromeProvidedSharingOptionsProviderTest.java
@@ -36,7 +36,6 @@
 import org.chromium.base.test.util.UserActionTester;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.share.ChromeShareExtras.DetailedContentType;
 import org.chromium.chrome.browser.share.ShareContentTypeHelper;
@@ -50,8 +49,6 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.AutomotiveContextWrapperTestRule;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.device_lock.DeviceLockActivityLauncher;
 import org.chromium.components.browser_ui.share.ShareParams;
@@ -66,13 +63,10 @@
 import org.chromium.url.GURL;
 import org.chromium.url.JUnitTestGURLs;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /** Instrumentation Unit tests {@link ChromeProvidedSharingOptionsProvider}. */
 @RunWith(BaseRobolectricTestRunner.class)
-@EnableFeatures(ChromeFeatureList.WEBNOTES_STYLIZE)
-@DisableFeatures({ChromeFeatureList.SHARE_SHEET_CUSTOM_ACTIONS_POLISH})
 public class ChromeProvidedSharingOptionsProviderTest {
     @Rule
     public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
@@ -179,22 +173,6 @@
     }
 
     @Test
-    public void getPropertyModels_multiWindow_doesNotIncludeScreenshot() {
-        setUpChromeProvidedSharingOptionsProviderTest(
-                /* isIncognito= */ false, /* printingEnabled= */ false, LinkGeneration.MAX);
-
-        List<PropertyModel> propertyModels =
-                mChromeProvidedSharingOptionsProvider.getPropertyModels(
-                        ShareContentTypeHelper.ALL_CONTENT_TYPES_FOR_TEST,
-                        DetailedContentType.NOT_SPECIFIED,
-                        /* isMultiWindow= */ true);
-
-        assertFalse(
-                "Property models should not contain Screenshot.",
-                propertyModelsContain(propertyModels, R.string.sharing_screenshot));
-    }
-
-    @Test
     public void getPropertyModels_isIncognito_doesNotIncludeQrCode() {
         setUpChromeProvidedSharingOptionsProviderTest(
                 /* isIncognito= */ true, /* printingEnabled= */ false, LinkGeneration.MAX);
@@ -238,15 +216,12 @@
                         /* isMultiWindow= */ false);
 
         // Long Screenshots is supported >= Android N (7.0).
-        List<String> expectedModels = new ArrayList<String>();
-        expectedModels.add(mActivity.getResources().getString(R.string.sharing_screenshot));
-        expectedModels.add(mActivity.getResources().getString(R.string.sharing_long_screenshot));
-        expectedModels.addAll(
+        List<String> expectedModels =
                 ImmutableList.of(
                         mActivity.getResources().getString(R.string.sharing_copy_url),
                         mActivity.getResources().getString(R.string.sharing_copy_image),
-                        mActivity.getResources().getString(R.string.qr_code_share_icon_label),
-                        mActivity.getResources().getString(R.string.sharing_save_image)));
+                        mActivity.getResources().getString(R.string.sharing_long_screenshot),
+                        mActivity.getResources().getString(R.string.qr_code_share_icon_label));
 
         assertCorrectModelsAreInTheRightOrder(propertyModels, expectedModels);
     }
@@ -261,14 +236,11 @@
                         DetailedContentType.IMAGE,
                         /* isMultiWindow= */ false);
 
-        List<String> expectedModels = new ArrayList<String>();
-        expectedModels.add(mActivity.getResources().getString(R.string.sharing_screenshot));
-        expectedModels.add(mActivity.getResources().getString(R.string.sharing_long_screenshot));
-        expectedModels.addAll(
+        List<String> expectedModels =
                 ImmutableList.of(
                         mActivity.getResources().getString(R.string.sharing_copy_image),
-                        mActivity.getResources().getString(R.string.qr_code_share_icon_label),
-                        mActivity.getResources().getString(R.string.sharing_save_image)));
+                        mActivity.getResources().getString(R.string.sharing_long_screenshot),
+                        mActivity.getResources().getString(R.string.qr_code_share_icon_label));
 
         assertCorrectModelsAreInTheRightOrder(propertyModels, expectedModels);
     }
@@ -286,7 +258,6 @@
         List<String> expectedModels =
                 ImmutableList.<String>builder()
                         .add(mActivity.getResources().getString(R.string.sharing_copy_image))
-                        .add(mActivity.getResources().getString(R.string.sharing_save_image))
                         .build();
 
         assertCorrectModelsAreInTheRightOrder(propertyModels, expectedModels);
@@ -332,7 +303,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.SHARE_SHEET_CUSTOM_ACTIONS_POLISH})
     public void getPropertyModels_sharingImageForAutomotiveIncognito() {
         mAutoTestRule.setIsAutomotive(true);
         setUpChromeProvidedSharingOptionsProviderTest(
@@ -350,7 +320,6 @@
     }
 
     @Test
-    @EnableFeatures({ChromeFeatureList.SHARE_SHEET_CUSTOM_ACTIONS_POLISH})
     public void getPropertyModels_textAndLinksIncognito() {
         mAutoTestRule.setIsAutomotive(true);
         setUpChromeProvidedSharingOptionsProviderTest(
@@ -543,9 +512,7 @@
             String label = propertyModel.get(ShareSheetItemViewProperties.LABEL);
             Resources res = mActivity.getResources();
             // There is no link generation for Stylize Cards / Screenshots / Long Screenshots.
-            if (label.equals(res.getString(R.string.sharing_webnotes_create_card))
-                    || label.equals(res.getString(R.string.sharing_screenshot))
-                    || label.equals(res.getString(R.string.sharing_long_screenshot))) {
+            if (label.equals(res.getString(R.string.sharing_long_screenshot))) {
                 continue;
             }
 
diff --git a/chrome/browser/sync/test/integration/web_apps_sync_test_base.cc b/chrome/browser/sync/test/integration/web_apps_sync_test_base.cc
index bdbc42c..4dd8791 100644
--- a/chrome/browser/sync/test/integration/web_apps_sync_test_base.cc
+++ b/chrome/browser/sync/test/integration/web_apps_sync_test_base.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/sync/test/integration/web_apps_sync_test_base.h"
 
 #include "base/containers/extend.h"
+#include "chrome/common/chrome_features.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/apps/link_capturing/link_capturing_features.h"
@@ -12,7 +13,6 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/constants/ash_features.h"
-#include "chrome/common/chrome_features.h"
 #include "chromeos/ash/components/standalone_browser/feature_refs.h"
 #endif
 
@@ -30,6 +30,9 @@
 #if BUILDFLAG(IS_CHROMEOS)
   // TODO(crbug.com/1357905): Update test driver to work with new UI.
   disabled_features.push_back(apps::features::kLinkCapturingUiUpdate);
+#else
+  // TOOD(b/313492499): Update test driver to work with new intent picker UI.
+  disabled_features.push_back(features::kDesktopPWAsLinkCapturing);
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index b336cff3..b1cfe4a 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -5764,6 +5764,8 @@
       "views/side_panel/read_anything/read_anything_model.h",
       "views/side_panel/read_anything/read_anything_side_panel_controller.cc",
       "views/side_panel/read_anything/read_anything_side_panel_controller.h",
+      "views/side_panel/read_anything/read_anything_side_panel_web_view.cc",
+      "views/side_panel/read_anything/read_anything_side_panel_web_view.h",
       "views/side_panel/read_anything/read_anything_toolbar_view.cc",
       "views/side_panel/read_anything/read_anything_toolbar_view.h",
       "views/side_panel/read_later_side_panel_web_view.cc",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 4ace597..8730c70 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -5261,18 +5261,10 @@
         Copy image with link
       </message>
 
-      <message name="IDS_SHARING_COPY_GIF" desc="Label for the Copy GIF button in the sharing hub.">
-        Copy GIF
-      </message>
-
       <message name="IDS_SHARING_COPY_HIGHLIGHT_WITHOUT_LINK" desc="Label for the Copy highlighted text without link button in the sharing hub. This is used when sharing highlighted text with a generated link.">
         Copy without link
       </message>
 
-      <message name="IDS_SHARING_SCREENSHOT" desc="Label for Screenshot button in the sharing hub.">
-        Screenshot
-      </message>
-
       <message name="IDS_SHARING_LONG_SCREENSHOT" desc="Label for Long Screenshot button in the sharing hub.">
         Long Screenshot
       </message>
@@ -5301,18 +5293,6 @@
         Something went wrong. Try again.
       </message>
 
-      <message name="IDS_SHARING_WEBNOTES_CREATE_CARD" desc="Label for the button in sharing hub for creating card for the highlight text.">
-        Create card
-      </message>
-
-      <message name="IDS_SHARING_WEBNOTES_ACCESSIBILITY_DESCRIPTION" desc="Accessibility description for the button in sharing hub for creating card for the highlight text.">
-        Create stylized card with highlight
-      </message>
-
-      <message name="IDS_SHARING_SAVE_IMAGE" desc="Label for the button in sharing hub for saving an image to device.">
-        Save to device
-      </message>
-
       <message name="IDS_SHARING_SEND_TAB_TO_SELF" desc="Label for the button in sharing hub for sending the current tab to other devices if user signed in (Send Tab to Self feature).">
         Send to devices
       </message>
@@ -5321,10 +5301,6 @@
         Including link: <ph name="ORIGIN">%1$s<ex>https://www.example.com</ex></ph>
       </message>
 
-      <message name="IDS_GIF_COPIED" desc="Text shown in the toast notification when Copy GIF is selected in the sharing hub.">
-        GIF Copied
-      </message>
-
       <message name="IDS_QR_CODE_OPEN_SETTINGS_LABEL" desc="Text on button on QR Code sharing tab triggering Android settings.">
         Open Settings
       </message>
@@ -5406,10 +5382,6 @@
         chrome_screenshot_<ph name="CURRENT_TIMESTAMP_MS">%1$s<ex>1582667748515</ex></ph>
       </message>
 
-      <message name="IDS_SAVE_IMAGE_FILENAME_PREFIX" desc="File name prefix for downloaded image that is followed by timestamp.">
-        chrome_image_<ph name="CURRENT_TIMESTAMP_MS">%1$s<ex>1582667748515</ex></ph>
-      </message>
-
       <!-- Chime DFM module strings -->
       <message name="IDS_CHIME_MODULE_TITLE" desc="Text shown when the chime module is referenced in install start, success, failure UI (e.g. in IDS_MODULE_INSTALL_START_TEXT, which will expand to 'Installing Google Notifications Platform for Chrome…').">
         Google Notifications Platform
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_GIF_COPIED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_GIF_COPIED.png.sha1
deleted file mode 100644
index a4dcdff..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_GIF_COPIED.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-eab081ae058a655fecef7798e0c2ed7a7e6e0f84
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAVE_IMAGE_FILENAME_PREFIX.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAVE_IMAGE_FILENAME_PREFIX.png.sha1
deleted file mode 100644
index 01b62f85..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SAVE_IMAGE_FILENAME_PREFIX.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-65b8d241d6ec39843d255bb0f76ef550724fb288
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_COPY_GIF.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_COPY_GIF.png.sha1
deleted file mode 100644
index 0cf2eeb0..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_COPY_GIF.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-5eb907dfe34856fdb381cc2caba78a4959746a60
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_SAVE_IMAGE.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_SAVE_IMAGE.png.sha1
deleted file mode 100644
index bcfe9a7..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_SAVE_IMAGE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-1f462a9327fef956d2cfc0896eb64fd2a0881a7a
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_SCREENSHOT.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_SCREENSHOT.png.sha1
deleted file mode 100644
index d0d45acb..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_SCREENSHOT.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ebb50a402703a36fb9900bb2f6368a623e561df7
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_WEBNOTES_ACCESSIBILITY_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_WEBNOTES_ACCESSIBILITY_DESCRIPTION.png.sha1
deleted file mode 100644
index ab9eb375..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_WEBNOTES_ACCESSIBILITY_DESCRIPTION.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-692a7c7abf41a758643dd308fd2c600200fe477d
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_WEBNOTES_CREATE_CARD.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_WEBNOTES_CREATE_CARD.png.sha1
deleted file mode 100644
index 19e905e..0000000
--- a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_SHARING_WEBNOTES_CREATE_CARD.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-973eaf1223954ff6e8f45883885aa9a1364bec2c
\ No newline at end of file
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarFeatures.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarFeatures.java
index 384e347..c0fed164 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarFeatures.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/ToolbarFeatures.java
@@ -18,24 +18,6 @@
     /** Private constructor to avoid instantiation. */
     private ToolbarFeatures() {}
 
-    /** Returns whether captures should be blocked as part of the ablation experiment. */
-    public static boolean shouldBlockCapturesForAblation() {
-        // The ablation experiment turns off toolbar scrolling off the screen. Initially this also
-        // turned off captures, which are unnecessary when the toolbar cannot scroll off. But this
-        // param
-        // allows half of this work to still be done, allowing measurement of both halves when
-        // compared
-        // to the original ablation and controls.
-        if (!ChromeFeatureList.sToolbarScrollAblation.isEnabled()) {
-            // Not in ablation or pre-native, allow captures like normal.
-            return false;
-        }
-
-        // Ablation is enabled, follow the param.
-        return !ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
-                ChromeFeatureList.TOOLBAR_SCROLL_ABLATION_ANDROID, ALLOW_CAPTURES, false);
-    }
-
     public static boolean shouldSuppressCaptures() {
         return ChromeFeatureList.sSuppressionToolbarCaptures.isEnabled();
     }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java
index 3fed416..ca01e19b 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/bottom/ScrollingBottomViewResourceFrameLayout.java
@@ -51,10 +51,6 @@
         return new ViewResourceAdapter(this) {
             @Override
             public boolean isDirty() {
-                if (ToolbarFeatures.shouldBlockCapturesForAblation()) {
-                    return false;
-                }
-
                 if (ToolbarFeatures.shouldSuppressCaptures()) {
                     // Dirty rect tracking will claim changes more often than token differences due
                     // to model changes. It is also cheaper to simply check a boolean, so do it
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index f448ee3..d3ac515f 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -1815,9 +1815,7 @@
 
     @Override
     public CaptureReadinessResult isReadyForTextureCapture() {
-        if (ToolbarFeatures.shouldBlockCapturesForAblation()) {
-            return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.SCROLL_ABLATION);
-        } else if (mForceTextureCapture) {
+        if (mForceTextureCapture) {
             return CaptureReadinessResult.readyForced();
         } else if (ToolbarFeatures.shouldSuppressCaptures()) {
             return getReadinessStateWithSuppression();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
index 57cf2db..96868ce 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTablet.java
@@ -439,9 +439,7 @@
 
     @Override
     public CaptureReadinessResult isReadyForTextureCapture() {
-        if (ToolbarFeatures.shouldBlockCapturesForAblation()) {
-            return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.SCROLL_ABLATION);
-        } else if (ToolbarFeatures.shouldSuppressCaptures()) {
+        if (ToolbarFeatures.shouldSuppressCaptures()) {
             if (urlHasFocus()) {
                 return CaptureReadinessResult.notReady(
                         TopToolbarBlockCaptureReason.URL_BAR_HAS_FOCUS);
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
index f927afe..4028118 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarTabletUnitTest.java
@@ -326,14 +326,6 @@
     }
 
     @Test
-    @EnableFeatures(ChromeFeatureList.TOOLBAR_SCROLL_ABLATION_ANDROID)
-    public void testIsReadyForTextureCapture_Ablation() {
-        CaptureReadinessResult result = mToolbarTablet.isReadyForTextureCapture();
-        Assert.assertFalse(result.isReady);
-        Assert.assertEquals(TopToolbarBlockCaptureReason.SCROLL_ABLATION, result.blockReason);
-    }
-
-    @Test
     @DisableFeatures(ChromeFeatureList.SUPPRESS_TOOLBAR_CAPTURES)
     public void testIsReadyForTextureCapture_NoSuppression() {
         CaptureReadinessResult result = mToolbarTablet.isReadyForTextureCapture();
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
index 065b96a..a6c5d25 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client.cc
@@ -4,7 +4,7 @@
 
 #include "chrome/browser/ui/ash/accessibility/accessibility_controller_client.h"
 
-#include "ash/public/cpp/accessibility_controller.h"
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
 #include "ash/wm/desks/templates/saved_desk_util.h"
 #include "chrome/browser/ash/accessibility/accessibility_manager.h"
diff --git a/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc b/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
index 5d91ca00..f021f97 100644
--- a/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
+++ b/chrome/browser/ui/ash/accessibility/accessibility_controller_client_unittest.cc
@@ -6,11 +6,11 @@
 
 #include <optional>
 
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/public/cpp/accessibility_controller_enums.h"
+#include "ash/test/ash_test_base.h"
 #include "base/time/time.h"
-#include "chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h"
 #include "chromeos/ash/components/audio/sounds.h"
-#include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/gfx/geometry/point_f.h"
@@ -86,7 +86,7 @@
 
 }  // namespace
 
-class AccessibilityControllerClientTest : public testing::Test {
+class AccessibilityControllerClientTest : public ash::AshTestBase {
  public:
   AccessibilityControllerClientTest() = default;
 
@@ -96,17 +96,14 @@
       const AccessibilityControllerClientTest&) = delete;
 
   ~AccessibilityControllerClientTest() override = default;
-
- private:
-  content::BrowserTaskEnvironment task_environment_;
 };
 
 TEST_F(AccessibilityControllerClientTest, MethodCalls) {
-  FakeAccessibilityController controller;
   FakeAccessibilityControllerClient client;
 
-  // Tests client is set.
-  EXPECT_TRUE(controller.was_client_set());
+  ash::AccessibilityController* controller =
+      ash::AccessibilityController::Get();
+  controller->SetClient(&client);
 
   // Tests TriggerAccessibilityAlert method call.
   const ash::AccessibilityAlert alert = ash::AccessibilityAlert::SCREEN_ON;
diff --git a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
deleted file mode 100644
index bc544209..0000000
--- a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.cc
+++ /dev/null
@@ -1,121 +0,0 @@
-// 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.
-
-#include "chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h"
-
-FakeAccessibilityController::FakeAccessibilityController() = default;
-
-FakeAccessibilityController::~FakeAccessibilityController() = default;
-
-void FakeAccessibilityController::SetClient(
-    ash::AccessibilityControllerClient* client) {
-  was_client_set_ = true;
-}
-
-void FakeAccessibilityController::SetDarkenScreen(bool darken) {}
-
-void FakeAccessibilityController::BrailleDisplayStateChanged(bool connected) {}
-
-void FakeAccessibilityController::SetFocusHighlightRect(
-    const gfx::Rect& bounds_in_screen) {}
-
-void FakeAccessibilityController::SetCaretBounds(
-    const gfx::Rect& bounds_in_screen) {}
-
-void FakeAccessibilityController::SetAccessibilityPanelAlwaysVisible(
-    bool always_visible) {}
-
-void FakeAccessibilityController::SetAccessibilityPanelBounds(
-    const gfx::Rect& bounds,
-    ash::AccessibilityPanelState state) {}
-
-void FakeAccessibilityController::SetSelectToSpeakState(
-    ash::SelectToSpeakState state) {}
-
-void FakeAccessibilityController::SetSelectToSpeakEventHandlerDelegate(
-    ash::SelectToSpeakEventHandlerDelegate* delegate) {}
-
-void FakeAccessibilityController::ShowSelectToSpeakPanel(
-    const gfx::Rect& anchor,
-    bool is_paused,
-    double speed) {}
-
-void FakeAccessibilityController::HideSelectToSpeakPanel() {}
-
-void FakeAccessibilityController::OnSelectToSpeakPanelAction(
-    ash::SelectToSpeakPanelAction action,
-    double value) {}
-
-void FakeAccessibilityController::HideSwitchAccessBackButton() {}
-
-void FakeAccessibilityController::HideSwitchAccessMenu() {}
-
-void FakeAccessibilityController::ShowSwitchAccessBackButton(
-    const gfx::Rect& anchor) {}
-
-void FakeAccessibilityController::ShowSwitchAccessMenu(
-    const gfx::Rect& anchor,
-    std::vector<std::string> actions) {}
-
-void FakeAccessibilityController::StartPointScan() {}
-
-void FakeAccessibilityController::StopPointScan() {}
-
-void FakeAccessibilityController::SetPointScanSpeedDipsPerSecond(
-    int point_scan_speed_dips_per_second) {}
-
-void FakeAccessibilityController::SetDictationActive(bool is_active) {}
-
-void FakeAccessibilityController::ToggleDictationFromSource(
-    ash::DictationToggleSource source) {}
-
-void FakeAccessibilityController::EnableOrToggleDictationFromSource(
-    ash::DictationToggleSource source) {}
-
-void FakeAccessibilityController::ShowDictationLanguageUpgradedNudge(
-    const std::string& dictation_locale,
-    const std::string& application_locale) {}
-
-void FakeAccessibilityController::HandleAutoclickScrollableBoundsFound(
-    const gfx::Rect& bounds_in_screen) {}
-
-std::u16string FakeAccessibilityController::GetBatteryDescription() const {
-  return std::u16string();
-}
-
-void FakeAccessibilityController::SetVirtualKeyboardVisible(bool is_visible) {}
-
-void FakeAccessibilityController::PerformAcceleratorAction(
-    ash::AcceleratorAction accelerator_action) {}
-
-void FakeAccessibilityController::NotifyAccessibilityStatusChanged() {}
-
-bool FakeAccessibilityController::IsAccessibilityFeatureVisibleInTrayMenu(
-    const std::string& path) {
-  return true;
-}
-
-void FakeAccessibilityController::
-    DisableSwitchAccessDisableConfirmationDialogTesting() {}
-
-void FakeAccessibilityController::
-    DisableSwitchAccessEnableNotificationTesting() {}
-
-void FakeAccessibilityController::
-    UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
-        int download_progress) {}
-
-void FakeAccessibilityController::ShowNotificationForDictation(
-    ash::DictationNotificationType type,
-    const std::u16string& display_language) {}
-
-void FakeAccessibilityController::UpdateDictationBubble(
-    bool visible,
-    ash::DictationBubbleIconType icon,
-    const std::optional<std::u16string>& text,
-    const std::optional<std::vector<ash::DictationBubbleHintType>>& hints) {}
-
-void FakeAccessibilityController::SilenceSpokenFeedback() {}
-
-void FakeAccessibilityController::ShowToast(ash::AccessibilityToastType type) {}
diff --git a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h b/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
deleted file mode 100644
index 4e518351..0000000
--- a/chrome/browser/ui/ash/accessibility/fake_accessibility_controller.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// 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.
-
-#ifndef CHROME_BROWSER_UI_ASH_ACCESSIBILITY_FAKE_ACCESSIBILITY_CONTROLLER_H_
-#define CHROME_BROWSER_UI_ASH_ACCESSIBILITY_FAKE_ACCESSIBILITY_CONTROLLER_H_
-
-#include "ash/public/cpp/accessibility_controller.h"
-
-// Fake implementation of ash's mojo AccessibilityController interface.
-class FakeAccessibilityController : ash::AccessibilityController {
- public:
-  FakeAccessibilityController();
-
-  FakeAccessibilityController(const FakeAccessibilityController&) = delete;
-  FakeAccessibilityController& operator=(const FakeAccessibilityController&) =
-      delete;
-
-  ~FakeAccessibilityController() override;
-
-  bool was_client_set() const { return was_client_set_; }
-
-  // ash::AccessibilityController:
-  void SetClient(ash::AccessibilityControllerClient* client) override;
-  void SetDarkenScreen(bool darken) override;
-  void BrailleDisplayStateChanged(bool connected) override;
-  void SetFocusHighlightRect(const gfx::Rect& bounds_in_screen) override;
-  void SetCaretBounds(const gfx::Rect& bounds_in_screen) override;
-  void SetAccessibilityPanelAlwaysVisible(bool always_visible) override;
-  void SetAccessibilityPanelBounds(const gfx::Rect& bounds,
-                                   ash::AccessibilityPanelState state) override;
-  void SetSelectToSpeakState(ash::SelectToSpeakState state) override;
-  void SetSelectToSpeakEventHandlerDelegate(
-      ash::SelectToSpeakEventHandlerDelegate* delegate) override;
-  void ShowSelectToSpeakPanel(const gfx::Rect& anchor,
-                              bool is_paused,
-                              double speed) override;
-  void HideSelectToSpeakPanel() override;
-  void OnSelectToSpeakPanelAction(ash::SelectToSpeakPanelAction action,
-                                  double value) override;
-  void HideSwitchAccessBackButton() override;
-  void HideSwitchAccessMenu() override;
-  void ShowSwitchAccessBackButton(const gfx::Rect& anchor) override;
-  void ShowSwitchAccessMenu(const gfx::Rect& anchor,
-                            std::vector<std::string> actions) override;
-  void StartPointScan() override;
-  void StopPointScan() override;
-  void SetDictationActive(bool is_active) override;
-  void SetPointScanSpeedDipsPerSecond(
-      int point_scan_speed_dips_per_second) override;
-  void ToggleDictationFromSource(ash::DictationToggleSource source) override;
-  void EnableOrToggleDictationFromSource(
-      ash::DictationToggleSource source) override;
-  void ShowDictationLanguageUpgradedNudge(
-      const std::string& dictation_locale,
-      const std::string& application_locale) override;
-  void HandleAutoclickScrollableBoundsFound(
-      const gfx::Rect& bounds_in_screen) override;
-  std::u16string GetBatteryDescription() const override;
-  void SetVirtualKeyboardVisible(bool is_visible) override;
-  void PerformAcceleratorAction(
-      ash::AcceleratorAction accelerator_action) override;
-  void NotifyAccessibilityStatusChanged() override;
-  bool IsAccessibilityFeatureVisibleInTrayMenu(
-      const std::string& path) override;
-  void DisableSwitchAccessDisableConfirmationDialogTesting() override;
-  void DisableSwitchAccessEnableNotificationTesting() override;
-  void UpdateDictationButtonOnSpeechRecognitionDownloadChanged(
-      int download_progress) override;
-  void ShowNotificationForDictation(
-      ash::DictationNotificationType type,
-      const std::u16string& display_language) override;
-  void UpdateDictationBubble(
-      bool visible,
-      ash::DictationBubbleIconType icon,
-      const std::optional<std::u16string>& text,
-      const std::optional<std::vector<ash::DictationBubbleHintType>>& hints)
-      override;
-  void SilenceSpokenFeedback() override;
-  void ShowToast(ash::AccessibilityToastType type) override;
-
- private:
-  bool was_client_set_ = false;
-};
-
-#endif  // CHROME_BROWSER_UI_ASH_ACCESSIBILITY_FAKE_ACCESSIBILITY_CONTROLLER_H_
diff --git a/chrome/browser/ui/ash/quick_settings_integration_test.cc b/chrome/browser/ui/ash/quick_settings_integration_test.cc
index 3f9a163..3ad8c4a 100644
--- a/chrome/browser/ui/ash/quick_settings_integration_test.cc
+++ b/chrome/browser/ui/ash/quick_settings_integration_test.cc
@@ -7,6 +7,7 @@
 #include "ash/shell.h"
 #include "ash/system/model/enterprise_domain_model.h"
 #include "ash/system/model/system_tray_model.h"
+#include "base/cpu.h"
 #include "base/test/gtest_tags.h"
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
@@ -16,6 +17,7 @@
 #include "chrome/test/base/chromeos/crosier/interactive_ash_test.h"
 #include "chromeos/ash/components/standalone_browser/standalone_browser_features.h"
 #include "components/strings/grit/components_strings.h"
+#include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/env.h"
 #include "ui/aura/env_observer.h"
 #include "ui/aura/window.h"
@@ -165,10 +167,13 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-// Flaky because Lacros can be older than Ash in chromeos_integration_tests,
-// causing NOTREACHED failures at the crosapi level. b/303359438
-IN_PROC_BROWSER_TEST_F(QuickSettingsLacrosIntegrationTest,
-                       DISABLED_ManagedDeviceInfo) {
+IN_PROC_BROWSER_TEST_F(QuickSettingsLacrosIntegrationTest, ManagedDeviceInfo) {
+  // On VM tryservers like chromeos-amd64-generic Lacros fails to start up
+  // correctly (it restarts in a loop). b/303359438
+  if (base::CPU().is_running_in_vm()) {
+    GTEST_SKIP();
+  }
+
   ASSERT_TRUE(crosapi::browser_util::IsLacrosEnabled());
 
   base::AddFeatureIdTagToTestResult(
diff --git a/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc b/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc
index 2cf6779..36c1d6ac 100644
--- a/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc
+++ b/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc
@@ -497,8 +497,8 @@
   if (!cluster_id_for_page_.has_value())
     return;
 
-  shopping_service_->IsClusterIdTrackedByUser(
-      cluster_id_for_page_.value(),
+  shopping_service_->IsSubscribed(
+      BuildUserSubscriptionForClusterId(cluster_id_for_page_.value()),
       base::BindOnce(
           [](base::WeakPtr<CommerceUiTabHelper> helper, bool is_tracked) {
             if (!helper) {
diff --git a/chrome/browser/ui/commerce/commerce_ui_tab_helper_unittest.cc b/chrome/browser/ui/commerce/commerce_ui_tab_helper_unittest.cc
index 00ec31a..bdb5703 100644
--- a/chrome/browser/ui/commerce/commerce_ui_tab_helper_unittest.cc
+++ b/chrome/browser/ui/commerce/commerce_ui_tab_helper_unittest.cc
@@ -17,6 +17,7 @@
 #include "components/bookmarks/test/test_bookmark_client.h"
 #include "components/commerce/core/commerce_feature_list.h"
 #include "components/commerce/core/mock_shopping_service.h"
+#include "components/commerce/core/price_tracking_utils.h"
 #include "components/commerce/core/shopping_service.h"
 #include "components/commerce/core/subscriptions/commerce_subscription.h"
 #include "components/commerce/core/test_utils.h"
@@ -160,20 +161,19 @@
 
   shopping_service_->SetResponseForGetProductInfoForUrl(info);
   shopping_service_->SetIsSubscribedCallbackValue(true);
-  shopping_service_->SetIsClusterIdTrackedByUserResponse(true);
 
   SimulateNavigationCommitted(GURL(kProductUrl));
 
   // First ensure that subscribe is successful.
-  tab_helper_->OnSubscribe(CreateUserTrackedSubscription(kClusterId), true);
+  tab_helper_->OnSubscribe(BuildUserSubscriptionForClusterId(kClusterId), true);
   base::RunLoop().RunUntilIdle();
 
   ASSERT_TRUE(tab_helper_->IsPriceTracking());
 
   // Now assume the user has unsubscribed again.
   shopping_service_->SetIsSubscribedCallbackValue(false);
-  shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
-  tab_helper_->OnUnsubscribe(CreateUserTrackedSubscription(kClusterId), true);
+  tab_helper_->OnUnsubscribe(BuildUserSubscriptionForClusterId(kClusterId),
+                             true);
   base::RunLoop().RunUntilIdle();
 
   ASSERT_FALSE(tab_helper_->IsPriceTracking());
@@ -241,7 +241,6 @@
 
   shopping_service_->SetResponseForGetProductInfoForUrl(info);
   shopping_service_->SetIsSubscribedCallbackValue(false);
-  shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
   shopping_service_->SetSubscribeCallbackValue(true);
 
   SimulateNavigationCommitted(GURL(kProductUrl));
@@ -255,7 +254,7 @@
                 testing::_))
       .Times(1);
 
-  shopping_service_->SetIsClusterIdTrackedByUserResponse(true);
+  shopping_service_->SetIsSubscribedCallbackValue(true);
   tab_helper_->SetPriceTrackingState(true, true, base::DoNothing());
   ASSERT_TRUE(GetPendingTrackingStateForTesting().has_value());
   ASSERT_TRUE(GetPendingTrackingStateForTesting().value());
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
index a4abb29..fab397a 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view.cc
@@ -43,7 +43,6 @@
 #include "components/image_fetcher/core/image_fetcher.h"
 #include "components/image_fetcher/core/image_fetcher_service.h"
 #include "components/page_image_service/image_service.h"
-#include "components/power_bookmarks/core/power_bookmark_features.h"
 #include "components/signin/public/base/signin_buildflags.h"
 #include "components/signin/public/base/signin_metrics.h"
 #include "components/strings/grit/components_strings.h"
@@ -380,9 +379,7 @@
       CreatePriceTrackingEmailCallback(profile, anchor_view, web_contents,
                                        bookmark_node);
 
-  bool show_simplified_flow =
-      !already_bookmarked && base::FeatureList::IsEnabled(
-                                 power_bookmarks::kSimplifiedBookmarkSaveFlow);
+  bool show_simplified_flow = !already_bookmarked;
 
   auto bubble_delegate_unique = std::make_unique<BookmarkBubbleDelegate>(
       std::move(delegate), browser, url, show_simplified_flow);
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_interactive_uitest.cc b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_interactive_uitest.cc
index 56f6498..b0eaca2e 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_interactive_uitest.cc
@@ -42,10 +42,7 @@
 class BookmarkBubbleViewInteractiveTest : public InteractiveBrowserTest {
  public:
   void SetUp() override {
-    test_features_.InitWithFeatures(
-        {commerce::kShoppingCollection,
-         power_bookmarks::kSimplifiedBookmarkSaveFlow},
-        {});
+    test_features_.InitWithFeatures({commerce::kShoppingCollection}, {});
 
     set_open_about_blank_on_browser_launch(true);
     ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
diff --git a/chrome/browser/ui/views/commerce/price_tracking_email_dialog_view_interactive_uitest.cc b/chrome/browser/ui/views/commerce/price_tracking_email_dialog_view_interactive_uitest.cc
index f85f755..45353380 100644
--- a/chrome/browser/ui/views/commerce/price_tracking_email_dialog_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/commerce/price_tracking_email_dialog_view_interactive_uitest.cc
@@ -130,7 +130,6 @@
     info.product_cluster_id.emplace(kClusterId);
     mock_shopping_service->SetResponseForGetProductInfoForUrl(info);
     mock_shopping_service->SetIsSubscribedCallbackValue(true);
-    mock_shopping_service->SetIsClusterIdTrackedByUserResponse(true);
   }
 
   base::WeakPtrFactory<PriceTrackingEmailDialogConsentViewInteractiveTest>
diff --git a/chrome/browser/ui/views/commerce/price_tracking_icon_view_integration_test.cc b/chrome/browser/ui/views/commerce/price_tracking_icon_view_integration_test.cc
index 4b368de..d6f338b4 100644
--- a/chrome/browser/ui/views/commerce/price_tracking_icon_view_integration_test.cc
+++ b/chrome/browser/ui/views/commerce/price_tracking_icon_view_integration_test.cc
@@ -22,6 +22,7 @@
 #include "components/bookmarks/test/bookmark_test_helpers.h"
 #include "components/commerce/core/commerce_feature_list.h"
 #include "components/commerce/core/mock_shopping_service.h"
+#include "components/commerce/core/price_tracking_utils.h"
 #include "components/commerce/core/test_utils.h"
 #include "components/omnibox/browser/vector_icons.h"
 #include "components/strings/grit/components_strings.h"
@@ -111,11 +112,11 @@
 
   void SimulateSubscriptionChangeEvent(bool is_subscribed) {
     if (is_subscribed) {
-      GetTabHelper()->OnSubscribe(commerce::CreateUserTrackedSubscription(0L),
-                                  true);
+      GetTabHelper()->OnSubscribe(
+          commerce::BuildUserSubscriptionForClusterId(0L), true);
     } else {
-      GetTabHelper()->OnUnsubscribe(commerce::CreateUserTrackedSubscription(0L),
-                                    true);
+      GetTabHelper()->OnUnsubscribe(
+          commerce::BuildUserSubscriptionForClusterId(0L), true);
     }
   }
 
diff --git a/chrome/browser/ui/views/commerce/price_tracking_icon_view_interactive_uitest.cc b/chrome/browser/ui/views/commerce/price_tracking_icon_view_interactive_uitest.cc
index 51c60ee..d210fbb6 100644
--- a/chrome/browser/ui/views/commerce/price_tracking_icon_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/commerce/price_tracking_icon_view_interactive_uitest.cc
@@ -115,7 +115,7 @@
     bookmarks::BookmarkModel* bookmark_model =
         BookmarkModelFactory::GetForBrowserContext(browser()->profile());
 
-    mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(true);
+    mock_shopping_service_->SetIsSubscribedCallbackValue(true);
 
     commerce::AddProductBookmark(bookmark_model, u"title", url, 0,
                                  is_price_tracked);
@@ -182,7 +182,7 @@
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingIconViewInteractiveTest,
                        FUEBubbleShownOnPress) {
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
   RunTestSequence(
       InstrumentTab(kShoppingTab),
       NavigateWebContents(kShoppingTab,
@@ -209,7 +209,7 @@
   commerce::AddProductBookmark(bookmark_model, u"title",
                                embedded_test_server()->GetURL(kShoppingURL), 0,
                                true);
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(true);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(true);
 
   RunTestSequence(
       InstrumentTab(kShoppingTab),
@@ -242,7 +242,7 @@
                        PriceTrackingBubbleShownOnPress_AfterFUE) {
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
 
   RunTestSequence(
       InstrumentTab(kShoppingTab),
@@ -262,7 +262,7 @@
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingIconViewInteractiveTest,
                        BubbleCanBeReshowOnPress) {
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
 
   RunTestSequence(
       InstrumentTab(kShoppingTab),
@@ -292,7 +292,7 @@
                        EnablePriceTrackOnPress) {
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
 
   const GURL shopping_url = embedded_test_server()->GetURL(kShoppingURL);
 
@@ -320,7 +320,7 @@
       WaitForShow(kPriceTrackingBubbleDialogId));
 
   SimulateServerPriceTrackStateUpdated(true, shopping_url);
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(true);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(true);
 
   RunTestSequence(
       CheckView(kPriceTrackingChipElementId,
@@ -349,7 +349,7 @@
                        CreateBookmarkOnPressIfNotExist) {
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
 
   const GURL shopping_url = embedded_test_server()->GetURL(kShoppingURL);
   RunTestSequence(InstrumentTab(kShoppingTab),
@@ -367,7 +367,7 @@
 IN_PROC_BROWSER_TEST_F(PriceTrackingIconViewInteractiveTest,
                        RecordOmniboxChipClicked) {
   base::UserActionTester user_action_tester;
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
   EXPECT_EQ(user_action_tester.GetActionCount(
                 "Commerce.PriceTracking.OmniboxChipClicked"),
             0);
@@ -385,7 +385,7 @@
 IN_PROC_BROWSER_TEST_F(PriceTrackingIconViewInteractiveTest,
                        RecordOmniboxChipTracked) {
   base::UserActionTester user_action_tester;
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
 
@@ -406,7 +406,7 @@
 IN_PROC_BROWSER_TEST_F(PriceTrackingIconViewInteractiveTest,
                        NoRecordOmniboxChipTracked_ForTrackedProduct) {
   base::UserActionTester user_action_tester;
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(true);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(true);
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
 
@@ -428,7 +428,7 @@
 IN_PROC_BROWSER_TEST_F(PriceTrackingIconViewInteractiveTest,
                        NoRecordOmniboxChipTracked_ForFUEFlow) {
   base::UserActionTester user_action_tester;
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
   EXPECT_EQ(user_action_tester.GetActionCount(
                 "Commerce.PriceTracking.OmniboxChip.Tracked"),
             0);
@@ -445,7 +445,7 @@
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingIconViewInteractiveTest,
                        IconViewAccessibleName) {
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(true);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(true);
   RunTestSequence(
       InstrumentTab(kShoppingTab),
       NavigateWebContents(kShoppingTab,
@@ -478,7 +478,7 @@
                        IconRevertedOnFailure) {
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
 
   // Simulate subscription failure.
   mock_shopping_service_->SetSubscribeCallbackValue(false);
@@ -545,7 +545,7 @@
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingBubbleInteractiveTest,
                        RecordFirstRunBubbleShown) {
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
   EXPECT_EQ(user_action_tester_.GetActionCount(
                 "Commerce.PriceTracking.FirstRunBubbleShown"),
             0);
@@ -565,7 +565,7 @@
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingBubbleInteractiveTest,
                        RecordConfirmationShown) {
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
   EXPECT_EQ(user_action_tester_.GetActionCount(
@@ -586,7 +586,7 @@
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingBubbleInteractiveTest,
                        RecordConfirmationUntracked) {
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
   EXPECT_EQ(user_action_tester_.GetActionCount(
@@ -614,7 +614,7 @@
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingBubbleInteractiveTest,
                        RecordEditedBookmarkFolderFromOmniboxBubble) {
-  mock_shopping_service_->SetIsClusterIdTrackedByUserResponse(false);
+  mock_shopping_service_->SetIsSubscribedCallbackValue(false);
   browser()->profile()->GetPrefs()->SetBoolean(
       prefs::kShouldShowPriceTrackFUEBubble, false);
   EXPECT_EQ(user_action_tester_.GetActionCount(
diff --git a/chrome/browser/ui/views/controls/subpage_view.cc b/chrome/browser/ui/views/controls/subpage_view.cc
index 8ed6b2d..8fa4a7b 100644
--- a/chrome/browser/ui/views/controls/subpage_view.cc
+++ b/chrome/browser/ui/views/controls/subpage_view.cc
@@ -18,14 +18,13 @@
 #include "ui/views/controls/button/image_button_factory.h"
 #include "ui/views/controls/highlight_path_generator.h"
 #include "ui/views/controls/separator.h"
-#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/box_layout_view.h"
 #include "ui/views/layout/flex_layout_view.h"
 
 namespace {
 constexpr int kSeparatorBottomMargin = 16;
 constexpr int kBackIconSize = 16;
 constexpr int kBackIconSizeRefreshStyle = 20;
-constexpr int kSpaceBetweenBackArrowAndTitle = 8;
 }  // namespace
 
 DEFINE_ELEMENT_IDENTIFIER_VALUE(kSubpageViewId);
@@ -46,9 +45,13 @@
 }
 
 void SubpageView::SetUpSubpageTitle(views::Button::PressedCallback callback) {
-  auto title_view = std::make_unique<views::View>();
-  title_view->SetLayoutManager(std::make_unique<views::FlexLayout>())
-      ->SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
+  const auto* layout_provider = ChromeLayoutProvider::Get();
+  auto title_view = std::make_unique<views::BoxLayoutView>();
+  title_view->SetBetweenChildSpacing(
+      layout_provider->GetDistanceMetric(
+          views::DISTANCE_RELATED_CONTROL_HORIZONTAL) -
+      layout_provider->GetInsetsMetric(views::INSETS_VECTOR_IMAGE_BUTTON)
+          .right());
 
   auto back_button = views::CreateVectorImageButtonWithNativeTheme(
       std::move(callback),
@@ -72,9 +75,22 @@
           .SetTextContext(views::style::CONTEXT_DIALOG_TITLE)
           .SetHorizontalAlignment(gfx::ALIGN_LEFT)
           .Build());
-  title_->SetProperty(
-      views::kMarginsKey,
-      gfx::Insets::TLBR(0, kSpaceBetweenBackArrowAndTitle, 0, 0));
+  title_->SetMultiLine(true);
+  // This limits the SubpageView only works for standard the preferred width
+  // bubble.
+  int title_width =
+      layout_provider->GetDistanceMetric(
+          views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
+      layout_provider->GetInsetsMetric(views::INSETS_DIALOG).width() -
+      layout_provider->GetInsetsMetric(views::INSETS_DIALOG_TITLE).width();
+  views::Button* close_button = bubble_frame_view_->close_button();
+  if (close_button && close_button->GetVisible()) {
+    int close_button_width =
+        bubble_frame_view_->close_button()->width() +
+        layout_provider->GetDistanceMetric(views::DISTANCE_CLOSE_BUTTON_MARGIN);
+    title_width -= close_button_width;
+  }
+  title_->SetMaximumWidth(title_width);
 
   bubble_frame_view_->SetTitleView(std::move(title_view));
 }
diff --git a/chrome/browser/ui/views/controls/subpage_view.h b/chrome/browser/ui/views/controls/subpage_view.h
index 7e22ee5..c045338f 100644
--- a/chrome/browser/ui/views/controls/subpage_view.h
+++ b/chrome/browser/ui/views/controls/subpage_view.h
@@ -19,7 +19,8 @@
 
 DECLARE_ELEMENT_IDENTIFIER_VALUE(kSubpageViewId);
 
-// A sub-page View for the PageSwitcherView. This view contains:
+// A sub-page View for the PageSwitcherView in a standard preferred width
+// bubble. This view contains:
 //   * a header that has a page navigation back button, a title label, a close
 //   button
 //   * a content view
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 34b8cc5..16e2a27 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -1756,7 +1756,7 @@
     contents->GetPrimaryMainFrame()
         ->GetBrowserContext()
         ->GetPermissionController()
-        ->UnsubscribePermissionStatusChange(
+        ->UnsubscribeFromPermissionStatusChange(
             window_management_subscription_id_.value());
     window_management_subscription_id_.reset();
   }
@@ -2336,7 +2336,7 @@
   // It is safe to bind base::Unretained(this) because WebContents is
   // owned by BrowserView.
   window_management_subscription_id_ =
-      controller->SubscribePermissionStatusChange(
+      controller->SubscribeToPermissionStatusChange(
           blink::PermissionType::WINDOW_MANAGEMENT, rfh->GetProcess(), origin,
           base::BindRepeating(&BrowserView::UpdateWindowManagementPermission,
                               base::Unretained(this)));
diff --git a/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc b/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
index 1931607..dab94d1 100644
--- a/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/intent_picker_bubble_view_browsertest.cc
@@ -38,12 +38,7 @@
 
 class IntentPickerBrowserTest : public web_app::WebAppNavigationBrowserTest {
  public:
-  IntentPickerBrowserTest() {
-    std::vector<base::test::FeatureRef> disabled_features = {
-        // TODO(crbug.com/1001189): Stop disabling Paint Holding.
-        blink::features::kPaintHolding};
-    scoped_feature_list_.InitWithFeatures({}, disabled_features);
-  }
+  IntentPickerBrowserTest() = default;
 
   template <typename Action>
   testing::AssertionResult DoAndWaitForIntentPickerIconUpdate(Action action) {
@@ -116,9 +111,6 @@
     EXPECT_EQ(test_web_app_id(), app_info[0].launch_name);
     EXPECT_EQ(GetAppName(), app_info[0].display_name);
   }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Tests to do with the behavior of the intent picker icon in the omnibox. Does
@@ -127,7 +119,31 @@
 // separately in intent_chip_button_browsertest.cc.
 class IntentPickerIconBrowserTest
     : public IntentPickerBrowserTest,
-      public ::testing::WithParamInterface<std::string> {};
+      public ::testing::WithParamInterface<std::tuple<std::string, bool>> {
+ public:
+  // TODO(crbug.com/1001189): Stop disabling Paint Holding.
+  IntentPickerIconBrowserTest() {
+    feature_list_.InitWithFeaturesAndParameters(
+        apps::test::GetFeaturesToEnableLinkCapturingUX(
+            /*override_captures_by_default=*/IsLinkCapturingEnabled()),
+        {blink::features::kPaintHolding});
+  }
+
+  bool IsLinkCapturingEnabled() { return std::get<bool>(GetParam()); }
+
+  std::string rel() { return std::get<std::string>(GetParam()); }
+
+  bool IsDefaultOnEnabled() {
+#if BUILDFLAG(IS_CHROMEOS)
+    return false;
+#else
+    return IsLinkCapturingEnabled();
+#endif  // BUILDFLAG(IS_CHROMEOS)
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
 
 // Tests that clicking a link from a tabbed browser to outside the scope of an
 // installed app does not show the intent picker.
@@ -139,7 +155,7 @@
       https_server().GetURL(GetAppUrlHost(), GetOutOfScopeUrlPath());
   NavigateToLaunchingPage(browser());
   ASSERT_TRUE(ExpectLinkClickNotCapturedIntoAppBrowser(
-      browser(), out_of_scope_url, GetParam()));
+      browser(), out_of_scope_url, rel()));
 
   views::Button* intent_picker_view = GetIntentPickerIcon();
   EXPECT_FALSE(intent_picker_view->GetVisible());
@@ -159,6 +175,9 @@
 #endif
 IN_PROC_BROWSER_TEST_P(IntentPickerIconBrowserTest,
                        MAYBE_NavigationToInScopeLinkShowsIntentPicker) {
+  if (IsDefaultOnEnabled()) {
+    GTEST_SKIP() << "Default On will launch app by default";
+  }
   InstallTestWebApp();
 
   const GURL in_scope_url =
@@ -168,8 +187,8 @@
 
   base::RunLoop run_loop;
   tab_helper->SetIconUpdateCallbackForTesting(run_loop.QuitClosure());
-  ASSERT_TRUE(ExpectLinkClickNotCapturedIntoAppBrowser(browser(), in_scope_url,
-                                                       GetParam()));
+  ASSERT_TRUE(
+      ExpectLinkClickNotCapturedIntoAppBrowser(browser(), in_scope_url, rel()));
   run_loop.Run();
 
   views::Button* intent_picker_icon = GetIntentPickerIcon();
@@ -197,9 +216,9 @@
   views::Button* intent_picker_icon = GetIntentPickerIcon();
 
   // OpenNewTab opens a new tab and focus on the new tab.
-  OpenNewTab(in_scope_url, /*rel=*/GetParam());
+  OpenNewTab(in_scope_url, /*rel=*/rel());
   EXPECT_TRUE(intent_picker_icon->GetVisible());
-  OpenNewTab(out_of_scope_url, /*rel=*/GetParam());
+  OpenNewTab(out_of_scope_url, /*rel=*/rel());
   EXPECT_FALSE(intent_picker_icon->GetVisible());
 
   chrome::SelectPreviousTab(browser());
@@ -209,7 +228,7 @@
 }
 
 // Tests that the navigation in iframe doesn't affect intent picker icon
-IN_PROC_BROWSER_TEST_F(IntentPickerIconBrowserTest,
+IN_PROC_BROWSER_TEST_P(IntentPickerIconBrowserTest,
                        IframeNavigationDoesNotAffectIntentPicker) {
   InstallTestWebApp();
 
@@ -259,11 +278,11 @@
 
   OpenNewTab(in_scope_url);
   EXPECT_TRUE(intent_picker_icon->GetVisible());
-  ASSERT_TRUE(DoAndWaitForIntentPickerIconUpdate([this, redirect_url,
-                                                  out_of_scope_url] {
-    ClickLinkAndWaitForURL(GetWebContents(), redirect_url, out_of_scope_url,
-                           LinkTarget::SELF, GetParam());
-  }));
+  ASSERT_TRUE(DoAndWaitForIntentPickerIconUpdate(
+      [this, redirect_url, out_of_scope_url] {
+        ClickLinkAndWaitForURL(GetWebContents(), redirect_url, out_of_scope_url,
+                               LinkTarget::SELF, rel());
+      }));
   EXPECT_FALSE(intent_picker_icon->GetVisible());
 }
 
@@ -277,7 +296,7 @@
 #define MAYBE_DoNotShowIconAndBubbleOnServicePages \
   DoNotShowIconAndBubbleOnServicePages
 #endif
-IN_PROC_BROWSER_TEST_F(IntentPickerIconBrowserTest,
+IN_PROC_BROWSER_TEST_P(IntentPickerIconBrowserTest,
                        MAYBE_DoNotShowIconAndBubbleOnServicePages) {
   InstallTestWebApp();
 
@@ -311,7 +330,7 @@
 #else
 #define MAYBE_DoNotShowIconOnErrorPages DoNotShowIconOnErrorPages
 #endif  // BUILDFLAG(IS_MAC)
-IN_PROC_BROWSER_TEST_F(IntentPickerIconBrowserTest,
+IN_PROC_BROWSER_TEST_P(IntentPickerIconBrowserTest,
                        MAYBE_DoNotShowIconOnErrorPages) {
   InstallTestWebApp();
   InstallTestWebApp("www.google.com", "/");
@@ -348,7 +367,7 @@
 #else
 #define MAYBE_PushStateURLChangeTest PushStateURLChangeTest
 #endif
-IN_PROC_BROWSER_TEST_F(IntentPickerIconBrowserTest,
+IN_PROC_BROWSER_TEST_P(IntentPickerIconBrowserTest,
                        MAYBE_PushStateURLChangeTest) {
   // Note: The test page is served from embedded_test_server() as https_server()
   // always returns empty responses.
@@ -378,17 +397,29 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     IntentPickerIconBrowserTest,
-    testing::Values("", "noopener", "noreferrer", "nofollow"));
+    testing::Combine(testing::Values("", "noopener", "noreferrer", "nofollow"),
+#if BUILDFLAG(IS_CHROMEOS)
+                     testing::Values(false)),
+#else
+                     testing::Values(true, false)),
+#endif  // BUILDFLAG(IS_CHROMEOS)
+    [](const testing::TestParamInfo<std::tuple<std::string, bool>>& info) {
+      std::string test_name;
+      test_name = std::get<std::string>(info.param);
+      test_name.append(std::get<bool>(info.param) ? "DefaultOn" : "DefaultOff");
+      return test_name;
+    });
 
 class IntentPickerIconBrowserBubbleTest
     : public IntentPickerBrowserTest,
       public testing::WithParamInterface<bool> {
  public:
+  // TODO(crbug.com/1001189): Stop disabling Paint Holding.
   IntentPickerIconBrowserBubbleTest() {
     feature_list_.InitWithFeaturesAndParameters(
         apps::test::GetFeaturesToEnableLinkCapturingUX(
             /*override_captures_by_default=*/GetParam()),
-        {});
+        {blink::features::kPaintHolding});
   }
   bool LinkCapturingEnabledByDefault() const { return GetParam(); }
 
@@ -454,6 +485,11 @@
                            return info.param ? "DefaultOn" : "DefaultOff";
                          });
 
+// This test only works when link capturing is set to default off for desktop
+// platforms, as prerendering navigations are aborted during link captured app
+// launches. See LinkCapturingNavigationThrottle::MaybeCreate for more
+// information.
+// TODO(b/297256243): Investigate prerendering integration with link capturing.
 class IntentPickerIconPrerenderingBrowserTest
     : public IntentPickerIconBrowserTest {
  public:
@@ -522,7 +558,14 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     IntentPickerIconPrerenderingBrowserTest,
-    testing::Values("", "noopener", "noreferrer", "nofollow"));
+    testing::Combine(testing::Values("", "noopener", "noreferrer", "nofollow"),
+                     testing::Values(false)),
+    [](const testing::TestParamInfo<std::tuple<std::string, bool>>& info) {
+      std::string test_name;
+      test_name = std::get<std::string>(info.param);
+      test_name.append("DefaultOff");
+      return test_name;
+    });
 
 class IntentPickerIconFencedFrameBrowserTest
     : public IntentPickerIconBrowserTest {
@@ -566,4 +609,15 @@
 INSTANTIATE_TEST_SUITE_P(
     All,
     IntentPickerIconFencedFrameBrowserTest,
-    testing::Values("", "noopener", "noreferrer", "nofollow"));
+    testing::Combine(testing::Values("", "noopener", "noreferrer", "nofollow"),
+#if BUILDFLAG(IS_CHROMEOS)
+                     testing::Values(false)),
+#else
+                     testing::Values(true, false)),
+#endif
+    [](const testing::TestParamInfo<std::tuple<std::string, bool>>& info) {
+      std::string test_name;
+      test_name = std::get<std::string>(info.param);
+      test_name.append(std::get<bool>(info.param) ? "DefaultOn" : "DefaultOff");
+      return test_name;
+    });
diff --git a/chrome/browser/ui/views/permissions/chip_controller.cc b/chrome/browser/ui/views/permissions/chip_controller.cc
index 261896e0..b813608 100644
--- a/chrome/browser/ui/views/permissions/chip_controller.cc
+++ b/chrome/browser/ui/views/permissions/chip_controller.cc
@@ -473,6 +473,13 @@
   }
 }
 
+void ChipController::OnOcclusionStateChanged(bool occluded) {
+  if (GetBubbleWidget()) {
+    // Disable the prompt if it's occluded by a picture-in-picture window.
+    GetBubbleWidget()->GetContentsView()->SetEnabled(!occluded);
+  }
+}
+
 void ChipController::HideChip() {
   if (!chip_->GetVisible())
     return;
@@ -554,6 +561,7 @@
     parent_was_visible_when_activation_changed_ =
         prompt_bubble_widget->GetPrimaryWindowWidget()->IsVisible();
     prompt_bubble_widget->AddObserver(this);
+    occlusion_observation_.Observe(prompt_bubble_widget);
   }
 }
 
diff --git a/chrome/browser/ui/views/permissions/chip_controller.h b/chrome/browser/ui/views/permissions/chip_controller.h
index 76c1b020b..908302366 100644
--- a/chrome/browser/ui/views/permissions/chip_controller.h
+++ b/chrome/browser/ui/views/permissions/chip_controller.h
@@ -11,6 +11,8 @@
 #include "base/check_is_test.h"
 #include "base/functional/callback_helpers.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_observer.h"
+#include "chrome/browser/picture_in_picture/scoped_picture_in_picture_occlusion_observation.h"
 #include "chrome/browser/ui/views/location_bar/omnibox_chip_button.h"
 #include "components/permissions/permission_prompt.h"
 #include "components/permissions/permission_request_manager.h"
@@ -38,7 +40,8 @@
 class ChipController : public permissions::PermissionRequestManager::Observer,
                        public views::WidgetObserver,
                        public BubbleOwnerDelegate,
-                       public OmniboxChipButton::Observer {
+                       public OmniboxChipButton::Observer,
+                       public PictureInPictureOcclusionObserver {
  public:
   ChipController(Browser* browser_, OmniboxChipButton* chip_view);
 
@@ -79,6 +82,9 @@
   void OnExpandAnimationEnded() override;
   void OnCollapseAnimationEnded() override;
 
+  // PictureInPictureOcclusionObserver:
+  void OnOcclusionStateChanged(bool occluded) override;
+
   // Initializes the permission prompt model as well as the permission request
   // manager and observes the prompt bubble.
   void InitializePermissionPrompt(
@@ -237,6 +243,8 @@
   base::ScopedObservation<OmniboxChipButton, OmniboxChipButton::Observer>
       observation_{this};
 
+  ScopedPictureInPictureOcclusionObservation occlusion_observation_{this};
+
   base::WeakPtrFactory<ChipController> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ui/views/permissions/permission_chip_interactive_test.cc b/chrome/browser/ui/views/permissions/permission_chip_interactive_test.cc
index 5246a536..8b1c8a66 100644
--- a/chrome/browser/ui/views/permissions/permission_chip_interactive_test.cc
+++ b/chrome/browser/ui/views/permissions/permission_chip_interactive_test.cc
@@ -8,6 +8,8 @@
 #include "base/time/time.h"
 #include "chrome/browser/permissions/quiet_notification_permission_ui_config.h"
 #include "chrome/browser/permissions/quiet_notification_permission_ui_state.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window.h"
@@ -43,6 +45,7 @@
 #include "content/public/test/browser_test.h"
 #include "content/public/test/permissions_test_utils.h"
 #include "content/public/test/test_navigation_observer.h"
+#include "media/base/media_switches.h"
 #include "net/dns/mock_host_resolver.h"
 #include "permission_prompt_chip.h"
 #include "ui/accessibility/ax_action_data.h"
@@ -1645,3 +1648,71 @@
 
   EXPECT_TRUE(content::EvalJs(main_rfh, kCheckNotifications).value.GetBool());
 }
+
+class PictureInPictureOcclusionTrackingEnabledPermissionChipInteractiveTest
+    : public PermissionChipInteractiveTest {
+ public:
+  PictureInPictureOcclusionTrackingEnabledPermissionChipInteractiveTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {media::kPictureInPictureOcclusionTracking}, {});
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    PictureInPictureOcclusionTrackingEnabledPermissionChipInteractiveTest,
+    ShouldHideChipWhenOccludedByAPictureInPictureWindow) {
+  RequestPermission(permissions::RequestType::kGeolocation);
+  base::RunLoop().RunUntilIdle();
+
+  // Chip should be visible. Click it to open the prompt.
+  EXPECT_TRUE(GetChip()->GetVisible());
+  ClickOnChip(GetChip());
+
+  views::View* prompt_view =
+      GetChipController()->GetBubbleWidget()->GetContentsView();
+  ASSERT_NE(prompt_view, nullptr);
+
+  // Create a picture-in-picture widget that does not occlude the prompt.
+  gfx::Rect prompt_widget_bounds =
+      prompt_view->GetWidget()->GetWindowBoundsInScreen();
+  gfx::Rect non_occluding_bounds =
+      gfx::Rect(prompt_widget_bounds.right() + 1, 0, 100, 100);
+  views::Widget::InitParams init_params(views::Widget::InitParams::TYPE_WINDOW);
+  init_params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  init_params.bounds = non_occluding_bounds;
+  auto pip_widget = std::make_unique<views::Widget>(std::move(init_params));
+  pip_widget->Show();
+  PictureInPictureWindowManager::GetInstance()
+      ->GetOcclusionTracker()
+      ->OnPictureInPictureWidgetOpened(pip_widget.get());
+
+  // The prompt should be enabled, as it's not occluded.
+  EXPECT_TRUE(prompt_view->GetEnabled());
+
+  // Move the picture-in-picture window to occlude the prompt.
+  pip_widget->SetBounds(prompt_widget_bounds);
+
+  // The prompt should be disabled. We may need to wait for that to happen.
+  if (prompt_view->GetEnabled()) {
+    base::RunLoop wait_loop;
+    auto subscription =
+        prompt_view->AddEnabledChangedCallback(wait_loop.QuitClosure());
+    wait_loop.Run();
+  }
+  EXPECT_FALSE(prompt_view->GetEnabled());
+
+  // Move the picture-in-picture window to no longer occlude the prompt.
+  pip_widget->SetBounds(non_occluding_bounds);
+
+  // The prompt should be enabled again. We may need to wait for that to happen.
+  if (!prompt_view->GetEnabled()) {
+    base::RunLoop wait_loop;
+    auto subscription =
+        prompt_view->AddEnabledChangedCallback(wait_loop.QuitClosure());
+    wait_loop.Run();
+  }
+  EXPECT_TRUE(prompt_view->GetEnabled());
+}
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
index 8addff3..886aa57 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble.cc
@@ -48,6 +48,7 @@
 
   disallowed_custom_cursors_scope_ =
       delegate()->GetAssociatedWebContents()->CreateDisallowCustomCursorScope();
+  occlusion_observation_.Observe(prompt_bubble->GetWidget());
 }
 
 void PermissionPromptBubble::CleanUpPromptBubble() {
@@ -141,3 +142,10 @@
   return static_cast<const PermissionPromptBubbleBaseView*>(
       prompt_bubble_tracker_.view());
 }
+
+void PermissionPromptBubble::OnOcclusionStateChanged(bool occluded) {
+  // Disable the prompt if it's occluded by a picture-in-picture window.
+  if (GetPromptBubble()) {
+    GetPromptBubble()->GetWidget()->GetContentsView()->SetEnabled(!occluded);
+  }
+}
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble.h b/chrome/browser/ui/views/permissions/permission_prompt_bubble.h
index a1bd0b6..278ebaf 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble.h
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble.h
@@ -6,6 +6,8 @@
 #define CHROME_BROWSER_UI_VIEWS_PERMISSIONS_PERMISSION_PROMPT_BUBBLE_H_
 
 #include "chip_controller.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_observer.h"
+#include "chrome/browser/picture_in_picture/scoped_picture_in_picture_occlusion_observation.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view.h"
 #include "chrome/browser/ui/views/permissions/permission_prompt_desktop.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -17,7 +19,8 @@
 }
 
 class PermissionPromptBubble : public PermissionPromptDesktop,
-                               public views::WidgetObserver {
+                               public views::WidgetObserver,
+                               public PictureInPictureOcclusionObserver {
  public:
   PermissionPromptBubble(Browser* browser,
                          content::WebContents* web_contents,
@@ -41,6 +44,9 @@
 
   views::Widget* GetPromptBubbleWidgetForTesting() override;
 
+  // PictureInPictureOcclusionObserver:
+  void OnOcclusionStateChanged(bool occluded) override;
+
  private:
   PermissionPromptBubbleBaseView* GetPromptBubble();
   const PermissionPromptBubbleBaseView* GetPromptBubble() const;
@@ -55,6 +61,8 @@
 
   base::ScopedClosureRunner disallowed_custom_cursors_scope_;
 
+  ScopedPictureInPictureOcclusionObservation occlusion_observation_{this};
+
   base::WeakPtrFactory<PermissionPromptBubble> weak_factory_{this};
 };
 
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.cc
index e751df8..3181edc 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.cc
@@ -7,30 +7,17 @@
 #include <memory>
 #include <utility>
 
-#include "base/metrics/histogram_functions.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.h"
-#include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
-#include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h"
-#include "chrome/common/accessibility/read_anything_constants.h"
-#include "chrome/common/webui_url_constants.h"
-#include "chrome/grit/generated_resources.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/gfx/geometry/geometry_export.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/separator.h"
 #include "ui/views/layout/flex_layout.h"
 
-using SidePanelWebUIViewT_ReadAnythingUntrustedUI =
-    SidePanelWebUIViewT<ReadAnythingUntrustedUI>;
-BEGIN_TEMPLATE_METADATA(SidePanelWebUIViewT_ReadAnythingUntrustedUI,
-                        SidePanelWebUIViewT)
-END_METADATA
-
 ReadAnythingContainerView::ReadAnythingContainerView(
     ReadAnythingCoordinator* coordinator,
     std::unique_ptr<ReadAnythingToolbarView> toolbar,
-    std::unique_ptr<SidePanelWebUIViewT<ReadAnythingUntrustedUI>> content)
+    std::unique_ptr<ReadAnythingSidePanelWebView> content)
     : coordinator_(std::move(coordinator)) {
   // Create and set a FlexLayout LayoutManager for this view, set background.
   auto layout = std::make_unique<views::FlexLayout>();
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.h
index 9841c18..89e5c78 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.h
@@ -9,13 +9,12 @@
 
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_model.h"
-#include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/views/controls/separator.h"
 #include "ui/views/view.h"
 
 class ReadAnythingToolbarView;
-class ReadAnythingUntrustedUI;
+class ReadAnythingSidePanelWebView;
 
 ///////////////////////////////////////////////////////////////////////////////
 // ReadAnythingContainerView
@@ -33,7 +32,7 @@
   ReadAnythingContainerView(
       ReadAnythingCoordinator* coordinator,
       std::unique_ptr<ReadAnythingToolbarView> toolbar,
-      std::unique_ptr<SidePanelWebUIViewT<ReadAnythingUntrustedUI>> content);
+      std::unique_ptr<ReadAnythingSidePanelWebView> content);
   ReadAnythingContainerView(const ReadAnythingContainerView&) = delete;
   ReadAnythingContainerView& operator=(const ReadAnythingContainerView&) =
       delete;
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
index 8aa8246..e11edc7 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 #include <string>
-#include <utility>
 #include <vector>
 
 #include "base/functional/bind.h"
@@ -22,13 +21,14 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.h"
+#include "chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.h"
+#include "chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h"
-#include "chrome/browser/ui/views/side_panel/side_panel_entry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_prefs.h"
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h"
-#include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/language/core/browser/language_model.h"
@@ -36,8 +36,6 @@
 #include "components/language/core/common/locale_util.h"
 #include "ui/accessibility/accessibility_features.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/base/models/combobox_model.h"
 
 namespace {
 
@@ -55,10 +53,6 @@
 
 }  // namespace
 
-using SidePanelWebUIViewT_ReadAnythingUntrustedUI =
-    SidePanelWebUIViewT<ReadAnythingUntrustedUI>;
-DECLARE_TEMPLATE_METADATA(SidePanelWebUIViewT_ReadAnythingUntrustedUI,
-                          SidePanelWebUIViewT);
 
 ReadAnythingCoordinator::ReadAnythingCoordinator(Browser* browser)
     : BrowserUserData<ReadAnythingCoordinator>(*browser),
@@ -249,17 +243,7 @@
 std::unique_ptr<views::View> ReadAnythingCoordinator::CreateContainerView() {
   Browser* browser = &GetBrowser();
   auto web_view =
-      std::make_unique<SidePanelWebUIViewT<ReadAnythingUntrustedUI>>(
-          /* on_show_cb= */ base::RepeatingClosure(),
-          /* close_cb= */ base::RepeatingClosure(),
-          /* contents_wrapper= */
-          std::make_unique<BubbleContentsWrapperT<ReadAnythingUntrustedUI>>(
-              /* webui_url= */ GURL(
-                  chrome::kChromeUIUntrustedReadAnythingSidePanelURL),
-              /* browser_context= */ browser->profile(),
-              /* task_manager_string_id= */ IDS_READING_MODE_TITLE,
-              /* webui_resizes_host= */ false,
-              /* esc_closes_ui= */ false));
+      std::make_unique<ReadAnythingSidePanelWebView>(browser->profile());
 
   if (features::IsReadAnythingWebUIToolbarEnabled()) {
     return std::move(web_view);
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.cc
index 51aed5f1..8e0f6ed 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_container_view.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h"
+#include "chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.h"
 #include "chrome/browser/ui/views/side_panel/read_anything/read_anything_toolbar_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
 #include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
@@ -84,15 +85,8 @@
 
 std::unique_ptr<views::View>
 ReadAnythingSidePanelController::CreateContainerView() {
-  auto web_view =
-      std::make_unique<SidePanelWebUIViewT<ReadAnythingUntrustedUI>>(
-          base::RepeatingClosure(), base::RepeatingClosure(),
-          std::make_unique<BubbleContentsWrapperT<ReadAnythingUntrustedUI>>(
-              GURL(chrome::kChromeUIUntrustedReadAnythingSidePanelURL),
-              Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
-              IDS_READING_MODE_TITLE,
-              /*webui_resizes_host=*/false,
-              /*esc_closes_ui=*/false));
+  auto web_view = std::make_unique<ReadAnythingSidePanelWebView>(
+      Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
 
   if (features::IsReadAnythingWebUIToolbarEnabled()) {
     return std::move(web_view);
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_navigation_throttle.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_navigation_throttle.cc
index a480c4c..390c1e86 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_navigation_throttle.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_navigation_throttle.cc
@@ -40,7 +40,8 @@
   const auto& url = navigation_handle()->GetURL();
   // Only cancel the request if a user initiated it. Otherwise when reading mode
   // opens, it will hit this block and cause a stack overflow.
-  if (url != chrome::kChromeUIUntrustedReadAnythingSidePanelURL ||
+  if (url.GetWithEmptyPath() !=
+          chrome::kChromeUIUntrustedReadAnythingSidePanelURL ||
       !ui::PageTransitionCoreTypeIs(navigation_handle()->GetPageTransition(),
                                     ui::PAGE_TRANSITION_TYPED)) {
     return content::NavigationThrottle::PROCEED;
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.cc
new file mode 100644
index 0000000..9142a956
--- /dev/null
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.cc
@@ -0,0 +1,40 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
+#include "chrome/common/webui_url_constants.h"
+#include "chrome/grit/generated_resources.h"
+#include "content/public/browser/context_menu_params.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+
+using SidePanelWebUIViewT_ReadAnythingUntrustedUI =
+    SidePanelWebUIViewT<ReadAnythingUntrustedUI>;
+BEGIN_TEMPLATE_METADATA(SidePanelWebUIViewT_ReadAnythingUntrustedUI,
+                        SidePanelWebUIViewT);
+
+END_METADATA
+
+ReadAnythingSidePanelWebView::ReadAnythingSidePanelWebView(Profile* profile)
+    : SidePanelWebUIViewT(
+          base::RepeatingClosure(),
+          base::RepeatingClosure(),
+          std::make_unique<BubbleContentsWrapperT<ReadAnythingUntrustedUI>>(
+              GURL(chrome::kChromeUIUntrustedReadAnythingSidePanelURL),
+              profile,
+              IDS_READING_MODE_TITLE,
+              /*webui_resizes_host=*/false,
+              /*esc_closes_ui=*/false)) {}
+
+bool ReadAnythingSidePanelWebView::HandleContextMenu(
+    content::RenderFrameHost& render_frame_host,
+    const content::ContextMenuParams& params) {
+  return false;
+}
+
+ReadAnythingSidePanelWebView::~ReadAnythingSidePanelWebView() = default;
+
+BEGIN_METADATA(ReadAnythingSidePanelWebView)
+END_METADATA
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.h
new file mode 100644
index 0000000..43b0b4a5
--- /dev/null
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_web_view.h
@@ -0,0 +1,31 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_READ_ANYTHING_READ_ANYTHING_SIDE_PANEL_WEB_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_READ_ANYTHING_READ_ANYTHING_SIDE_PANEL_WEB_VIEW_H_
+
+#include "chrome/browser/ui/views/side_panel/side_panel_web_ui_view.h"
+#include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_ui.h"
+
+class ReadAnythingSidePanelWebView
+    : public SidePanelWebUIViewT<ReadAnythingUntrustedUI> {
+  using SidePanelWebUIViewT_ReadAnythingUntrustedUI =
+      SidePanelWebUIViewT<ReadAnythingUntrustedUI>;
+  METADATA_HEADER(ReadAnythingSidePanelWebView,
+                  SidePanelWebUIViewT_ReadAnythingUntrustedUI)
+ public:
+  explicit ReadAnythingSidePanelWebView(Profile* profile);
+  ReadAnythingSidePanelWebView(const ReadAnythingSidePanelWebView&) = delete;
+  ReadAnythingSidePanelWebView& operator=(const ReadAnythingSidePanelWebView&) =
+      delete;
+  ~ReadAnythingSidePanelWebView() override;
+
+  bool HandleContextMenu(content::RenderFrameHost& render_frame_host,
+                         const content::ContextMenuParams& params) override;
+
+ private:
+  base::WeakPtrFactory<ReadAnythingSidePanelWebView> weak_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_SIDE_PANEL_READ_ANYTHING_READ_ANYTHING_SIDE_PANEL_WEB_VIEW_H_
diff --git a/chrome/browser/ui/views/side_panel/side_panel_interactive_uitest.cc b/chrome/browser/ui/views/side_panel/side_panel_interactive_uitest.cc
index f4068ecf..fa00fba 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_interactive_uitest.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_interactive_uitest.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
+#include "chrome/browser/ui/side_panel/side_panel_entry_key.h"
 #include "chrome/browser/ui/side_search/side_search_config.h"
 #include "chrome/browser/ui/toolbar/app_menu_model.h"
 #include "chrome/browser/ui/toolbar/bookmark_sub_menu_model.h"
@@ -29,6 +30,7 @@
 #include "chrome/test/interaction/interactive_browser_test.h"
 #include "chrome/test/interaction/tracked_element_webcontents.h"
 #include "chrome/test/interaction/webcontents_interaction_test_util.h"
+#include "components/reading_list/core/reading_list_entry.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/accessibility_features.h"
@@ -63,6 +65,8 @@
 IN_PROC_BROWSER_TEST_F(SidePanelInteractiveTest, SidePanelNotShownOnPwa) {
   DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTabElementId);
   GURL second_tab_url("https://test.com");
+  auto* coordinator =
+      SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser());
 
   RunTestSequence(
       // Add a second tab to the tab strip
@@ -79,22 +83,23 @@
                         ->GetActiveWebContents()
                         ->GetLastCommittedURL();
                   }),
-                  second_tab_url));
-
-  // Register side search entry to second_tab.
-  content::WebContents* active_contents =
-      browser()->tab_strip_model()->GetActiveWebContents();
-  auto* registry = SidePanelRegistry::Get(active_contents);
-  registry->Register(std::make_unique<SidePanelEntry>(
-      SidePanelEntry::Id::kSideSearch, u"testing1", ui::ImageModel(),
-      base::BindRepeating([]() { return std::make_unique<views::View>(); })));
-
-  // Toggle side search entry to show on second_tab.
-  auto* coordinator =
-      SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser());
-  coordinator->Show(SidePanelEntry::Id::kSideSearch);
-  EXPECT_EQ(coordinator->GetComboboxDisplayedEntryIdForTesting(),
-            SidePanelEntry::Id::kSideSearch);
+                  second_tab_url),
+      Do(base::BindLambdaForTesting([=]() {
+        content::WebContents* active_contents =
+            browser()->tab_strip_model()->GetActiveWebContents();
+        auto* registry = SidePanelRegistry::Get(active_contents);
+        registry->Register(std::make_unique<SidePanelEntry>(
+            SidePanelEntry::Id::kCustomizeChrome, u"testing1", ui::ImageModel(),
+            base::BindRepeating(
+                []() { return std::make_unique<views::View>(); })));
+        coordinator->Show(SidePanelEntry::Id::kCustomizeChrome);
+      })),
+      WaitForShow(kSidePanelElementId), FlushEvents(),
+      CheckResult(base::BindLambdaForTesting([coordinator]() {
+                    return coordinator->IsSidePanelEntryShowing(
+                        SidePanelEntryKey(SidePanelEntryId::kCustomizeChrome));
+                  }),
+                  true));
 
   // Install an app using second_tab_url.
   auto app_id = web_app::test::InstallDummyWebApp(browser()->profile(),
@@ -113,6 +118,11 @@
 }
 
 IN_PROC_BROWSER_TEST_F(SidePanelInteractiveTest, ToggleSidePanelVisibility) {
+  if (base::FeatureList::IsEnabled(features::kSidePanelPinning)) {
+    GTEST_SKIP()
+        << "Default sidepanel button is not present with pinning feature.";
+  }
+
   RunTestSequence(
       // Ensure the side panel isn't open
       EnsureNotPresent(kSidePanelElementId),
@@ -132,6 +142,11 @@
 
 IN_PROC_BROWSER_TEST_F(SidePanelInteractiveTest,
                        SwitchBetweenDifferentEntries) {
+  if (base::FeatureList::IsEnabled(features::kSidePanelPinning)) {
+    GTEST_SKIP()
+        << "Default sidepanel button is not present with pinning feature.";
+  }
+
   DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kBookmarksWebContentsId);
   DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kReadLaterWebContentsId);
 
@@ -160,6 +175,11 @@
 
 IN_PROC_BROWSER_TEST_F(SidePanelInteractiveTest,
                        StaysOpenOnTabSwitchWithActiveGlobalEntry) {
+  if (base::FeatureList::IsEnabled(features::kSidePanelPinning)) {
+    GTEST_SKIP()
+        << "Default sidepanel button is not present with pinning feature.";
+  }
+
   DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTabElementId);
 
   RunTestSequence(
@@ -185,6 +205,14 @@
 
 IN_PROC_BROWSER_TEST_F(SidePanelInteractiveTest,
                        ReopensToLastActiveGlobalEntry) {
+  // This test does not make sense with pinned feature the default toolbar
+  // sidepanel button is not present to show the last active global entry.
+  // A particular sidepanel has to be opened.
+  if (base::FeatureList::IsEnabled(features::kSidePanelPinning)) {
+    GTEST_SKIP()
+        << "Default sidepanel button is not present with pinning feature.";
+  }
+
   RunTestSequence(
       // Ensure the side panel isn't open
       EnsureNotPresent(kSidePanelElementId),
@@ -281,6 +309,12 @@
                              is_active));
   }
 
+  auto ShowSidePanelForKey(SidePanelEntryKey key) {
+    return Do(base::BindLambdaForTesting([=]() {
+      SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser())->Show(key);
+    }));
+  }
+
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 };
@@ -401,3 +435,84 @@
       // Verify the bookmarks pinned toolbar button is not highlighted.
       CheckPinnedToolbarActionsContainerChildInkDropState(0, false));
 }
+
+IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
+                       ToggleSidePanelVisibility) {
+  constexpr char kBookmarksButton[] = "bookmarks_button";
+  RunTestSequence(
+      // Ensure the side panel isn't open
+      EnsureNotPresent(kSidePanelElementId),
+      // Open bookmarks sidepanel
+      OpenBookmarksSidePanel(), WaitForShow(kSidePanelElementId), FlushEvents(),
+      WaitForShow(kPinnedToolbarActionsContainerElementId), FlushEvents(),
+      // Pin the button
+      CheckPinButtonToggleState(false),
+      PressButton(kSidePanelPinButtonElementId),
+      CheckActionPinnedToToolbar(kActionSidePanelShowBookmarks, true),
+      EnsurePresent(kPinnedToolbarActionsContainerElementId),
+      NameChildViewByType<
+          PinnedToolbarActionsContainer::PinnedActionToolbarButton>(
+          kPinnedToolbarActionsContainerElementId, kBookmarksButton),
+      WaitForShow(kBookmarksButton), FlushEvents(),
+      // Toggle side panel
+      PressButton(kBookmarksButton), WaitForHide(kSidePanelElementId));
+}
+
+IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
+                       SwitchBetweenDifferentEntries) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kBookmarksWebContentsId);
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kReadLaterWebContentsId);
+  auto* coordinator =
+      SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser());
+
+  RunTestSequence(
+      // Ensure the side panel isn't open
+      EnsureNotPresent(kSidePanelElementId),
+      // Click the toolbar button to open the side panel
+      OpenBookmarksSidePanel(), WaitForShow(kSidePanelElementId), FlushEvents(),
+      InstrumentNonTabWebView(kBookmarksWebContentsId,
+                              kBookmarkSidePanelWebViewElementId),
+      FlushEvents(),
+      CheckResult(base::BindLambdaForTesting([coordinator]() {
+                    return coordinator->IsSidePanelEntryShowing(
+                        SidePanelEntryKey(SidePanelEntryId::kBookmarks));
+                  }),
+                  true),
+      // Switch to the reading list entry.
+      ShowSidePanelForKey(SidePanelEntryKey(SidePanelEntry::Id::kReadingList)),
+      InstrumentNonTabWebView(kReadLaterWebContentsId,
+                              kReadLaterSidePanelWebViewElementId),
+      FlushEvents(),
+      CheckResult(base::BindLambdaForTesting([coordinator]() {
+                    return coordinator->IsSidePanelEntryShowing(
+                        SidePanelEntryKey(SidePanelEntryId::kReadingList));
+                  }),
+                  true),
+      // Click on the close button to dismiss the side panel
+      PressButton(kSidePanelCloseButtonElementId),
+      WaitForHide(kSidePanelElementId));
+}
+
+IN_PROC_BROWSER_TEST_F(PinnedSidePanelInteractiveTest,
+                       StaysOpenOnTabSwitchWithActiveGlobalEntry) {
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTabElementId);
+
+  RunTestSequence(
+      // Add a second tab to the tab strip
+      AddInstrumentedTab(kSecondTabElementId, GURL(url::kAboutBlankURL)),
+      CheckResult(base::BindLambdaForTesting([this]() {
+                    return browser()->tab_strip_model()->active_index();
+                  }),
+                  testing::Eq(1)),
+      // Ensure the side panel isn't open
+      EnsureNotPresent(kSidePanelElementId),
+      // Click the toolbar button to open the side panel
+      OpenBookmarksSidePanel(), WaitForShow(kSidePanelElementId), FlushEvents(),
+      // Switch to the first tab again with the side panel open
+      SelectTab(kTabStripElementId, 0),
+      // Ensure the side panel is still visible
+      CheckViewProperty(kSidePanelElementId, &views::View::GetVisible, true),
+      // Click on the close button to dismiss the side panel
+      PressButton(kSidePanelCloseButtonElementId),
+      WaitForHide(kSidePanelElementId));
+}
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 9b110ff..f618f50 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
@@ -4575,6 +4575,9 @@
 #if BUILDFLAG(IS_CHROMEOS)
   // TODO(crbug.com/1357905): Update test driver to work with new UI.
   disabled_features.push_back(apps::features::kLinkCapturingUiUpdate);
+#else
+  // TOOD(b/313492499): Update test driver to work with new intent picker UI.
+  disabled_features.push_back(features::kDesktopPWAsLinkCapturing);
 #endif  // BUILDFLAG(IS_CHROMEOS)
   scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
 }
diff --git a/chrome/browser/ui/web_applications/pwa_mixed_content_browsertest.cc b/chrome/browser/ui/web_applications/pwa_mixed_content_browsertest.cc
index 29762fd..8d51f837 100644
--- a/chrome/browser/ui/web_applications/pwa_mixed_content_browsertest.cc
+++ b/chrome/browser/ui/web_applications/pwa_mixed_content_browsertest.cc
@@ -170,7 +170,7 @@
   const GURL app_url = GetMixedContentAppURL();
   const webapps::AppId app_id = InstallPWA(app_url);
 
-  NavigateToURLAndWait(browser(), GetMixedContentAppURL());
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), GetMixedContentAppURL()));
   content::WebContents* tab_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   ASSERT_EQ(tab_contents->GetLastCommittedURL(), GetMixedContentAppURL());
@@ -220,7 +220,7 @@
   const GURL app_url = GetSecureIFrameAppURL();
   const webapps::AppId app_id = InstallPWA(app_url);
 
-  NavigateToURLAndWait(browser(), app_url);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
   CheckMixedContentFailedToLoad(browser());
 
   Browser* const app_browser = ReparentWebContentsIntoAppBrowser(
diff --git a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
index f3651fb9..3f249c2 100644
--- a/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
+++ b/chrome/browser/ui/web_applications/test/web_app_browsertest_util.cc
@@ -94,7 +94,7 @@
 }  // namespace
 
 webapps::AppId InstallWebAppFromPage(Browser* browser, const GURL& app_url) {
-  NavigateToURLAndWait(browser, app_url);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, app_url));
 
   webapps::AppId app_id;
   base::RunLoop run_loop;
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 4dcd1cef..96a661e1 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -1373,7 +1373,7 @@
   const GURL app_url = GetSecureAppURL();
   const webapps::AppId app_id = InstallPWA(app_url);
 
-  NavigateToURLAndWait(browser(), app_url);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
   content::WebContents* tab_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   ASSERT_EQ(tab_contents->GetLastCommittedURL(), app_url);
@@ -1390,10 +1390,10 @@
   std::unique_ptr<OsIntegrationTestOverrideImpl::BlockingRegistration>
       registration = OsIntegrationTestOverrideImpl::OverrideForTesting();
 
-  NavigateToURLAndWait(
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(
       browser(),
       https_server()->GetURL(
-          "/banners/manifest_test_page.html?manifest=manifest_one_icon.json"));
+          "/banners/manifest_test_page.html?manifest=manifest_one_icon.json")));
 
   // Wait for OS hooks and installation to complete and the app to launch.
   base::RunLoop run_loop_install;
@@ -1699,7 +1699,7 @@
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, ReparentLastBrowserTab) {
   const GURL app_url = GetSecureAppURL();
   const webapps::AppId app_id = InstallPWA(app_url);
-  NavigateToURLAndWait(browser(), app_url);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
 
   Browser* const app_browser = ReparentWebAppForActiveTab(browser());
   ASSERT_EQ(app_browser->app_controller()->app_id(), app_id);
@@ -1824,7 +1824,7 @@
   const webapps::AppId app_id = InstallWebApp(std::move(web_app_info));
 
   base::HistogramTester tester;
-  NavigateToURLAndWait(browser(), app_url);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
   content::WebContents* tab_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   ASSERT_EQ(tab_contents->GetLastCommittedURL(), app_url);
@@ -2533,7 +2533,7 @@
   const GURL app_url = GetSecureAppURL();
   InstallPWA(app_url);
 
-  NavigateToURLAndWait(browser(), app_url);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
   content::WebContents* tab_contents =
       browser()->tab_strip_model()->GetActiveWebContents();
   ASSERT_EQ(tab_contents->GetLastCommittedURL(), app_url);
diff --git a/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc b/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
index ddaca20..9825e215 100644
--- a/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_controller_browsertest.cc
@@ -141,7 +141,7 @@
   EXPECT_TRUE(new_contents);
   WaitForLoadStop(new_contents);
 
-  EXPECT_EQ(url, new_contents->GetLastCommittedURL());
+  EXPECT_EQ(url, contents->GetController().GetLastCommittedEntry()->GetURL());
   EXPECT_EQ(
       content::PAGE_TYPE_NORMAL,
       new_contents->GetController().GetLastCommittedEntry()->GetPageType());
@@ -167,7 +167,7 @@
     const GURL& url) {
   auto* manager = webapps::TestAppBannerManagerDesktop::FromWebContents(
       browser->tab_strip_model()->GetActiveWebContents());
-  NavigateToURLAndWait(browser, url);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, url));
   return manager->WaitForInstallableCheck();
 }
 
diff --git a/chrome/browser/ui/web_applications/web_app_dark_mode_browsertest.cc b/chrome/browser/ui/web_applications/web_app_dark_mode_browsertest.cc
index c477002e..89fd9e1 100644
--- a/chrome/browser/ui/web_applications/web_app_dark_mode_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_dark_mode_browsertest.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/test/base/ui_test_utils.h"
 #include "components/embedder_support/switches.h"
 #include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
 #include "content/public/test/browser_test.h"
@@ -240,7 +241,7 @@
     UpdateAwaiter update_awaiter(provider.install_manager());
 
     serve_token = false;
-    NavigateToURLAndWait(browser(), GURL(kTestWebAppUrl));
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kTestWebAppUrl)));
 
     update_awaiter.AwaitUpdate();
   }
diff --git a/chrome/browser/ui/web_applications/web_app_launch_handler_browsertest.cc b/chrome/browser/ui/web_applications/web_app_launch_handler_browsertest.cc
index 416dde0..b4533eb9 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_handler_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_handler_browsertest.cc
@@ -205,7 +205,7 @@
   // start_url again.
   {
     GURL alt_url = embedded_test_server()->GetURL("/web_apps/basic.html");
-    NavigateToURLAndWait(app_browser, alt_url);
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(app_browser, alt_url));
     EXPECT_EQ(app_web_contents->GetLastCommittedURL(), alt_url);
 
     Browser* app_browser_2 = LaunchWebAppBrowserAndWait(app_id);
@@ -222,7 +222,7 @@
 
     chrome::NewTab(browser());
     EXPECT_EQ(browser()->tab_strip_model()->count(), 2);
-    NavigateToURLAndWait(browser(), start_url);
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), start_url));
     ReparentWebAppForActiveTab(browser());
     EXPECT_EQ(browser()->tab_strip_model()->count(), 1);
 
@@ -642,7 +642,7 @@
     UpdateAwaiter update_awaiter(provider.install_manager());
 
     serve_token = false;
-    NavigateToURLAndWait(browser(), GURL(kTestWebAppUrl));
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kTestWebAppUrl)));
 
     update_awaiter.AwaitUpdate();
   }
diff --git a/chrome/browser/ui/webui/ash/settings/pages/a11y/DEPS b/chrome/browser/ui/webui/ash/settings/pages/a11y/DEPS
new file mode 100644
index 0000000..02f2c7f
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/settings/pages/a11y/DEPS
@@ -0,0 +1,5 @@
+specific_include_rules = {
+  'switch_access_handler.cc': [
+    "+ash/accessibility/accessibility_controller_impl.h",
+  ]
+}
\ No newline at end of file
diff --git a/chrome/browser/ui/webui/ash/settings/pages/a11y/switch_access_handler.cc b/chrome/browser/ui/webui/ash/settings/pages/a11y/switch_access_handler.cc
index bdcdfc29..4c58b10 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/a11y/switch_access_handler.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/a11y/switch_access_handler.cc
@@ -6,9 +6,9 @@
 
 #include <memory>
 
+#include "ash/accessibility/accessibility_controller_impl.h"
 #include "ash/constants/ash_constants.h"
 #include "ash/constants/ash_pref_names.h"
-#include "ash/public/cpp/accessibility_controller.h"
 #include "base/functional/bind.h"
 #include "base/no_destructor.h"
 #include "base/values.h"
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc b/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
index fc2c009..3b0e4279 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
@@ -1756,6 +1756,8 @@
        IDS_SETTINGS_CUSTOMIZE_BUTTONS_RENAMING_DIALOG_INPUT_LABEL},
       {"buttonRemappingDialogCancelLabel",
        IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CANCEL},
+      {"buttonRemappingDialogChangeLabel",
+       IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CHANGE},
       {"buttonRemappingDialogDescription",
        IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_DESCRIPTION},
       {"buttonRemappingDialogSaveLabel",
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler_unittest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler_unittest.cc
index 6ea53ad..2df39e6 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler_unittest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_handler_unittest.cc
@@ -110,7 +110,7 @@
       base::BindRepeating([](content::BrowserContext* context)
                               -> std::unique_ptr<KeyedService> {
         return std::make_unique<
-            testing::NiceMock<MockOptimizationGuideKeyedService>>(context);
+            testing::NiceMock<MockOptimizationGuideKeyedService>>();
       }));
   profile_builder.SetSharedURLLoaderFactory(url_loader_factory);
   auto profile = profile_builder.Build();
diff --git a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_interactive_uitest.cc b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_interactive_uitest.cc
index 493a093..38d4f37 100644
--- a/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_interactive_uitest.cc
+++ b/chrome/browser/ui/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_interactive_uitest.cc
@@ -194,7 +194,7 @@
         context, base::BindRepeating([](content::BrowserContext* context)
                                          -> std::unique_ptr<KeyedService> {
           return std::make_unique<
-              testing::NiceMock<MockOptimizationGuideKeyedService>>(context);
+              testing::NiceMock<MockOptimizationGuideKeyedService>>();
         }));
   }
 
diff --git a/chrome/browser/v8_compile_hints/v8_compile_hints_tab_helper_unittest.cc b/chrome/browser/v8_compile_hints/v8_compile_hints_tab_helper_unittest.cc
index 3b84635..f10b316 100644
--- a/chrome/browser/v8_compile_hints/v8_compile_hints_tab_helper_unittest.cc
+++ b/chrome/browser/v8_compile_hints/v8_compile_hints_tab_helper_unittest.cc
@@ -58,7 +58,7 @@
                   base::BindRepeating([](content::BrowserContext* context)
                                           -> std::unique_ptr<KeyedService> {
                     return std::make_unique<
-                        NiceMock<MockOptimizationGuideKeyedService>>(context);
+                        NiceMock<MockOptimizationGuideKeyedService>>();
                   })));
   V8CompileHintsTabHelper::MaybeCreateForWebContents(web_contents());
   tab_helper_ = V8CompileHintsTabHelper::FromWebContents(web_contents());
diff --git a/chrome/browser/web_applications/commands/compute_app_size_command_browsertest.cc b/chrome/browser/web_applications/commands/compute_app_size_command_browsertest.cc
index 8acf952..3f5463da 100644
--- a/chrome/browser/web_applications/commands/compute_app_size_command_browsertest.cc
+++ b/chrome/browser/web_applications/commands/compute_app_size_command_browsertest.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 "base/test/run_until.h"
 #include "base/test/test_future.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
@@ -9,6 +10,7 @@
 #include "chrome/browser/web_applications/commands/compute_app_size_command.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
+#include "chrome/test/base/ui_test_utils.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
@@ -22,7 +24,7 @@
 
   GURL app_url = embedded_test_server()->GetURL("/web_apps/basic.html");
   webapps::AppId app_id = InstallWebAppFromPage(browser(), app_url);
-  NavigateToURLAndWait(browser(), app_url);
+  EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), app_url));
 
   const char* script = R"(
         localStorage.setItem('data', 'data'.repeat(5000));
@@ -38,16 +40,13 @@
   // renderer process. As updates to quota manager usage occurs on a different
   // sequence to this procress, it requires multiple events. Due to all of this,
   // we are resorting to polling for non-zero values.
-
-  while (true) {
-    base::test::TestFuture<absl::optional<ComputeAppSizeCommand::Size>>
-        app_size;
-    provider().scheduler().ComputeAppSize(app_id, app_size.GetCallback());
-    if (app_size.Get().value().app_size_in_bytes != 0u &&
-        app_size.Get().value().data_size_in_bytes != 0u) {
-      break;
-    }
-  }
+  base::test::TestFuture<absl::optional<ComputeAppSizeCommand::Size>>
+      app_size_future;
+  provider().scheduler().ComputeAppSize(app_id, app_size_future.GetCallback());
+  EXPECT_TRUE(base::test::RunUntil([&]() {
+    return app_size_future.Get().value().app_size_in_bytes != 0u &&
+           app_size_future.Get().value().data_size_in_bytes != 0u;
+  }));
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_scope_extensions_browsertest.cc b/chrome/browser/web_applications/web_app_scope_extensions_browsertest.cc
index a8f4c089..3a626a5 100644
--- a/chrome/browser/web_applications/web_app_scope_extensions_browsertest.cc
+++ b/chrome/browser/web_applications/web_app_scope_extensions_browsertest.cc
@@ -472,7 +472,7 @@
   {
     UpdateAwaiter update_awaiter(provider.install_manager());
     serve_token = false;
-    NavigateToURLAndWait(browser(), GURL(kTestWebAppUrl));
+    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kTestWebAppUrl)));
     update_awaiter.AwaitUpdate();
   }
 
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index ecf7627..4c212fc 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1702382390-aedc3b176118e492520a9cfe7aad8b29e4f1be22.profdata
+chrome-android32-main-1702403939-f8050953af8eace9d59c81c55793f0e2edf1337d.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index ee5fedac..9992beb5 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1702382390-8adedea8df7860f03fb7285696c7f874428ba41d.profdata
+chrome-android64-main-1702403939-b69e446c9cbd1b1c857bd308ec679d1fbd9283b3.profdata
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 47c6c15..4bddbc9 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -1528,11 +1528,9 @@
 inline constexpr char kImportDialogSearchEngine[] =
     "import_dialog_search_engine";
 
-#if BUILDFLAG(IS_CHROMEOS)
 // Boolean controlling whether native client is force allowed by policy.
 inline constexpr char kNativeClientForceAllowed[] =
     "native_client_force_allowed";
-#endif  // BUILDFLAG(IS_CHROMEOS)
 
 // Profile avatar and name
 inline constexpr char kProfileAvatarIndex[] = "profile.avatar_index";
diff --git a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
index d89426f..f0243d2 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
@@ -118,13 +118,21 @@
 
     // Create simple AXTreeUpdate with a root node and 3 children.
     ui::AXTreeUpdate snapshot;
-    snapshot.root_id = 1;
-    snapshot.nodes.resize(4);
-    snapshot.nodes[0].id = 1;
-    snapshot.nodes[0].child_ids = {2, 3, 4};
-    snapshot.nodes[1].id = 2;
-    snapshot.nodes[2].id = 3;
-    snapshot.nodes[3].id = 4;
+    ui::AXNodeData root;
+    root.id = 1;
+
+    ui::AXNodeData child1;
+    child1.id = 2;
+
+    ui::AXNodeData child2;
+    child2.id = 3;
+
+    ui::AXNodeData child3;
+    child3.id = 4;
+
+    root.child_ids = {child1.id, child2.id, child3.id};
+    snapshot.root_id = root.id;
+    snapshot.nodes = {root, child1, child2, child3};
     SetUpdateTreeID(&snapshot);
 
     // Send the snapshot to the controller and set its tree ID to be the active
@@ -149,17 +157,19 @@
     // Send update for main web content with child tree (pdf web contents).
     ui::AXTreeUpdate main_web_contents_update;
     SetUpdateTreeID(&main_web_contents_update);
-    main_web_contents_update.nodes.resize(1);
-    main_web_contents_update.nodes[0].id = 1;
-    main_web_contents_update.nodes[0].AddChildTreeId(pdf_web_contents_tree_id);
+    ui::AXNodeData node;
+    node.id = 1;
+    node.AddChildTreeId(pdf_web_contents_tree_id);
+    main_web_contents_update.nodes = {node};
     AccessibilityEventReceived({main_web_contents_update});
 
     // Send update for pdf web contents with child tree (iframe).
     ui::AXTreeUpdate pdf_web_contents_update;
-    pdf_web_contents_update.nodes.resize(1);
-    pdf_web_contents_update.root_id = 1;
-    pdf_web_contents_update.nodes[0].id = 1;
-    pdf_web_contents_update.nodes[0].AddChildTreeId(pdf_iframe_tree_id);
+    ui::AXNodeData pdfNode;
+    pdfNode.id = 1;
+    pdfNode.AddChildTreeId(pdf_iframe_tree_id);
+    pdf_web_contents_update.nodes = {pdfNode};
+    pdf_web_contents_update.root_id = pdfNode.id;
     SetUpdateTreeID(&pdf_web_contents_update, pdf_web_contents_tree_id);
     AccessibilityEventReceived({pdf_web_contents_update});
 
@@ -399,9 +409,10 @@
 TEST_F(ReadAnythingAppControllerTest, GetChildren_NoSelectionOrContentNodes) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(1);
-  update.nodes[0].id = 3;
-  update.nodes[0].role = ax::mojom::Role::kNone;
+  ui::AXNodeData node;
+  node.id = 3;
+  node.role = ax::mojom::Role::kNone;
+  update.nodes = {node};
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(0u, GetChildren(1).size());
@@ -413,9 +424,10 @@
 TEST_F(ReadAnythingAppControllerTest, GetChildren_WithContentNodes) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(1);
-  update.nodes[0].id = 3;
-  update.nodes[0].role = ax::mojom::Role::kNone;
+  ui::AXNodeData node;
+  node.id = 3;
+  node.role = ax::mojom::Role::kNone;
+  update.nodes = {node};
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({1, 2, 3, 4});
   EXPECT_EQ(2u, GetChildren(1).size());
@@ -479,14 +491,19 @@
   std::string ul = "ul";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
-                                     span);
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, h1);
-  update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, ul);
+  ui::AXNodeData spanNode;
+  spanNode.id = 2;
+  spanNode.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, span);
+
+  ui::AXNodeData h1Node;
+  h1Node.id = 3;
+  h1Node.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, h1);
+
+  ui::AXNodeData ulNode;
+  ulNode.id = 4;
+  ulNode.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, ul);
+  update.nodes = {spanNode, h1Node, ulNode};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(span, GetHtmlTag(2));
@@ -501,16 +518,21 @@
   std::string div = "div";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
-                                     span);
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, h1);
-  update.nodes[1].role = ax::mojom::Role::kTextField;
-  update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, ul);
-  update.nodes[2].role = ax::mojom::Role::kTextFieldWithComboBox;
+  ui::AXNodeData spanNode;
+  spanNode.id = 2;
+  spanNode.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, span);
+
+  ui::AXNodeData h1Node;
+  h1Node.id = 3;
+  h1Node.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, h1);
+  h1Node.role = ax::mojom::Role::kTextField;
+
+  ui::AXNodeData ulNode;
+  ulNode.id = 4;
+  ulNode.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, ul);
+  ulNode.role = ax::mojom::Role::kTextFieldWithComboBox;
+  update.nodes = {spanNode, h1Node, ulNode};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(span, GetHtmlTag(2));
@@ -524,12 +546,16 @@
   ui::AXTreeUpdate update;
   ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
   SetUpdateTreeID(&update, id_1);
-  update.root_id = 1;
-  update.nodes.resize(2);
-  update.nodes[0].id = 1;
-  update.nodes[0].child_ids = {2};
-  update.nodes[1].id = 2;
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, svg);
+  ui::AXNodeData node;
+  node.id = 2;
+  node.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, svg);
+
+  ui::AXNodeData root;
+  root.id = 1;
+  root.child_ids = {node.id};
+  update.nodes = {root, node};
+  update.root_id = root.id;
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   OnActiveAXTreeIDChanged(
@@ -547,16 +573,21 @@
   ui::AXTreeUpdate update;
   ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
   SetUpdateTreeID(&update, id_1);
-  update.root_id = 1;
-  update.nodes.resize(3);
-  update.nodes[0].id = 1;
-  update.nodes[0].child_ids = {2, 3};
-  update.nodes[1].id = 2;
-  update.nodes[2].id = 3;
-  update.nodes[0].role = ax::mojom::Role::kParagraph;
-  update.nodes[1].role = ax::mojom::Role::kParagraph;
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, g);
-  update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, g);
+  ui::AXNodeData paragraphNode;
+  paragraphNode.id = 2;
+  paragraphNode.role = ax::mojom::Role::kParagraph;
+  paragraphNode.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, g);
+
+  ui::AXNodeData svgNode;
+  svgNode.id = 3;
+  svgNode.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, g);
+
+  ui::AXNodeData root;
+  root.role = ax::mojom::Role::kParagraph;
+  root.id = 1;
+  root.child_ids = {paragraphNode.id, svgNode.id};
+  update.root_id = root.id;
+  update.nodes = {root, paragraphNode, svgNode};
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   OnActiveAXTreeIDChanged(
@@ -575,12 +606,17 @@
   std::string div = "div";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[1].role = ax::mojom::Role::kHeading;
-  update.nodes[1].html_attributes.emplace_back("aria-level", "3");
+  ui::AXNodeData node1;
+  node1.id = 2;
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.role = ax::mojom::Role::kHeading;
+  node2.html_attributes.emplace_back("aria-level", "3");
+
+  ui::AXNodeData node3;
+  node3.id = 4;
+  update.nodes = {node1, node2, node3};
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(h3, GetHtmlTag(3));
@@ -592,17 +628,20 @@
   // Send pdf iframe update with html tags to test.
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update, pdf_iframe_tree_id);
-  update.nodes.resize(3);
-  update.root_id = 1;
-  update.nodes[0].id = 1;
-  update.nodes[0].child_ids = {2, 3};
-  update.nodes[1].id = 2;
-  update.nodes[2].id = 3;
-  update.nodes[0].role = ax::mojom::Role::kPdfRoot;
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
-                                     "h1");
-  update.nodes[2].role = ax::mojom::Role::kHeading;
-  update.nodes[2].html_attributes.emplace_back("aria-level", "2");
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "h1");
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.role = ax::mojom::Role::kHeading;
+  node2.html_attributes.emplace_back("aria-level", "2");
+
+  ui::AXNodeData root;
+  root.id = 1;
+  root.child_ids = {node1.id, node2.id};
+  root.role = ax::mojom::Role::kPdfRoot;
+  update.root_id = root.id;
+  update.nodes = {root, node1, node2};
   AccessibilityEventReceived({update});
 
   OnAXTreeDistilled({});
@@ -620,28 +659,34 @@
   // paragraph.
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update, pdf_iframe_tree_id);
-  update.nodes.resize(5);
-  update.root_id = 1;
-  update.nodes[0].id = 1;
-  update.nodes[0].child_ids = {2, 3, 4, 5};
-  update.nodes[1].id = 2;
-  update.nodes[2].id = 3;
-  update.nodes[3].id = 4;
-  update.nodes[4].id = 5;
-  update.nodes[0].role = ax::mojom::Role::kPdfRoot;
-  update.nodes[1].role = ax::mojom::Role::kHeading;
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
-                                     "h1");
-  update.nodes[2].role = ax::mojom::Role::kHeading;
-  update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag,
-                                     "h1");
-  update.nodes[3].role = ax::mojom::Role::kLink;
-  update.nodes[4].role = ax::mojom::Role::kHeading;
-  update.nodes[4].html_attributes.emplace_back("aria-level", "1");
-  update.nodes[4].SetNameChecked(
+  ui::AXNodeData headingNode1;
+  headingNode1.id = 2;
+  headingNode1.role = ax::mojom::Role::kHeading;
+  headingNode1.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "h1");
+  ui::AXNodeData headingNode2;
+  headingNode2.id = 3;
+  headingNode2.role = ax::mojom::Role::kHeading;
+  headingNode2.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "h1");
+
+  ui::AXNodeData linkNode;
+  linkNode.id = 4;
+  linkNode.role = ax::mojom::Role::kLink;
+
+  ui::AXNodeData ariaNode;
+  ariaNode.id = 5;
+  ariaNode.role = ax::mojom::Role::kHeading;
+  ariaNode.html_attributes.emplace_back("aria-level", "1");
+  ariaNode.SetNameChecked(
       "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
       "tempor incididunt ut labore et dolore magna aliqua.");
-  update.nodes[4].SetNameFrom(ax::mojom::NameFrom::kContents);
+  ariaNode.SetNameFrom(ax::mojom::NameFrom::kContents);
+
+  ui::AXNodeData root;
+  root.id = 1;
+  root.child_ids = {headingNode1.id, headingNode2.id, linkNode.id, ariaNode.id};
+  root.role = ax::mojom::Role::kPdfRoot;
+  update.root_id = root.id;
+  update.nodes = {root, headingNode1, headingNode2, linkNode, ariaNode};
 
   AccessibilityEventReceived({update});
 
@@ -659,15 +704,18 @@
   // Send pdf iframe update with html tags to test.
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update, pdf_iframe_tree_id);
-  update.nodes.resize(2);
+  ui::AXNodeData node;
+  node.id = 2;
+  node.role = ax::mojom::Role::kContentInfo;
+  node.SetNameChecked(string_constants::kPDFPageEnd);
+  node.SetNameFrom(ax::mojom::NameFrom::kContents);
+
+  ui::AXNodeData root;
+  root.id = 1;
+  root.child_ids = {node.id};
+  root.role = ax::mojom::Role::kPdfRoot;
   update.root_id = 1;
-  update.nodes[0].id = 1;
-  update.nodes[0].child_ids = {2};
-  update.nodes[1].id = 2;
-  update.nodes[0].role = ax::mojom::Role::kPdfRoot;
-  update.nodes[1].role = ax::mojom::Role::kContentInfo;
-  update.nodes[1].SetNameChecked(string_constants::kPDFPageEnd);
-  update.nodes[1].SetNameFrom(ax::mojom::NameFrom::kContents);
+  update.nodes = {root, node};
   AccessibilityEventReceived({update});
 
   OnAXTreeDistilled({});
@@ -680,16 +728,21 @@
   std::string more_text_content = " world";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[0].SetNameChecked(text_content);
-  update.nodes[1].role = ax::mojom::Role::kStaticText;
-  update.nodes[1].SetNameExplicitlyEmpty();
-  update.nodes[2].role = ax::mojom::Role::kStaticText;
-  update.nodes[2].SetNameChecked(more_text_content);
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.role = ax::mojom::Role::kStaticText;
+  node1.SetNameChecked(text_content);
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.role = ax::mojom::Role::kStaticText;
+  node2.SetNameExplicitlyEmpty();
+
+  ui::AXNodeData node3;
+  node3.id = 4;
+  node3.role = ax::mojom::Role::kStaticText;
+  node3.SetNameChecked(more_text_content);
+  update.nodes = {node1, node2, node3};
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ("Hello world", GetTextContent(1));
@@ -704,16 +757,22 @@
   std::string text_content_3 = " friend";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[0].SetNameChecked(text_content_1);
-  update.nodes[1].role = ax::mojom::Role::kStaticText;
-  update.nodes[1].SetNameChecked(text_content_2);
-  update.nodes[2].role = ax::mojom::Role::kStaticText;
-  update.nodes[2].SetNameChecked(text_content_3);
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.role = ax::mojom::Role::kStaticText;
+  node1.SetNameChecked(text_content_1);
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.role = ax::mojom::Role::kStaticText;
+  node2.SetNameChecked(text_content_2);
+
+  ui::AXNodeData node3;
+  node3.id = 4;
+  node3.role = ax::mojom::Role::kStaticText;
+  node3.SetNameChecked(text_content_3);
+  update.nodes = {node1, node2, node3};
+
   // Create selection from node 2-3.
   update.tree_data.sel_anchor_object_id = 2;
   update.tree_data.sel_focus_object_id = 3;
@@ -735,17 +794,21 @@
   ui::AXTreeUpdate update;
   ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
   SetUpdateTreeID(&update, id_1);
-  update.root_id = 1;
-  update.nodes.resize(3);
-  update.nodes[0].id = 1;
-  update.nodes[0].child_ids = {2, 3};
-  update.nodes[0].role = ax::mojom::Role::kParagraph;
-  update.nodes[1].id = 2;
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName,
-                                     text_content);
-  update.nodes[2].id = 3;
-  update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kName,
-                                     more_text_content);
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.AddStringAttribute(ax::mojom::StringAttribute::kName, text_content);
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.AddStringAttribute(ax::mojom::StringAttribute::kName,
+                           more_text_content);
+  ui::AXNodeData root;
+  root.id = 1;
+  root.child_ids = {node1.id, node2.id};
+  root.role = ax::mojom::Role::kParagraph;
+  update.root_id = root.id;
+  update.nodes = {root, node1, node2};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   OnActiveAXTreeIDChanged(
@@ -765,17 +828,22 @@
   ui::AXTreeUpdate update;
   ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
   SetUpdateTreeID(&update, id_1);
-  update.root_id = 1;
-  update.nodes.resize(3);
-  update.nodes[0].id = 1;
-  update.nodes[0].child_ids = {2, 3};
-  update.nodes[0].role = ax::mojom::Role::kParagraph;
-  update.nodes[1].id = 2;
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName,
-                                     text_content);
-  update.nodes[2].id = 3;
-  update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kName,
-                                     more_text_content);
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.AddStringAttribute(ax::mojom::StringAttribute::kName, text_content);
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.AddStringAttribute(ax::mojom::StringAttribute::kName,
+                           more_text_content);
+
+  ui::AXNodeData root;
+  root.id = 1;
+  root.child_ids = {node1.id, node2.id};
+  root.role = ax::mojom::Role::kParagraph;
+  update.root_id = root.id;
+  update.nodes = {root, node1, node2};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   OnActiveAXTreeIDChanged(id_1, GURL("https://www.google.com/"));
@@ -793,23 +861,32 @@
   std::string js = "javascript:alert(origin)";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(6);
-  update.nodes[0].id = 1;
-  update.nodes[1].id = 2;
-  update.nodes[2].id = 3;
-  update.nodes[3].id = 4;
-  update.nodes[4].id = 5;
-  update.nodes[5].id = 6;
-  update.nodes[0].child_ids = {2, 3, 4, 5, 6};
-  update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kUrl,
-                                     http_url);
-  update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kUrl,
-                                     https_url);
-  update.nodes[3].AddStringAttribute(ax::mojom::StringAttribute::kUrl,
-                                     invalid_url);
-  update.nodes[4].AddStringAttribute(ax::mojom::StringAttribute::kUrl,
-                                     missing_url);
-  update.nodes[5].AddStringAttribute(ax::mojom::StringAttribute::kUrl, js);
+
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.AddStringAttribute(ax::mojom::StringAttribute::kUrl, http_url);
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.AddStringAttribute(ax::mojom::StringAttribute::kUrl, https_url);
+
+  ui::AXNodeData node3;
+  node3.id = 4;
+  node3.AddStringAttribute(ax::mojom::StringAttribute::kUrl, invalid_url);
+
+  ui::AXNodeData node4;
+  node4.id = 5;
+  node4.AddStringAttribute(ax::mojom::StringAttribute::kUrl, missing_url);
+
+  ui::AXNodeData node5;
+  node5.id = 6;
+  node5.AddStringAttribute(ax::mojom::StringAttribute::kUrl, js);
+
+  ui::AXNodeData root;
+  root.id = 1;
+  root.child_ids = {node1.id, node2.id, node3.id, node4.id, node5.id};
+  update.nodes = {root, node1, node2, node3, node4, node5};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(http_url, GetUrl(2));
@@ -822,13 +899,19 @@
 TEST_F(ReadAnythingAppControllerTest, ShouldBold) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].AddTextStyle(ax::mojom::TextStyle::kOverline);
-  update.nodes[1].AddTextStyle(ax::mojom::TextStyle::kUnderline);
-  update.nodes[2].AddTextStyle(ax::mojom::TextStyle::kItalic);
+  ui::AXNodeData overlineNode;
+  overlineNode.id = 2;
+  overlineNode.AddTextStyle(ax::mojom::TextStyle::kOverline);
+
+  ui::AXNodeData underlineNode;
+  underlineNode.id = 3;
+  underlineNode.AddTextStyle(ax::mojom::TextStyle::kUnderline);
+
+  ui::AXNodeData italicNode;
+  italicNode.id = 4;
+  italicNode.AddTextStyle(ax::mojom::TextStyle::kItalic);
+  update.nodes = {overlineNode, underlineNode, italicNode};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(false, ShouldBold(2));
@@ -840,9 +923,10 @@
   std::string dataFontCss = "italic 400 14.6667px 'Courier New'";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(1);
-  update.nodes[0].id = 2;
-  update.nodes[0].html_attributes.emplace_back("data-font-css", dataFontCss);
+  ui::AXNodeData node;
+  node.id = 2;
+  node.html_attributes.emplace_back("data-font-css", dataFontCss);
+  update.nodes = {node};
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(dataFontCss, GetDataFontCss(2));
@@ -851,11 +935,15 @@
 TEST_F(ReadAnythingAppControllerTest, IsOverline) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(2);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[0].AddTextStyle(ax::mojom::TextStyle::kOverline);
-  update.nodes[1].AddTextStyle(ax::mojom::TextStyle::kUnderline);
+  ui::AXNodeData overlineNode;
+  overlineNode.id = 2;
+  overlineNode.AddTextStyle(ax::mojom::TextStyle::kOverline);
+
+  ui::AXNodeData underlineNode;
+  underlineNode.id = 3;
+  underlineNode.AddTextStyle(ax::mojom::TextStyle::kUnderline);
+  update.nodes = {overlineNode, underlineNode};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(true, IsOverline(2));
@@ -865,12 +953,20 @@
 TEST_F(ReadAnythingAppControllerTest, IsLeafNode) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(4);
-  update.nodes[0].id = 1;
-  update.nodes[0].child_ids = {2, 3, 4};
-  update.nodes[1].id = 2;
-  update.nodes[2].id = 3;
-  update.nodes[3].id = 4;
+  ui::AXNodeData node1;
+  node1.id = 2;
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+
+  ui::AXNodeData node3;
+  node3.id = 4;
+
+  ui::AXNodeData parent;
+  parent.id = 1;
+  parent.child_ids = {node1.id, node2.id, node3.id};
+  update.nodes = {parent, node1, node2, node3};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(false, IsLeafNode(1));
@@ -894,13 +990,19 @@
 TEST_F(ReadAnythingAppControllerTest, IsNodeIgnoredForReadAnything) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[1].role = ax::mojom::Role::kComboBoxGrouping;
-  update.nodes[2].role = ax::mojom::Role::kButton;
+  ui::AXNodeData staticTextNode;
+  staticTextNode.id = 2;
+  staticTextNode.role = ax::mojom::Role::kStaticText;
+
+  ui::AXNodeData comboboxNode;
+  comboboxNode.id = 3;
+  comboboxNode.role = ax::mojom::Role::kComboBoxGrouping;
+
+  ui::AXNodeData buttonNode;
+  buttonNode.id = 4;
+  buttonNode.role = ax::mojom::Role::kButton;
+  update.nodes = {staticTextNode, comboboxNode, buttonNode};
+
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
   EXPECT_EQ(false, IsNodeIgnoredForReadAnything(2));
@@ -948,8 +1050,9 @@
 TEST_F(ReadAnythingAppControllerTest, DisplayNodeIdsContains_ContentNodes) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(1);
-  update.nodes[0].id = 3;
+  ui::AXNodeData node;
+  node.id = 3;
+  update.nodes = {node};
   // This update says the page loaded. When the controller receives it in
   // AccessibilityEventReceived, it will re-distill the tree. This is an
   // example of a non-generated event.
@@ -986,10 +1089,11 @@
   // Send a new update which settings the text content of node 2.
   ui::AXTreeUpdate update_1;
   SetUpdateTreeID(&update_1);
-  update_1.nodes.resize(1);
-  update_1.nodes[0].id = 2;
-  update_1.nodes[0].role = ax::mojom::Role::kStaticText;
-  update_1.nodes[0].SetNameChecked("Hello world");
+  ui::AXNodeData node;
+  node.id = 2;
+  node.role = ax::mojom::Role::kStaticText;
+  node.SetNameChecked("Hello world");
+  update_1.nodes = {node};
   AccessibilityEventReceived({update_1});
   EXPECT_EQ("Hello world", GetTextContent(1));
   EXPECT_EQ("Hello world", GetTextContent(2));
@@ -1001,10 +1105,11 @@
   for (int i = 2; i < 5; i++) {
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update);
-    update.nodes.resize(1);
-    update.nodes[0].id = i;
-    update.nodes[0].role = ax::mojom::Role::kStaticText;
-    update.nodes[0].SetNameChecked("Node " + base::NumberToString(i));
+    ui::AXNodeData staticTextNode;
+    staticTextNode.id = i;
+    staticTextNode.role = ax::mojom::Role::kStaticText;
+    staticTextNode.SetNameChecked("Node " + base::NumberToString(i));
+    update.nodes = {staticTextNode};
     batch_updates.push_back(update);
   }
   AccessibilityEventReceived(batch_updates);
@@ -1018,8 +1123,9 @@
   SetUpdateTreeID(&clear_update);
   clear_update.root_id = 1;
   clear_update.node_id_to_clear = 1;
-  clear_update.nodes.resize(1);
-  clear_update.nodes[0].id = 1;
+  ui::AXNodeData clearNode;
+  clearNode.id = 1;
+  clear_update.nodes = {clearNode};
   AccessibilityEventReceived({clear_update});
   EXPECT_EQ("", GetTextContent(1));
 }
@@ -1035,10 +1141,11 @@
   // Send a new update which settings the text content of node 2.
   ui::AXTreeUpdate update_1;
   SetUpdateTreeID(&update_1);
-  update_1.nodes.resize(1);
-  update_1.nodes[0].id = 2;
-  update_1.nodes[0].role = ax::mojom::Role::kStaticText;
-  update_1.nodes[0].SetNameChecked("Hello world");
+  ui::AXNodeData startNode;
+  startNode.id = 2;
+  startNode.role = ax::mojom::Role::kStaticText;
+  startNode.SetNameChecked("Hello world");
+  update_1.nodes = {startNode};
   AccessibilityEventReceived({update_1});
   EXPECT_EQ("Hello world", GetTextContent(1));
   EXPECT_EQ("Hello world", GetTextContent(2));
@@ -1051,10 +1158,11 @@
   for (int i = 2; i < 5; i++) {
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update);
-    update.nodes.resize(1);
-    update.nodes[0].id = i;
-    update.nodes[0].role = ax::mojom::Role::kStaticText;
-    update.nodes[0].SetNameChecked("Node " + base::NumberToString(i));
+    ui::AXNodeData node;
+    node.id = i;
+    node.role = ax::mojom::Role::kStaticText;
+    node.SetNameChecked("Node " + base::NumberToString(i));
+    update.nodes = {node};
     batch_updates.push_back(update);
   }
   AccessibilityEventReceived(batch_updates);
@@ -1069,10 +1177,11 @@
   SetDistillationInProgress(false);
   ui::AXTreeUpdate update_2;
   SetUpdateTreeID(&update_2);
-  update_2.nodes.resize(1);
-  update_2.nodes[0].id = 2;
-  update_2.nodes[0].role = ax::mojom::Role::kStaticText;
-  update_2.nodes[0].SetNameChecked("Final update");
+  ui::AXNodeData finalNode;
+  finalNode.id = 2;
+  finalNode.role = ax::mojom::Role::kStaticText;
+  finalNode.SetNameChecked("Final update");
+  update_2.nodes = {finalNode};
   AccessibilityEventReceived({update_2});
 
   EXPECT_EQ("Final updateNode 3Node 4", GetTextContent(1));
@@ -1090,11 +1199,12 @@
   for (int i = 0; i < 3; i++) {
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update, tree_ids[i]);
-    update.root_id = 1;
-    update.nodes.resize(1);
-    update.nodes[0].id = 1;
-    update.nodes[0].role = ax::mojom::Role::kStaticText;
-    update.nodes[0].SetNameChecked("Tree " + base::NumberToString(i));
+    ui::AXNodeData node;
+    node.id = 1;
+    node.role = ax::mojom::Role::kStaticText;
+    node.SetNameChecked("Tree " + base::NumberToString(i));
+    update.root_id = node.id;
+    update.nodes = {node};
     updates.push_back(update);
   }
   // Add the three updates separately since they have different tree IDs.
@@ -1121,8 +1231,9 @@
   ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
   SetUpdateTreeID(&update, id_1);
   update.root_id = 1;
-  update.nodes.resize(1);
-  update.nodes[0].id = 1;
+  ui::AXNodeData node;
+  node.id = 1;
+  update.nodes = {node};
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({1});
 
@@ -1133,9 +1244,10 @@
 
   ui::AXTreeUpdate update_1;
   SetUpdateTreeID(&update_1, tree_id_);
-  update_1.root_id = 1;
-  update_1.nodes.resize(1);
-  update_1.nodes[0].id = 1;
+  ui::AXNodeData root;
+  root.id = 1;
+  update_1.root_id = root.id;
+  update_1.nodes = {root};
   AccessibilityEventReceived({update_1});
   OnAXTreeDistilled({1});
 
@@ -1171,9 +1283,10 @@
   for (int i = 0; i < 2; i++) {
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update, tree_ids[i]);
-    update.root_id = 1;
-    update.nodes.resize(1);
-    update.nodes[0].id = 1;
+    ui::AXNodeData node;
+    node.id = 1;
+    update.root_id = node.id;
+    update.nodes = {node};
     updates.push_back(update);
   }
 
@@ -1230,13 +1343,16 @@
 
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update);
-    update.root_id = 1;
-    update.nodes.resize(2);
-    update.nodes[0].id = 1;
-    update.nodes[0].child_ids = child_ids;
-    update.nodes[1].id = id;
-    update.nodes[1].role = ax::mojom::Role::kStaticText;
-    update.nodes[1].SetNameChecked(base::NumberToString(id));
+    ui::AXNodeData root;
+    root.id = 1;
+    root.child_ids = child_ids;
+
+    ui::AXNodeData node;
+    node.id = id;
+    node.role = ax::mojom::Role::kStaticText;
+    node.SetNameChecked(base::NumberToString(id));
+    update.root_id = root.id;
+    update.nodes = {root, node};
     updates.push_back(update);
   }
 
@@ -1286,13 +1402,16 @@
 
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update);
-    update.root_id = 1;
-    update.nodes.resize(2);
-    update.nodes[0].id = 1;
-    update.nodes[0].child_ids = child_ids;
-    update.nodes[1].id = id;
-    update.nodes[1].role = ax::mojom::Role::kStaticText;
-    update.nodes[1].SetNameChecked(base::NumberToString(id));
+    ui::AXNodeData root;
+    root.id = 1;
+    root.child_ids = child_ids;
+
+    ui::AXNodeData node;
+    node.id = id;
+    node.role = ax::mojom::Role::kStaticText;
+    node.SetNameChecked(base::NumberToString(id));
+    update.root_id = root.id;
+    update.nodes = {root, node};
     updates.push_back(update);
   }
 
@@ -1358,13 +1477,16 @@
 
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update);
-    update.root_id = 1;
-    update.nodes.resize(2);
-    update.nodes[0].id = 1;
-    update.nodes[0].child_ids = child_ids;
-    update.nodes[1].id = id;
-    update.nodes[1].role = ax::mojom::Role::kStaticText;
-    update.nodes[1].SetNameChecked(base::NumberToString(id));
+    ui::AXNodeData root;
+    root.id = 1;
+    root.child_ids = child_ids;
+
+    ui::AXNodeData node;
+    node.id = id;
+    node.role = ax::mojom::Role::kStaticText;
+    node.SetNameChecked(base::NumberToString(id));
+    update.root_id = root.id;
+    update.nodes = {root, node};
 
     updates.push_back(update);
   }
@@ -1401,13 +1523,16 @@
 
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update);
-    update.root_id = 1;
-    update.nodes.resize(2);
-    update.nodes[0].id = 1;
-    update.nodes[0].child_ids = child_ids;
-    update.nodes[1].id = id;
-    update.nodes[1].role = ax::mojom::Role::kStaticText;
-    update.nodes[1].SetNameChecked(base::NumberToString(id));
+    ui::AXNodeData root;
+    root.id = 1;
+    root.child_ids = child_ids;
+
+    ui::AXNodeData node;
+    node.id = id;
+    node.role = ax::mojom::Role::kStaticText;
+    node.SetNameChecked(base::NumberToString(id));
+    update.root_id = root.id;
+    update.nodes = {root, node};
     updates.push_back(update);
   }
 
@@ -1472,21 +1597,25 @@
 
     ui::AXTreeUpdate update;
     SetUpdateTreeID(&update);
-    update.root_id = 1;
-    update.nodes.resize(2);
-    update.nodes[0].id = 1;
-    update.nodes[0].child_ids = child_ids;
-    update.nodes[1].id = id;
-    update.nodes[1].role = ax::mojom::Role::kStaticText;
-    update.nodes[1].SetNameChecked(base::NumberToString(id));
+    ui::AXNodeData root;
+    root.id = 1;
+    root.child_ids = child_ids;
+
+    ui::AXNodeData node;
+    node.id = id;
+    node.role = ax::mojom::Role::kStaticText;
+    node.SetNameChecked(base::NumberToString(id));
+    update.root_id = root.id;
+    update.nodes = {root, node};
     updates.push_back(update);
   }
 
   // Create an update which has no tree id.
   ui::AXTreeUpdate update;
-  update.nodes.resize(1);
-  update.nodes[0].id = 1;
-  update.nodes[0].role = ax::mojom::Role::kGenericContainer;
+  ui::AXNodeData genericContainerNode;
+  genericContainerNode.id = 1;
+  genericContainerNode.role = ax::mojom::Role::kGenericContainer;
+  update.nodes = {genericContainerNode};
   updates.push_back(update);
 
   // Add the three updates.
@@ -1513,9 +1642,10 @@
   ui::AXTreeID new_tree_id = ui::AXTreeID::CreateNewAXTreeID();
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update, new_tree_id);
-  update.root_id = 1;
-  update.nodes.resize(1);
-  update.nodes[0].id = 1;
+  ui::AXNodeData node;
+  node.id = 1;
+  update.root_id = node.id;
+  update.nodes = {node};
   AccessibilityEventReceived({update});
 
   EXPECT_CALL(*distiller_, Distill).Times(1);
@@ -1532,13 +1662,18 @@
 TEST_F(ReadAnythingAppControllerTest, OnSelectionChange) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[1].role = ax::mojom::Role::kStaticText;
-  update.nodes[2].role = ax::mojom::Role::kStaticText;
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.role = ax::mojom::Role::kStaticText;
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.role = ax::mojom::Role::kStaticText;
+
+  ui::AXNodeData node3;
+  node3.id = 4;
+  node3.role = ax::mojom::Role::kStaticText;
+  update.nodes = {node1, node2, node3};
   AccessibilityEventReceived({update});
   ui::AXNodeID anchor_node_id = 2;
   int anchor_offset = 0;
@@ -1555,13 +1690,18 @@
 TEST_F(ReadAnythingAppControllerTest, OnCollapseSelection) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[1].role = ax::mojom::Role::kStaticText;
-  update.nodes[2].role = ax::mojom::Role::kStaticText;
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.role = ax::mojom::Role::kStaticText;
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.role = ax::mojom::Role::kStaticText;
+
+  ui::AXNodeData node3;
+  node3.id = 4;
+  node3.role = ax::mojom::Role::kStaticText;
+  update.nodes = {node1, node2, node3};
   AccessibilityEventReceived({update});
   EXPECT_CALL(page_handler_, OnCollapseSelection()).Times(1);
   OnCollapseSelection();
@@ -1572,11 +1712,14 @@
        OnSelectionChange_ClickAfterClickDoesNotUpdateSelection) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(2);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[1].role = ax::mojom::Role::kStaticText;
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.role = ax::mojom::Role::kStaticText;
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.role = ax::mojom::Role::kStaticText;
+  update.nodes = {node1, node2};
   AccessibilityEventReceived({update});
 
   ui::AXTreeUpdate selection;
@@ -1598,11 +1741,14 @@
        OnSelectionChange_ClickAfterSelectionClearsSelection) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(2);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[1].role = ax::mojom::Role::kStaticText;
+  ui::AXNodeData node1;
+  node1.id = 2;
+  node1.role = ax::mojom::Role::kStaticText;
+
+  ui::AXNodeData node2;
+  node2.id = 3;
+  node2.role = ax::mojom::Role::kStaticText;
+  update.nodes = {node1, node2};
   AccessibilityEventReceived({update});
 
   ui::AXTreeUpdate selection;
@@ -1630,10 +1776,11 @@
   ui::AXTreeID new_tree_id = ui::AXTreeID::CreateNewAXTreeID();
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update, new_tree_id);
-  update.root_id = 1;
-  update.nodes.resize(1);
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[0].id = 1;
+  ui::AXNodeData root;
+  root.id = 1;
+  root.role = ax::mojom::Role::kStaticText;
+  update.root_id = root.id;
+  update.nodes = {root};
   AccessibilityEventReceived({update});
   EXPECT_CALL(*distiller_, Distill).Times(1);
   OnActiveAXTreeIDChanged(new_tree_id);
@@ -1650,13 +1797,19 @@
        OnSelectionChange_NonTextFieldDoesNotUpdateSelection) {
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(3);
-  update.nodes[0].id = 2;
-  update.nodes[1].id = 3;
-  update.nodes[2].id = 4;
-  update.nodes[0].role = ax::mojom::Role::kTextField;
-  update.nodes[1].role = ax::mojom::Role::kGenericContainer;
-  update.nodes[2].role = ax::mojom::Role::kTextField;
+  ui::AXNodeData textFieldNode1;
+  textFieldNode1.id = 2;
+  textFieldNode1.role = ax::mojom::Role::kTextField;
+
+  ui::AXNodeData containerNode;
+  containerNode.id = 3;
+  containerNode.role = ax::mojom::Role::kGenericContainer;
+
+  ui::AXNodeData textFieldNode2;
+  textFieldNode2.id = 4;
+  textFieldNode2.role = ax::mojom::Role::kTextField;
+  update.nodes = {textFieldNode1, containerNode, textFieldNode2};
+
   AccessibilityEventReceived({update});
   ui::AXNodeID anchor_node_id = 2;
   int anchor_offset = 0;
@@ -1712,12 +1865,15 @@
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
   update.root_id = 1;
-  update.nodes.resize(2);
-  update.nodes[0].id = 3;
-  update.nodes[1].id = 4;
-  update.nodes[0].role = ax::mojom::Role::kStaticText;
-  update.nodes[0].SetNameChecked("Hello");
-  update.nodes[1].role = ax::mojom::Role::kNone;  // This node is ignored.
+  ui::AXNodeData textNode;
+  textNode.id = 3;
+  textNode.role = ax::mojom::Role::kStaticText;
+  textNode.SetNameChecked("Hello");
+
+  ui::AXNodeData ignoredNode;
+  ignoredNode.id = 4;
+  ignoredNode.role = ax::mojom::Role::kNone;  // This node is ignored.
+  update.nodes = {textNode, ignoredNode};
   AccessibilityEventReceived({update});
   OnAXTreeDistilled({});
 
@@ -1853,16 +2009,18 @@
   ui::AXTreeID pdf_web_contents_tree_id = ui::AXTreeID::CreateNewAXTreeID();
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  update.nodes.resize(1);
-  update.nodes[0].id = 1;
-  update.nodes[0].AddChildTreeId(pdf_web_contents_tree_id);
+  ui::AXNodeData node;
+  node.id = 1;
+  node.AddChildTreeId(pdf_web_contents_tree_id);
+  update.nodes = {node};
   AccessibilityEventReceived({update});
 
   // Send update for pdf web contents.
   ui::AXTreeUpdate pdf_web_contents_update;
-  pdf_web_contents_update.nodes.resize(1);
-  pdf_web_contents_update.root_id = 1;
-  pdf_web_contents_update.nodes[0].id = 1;
+  ui::AXNodeData pdfNode;
+  pdfNode.id = 1;
+  pdf_web_contents_update.root_id = pdfNode.id;
+  pdf_web_contents_update.nodes = {pdfNode};
   SetUpdateTreeID(&pdf_web_contents_update, pdf_web_contents_tree_id);
   AccessibilityEventReceived({pdf_web_contents_update});
 
@@ -1878,9 +2036,6 @@
   std::u16string sentence3 = u"And this is yet another sentence.";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
-  // TODO(crbug.com/1474951): Update the rest of the tests to use this
-  // formatting
-  //  instead of update.nodes.resize where possible to improve readability.
   ui::AXNodeData staticText1;
   staticText1.id = 2;
   staticText1.role = ax::mojom::Role::kStaticText;
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index ab34069..662baa26 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -810,8 +810,6 @@
       "../browser/enterprise/connectors/analysis/source_destination_test_util.h",
       "../browser/nearby_sharing/fake_nearby_connection.cc",
       "../browser/nearby_sharing/fake_nearby_connection.h",
-      "../browser/ui/ash/accessibility/fake_accessibility_controller.cc",
-      "../browser/ui/ash/accessibility/fake_accessibility_controller.h",
       "../browser/ui/ash/clipboard_history_test_util.cc",
       "../browser/ui/ash/clipboard_history_test_util.h",
       "../browser/ui/webui/ash/login/fake_app_launch_splash_screen_handler.cc",
@@ -10537,7 +10535,6 @@
 
     if (is_chromeos_lacros) {
       deps += [
-        ":lacros_test_support_ui",
         "//chromeos/lacros",
         "//chromeos/lacros:test_support",
         "//components/account_manager_core",
diff --git a/chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h b/chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h
index 76d3496..00b903c 100644
--- a/chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h
+++ b/chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h
@@ -57,6 +57,8 @@
   void SetUp() override;
   void SetUpCommandLine(base::CommandLine* command_line) override;
 
+  Mode mode() const { return mode_; }
+
  private:
   bool ShouldStartLoginScreen() const;
   void PrepareForNewUserLogin();
diff --git a/chrome/test/base/chromeos/crosier/interactive_ash_test.cc b/chrome/test/base/chromeos/crosier/interactive_ash_test.cc
index c4b0b33..2887af7 100644
--- a/chrome/test/base/chromeos/crosier/interactive_ash_test.cc
+++ b/chrome/test/base/chromeos/crosier/interactive_ash_test.cc
@@ -27,13 +27,30 @@
 #include "chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/chromeos/crosier/aura_window_title_observer.h"
+#include "chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h"
+#include "google_apis/gaia/gaia_switches.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_response.h"
 #include "ui/aura/test/find_window.h"
 #include "url/gurl.h"
 
+namespace {
+
 using InteractiveMixinBasedBrowserTest =
     InteractiveBrowserTestT<MixinBasedInProcessBrowserTest>;
 
+#if BUILDFLAG(IS_CHROMEOS_DEVICE)
+// Simulates a failure for a Gaia URL request.
+std::unique_ptr<net::test_server::HttpResponse> HandleGaiaURL(
+    const net::test_server::HttpRequest& request) {
+  return std::make_unique<net::test_server::HungResponse>();
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_DEVICE)
+
+}  // namespace
+
 InteractiveAshTest::InteractiveAshTest() {
   // See header file class comment.
   set_launch_browser_for_testing(nullptr);
@@ -85,6 +102,10 @@
     base::CommandLine* command_line) {
   CHECK(command_line);
 
+#if BUILDFLAG(IS_CHROMEOS_DEVICE)
+  OverrideGaiaUrlForLacros(command_line);
+#endif
+
   // Enable the Wayland server.
   command_line->AppendSwitch(ash::switches::kAshEnableWaylandServer);
 
@@ -127,6 +148,15 @@
   CHECK(extra_parts->did_post_browser_start());
 }
 
+void InteractiveAshTest::SetUpOnMainThread() {
+  InteractiveBrowserTestT<MixinBasedInProcessBrowserTest>::SetUpOnMainThread();
+
+  // The embedded test server starts accepting connections after fork.
+  if (https_server_) {
+    https_server_->StartAcceptingConnections();
+  }
+}
+
 void InteractiveAshTest::TearDownOnMainThread() {
   // Passing --test-launcher-interactive leaves the browser running after the
   // end of the test.
@@ -189,3 +219,31 @@
   state_change.event = kTextFound;
   return WaitForStateChange(element_id, state_change);
 }
+
+#if BUILDFLAG(IS_CHROMEOS_DEVICE)
+void InteractiveAshTest::OverrideGaiaUrlForLacros(
+    base::CommandLine* command_line) {
+  // When using real Gaia login, don't override the Gaia URL.
+  if (login_mixin_.mode() == ChromeOSIntegrationLoginMixin::Mode::kGaiaLogin) {
+    return;
+  }
+
+  // Set up an embedded test server.
+  https_server_ = std::make_unique<net::test_server::EmbeddedTestServer>(
+      net::test_server::EmbeddedTestServer::TYPE_HTTPS);
+  https_server_->RegisterRequestHandler(base::BindRepeating(&HandleGaiaURL));
+  CHECK(https_server_->InitializeAndListen());
+
+  std::vector<std::string> lacros_args;
+  lacros_args.emplace_back(base::StringPrintf("--%s", switches::kNoFirstRun));
+  // Override Gaia url in Lacros so that the gaia requests will NOT be handled
+  // with the real internet connection, but with the embedded test server. The
+  // embedded test server will simulate failure of the Gaia url requests which
+  // is expected in testing environment for Gaia authentication flow. See
+  // crbug.com/1371655.
+  lacros_args.emplace_back(base::StringPrintf(
+      "--%s=%s", switches::kGaiaUrl, https_server_->base_url().spec().c_str()));
+  command_line->AppendSwitchASCII(ash::switches::kLacrosChromeAdditionalArgs,
+                                  base::JoinString(lacros_args, "####"));
+}
+#endif  // BUILDFLAG(IS_CHROMEOS_DEVICE)
diff --git a/chrome/test/base/chromeos/crosier/interactive_ash_test.h b/chrome/test/base/chromeos/crosier/interactive_ash_test.h
index 14ea6e1..89d1b57 100644
--- a/chrome/test/base/chromeos/crosier/interactive_ash_test.h
+++ b/chrome/test/base/chromeos/crosier/interactive_ash_test.h
@@ -31,6 +31,10 @@
 class NavigationHandle;
 }
 
+namespace net::test_server {
+class EmbeddedTestServer;
+}
+
 // Base class for tests of ash-chrome integration with the ChromeOS platform,
 // like hardware daemons, graphics, kernel, etc.
 //
@@ -91,6 +95,7 @@
   void WaitForAshFullyStarted();
 
   // MixinBasedInProcessBrowserTest:
+  void SetUpOnMainThread() override;
   void TearDownOnMainThread() override;
 
   // Blocks until a window exists with the given title. If a matching window
@@ -134,6 +139,10 @@
 
  private:
 #if BUILDFLAG(IS_CHROMEOS_DEVICE)
+  // Overrides the Gaia URL to point to a local test server that produces an
+  // error, which is expected behavior in test environments.
+  void OverrideGaiaUrlForLacros(base::CommandLine* command_line);
+
   // This test runs on linux-chromeos in interactive_ui_tests and on a DUT in
   // chromeos_integration_tests.
   ChromeOSIntegrationTestMixin chromeos_integration_test_mixin_{&mixin_host_};
@@ -144,5 +153,7 @@
 
   // Directory used by Wayland/Lacros in environment variable XDG_RUNTIME_DIR.
   base::ScopedTempDir scoped_temp_dir_xdg_;
+
+  std::unique_ptr<net::test_server::EmbeddedTestServer> https_server_;
 };
 #endif  // CHROME_TEST_BASE_CHROMEOS_CROSIER_INTERACTIVE_ASH_TEST_H_
diff --git a/chrome/test/base/in_process_browser_test.cc b/chrome/test/base/in_process_browser_test.cc
index 852c4ff1..f01709c 100644
--- a/chrome/test/base/in_process_browser_test.cc
+++ b/chrome/test/base/in_process_browser_test.cc
@@ -155,9 +155,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/uuid.h"
 #include "base/version.h"
-#include "chrome/browser/lacros/browser_test_util.h"
 #include "chrome/browser/lacros/cert/cert_db_initializer_factory.h"
-#include "chrome/browser/ui/lacros/window_utility.h"
 #include "chromeos/crosapi/mojom/crosapi.mojom.h"
 #include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
 #include "chromeos/lacros/lacros_service.h"
@@ -292,28 +290,6 @@
   NotificationDisplayServiceTester::EnsureFactoryBuilt();
 }
 
-// TODO(neis): The name WaitForWindowCreation is a bit confusing. Technically,
-// we are waiting for the window to become visible (or minimized) in Ash.
-// Try to find a better name.
-bool WaitForWindowCreation(Browser* browser) {
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  // TODO(crbug.com/1508245): Get rid of the IsTestControllerAvailable condition
-  // by making it always true (when crosapi is enabled). Moreover, propagate the
-  // WaitForWindowCreation return value. This requires fixing some tests that
-  // improperly override init params.
-  if (IsCrosapiEnabled() && IsTestControllerAvailable()) {
-    // Wait for window creation to complete in Ash in order to avoid
-    // wayland-crosapi race conditions in subsequent test steps.
-    aura::Window* window = browser->window()->GetNativeWindow();
-    std::string id =
-        lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
-    std::ignore = browser_test_util::WaitForWindowCreation(id);
-    return true;
-  }
-#endif
-  return true;
-}
-
 InProcessBrowserTest* g_current_test;
 
 }  // namespace
@@ -899,7 +875,6 @@
   observer.Wait();
 
   browser->window()->Show();
-  ASSERT_TRUE(WaitForWindowCreation(browser));
 }
 
 #if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -980,13 +955,8 @@
   // browser.
   content::RunAllPendingInMessageLoop();
 
-  if (browser_) {
-    ASSERT_TRUE(WaitForWindowCreation(browser_));
-
-    if (global_browser_set_up_function_) {
-      ASSERT_TRUE(global_browser_set_up_function_(browser_));
-    }
-  }
+  if (browser_ && global_browser_set_up_function_)
+    ASSERT_TRUE(global_browser_set_up_function_(browser_));
 
 #if BUILDFLAG(IS_MAC)
   autorelease_pool_->Recycle();
diff --git a/chrome/test/data/webui/settings/chromeos/os_people_page/account_manager_settings_card_test.ts b/chrome/test/data/webui/settings/chromeos/os_people_page/account_manager_settings_card_test.ts
index 377f183..b060fae 100644
--- a/chrome/test/data/webui/settings/chromeos/os_people_page/account_manager_settings_card_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_people_page/account_manager_settings_card_test.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://os-settings/os_settings.js';
 
-import {Account, AccountManagerBrowserProxyImpl} from 'chrome://os-settings/lazy_load.js';
+import {Account} from 'chrome://os-settings/lazy_load.js';
 import {AccountManagerSettingsCardElement, CrIconButtonElement, ParentalControlsBrowserProxyImpl, Router, routes} from 'chrome://os-settings/os_settings.js';
 import {assert} from 'chrome://resources/js/assert.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
@@ -12,27 +12,32 @@
 import {assertEquals, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 
-import {TestAccountManagerBrowserProxy, TestAccountManagerBrowserProxyForUnmanagedAccounts} from './test_account_manager_browser_proxy.js';
 import {TestParentalControlsBrowserProxy} from './test_parental_controls_browser_proxy.js';
 
 suite('<account-manager-settings-card>', () => {
-  let browserProxy: TestAccountManagerBrowserProxy;
   let accountManagerSettingsCard: AccountManagerSettingsCardElement;
-  let primaryDeviceAccount: Account|undefined;
+  const managedAccount: Account = {
+    id: '123',
+    accountType: 1,
+    isDeviceAccount: true,
+    isSignedIn: true,
+    unmigrated: false,
+    isManaged: true,
+    fullName: 'Primary Account',
+    pic: 'data:image/png;base64,primaryAccountPicData',
+    email: 'primary@gmail.com',
+    isAvailableInArc: true,
+    organization: 'Family Link',
+  };
 
   suiteSetup(async () => {
     loadTimeData.overrideValues({isDeviceAccountManaged: true});
-
-    browserProxy = new TestAccountManagerBrowserProxy();
-    AccountManagerBrowserProxyImpl.setInstanceForTesting(browserProxy);
-
-    primaryDeviceAccount = (await browserProxy.getAccounts())
-                               .find(account => account.isDeviceAccount);
   });
 
   setup(() => {
     accountManagerSettingsCard =
         document.createElement('account-manager-settings-card');
+    accountManagerSettingsCard.deviceAccount = managedAccount;
     document.body.appendChild(accountManagerSettingsCard);
 
     Router.getInstance().navigateTo(routes.OS_PEOPLE);
@@ -41,64 +46,62 @@
 
   teardown(() => {
     accountManagerSettingsCard.remove();
-    browserProxy.reset();
     Router.getInstance().resetRouteForTesting();
   });
 
-  test('managed badge is visible if device account is managed', async () => {
-    await browserProxy.whenCalled('getAccounts');
-    flush();
-
+  test('managed badge is visible if device account is managed', () => {
     const managedBadge = accountManagerSettingsCard.shadowRoot!.querySelector(
         '.device-account-icon .managed-badge');
 
     assertTrue(isVisible(managedBadge));
   });
 
-  test('account full name is correct and visible', async () => {
-    await browserProxy.whenCalled('getAccounts');
-    flush();
-
+  test('account full name is correct and visible', () => {
     const accountFullNameEl =
         accountManagerSettingsCard.shadowRoot!.querySelector(
             '#deviceAccountFullName');
 
     assert(accountFullNameEl);
-    assert(primaryDeviceAccount);
+    assert(managedAccount);
     assertTrue(isVisible(accountFullNameEl));
     assertEquals(
-        primaryDeviceAccount.fullName, accountFullNameEl.textContent!.trim());
+        managedAccount.fullName, accountFullNameEl.textContent!.trim());
   });
 
-  test('account email is correct and visible', async () => {
-    await browserProxy.whenCalled('getAccounts');
-    flush();
-
+  test('account email is correct and visible', () => {
     const accountEmailEl = accountManagerSettingsCard.shadowRoot!.querySelector(
         '#deviceAccountEmail');
 
     assert(accountEmailEl);
-    assert(primaryDeviceAccount);
+    assert(managedAccount);
     assertTrue(isVisible(accountEmailEl));
-    assertEquals(
-        primaryDeviceAccount.email, accountEmailEl.textContent!.trim());
+    assertEquals(managedAccount.email, accountEmailEl.textContent!.trim());
   });
 });
 
 suite('AccountManagerUnmanagedAccountTests', () => {
-  let browserProxy: TestAccountManagerBrowserProxyForUnmanagedAccounts;
   let accountManagerSettingsCard: AccountManagerSettingsCardElement;
+  const unmanagedAccount = {
+    id: '123',
+    accountType: 1,
+    isDeviceAccount: true,
+    isSignedIn: true,
+    unmigrated: false,
+    isManaged: false,
+    fullName: 'Device Account',
+    email: 'admin@domain.com',
+    pic: 'data:image/png;base64,abc123',
+    isAvailableInArc: false,
+  };
 
   suiteSetup(() => {
     loadTimeData.overrideValues({isDeviceAccountManaged: false});
-
-    browserProxy = new TestAccountManagerBrowserProxyForUnmanagedAccounts();
-    AccountManagerBrowserProxyImpl.setInstanceForTesting(browserProxy);
   });
 
   setup(() => {
     accountManagerSettingsCard =
         document.createElement('account-manager-settings-card');
+    accountManagerSettingsCard.deviceAccount = unmanagedAccount;
     document.body.appendChild(accountManagerSettingsCard);
 
     Router.getInstance().navigateTo(routes.OS_PEOPLE);
@@ -106,14 +109,10 @@
 
   teardown(() => {
     accountManagerSettingsCard.remove();
-    browserProxy.reset();
     Router.getInstance().resetRouteForTesting();
   });
 
-  test('ManagementStatusForUnmanagedAccounts', async () => {
-    await browserProxy.whenCalled('getAccounts');
-    flush();
-
+  test('ManagementStatusForUnmanagedAccounts', () => {
     const managedBadge = accountManagerSettingsCard.shadowRoot!.querySelector(
         '.device-account-icon .managed-badge');
     // Managed badge should not be shown for unmanaged accounts.
@@ -148,11 +147,12 @@
     Router.getInstance().resetRouteForTesting();
   });
 
-  test('FamilyLinkIcon', () => {
+  test('Family link icon is visible and launches family link settings', () => {
     const icon = accountManagerSettingsCard.shadowRoot!
                      .querySelector<CrIconButtonElement>(
                          '.managed-message cr-icon-button');
     assertTrue(!!icon, 'Could not find the managed icon');
+    assertTrue(isVisible(icon));
 
     assertEquals('cr20:kite', icon.ironIcon);
 
diff --git a/chrome/test/data/webui/settings/chromeos/os_people_page/additional_accounts_settings_card_test.ts b/chrome/test/data/webui/settings/chromeos/os_people_page/additional_accounts_settings_card_test.ts
index 0c78417..8c899d00 100644
--- a/chrome/test/data/webui/settings/chromeos/os_people_page/additional_accounts_settings_card_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_people_page/additional_accounts_settings_card_test.ts
@@ -7,7 +7,6 @@
 import {AccountManagerBrowserProxyImpl} from 'chrome://os-settings/lazy_load.js';
 import {AdditionalAccountsSettingsCardElement, CrTooltipIconElement, Router, routes, settingMojom, setUserActionRecorderForTesting} from 'chrome://os-settings/os_settings.js';
 import {assert} from 'chrome://resources/js/assert.js';
-import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {getDeepActiveElement} from 'chrome://resources/js/util.js';
 import {DomRepeat, flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -27,16 +26,18 @@
   suiteSetup(() => {
     loadTimeData.overrideValues({isDeviceAccountManaged: true});
 
-    browserProxy = new TestAccountManagerBrowserProxy();
-    AccountManagerBrowserProxyImpl.setInstanceForTesting(browserProxy);
+    userActionRecorder = new FakeUserActionRecorder();
+    setUserActionRecorderForTesting(userActionRecorder);
   });
 
   setup(async () => {
-    userActionRecorder = new FakeUserActionRecorder();
-    setUserActionRecorderForTesting(userActionRecorder);
+    browserProxy = new TestAccountManagerBrowserProxy();
+    AccountManagerBrowserProxyImpl.setInstanceForTesting(browserProxy);
+    const accounts = await browserProxy.getAccounts();
 
     additionalAccountSettingsCard =
         document.createElement('additional-accounts-settings-card');
+    additionalAccountSettingsCard.accounts = accounts;
     document.body.appendChild(additionalAccountSettingsCard);
     const list =
         additionalAccountSettingsCard.shadowRoot!.querySelector<DomRepeat>(
@@ -46,9 +47,6 @@
 
     Router.getInstance().navigateTo(routes.OS_PEOPLE);
     flush();
-
-    await browserProxy.whenCalled('getAccounts');
-    flush();
   });
 
   teardown(() => {
@@ -155,12 +153,6 @@
         `Kebab menu should be focused for settingId${removeAccountSettingId}.`);
   });
 
-  test('accountList is updated when account manager updates', () => {
-    assertEquals(1, browserProxy.getCallCount('getAccounts'));
-    webUIListenerCallback('accounts-changed');
-    assertEquals(2, browserProxy.getCallCount('getAccounts'));
-  });
-
   if (loadTimeData.getBoolean('arcAccountRestrictionsEnabled')) {
     test('arc availability is shown for secondary accounts', () => {
       accountList.items!.forEach((item, i) => {
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index 033b55f..572f08c 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -1130,7 +1130,13 @@
  ['OsSettingsUiToolbar', 'os_settings_ui/os_settings_ui_toolbar_test.js'],
  [
    'OsSettingsUiUserActionRecorder',
-   'os_settings_ui/user_action_recorder_test.js'
+   'os_settings_ui/user_action_recorder_test.js',
+   {disabled: ['ash::features::kOsSettingsRevampWayfinding']},
+ ],
+ [
+   'OsSettingsUiUserActionRecorderRevamp',
+   'os_settings_ui/user_action_recorder_test.js',
+   {enabled: ['ash::features::kOsSettingsRevampWayfinding']},
  ],
  [
    'ParentalControlsPage',
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_ui/user_action_recorder_test.ts b/chrome/test/data/webui/settings/chromeos/os_settings_ui/user_action_recorder_test.ts
index 38ed7502..d177bae 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_ui/user_action_recorder_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_ui/user_action_recorder_test.ts
@@ -4,17 +4,20 @@
 
 import 'chrome://os-settings/os_settings.js';
 
+import {AccountManagerBrowserProxyImpl} from 'chrome://os-settings/lazy_load.js';
 import {CrSettingsPrefs, OsSettingsUiElement, Router, routes, setNearbyShareSettingsForTesting, setUserActionRecorderForTesting} from 'chrome://os-settings/os_settings.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {FakeNearbyShareSettings} from 'chrome://webui-test/nearby_share/shared/fake_nearby_share_settings.js';
 
 import {FakeUserActionRecorder} from '../fake_user_action_recorder.js';
+import {TestAccountManagerBrowserProxy} from '../os_people_page/test_account_manager_browser_proxy.js';
 
 suite('User action recorder', () => {
   let ui: OsSettingsUiElement;
   let fakeUserActionRecorder: FakeUserActionRecorder;
   let fakeNearbySettings: FakeNearbyShareSettings;
+  let testAccountManagerBrowserProxy: TestAccountManagerBrowserProxy;
 
   async function createElement(): Promise<OsSettingsUiElement> {
     const element = document.createElement('os-settings-ui');
@@ -27,6 +30,11 @@
   suiteSetup(() => {
     fakeNearbySettings = new FakeNearbyShareSettings();
     setNearbyShareSettingsForTesting(fakeNearbySettings);
+
+    // Setup fake accounts.
+    testAccountManagerBrowserProxy = new TestAccountManagerBrowserProxy();
+    AccountManagerBrowserProxyImpl.setInstanceForTesting(
+        testAccountManagerBrowserProxy);
   });
 
   setup(async () => {
@@ -38,6 +46,7 @@
   teardown(() => {
     ui.remove();
     Router.getInstance().resetRouteForTesting();
+    testAccountManagerBrowserProxy.reset();
   });
 
   test('Records navigation changes', () => {
diff --git a/chrome/test/data/webui/settings/safety_hub_card_test.ts b/chrome/test/data/webui/settings/safety_hub_card_test.ts
index 90faa14..414aad9 100644
--- a/chrome/test/data/webui/settings/safety_hub_card_test.ts
+++ b/chrome/test/data/webui/settings/safety_hub_card_test.ts
@@ -53,7 +53,7 @@
     // Check icon for INFO state.
     testElement.data = getMockDataForState(CardState.INFO);
     flushTasks();
-    assertEquals('cr:error', testElement.$.icon.icon);
+    assertEquals('cr:info', testElement.$.icon.icon);
     assertTrue(testElement.$.icon.classList.contains('grey'));
 
     // Check icon for WEAK state.
diff --git a/chrome/test/data/webui/settings/safety_hub_page_test.ts b/chrome/test/data/webui/settings/safety_hub_page_test.ts
index bc8c0bf..b6f6717 100644
--- a/chrome/test/data/webui/settings/safety_hub_page_test.ts
+++ b/chrome/test/data/webui/settings/safety_hub_page_test.ts
@@ -6,8 +6,8 @@
 import 'chrome://settings/lazy_load.js';
 
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
-import {CardInfo, CardState, ContentSettingsTypes, SafetyHubBrowserProxyImpl, SafetyHubEvent,SettingsSafetyHubPageElement} from 'chrome://settings/lazy_load.js';
-import {LifetimeBrowserProxyImpl, MetricsBrowserProxyImpl, PasswordManagerImpl, PasswordManagerPage, Router, routes} from 'chrome://settings/settings.js';
+import {CardInfo, CardState, ContentSettingsTypes, SafetyHubBrowserProxyImpl, SafetyHubEvent, SettingsSafetyHubPageElement} from 'chrome://settings/lazy_load.js';
+import {LifetimeBrowserProxyImpl, MetricsBrowserProxyImpl, PasswordManagerImpl, PasswordManagerPage, Router, routes, SafetyHubModuleType, SafetyHubSurfaces} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {isChildVisible} from 'chrome://webui-test/test_util.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
@@ -364,4 +364,110 @@
     Router.getInstance().navigateTo(routes.SAFETY_HUB);
     await safetyHubBrowserProxy.whenCalled('dismissActiveMenuNotification');
   });
+
+  test('Metric Recording', async function() {
+    const safeCardData: CardInfo = {
+      header: 'Dummy header',
+      subheader: 'Dummy subheader',
+      state: CardState.SAFE,
+    };
+
+    const unsafeCardData: CardInfo = {
+      header: 'Dummy header',
+      subheader: 'Dummy subheader',
+      state: CardState.WARNING,
+    };
+
+    function reset() {
+      // Reset all cards and modules on safety hub page.
+      safetyHubBrowserProxy.setPasswordCardData(safeCardData);
+      safetyHubBrowserProxy.setVersionCardData(safeCardData);
+      safetyHubBrowserProxy.setSafeBrowsingCardData(safeCardData);
+      safetyHubBrowserProxy.setUnusedSitePermissions([]);
+      safetyHubBrowserProxy.setNotificationPermissionReview([]);
+      safetyHubBrowserProxy.setNumberOfExtensionsThatNeedReview(0);
+      metricsBrowserProxy.reset();
+    }
+
+    async function refresh(): Promise<void> {
+      // Refresh the page to consume recent mock data.
+      document.body.removeChild(testElement);
+      testElement = document.createElement('settings-safety-hub-page');
+      document.body.appendChild(testElement);
+      await flushTasks();
+    }
+
+    reset();
+    await refresh();
+    // Expect recordSafetyHubDashboardAnyWarning is called as false since
+    // there is no warning.
+    let result = await metricsBrowserProxy.whenCalled(
+        'recordSafetyHubDashboardAnyWarning');
+    assertEquals(false, result);
+
+    // Check general interaction and impression metrics.
+    result = await metricsBrowserProxy.whenCalled('recordSafetyHubImpression');
+    assertEquals(SafetyHubSurfaces.SAFETY_HUB_PAGE, result);
+    result = await metricsBrowserProxy.whenCalled('recordSafetyHubInteraction');
+    assertEquals(SafetyHubSurfaces.SAFETY_HUB_PAGE, result);
+
+    // Expect recordSafetyHubModuleWarningImpression is called for password
+    // card.
+    reset();
+    safetyHubBrowserProxy.setPasswordCardData(unsafeCardData);
+    await refresh();
+    result = await metricsBrowserProxy.whenCalled(
+        'recordSafetyHubModuleWarningImpression');
+    assertEquals(SafetyHubModuleType.PASSWORDS, result);
+
+    // Expect recordSafetyHubModuleWarningImpression is called for version card.
+    reset();
+    safetyHubBrowserProxy.setVersionCardData(unsafeCardData);
+    await refresh();
+    result = await metricsBrowserProxy.whenCalled(
+        'recordSafetyHubModuleWarningImpression');
+    assertEquals(SafetyHubModuleType.VERSION, result);
+
+    // Expect recordSafetyHubModuleWarningImpression is called for safe browsing
+    // card.
+    reset();
+    safetyHubBrowserProxy.setSafeBrowsingCardData(unsafeCardData);
+    await refresh();
+    result = await metricsBrowserProxy.whenCalled(
+        'recordSafetyHubModuleWarningImpression');
+    assertEquals(SafetyHubModuleType.SAFE_BROWSING, result);
+
+    // Expect recordSafetyHubModuleWarningImpression is called for unused site
+    // permissions.
+    reset();
+    safetyHubBrowserProxy.setUnusedSitePermissions(
+        unusedSitePermissionMockData);
+    await refresh();
+    result = await metricsBrowserProxy.whenCalled(
+        'recordSafetyHubModuleWarningImpression');
+    assertEquals(SafetyHubModuleType.PERMISSIONS, result);
+
+    // Expect recordSafetyHubModuleWarningImpression is called for notification
+    // permissions.
+    reset();
+    safetyHubBrowserProxy.setNotificationPermissionReview(
+        notificationPermissionMockData);
+    await refresh();
+    result = await metricsBrowserProxy.whenCalled(
+        'recordSafetyHubModuleWarningImpression');
+    assertEquals(SafetyHubModuleType.NOTIFICATIONS, result);
+
+    // Expect recordSafetyHubModuleWarningImpression is called for extensions.
+    reset();
+    safetyHubBrowserProxy.setNumberOfExtensionsThatNeedReview(1);
+    await refresh();
+    result = await metricsBrowserProxy.whenCalled(
+        'recordSafetyHubModuleWarningImpression');
+    assertEquals(SafetyHubModuleType.EXTENSIONS, result);
+
+    // Expect recordSafetyHubDashboardAnyWarning is called as true.
+    result = await metricsBrowserProxy.whenCalled(
+        'recordSafetyHubDashboardAnyWarning');
+    assertEquals(true, result);
+  });
 });
diff --git a/chrome/test/data/webui/settings/test_metrics_browser_proxy.ts b/chrome/test/data/webui/settings/test_metrics_browser_proxy.ts
index 1fe51204..08a3542 100644
--- a/chrome/test/data/webui/settings/test_metrics_browser_proxy.ts
+++ b/chrome/test/data/webui/settings/test_metrics_browser_proxy.ts
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {DeleteBrowsingDataAction, MetricsBrowserProxy, PrivacyElementInteractions, PrivacyGuideInteractions, PrivacyGuideSettingsStates, PrivacyGuideStepsEligibleAndReached, SafeBrowsingInteractions, SafetyCheckInteractions, SafetyCheckNotificationsModuleInteractions, SafetyCheckUnusedSitePermissionsModuleInteractions, SafetyHubCardState, SafetyHubSurfaces} from 'chrome://settings/settings.js';
+import {DeleteBrowsingDataAction, MetricsBrowserProxy, PrivacyElementInteractions, PrivacyGuideInteractions, PrivacyGuideSettingsStates, PrivacyGuideStepsEligibleAndReached, SafeBrowsingInteractions, SafetyCheckInteractions, SafetyCheckNotificationsModuleInteractions, SafetyCheckUnusedSitePermissionsModuleInteractions, SafetyHubCardState, SafetyHubEntryPoint, SafetyHubModuleType, SafetyHubSurfaces} from 'chrome://settings/settings.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
 export class TestMetricsBrowserProxy extends TestBrowserProxy implements
@@ -28,6 +28,10 @@
       'recordDeleteBrowsingDataAction',
       'recordSafetyHubImpression',
       'recordSafetyHubInteraction',
+      'recordSafetyHubEntryPointShown',
+      'recordSafetyHubEntryPointClicked',
+      'recordSafetyHubModuleWarningImpression',
+      'recordSafetyHubDashboardAnyWarning',
     ]);
   }
 
@@ -125,4 +129,20 @@
   recordSafetyHubInteraction(surface: SafetyHubSurfaces) {
     this.methodCalled('recordSafetyHubInteraction', surface);
   }
+
+  recordSafetyHubEntryPointShown(page: SafetyHubEntryPoint) {
+    this.methodCalled('recordSafetyHubModuleWarningImpression', page);
+  }
+
+  recordSafetyHubEntryPointClicked(page: SafetyHubEntryPoint) {
+    this.methodCalled('recordSafetyHubEntryPointClicked', page);
+  }
+
+  recordSafetyHubModuleWarningImpression(module: SafetyHubModuleType) {
+    this.methodCalled('recordSafetyHubModuleWarningImpression', module);
+  }
+
+  recordSafetyHubDashboardAnyWarning(visible: boolean) {
+    this.methodCalled('recordSafetyHubDashboardAnyWarning', visible);
+  }
 }
diff --git a/chrome/test/data/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_test.ts b/chrome/test/data/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_test.ts
index 9ee02276..a617b9fe 100644
--- a/chrome/test/data/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_test.ts
+++ b/chrome/test/data/webui/side_panel/customize_chrome/wallpaper_search/wallpaper_search_test.ts
@@ -130,7 +130,7 @@
       assertEquals(
           6,
           wallpaperSearchElement.shadowRoot!
-              .querySelectorAll('#descriptorMenuD cr-button')
+              .querySelectorAll('#descriptorMenuD button')
               .length);
     });
 
@@ -209,13 +209,13 @@
       await flushTasks();
 
       assertFalse(
-          !!$$(wallpaperSearchElement, '#descriptorMenuD cr-button [checked]'));
+          !!$$(wallpaperSearchElement, '#descriptorMenuD button [checked]'));
 
       $$<HTMLElement>(wallpaperSearchElement, '.default-color')!.click();
 
       let checkedMarkedColors =
           wallpaperSearchElement.shadowRoot!.querySelectorAll(
-              '#descriptorMenuD cr-button [checked]');
+              '#descriptorMenuD button [checked]');
       assertEquals(1, checkedMarkedColors.length);
       assertEquals(
           checkedMarkedColors[0],
@@ -229,7 +229,7 @@
           new Event('selected-hue-changed'));
 
       checkedMarkedColors = wallpaperSearchElement.shadowRoot!.querySelectorAll(
-          '#descriptorMenuD cr-button [checked]');
+          '#descriptorMenuD button [checked]');
       assertEquals(1, checkedMarkedColors.length);
       assertEquals(
           checkedMarkedColors[0],
@@ -277,7 +277,7 @@
           wallpaperSearchElement,
           '#descriptorComboboxC .dropdown-item')!.click();
       $$<HTMLElement>(
-          wallpaperSearchElement, '#descriptorMenuD cr-button')!.click();
+          wallpaperSearchElement, '#descriptorMenuD button')!.click();
       wallpaperSearchElement.$.submitButton.click();
 
       assertEquals(1, handler.getCallCount('getWallpaperSearchResults'));
@@ -298,7 +298,7 @@
       await flushTasks();
 
       $$<HTMLElement>(
-          wallpaperSearchElement, '#descriptorMenuD cr-button')!.click();
+          wallpaperSearchElement, '#descriptorMenuD button')!.click();
 
       wallpaperSearchElement.$.hueSlider.selectedHue = 10;
       wallpaperSearchElement.$.hueSlider.dispatchEvent(
diff --git a/chrome/test/data/webui/side_panel/read_anything/checkmark_visible_on_selected.ts b/chrome/test/data/webui/side_panel/read_anything/checkmark_visible_on_selected.ts
index b49f751..6888c0c 100644
--- a/chrome/test/data/webui/side_panel/read_anything/checkmark_visible_on_selected.ts
+++ b/chrome/test/data/webui/side_panel/read_anything/checkmark_visible_on_selected.ts
@@ -13,6 +13,7 @@
   let toolbar: ReadAnythingToolbarElement;
 
   setup(function() {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
     const readingMode = new FakeReadingMode();
     chrome.readingMode = readingMode as unknown as typeof chrome.readingMode;
 
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc b/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
index 16ffa49..81073fcde 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
+++ b/chrome/test/data/webui/side_panel/read_anything/read_anything_browsertest.cc
@@ -2,7 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ui/side_panel/side_panel_entry_id.h"
+#include "chrome/browser/ui/side_panel/side_panel_ui.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/base/web_ui_mocha_browser_test.h"
 #include "content/public/common/url_constants.h"
@@ -22,12 +25,26 @@
     set_test_loader_scheme(content::kChromeUIUntrustedScheme);
   }
 
+  void RunSidePanelTest(const std::string& file,
+                        const std::string& trigger,
+                        const SidePanelEntryId id) {
+    auto* side_panel_ui = SidePanelUI::GetSidePanelUIForBrowser(browser());
+    side_panel_ui->Show(id);
+    auto* web_contents = side_panel_ui->GetWebContentsForTest(id);
+    ASSERT_TRUE(web_contents);
+
+    content::WaitForLoadStop(web_contents);
+
+    ASSERT_TRUE(RunTestOnWebContents(web_contents, file, trigger, true));
+    side_panel_ui->Close();
+  }
+
   base::test::ScopedFeatureList scoped_feature_list_{features::kReadAnything};
 };
 
 using ReadAnythingMochaTest = ReadAnythingMochaBrowserTest;
 
 IN_PROC_BROWSER_TEST_F(ReadAnythingMochaTest, CheckmarkVisibleOnSelected) {
-  RunTest("side_panel/read_anything/checkmark_visible_on_selected.js",
-          "mocha.run()");
+  RunSidePanelTest("side_panel/read_anything/checkmark_visible_on_selected.js",
+                   "mocha.run()", SidePanelEntryId::kReadAnything);
 }
diff --git a/chrome/test/variations/pytest.ini b/chrome/test/variations/pytest.ini
index 2873107..34a1b31 100644
--- a/chrome/test/variations/pytest.ini
+++ b/chrome/test/variations/pytest.ini
@@ -1,2 +1,6 @@
 [pytest]
-pythonpath=../../../
\ No newline at end of file
+pythonpath=../../../
+
+filterwarnings =
+    # ignore warnings about re-imported modules.
+    ignore::pytest.PytestAssertRewriteWarning
diff --git a/chromecast/browser/cast_permission_manager.cc b/chromecast/browser/cast_permission_manager.cc
index c2d1060..5df307b 100644
--- a/chromecast/browser/cast_permission_manager.cc
+++ b/chromecast/browser/cast_permission_manager.cc
@@ -210,7 +210,7 @@
 }
 
 CastPermissionManager::SubscriptionId
-CastPermissionManager::SubscribePermissionStatusChange(
+CastPermissionManager::SubscribeToPermissionStatusChange(
     blink::PermissionType permission,
     content::RenderProcessHost* render_process_host,
     content::RenderFrameHost* render_frame_host,
@@ -219,7 +219,7 @@
   return SubscriptionId();
 }
 
-void CastPermissionManager::UnsubscribePermissionStatusChange(
+void CastPermissionManager::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {}
 
 }  // namespace shell
diff --git a/chromecast/browser/cast_permission_manager.h b/chromecast/browser/cast_permission_manager.h
index 16582c2..3093f67b5 100644
--- a/chromecast/browser/cast_permission_manager.h
+++ b/chromecast/browser/cast_permission_manager.h
@@ -62,14 +62,14 @@
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host,
       const url::Origin& requesting_origin) override;
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback)
       override;
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
 };
 
diff --git a/chromeos/ash/services/recording/gif_encoder.cc b/chromeos/ash/services/recording/gif_encoder.cc
index 5153049b3..71b16cd 100644
--- a/chromeos/ash/services/recording/gif_encoder.cc
+++ b/chromeos/ash/services/recording/gif_encoder.cc
@@ -29,8 +29,17 @@
 constexpr uint8_t kExtensionIntroducer = 0x21;
 
 // The minimum number of frames that needs to be received since the last time we
-// built the color palette, before we build a new one.
-constexpr uint8_t kMinNumberOfFramesBetweenPaletteRebuilds = 20;
+// built the color palette, before we build a new one. Since we are dithering
+// the image, we can work with an old color palette for a larger number of
+// frames before we have to rebuild it.
+constexpr uint8_t kMinNumberOfFramesBetweenPaletteRebuilds = 60;
+
+// If the screen doesn't have any damage, video frames may never be generated.
+// As a result, there can be a large time interval between one frame and the
+// next, in which case the existing color palette might be very stale, and needs
+// to be rebuilt.
+constexpr base::TimeDelta kMaxDurationBetweenSuccessiveFrames =
+    base::Seconds(2);
 
 // Calculates and returns the color bit depth based on the size of the given
 // `color_palette`. The color bit depth is the least number of bits needed to be
@@ -207,12 +216,31 @@
   return bitmap;
 }
 
-OctreeColorQuantizer CreateQuantizer(const RgbVideoFrame& rgb_video_frame) {
-  return OctreeColorQuantizer(rgb_video_frame);
+QuantizerPalettePair CreateQuantizer(const RgbVideoFrame& rgb_video_frame) {
+  return QuantizerPalettePair(OctreeColorQuantizer(rgb_video_frame));
 }
 
 }  // namespace
 
+// -----------------------------------------------------------------------------
+// QuantizerPalettePair:
+
+QuantizerPalettePair::QuantizerPalettePair(OctreeColorQuantizer&& new_quantizer)
+    : quantizer(std::move(new_quantizer)) {
+  color_palette.reserve(kMaxNumberOfColorsInPalette);
+  quantizer.ExtractColorPalette(color_palette);
+}
+
+QuantizerPalettePair::QuantizerPalettePair(QuantizerPalettePair&&) = default;
+
+QuantizerPalettePair& QuantizerPalettePair::operator=(QuantizerPalettePair&&) =
+    default;
+
+QuantizerPalettePair::~QuantizerPalettePair() = default;
+
+// -----------------------------------------------------------------------------
+// GifEncoder:
+
 // static
 base::SequenceBound<GifEncoder> GifEncoder::Create(
     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
@@ -302,12 +330,18 @@
   // sequence. We don't want the in-flight frame pool in
   // `FrameSinkVideoCapturerImpl` to fill up because we're not returning the
   // frames quick enough.
+  // Note that we don't allow the duration between any two successive frames to
+  // exceed `kMaxDurationBetweenSuccessiveFrames` without rebuilding the color
+  // palette as it may be very stale.
   if (color_palette_.empty()) {
-    SetQuantizer(OctreeColorQuantizer(rgb_video_frame));
-    color_quantizer_.ExtractPixelColorIndices(rgb_video_frame,
+    SetQuantizer(CreateQuantizer(rgb_video_frame));
+    color_quantizer_.ExtractPixelColorIndices(rgb_video_frame, color_palette_,
                                               pixel_color_indices_);
   } else {
-    if (frame_count_ % kMinNumberOfFramesBetweenPaletteRebuilds == 0) {
+    if (frame_count_ % kMinNumberOfFramesBetweenPaletteRebuilds == 0 ||
+        (!last_frame_time_.is_null() &&
+         frame_time - last_frame_time_ >=
+             kMaxDurationBetweenSuccessiveFrames)) {
       // Note that we have to clone the `rgb_video_frame` as the one we have
       // here will be disposed once this function returns.
       color_palette_task_runner_->PostTaskAndReplyWithResult(
@@ -317,7 +351,7 @@
     }
 
     // Rebuild the pixel color indices using the existing palette.
-    color_quantizer_.ExtractPixelColorIndices(rgb_video_frame,
+    color_quantizer_.ExtractPixelColorIndices(rgb_video_frame, color_palette_,
                                               pixel_color_indices_);
   }
 
@@ -483,9 +517,9 @@
   }
 }
 
-void GifEncoder::SetQuantizer(OctreeColorQuantizer&& new_color_quantizer) {
-  color_quantizer_ = std::move(new_color_quantizer);
-  color_quantizer_.ExtractColorPalette(color_palette_);
+void GifEncoder::SetQuantizer(QuantizerPalettePair&& quantizer_pair) {
+  color_quantizer_ = std::move(quantizer_pair.quantizer);
+  color_palette_ = std::move(quantizer_pair.color_palette);
 }
 
 }  // namespace recording
diff --git a/chromeos/ash/services/recording/gif_encoder.h b/chromeos/ash/services/recording/gif_encoder.h
index e6aee6b..b37727e 100644
--- a/chromeos/ash/services/recording/gif_encoder.h
+++ b/chromeos/ash/services/recording/gif_encoder.h
@@ -21,6 +21,18 @@
 
 class RgbVideoFrame;
 
+// Defines a pair of an `OctreeColorQuantizer` and the extracted
+// `color_palette` from it.
+struct QuantizerPalettePair {
+  explicit QuantizerPalettePair(OctreeColorQuantizer&& new_quantizer);
+  QuantizerPalettePair(QuantizerPalettePair&&);
+  QuantizerPalettePair& operator=(QuantizerPalettePair&&);
+  ~QuantizerPalettePair();
+
+  OctreeColorQuantizer quantizer;
+  ColorTable color_palette;
+};
+
 // Encapsulates encoding video frames into an animated GIF and writes the
 // encoded output to a file that it creates at the given `gif_file_path`. An
 // instance of this object can only be interacted with via a
@@ -112,9 +124,9 @@
   // implementation.
   void WriteColorPalette(uint8_t color_bit_depth);
 
-  // Moves the given `new_color_quantizer` into `color_quantizer_` and extracts
-  // a new color palette from it into `color_palette_`.
-  void SetQuantizer(OctreeColorQuantizer&& new_color_quantizer);
+  // Moves the quantizer and its extracted color palette from the given
+  // `quantizer_pair` into `color_quantizer_` and `color_palette_` respectively.
+  void SetQuantizer(QuantizerPalettePair&& quantizer_pair);
 
   // The thread pool task runner on which the color palettes are built every
   // `kMinNumberOfFramesBetweenPaletteRebuilds` frames except for the very first
diff --git a/chromeos/ash/services/recording/octree_color_quantizer.cc b/chromeos/ash/services/recording/octree_color_quantizer.cc
index 90778525..1bb7334 100644
--- a/chromeos/ash/services/recording/octree_color_quantizer.cc
+++ b/chromeos/ash/services/recording/octree_color_quantizer.cc
@@ -64,6 +64,44 @@
   ForEachPixelColor(const_cast<RgbVideoFrame&>(rgb_video_frame), f);
 }
 
+// Defines a color error per each color channel (R, G, and B), which is the
+// difference between the original color of a pixel, and the quantized
+// (predicted) color that we get from the Octree.
+//
+// We use this type instead of `RgbColor` (whose components are `uint8_t`s), as
+// we need the components to be represented as `int`s, since the difference can
+// be negative, and when scaled by the Floyd-Steinberg factors, the values can
+// exceed the maximum of 255.
+struct ErrorVector {
+  inline bool IsZero() const { return r == 0 && g == 0 && b == 0; }
+
+  int r;
+  int g;
+  int b;
+};
+
+// Given the `original_color` of a pixel, and its `quantized_color`, returns the
+// color error vector, which is the difference between the two.
+ErrorVector GetErrorVector(const RgbColor& original_color,
+                           const RgbColor& quantized_color) {
+  return ErrorVector(original_color.r - quantized_color.r,
+                     original_color.g - quantized_color.g,
+                     original_color.b - quantized_color.b);
+}
+
+// Diffuses the given color `error_vector` over the given `color` by a factor
+// equal to `factor / 16`. This means that each color component of
+// `error_vector` will be multiplied by a `factor / 16` and added to the
+// corresponding color component of `color`. The resulting `RgbColor` is
+// returned.
+RgbColor DiffuseErrorOnColor(const ErrorVector& error_vector,
+                             const RgbColor& color,
+                             int factor) {
+  return RgbColor(std::clamp(error_vector.r * factor / 16 + color.r, 0, 255),
+                  std::clamp(error_vector.g * factor / 16 + color.g, 0, 255),
+                  std::clamp(error_vector.b * factor / 16 + color.b, 0, 255));
+}
+
 }  // namespace
 
 // -----------------------------------------------------------------------------
@@ -101,20 +139,83 @@
   Node* curr = leaf_nodes_head_;
   while (curr != nullptr) {
     out_color_palette.push_back(curr->GetColor());
-    curr->palette_index_ = color_palette_index;
+    curr->palette_index_ = color_palette_index++;
     curr = curr->next_;
-    ++color_palette_index;
   }
 }
 
 void OctreeColorQuantizer::ExtractPixelColorIndices(
-    const RgbVideoFrame& rgb_video_frame,
+    RgbVideoFrame& rgb_video_frame,
+    const ColorTable& color_palette,
     ColorIndices& out_pixel_color_indices) const {
   size_t pixel_index = 0;
+  const int width = rgb_video_frame.width();
+  const int height = rgb_video_frame.height();
   ForEachPixelColor(rgb_video_frame, [&](const RgbColor& color) {
     const auto color_index = FindColorIndex(color);
     out_pixel_color_indices[pixel_index] = color_index;
+
+    const int row = pixel_index / width;
+    const int column = pixel_index % width;
+
     ++pixel_index;
+
+    // The below implements the "Floyd-Steinberg" dithering algorithm (see
+    // https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering). It
+    // works by diffusing (i.e. distributing) the color error of a pixel over
+    // neighboring pixels by the following factors:
+    //
+    // ----------------+----------+---------------+----------+------------------
+    //                 |          | current pixel |  7 / 16  |
+    // ----------------+----------+---------------+----------+------------------
+    //                 |  3 / 16  |     5 / 16    |  1 / 16  |
+    // ----------------+----------+---------------+----------+------------------
+    //
+    // It actually modifies the colors of the pixels that haven't been processed
+    // yet in the `rgb_video_frame` which will affect their quantized color when
+    // they get processed in the upcoming iterations. This results in the
+    // dithering of the quantized image.
+    const ErrorVector error_vector =
+        GetErrorVector(/*original_color=*/color,
+                       /*quantized_color=*/color_palette[color_index]);
+    if (error_vector.IsZero()) {
+      return;
+    }
+
+    const auto next_column = column + 1;
+    const auto next_row = row + 1;
+    const bool is_next_row_valid = next_row < height;
+
+    if (next_column < width) {
+      // Same row, next column. Add error with a factor of `7 / 16`.
+      auto& next_col_color = rgb_video_frame.pixel_color(row, next_column);
+      next_col_color =
+          DiffuseErrorOnColor(error_vector, next_col_color, /*factor=*/7);
+
+      if (is_next_row_valid) {
+        // Next row, next column. Add error with a factor of `1 / 16`.
+        auto& next_row_col_color =
+            rgb_video_frame.pixel_color(next_row, next_column);
+        next_row_col_color =
+            DiffuseErrorOnColor(error_vector, next_row_col_color, /*factor=*/1);
+      }
+    }
+
+    if (is_next_row_valid) {
+      // Next row, same column. Add error with a factor of `5 / 16`.
+      auto& next_row_color = rgb_video_frame.pixel_color(next_row, column);
+      next_row_color =
+          DiffuseErrorOnColor(error_vector, next_row_color, /*factor=*/5);
+
+      // Next row, previous column. Add error with a factor of `3 / 16`.
+      const auto prev_column = column - 1;
+      if (prev_column >= 0) {
+        auto& next_row_prev_col_color =
+            rgb_video_frame.pixel_color(next_row, prev_column);
+        next_row_prev_col_color = DiffuseErrorOnColor(
+            error_vector, next_row_prev_col_color, /*factor=*/3);
+      }
+    }
   });
 }
 
diff --git a/chromeos/ash/services/recording/octree_color_quantizer.h b/chromeos/ash/services/recording/octree_color_quantizer.h
index b4f47cc..395d9a9 100644
--- a/chromeos/ash/services/recording/octree_color_quantizer.h
+++ b/chromeos/ash/services/recording/octree_color_quantizer.h
@@ -47,9 +47,16 @@
   // closest colors for each pixel in the given `rgb_video_frame` from the
   // quantized color palette extracted by calling `ExtractColorPalette()` above.
   // This means `ExtractColorPalette()` must be called once before calling this
-  // for every received video frame (provided that the same color palette is
+  // for every received video frame (provided that the same `color_palette` is
   // still desired to be reused).
-  void ExtractPixelColorIndices(const RgbVideoFrame& rgb_video_frame,
+  // This also implements the Floyd-Steinberg dithering, meaning that the
+  // resulting color indices in `out_pixel_color_indices` will be of a
+  // quantized and dithered image of the given `rgb_video_frame` using the given
+  // `color_palette`. The given `rgb_video_frame` will be modified in the
+  // process of dithering to diffuse the color errors in each pixel over the
+  // colors of neighboring pixels (See implementation for details).
+  void ExtractPixelColorIndices(RgbVideoFrame& rgb_video_frame,
+                                const ColorTable& color_palette,
                                 ColorIndices& out_pixel_color_indices) const;
 
  private:
diff --git a/chromeos/profiles/atom.afdo.newest.txt b/chromeos/profiles/atom.afdo.newest.txt
index c917380..45f29b8 100644
--- a/chromeos/profiles/atom.afdo.newest.txt
+++ b/chromeos/profiles/atom.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-atom-121-6154.0-1701689968-benchmark-122.0.6171.0-r1-redacted.afdo.xz
+chromeos-chrome-amd64-atom-121-6167.9-1702295304-benchmark-122.0.6179.0-r1-redacted.afdo.xz
diff --git a/clank b/clank
index 3d55fc4..b570761 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 3d55fc4f1877714a5aefaf5ca58e533d53b61244
+Subproject commit b570761166a159d8217203052bdfc29ec90ebef9
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 34d7225..fb54f3dd 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -540,6 +540,25 @@
   form_tracker_->TextFieldDidChange(element);
 }
 
+void AutofillAgent::ContentEditableDidChange(const WebElement& element) {
+  DCHECK(MaybeWasOwnedByFrame(element, unsafe_render_frame()));
+  if (!base::FeatureList::IsEnabled(
+          features::kAutofillContentEditableChangeEvents) ||
+      !base::FeatureList::IsEnabled(features::kAutofillContentEditables) ||
+      !base::FeatureList::IsEnabled(features::kAutofillTextAreaChangeEvents)) {
+    return;
+  }
+  // TODO(crbug.com/1494479): Add throttling to avoid sending this event for
+  // rapid changes.
+  if (std::optional<FormData> form = FindFormForContentEditable(element)) {
+    const FormFieldData& field = form->fields.front();
+    if (auto* autofill_driver = unsafe_autofill_driver()) {
+      autofill_driver->TextFieldDidChange(*form, field, field.bounds,
+                                          AutofillTickClock::NowTicks());
+    }
+  }
+}
+
 void AutofillAgent::OnTextFieldDidChange(const WebFormControlElement& element) {
   DCHECK(MaybeWasOwnedByFrame(element, unsafe_render_frame()));
   // TODO(crbug.com/1494479): Add throttling to avoid sending this event for
diff --git a/components/autofill/content/renderer/autofill_agent.h b/components/autofill/content/renderer/autofill_agent.h
index 38da23e..eccb3ea 100644
--- a/components/autofill/content/renderer/autofill_agent.h
+++ b/components/autofill/content/renderer/autofill_agent.h
@@ -272,6 +272,7 @@
   // blink::WebAutofillClient:
   void TextFieldDidEndEditing(const blink::WebInputElement& element) override;
   void TextFieldDidChange(const blink::WebFormControlElement& element) override;
+  void ContentEditableDidChange(const blink::WebElement& element) override;
   void TextFieldDidReceiveKeyDown(
       const blink::WebInputElement& element,
       const blink::WebKeyboardEvent& event) override;
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index 0625be6..3697e1b 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -2333,13 +2333,14 @@
   }
   field.aria_label = GetAriaLabel(document, content_editable);
   field.aria_description = GetAriaDescription(document, content_editable);
-  // TextContent() includes hidden elements and does not add linebreaks. If this
-  // is not sufficient in the future, consider calling HTMLElement::innerText(),
-  // which returns the text "as rendered" (i.e., it inserts whitespace at the
-  // right places and it ignores "display:none" subtrees), but is significantly
-  // more expensive because it triggers a layout.
+  // TextContentAbridged() includes hidden elements and does not add linebreaks.
+  // If this is not sufficient in the future, consider calling
+  // HTMLElement::innerText(), which returns the text "as rendered" (i.e., it
+  // inserts whitespace at the right places and it ignores "display:none"
+  // subtrees), but is significantly more expensive because it triggers a layout.
   field.value =
-      content_editable.TextContent().Utf16().substr(0, kMaxStringLength);
+      content_editable.TextContentAbridged(kMaxStringLength).Utf16();
+  DCHECK_LE(field.value.length(), kMaxStringLength);
   field.selected_text =
       content_editable.SelectedText().Utf16().substr(0, kMaxSelectedTextLength);
   return form;
diff --git a/components/autofill/content/renderer/form_autofill_util_browsertest.cc b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
index 0072756..2de5273 100644
--- a/components/autofill/content/renderer/form_autofill_util_browsertest.cc
+++ b/components/autofill/content/renderer/form_autofill_util_browsertest.cc
@@ -2082,6 +2082,52 @@
   EXPECT_EQ(field.value, u"\n            This is the textContent!\n         ");
 }
 
+TEST_F(FormAutofillUtilsTest, FindFormForContentEditableAbridgedSuccess) {
+  // HTML with 1500 characters of pi in the contenteditable div
+  LoadHTML(
+      R"(<body>
+         <div id=my-id
+              name=my-name
+              class=my-class
+              autocomplete=given-name
+              contenteditable>3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029</div>
+         </body>)");
+  WebElement content_editable =
+      GetMainFrame()->GetDocument().GetElementById("my-id");
+  ASSERT_FALSE(content_editable.IsNull());
+  std::optional<FormData> form = FindFormForContentEditable(content_editable);
+  ASSERT_EQ(form->fields.size(), 1u);
+  const FormFieldData& field = form->fields[0];
+  EXPECT_TRUE(form->unique_renderer_id);
+  EXPECT_EQ(*form->unique_renderer_id, *field.unique_renderer_id);
+  EXPECT_EQ(form->unique_renderer_id, field.host_form_id);
+  EXPECT_EQ(field.parsed_autocomplete->field_type, HtmlFieldType::kGivenName);
+  EXPECT_EQ(field.name, u"my-id");
+  EXPECT_EQ(field.id_attribute, u"my-id");
+  EXPECT_EQ(field.name_attribute, u"my-name");
+  EXPECT_EQ(field.css_classes, u"my-class");
+  // Only extract 1024 characters from the div.
+  EXPECT_EQ(field.value.length(), 1024u);
+  EXPECT_EQ(
+      field.value,
+      u"3."
+      u"14159265358979323846264338327950288419716939937510582097494459230781640"
+      u"62862089986280348253421170679821480865132823066470938446095505822317253"
+      u"59408128481117450284102701938521105559644622948954930381964428810975665"
+      u"93344612847564823378678316527120190914564856692346034861045432664821339"
+      u"36072602491412737245870066063155881748815209209628292540917153643678925"
+      u"90360011330530548820466521384146951941511609433057270365759591953092186"
+      u"11738193261179310511854807446237996274956735188575272489122793818301194"
+      u"91298336733624406566430860213949463952247371907021798609437027705392171"
+      u"76293176752384674818467669405132000568127145263560827785771342757789609"
+      u"17363717872146844090122495343014654958537105079227968925892354201995611"
+      u"21290219608640344181598136297747713099605187072113499999983729780499510"
+      u"59731732816096318595024459455346908302642522308253344685035261931188171"
+      u"01000313783875288658753320838142061717766914730359825349042875546873115"
+      u"95628638823537875937519577818577805321712268066130019278766111959092164"
+      u"2019893809525720106548586327");
+}
+
 TEST_F(FormAutofillUtilsTest, FindFormForContentEditableFailures) {
   LoadHTML(
       R"(<body>
diff --git a/components/autofill/core/common/autofill_features.cc b/components/autofill/core/common/autofill_features.cc
index 6f07078..8e27f23 100644
--- a/components/autofill/core/common/autofill_features.cc
+++ b/components/autofill/core/common/autofill_features.cc
@@ -618,11 +618,17 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Sends text change events for textarea elements. When this is off, only input
-// elements send text change events.
+// elements and maybe contenteditable elements send text change events.
 BASE_FEATURE(kAutofillTextAreaChangeEvents,
              "AutofillTextAreaChangeEvents",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Sends text change events for contenteditable elements. When this is off,
+// only input elements and maybe textarea elements send text change events.
+BASE_FEATURE(kAutofillContentEditableChangeEvents,
+             "AutofillContentEditableChangeEvents",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // When enabled, on form submit, observations for every used profile are
 // collected into the profile's `token_quality()`.
 // TODO(crbug.com/1453650): Remove when launched.
diff --git a/components/autofill/core/common/autofill_features.h b/components/autofill/core/common/autofill_features.h
index 20f791fa..08f4910 100644
--- a/components/autofill/core/common/autofill_features.h
+++ b/components/autofill/core/common/autofill_features.h
@@ -206,6 +206,8 @@
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillTextAreaChangeEvents);
 COMPONENT_EXPORT(AUTOFILL)
+BASE_DECLARE_FEATURE(kAutofillContentEditableChangeEvents);
+COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillTrackProfileTokenQuality);
 COMPONENT_EXPORT(AUTOFILL)
 BASE_DECLARE_FEATURE(kAutofillUseImprovedLabelDisambiguation);
diff --git a/components/certificate_transparency/chrome_ct_policy_enforcer.cc b/components/certificate_transparency/chrome_ct_policy_enforcer.cc
index 0216883..bd75165d 100644
--- a/components/certificate_transparency/chrome_ct_policy_enforcer.cc
+++ b/components/certificate_transparency/chrome_ct_policy_enforcer.cc
@@ -83,9 +83,19 @@
     base::Time log_list_date,
     std::vector<std::pair<std::string, base::Time>> disqualified_logs,
     std::map<std::string, OperatorHistoryEntry> log_operator_history)
+    : ChromeCTPolicyEnforcer(log_list_date,
+                             std::move(disqualified_logs),
+                             std::move(log_operator_history),
+                             base::DefaultClock::GetInstance()) {}
+
+ChromeCTPolicyEnforcer::ChromeCTPolicyEnforcer(
+    base::Time log_list_date,
+    std::vector<std::pair<std::string, base::Time>> disqualified_logs,
+    std::map<std::string, OperatorHistoryEntry> log_operator_history,
+    const base::Clock* clock)
     : disqualified_logs_(std::move(disqualified_logs)),
       log_operator_history_(std::move(log_operator_history)),
-      clock_(base::DefaultClock::GetInstance()),
+      clock_(clock),
       log_list_date_(log_list_date) {}
 
 ChromeCTPolicyEnforcer::~ChromeCTPolicyEnforcer() {}
@@ -93,7 +103,7 @@
 CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCompliance(
     net::X509Certificate* cert,
     const net::ct::SCTList& verified_scts,
-    const net::NetLogWithSource& net_log) {
+    const net::NetLogWithSource& net_log) const {
   // If the build is not timely, no certificate is considered compliant
   // with CT policy. The reasoning is that, for example, a log might
   // have been pulled and is no longer considered valid; thus, a client
@@ -122,10 +132,6 @@
   log_list_date_ = update_time;
   disqualified_logs_ = std::move(disqualified_logs);
   log_operator_history_ = std::move(log_operator_history);
-
-  if (disqualified_log_for_testing_.has_value()) {
-    disqualified_log_for_testing_ = std::nullopt;
-  }
 }
 
 bool ChromeCTPolicyEnforcer::IsLogDisqualified(
@@ -133,12 +139,6 @@
     base::Time* disqualification_date) const {
   CHECK_EQ(log_id.size(), crypto::kSHA256Length);
 
-  if (disqualified_log_for_testing_.has_value() &&
-      log_id == disqualified_log_for_testing_.value().first) {
-    *disqualification_date = disqualified_log_for_testing_.value().second;
-    return *disqualification_date < base::Time::Now();
-  }
-
   auto p = std::lower_bound(
       std::begin(disqualified_logs_), std::end(disqualified_logs_), log_id,
       [](const auto& a, std::string_view b) { return a.first < b; });
diff --git a/components/certificate_transparency/chrome_ct_policy_enforcer.h b/components/certificate_transparency/chrome_ct_policy_enforcer.h
index 992caeb..44ba99d 100644
--- a/components/certificate_transparency/chrome_ct_policy_enforcer.h
+++ b/components/certificate_transparency/chrome_ct_policy_enforcer.h
@@ -54,13 +54,18 @@
       base::Time log_list_date,
       std::vector<std::pair<std::string, base::Time>> disqualified_logs,
       std::map<std::string, OperatorHistoryEntry> log_operator_history);
+  ChromeCTPolicyEnforcer(
+      base::Time log_list_date,
+      std::vector<std::pair<std::string, base::Time>> disqualified_logs,
+      std::map<std::string, OperatorHistoryEntry> log_operator_history,
+      const base::Clock* clock);
 
   ~ChromeCTPolicyEnforcer() override;
 
   net::ct::CTPolicyCompliance CheckCompliance(
       net::X509Certificate* cert,
       const net::ct::SCTList& verified_scts,
-      const net::NetLogWithSource& net_log) override;
+      const net::NetLogWithSource& net_log) const override;
 
   // Updates the list of logs used for compliance checks. |disqualified_logs|
   // is a map of log ID to disqualification date.
@@ -69,8 +74,6 @@
       std::vector<std::pair<std::string, base::Time>> disqualified_logs,
       std::map<std::string, OperatorHistoryEntry> log_operator_history);
 
-  void SetClockForTesting(const base::Clock* clock) { clock_ = clock; }
-
   // TODO(https://crbug.com/999240): These are exposed to allow end-to-end
   // testing by higher layers (i.e. that the ChromeCTPolicyEnforcer is
   // correctly constructed). When either this issue or https://crbug.com/848277
@@ -90,16 +93,6 @@
     ct_log_list_always_timely_for_testing_ = always_timely;
   }
 
-  void SetOperatorHistoryForTesting(
-      std::map<std::string, OperatorHistoryEntry> log_operator_history) {
-    log_operator_history_ = std::move(log_operator_history);
-  }
-
-  void SetDisqualifiedLogForTesting(
-      const std::pair<std::string, base::Time>& disqualified_log) {
-    disqualified_log_for_testing_ = disqualified_log;
-  }
-
  private:
   FRIEND_TEST_ALL_PREFIXES(ChromeCTPolicyEnforcerTest,
                            IsLogDisqualifiedTimestamp);
@@ -128,7 +121,7 @@
 
   std::map<std::string, OperatorHistoryEntry> log_operator_history_;
 
-  raw_ptr<const base::Clock> clock_;
+  const raw_ptr<const base::Clock> clock_;
 
   // The time at which |disqualified_logs_| and |log_operator_history_| were
   // generated.
@@ -137,12 +130,6 @@
   // If set, the CT log list will be considered timely regardless of its last
   // update time.
   bool ct_log_list_always_timely_for_testing_ = false;
-
-  // If set, this log ID will be considered a disqualified log, effective at the
-  // specified time.
-  // Calling UpdateCTLogList clears this value if set.
-  std::optional<std::pair<std::string, base::Time>>
-      disqualified_log_for_testing_;
 };
 
 }  // namespace certificate_transparency
diff --git a/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc b/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc
index c5434418e..19d70933 100644
--- a/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc
+++ b/components/certificate_transparency/chrome_ct_policy_enforcer_unittest.cc
@@ -49,11 +49,7 @@
 class ChromeCTPolicyEnforcerTest : public ::testing::Test {
  public:
   void SetUp() override {
-    auto enforcer = std::make_unique<ChromeCTPolicyEnforcer>(
-        base::Time::Now(), GetDisqualifiedLogs(),
-        std::map<std::string, OperatorHistoryEntry>());
-    enforcer->SetClockForTesting(&clock_);
-    policy_enforcer_ = std::move(enforcer);
+    test_now_ = base::Time::Now();
 
     std::string der_test_cert(net::ct::GetDerEncodedX509Cert());
     chain_ = X509Certificate::CreateFromBytes(
@@ -61,7 +57,15 @@
     ASSERT_TRUE(chain_.get());
     test_log_id_ = std::string(kTestLogID, crypto::kSHA256Length);
     another_log_id_.assign(crypto::kSHA256Length, 1);
-    clock_.SetNow(base::Time::Now());
+    clock_.SetNow(test_now_);
+  }
+
+  std::unique_ptr<ChromeCTPolicyEnforcer> MakeChromeCTPolicyEnforcer(
+      std::vector<std::pair<std::string, base::Time>> disqualified_logs,
+      std::map<std::string, OperatorHistoryEntry> log_operator_history) {
+    return std::make_unique<ChromeCTPolicyEnforcer>(
+        test_now_, std::move(disqualified_logs),
+        std::move(log_operator_history), &clock_);
   }
 
   void FillListWithSCTsOfOrigin(
@@ -85,9 +89,11 @@
     }
   }
 
-  void AddDisqualifiedLogSCT(SignedCertificateTimestamp::Origin desired_origin,
-                             bool timestamp_after_disqualification_date,
-                             SCTList* verified_scts) {
+  void AddDisqualifiedLogSCT(
+      SignedCertificateTimestamp::Origin desired_origin,
+      bool timestamp_after_disqualification_date,
+      std::pair<std::string, base::Time>* disqualified_log,
+      SCTList* verified_scts) {
     static const char kTestRetiredLogID[] =
         "\xcd\xb5\x17\x9b\x7f\xc1\xc0\x46\xfe\xea\x31\x13\x6a\x3f\x8f\x00\x2e"
         "\x61\x82\xfa\xf8\x89\x6f\xec\xc8\xb2\xf5\xb5\xab\x60\x49\x00";
@@ -97,8 +103,8 @@
     ASSERT_TRUE(base::Time::FromUTCExploded({2022, 4, 0, 16, 0, 0, 0, 0},
                                             &retirement_time));
 
-    policy_enforcer_->SetDisqualifiedLogForTesting(
-        std::make_pair(std::string(kTestRetiredLogID, 32), retirement_time));
+    *disqualified_log =
+        std::make_pair(std::string(kTestRetiredLogID, 32), retirement_time);
 
     scoped_refptr<SignedCertificateTimestamp> sct(
         new SignedCertificateTimestamp());
@@ -142,8 +148,8 @@
   }
 
  protected:
+  base::Time test_now_;
   base::SimpleTestClock clock_;
-  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer_;
   scoped_refptr<X509Certificate> chain_;
   std::string test_log_id_;
   std::string another_log_id_;
@@ -152,6 +158,7 @@
 
 TEST_F(ChromeCTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughFreshSCTs) {
   SCTList scts;
+  std::pair<std::string, base::Time> disqualified_log;
   std::map<std::string, OperatorHistoryEntry> operator_history;
 
   // The results should be the same before and after disqualification,
@@ -164,12 +171,13 @@
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
                            1, &scts);
   AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
-                        false, &scts);
+                        false, &disqualified_log, &scts);
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 
   // Two SCTs from TLS, one of them from a disqualified log after the
   // disqualification time.
@@ -178,41 +186,47 @@
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
                            1, &scts);
   AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
-                        true, &scts);
+                        true, &disqualified_log, &scts);
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 
   // Two embedded SCTs, one of them from a disqualified log before the
   // disqualification time.
   scts.clear();
   operator_history.clear();
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_EMBEDDED, 1, &scts);
-  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, false, &scts);
+  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, false,
+                        &disqualified_log, &scts);
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 
   // Two embedded SCTs, one of them from a disqualified log after the
   // disqualification time.
   scts.clear();
   operator_history.clear();
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_EMBEDDED, 1, &scts);
-  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, true, &scts);
+  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, true,
+                        &disqualified_log, &scts);
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest,
        ConformsToCTPolicyWithMixOfEmbeddedAndNonEmbedded) {
   SCTList scts;
+  std::pair<std::string, base::Time> disqualified_log;
   std::map<std::string, OperatorHistoryEntry> operator_history;
 
   // One SCT from TLS, one Embedded SCT from before disqualification time.
@@ -220,12 +234,14 @@
   operator_history.clear();
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
                            1, &scts);
-  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, false, &scts);
+  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, false,
+                        &disqualified_log, &scts);
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 
   // One SCT from TLS, one Embedded SCT from after disqualification time.
   // The embedded SCT is still counted towards the diversity requirement even
@@ -234,12 +250,14 @@
   operator_history.clear();
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
                            1, &scts);
-  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, true, &scts);
+  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, true,
+                        &disqualified_log, &scts);
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, ConformsToCTPolicyWithNonEmbeddedSCTs) {
@@ -249,11 +267,13 @@
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, EnforcementDisabledByBinaryAge) {
@@ -263,17 +283,19 @@
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 
   clock_.Advance(base::Days(71));
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, ConformsToCTPolicyWithEmbeddedSCTs) {
@@ -283,11 +305,13 @@
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest,
@@ -309,11 +333,13 @@
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, ConformsToCTPolicyWithPooledEmbeddedSCTs) {
@@ -334,11 +360,13 @@
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughSCTs) {
@@ -348,11 +376,13 @@
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest,
@@ -364,16 +394,21 @@
                            desired_log_ids, &scts);
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_EMBEDDED, 1,
                            std::vector<std::string>(), &scts);
-  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, false, &scts);
+  std::pair<std::string, base::Time> disqualified_log;
+  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED,
+                        /*timestamp_after_disqualification_date=*/false,
+                        &disqualified_log, &scts);
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
+
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
 
   // |chain_| is valid for 10 years - over 180 days - so requires 3 SCTs.
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest,
@@ -381,22 +416,30 @@
   SCTList scts;
   // Add required - 1 valid SCTs.
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_EMBEDDED, 2, &scts);
-  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, true, &scts);
+  std::pair<std::string, base::Time> disqualified_log;
+  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED,
+                        /*timestamp_after_disqualification_date=*/true,
+                        &disqualified_log, &scts);
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
+
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
 
   // |chain_| is valid for 10 years - over 180 days - so requires 3 SCTs.
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest,
        DoesNotConformWithIssuanceDateAfterDisqualificationDate) {
   SCTList scts;
-  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED, true, &scts);
+  std::pair<std::string, base::Time> disqualified_log;
+  AddDisqualifiedLogSCT(SignedCertificateTimestamp::SCT_EMBEDDED,
+                        /*timestamp_after_disqualification_date=*/true,
+                        &disqualified_log, &scts);
   // Add required - 1 valid SCTs.
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_EMBEDDED, 2, &scts);
   // Make sure all SCTs are after the disqualification date.
@@ -405,15 +448,21 @@
 
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
+
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer({disqualified_log}, operator_history);
 
   // |chain_| is valid for 10 years - over 180 days - so requires 3 SCTs.
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, UpdateCTLogList) {
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(),
+                                 std::map<std::string, OperatorHistoryEntry>());
+
   SCTList scts;
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
                            2, &scts);
@@ -426,26 +475,30 @@
     operator_history[sct->log_id] = entry;
   }
 
-  policy_enforcer_->UpdateCTLogList(base::Time::Now(), disqualified_logs,
-                                    operator_history);
+  policy_enforcer->UpdateCTLogList(base::Time::Now(), disqualified_logs,
+                                   operator_history);
 
   // The check should fail since the logs all have the same operator.
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 
   // Update the list again, this time including diverse operators.
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->UpdateCTLogList(base::Time::Now(), disqualified_logs,
-                                    operator_history);
+  policy_enforcer->UpdateCTLogList(base::Time::Now(), disqualified_logs,
+                                   operator_history);
 
   // The check should now succeed.
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, TimestampUpdates) {
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(),
+                                 std::map<std::string, OperatorHistoryEntry>());
+
   SCTList scts;
   FillListWithSCTsOfOrigin(SignedCertificateTimestamp::SCT_FROM_TLS_EXTENSION,
                            1, &scts);
@@ -455,23 +508,23 @@
   std::map<std::string, OperatorHistoryEntry> log_operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &log_operator_history);
 
-  policy_enforcer_->UpdateCTLogList(base::Time::Now() - base::Days(71),
-                                    disqualified_logs, log_operator_history);
+  policy_enforcer->UpdateCTLogList(base::Time::Now() - base::Days(71),
+                                   disqualified_logs, log_operator_history);
 
   // The check should return build not timely even though there are not enough
   // SCTs.
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 
   // Update the last update time value again, this time with a recent time.
-  policy_enforcer_->UpdateCTLogList(base::Time::Now(), disqualified_logs,
-                                    log_operator_history);
+  policy_enforcer->UpdateCTLogList(base::Time::Now(), disqualified_logs,
+                                   log_operator_history);
 
   // The check should now fail
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, IsLogDisqualifiedTimestamp) {
@@ -486,15 +539,16 @@
   base::Time future_disqualification = base::Time::Now() + base::Hours(1);
   disqualified_logs.emplace_back(kModifiedTestLogID, future_disqualification);
   disqualified_logs.emplace_back(kTestLogID, past_disqualification);
-  policy_enforcer_->UpdateCTLogList(base::Time::Now(), disqualified_logs,
-                                    log_operator_history);
+
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(disqualified_logs, log_operator_history);
 
   base::Time disqualification_time;
   EXPECT_TRUE(
-      policy_enforcer_->IsLogDisqualified(kTestLogID, &disqualification_time));
+      policy_enforcer->IsLogDisqualified(kTestLogID, &disqualification_time));
   EXPECT_EQ(disqualification_time, past_disqualification);
-  EXPECT_FALSE(policy_enforcer_->IsLogDisqualified(kModifiedTestLogID,
-                                                   &disqualification_time));
+  EXPECT_FALSE(policy_enforcer->IsLogDisqualified(kModifiedTestLogID,
+                                                  &disqualification_time));
   EXPECT_EQ(disqualification_time, future_disqualification);
 }
 
@@ -508,13 +562,14 @@
   std::map<std::string, OperatorHistoryEntry> log_operator_history;
   disqualified_logs.emplace_back(kModifiedTestLogID,
                                  base::Time::Now() - base::Days(1));
-  policy_enforcer_->UpdateCTLogList(base::Time::Now(), disqualified_logs,
-                                    log_operator_history);
+
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(disqualified_logs, log_operator_history);
 
   base::Time unused;
   // IsLogDisqualified should return false for a log that is not in the
   // disqualified list.
-  EXPECT_FALSE(policy_enforcer_->IsLogDisqualified(kTestLogID, &unused));
+  EXPECT_FALSE(policy_enforcer->IsLogDisqualified(kTestLogID, &unused));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest,
@@ -538,13 +593,13 @@
   }
   std::sort(std::begin(disqualified_logs), std::end(disqualified_logs));
 
-  policy_enforcer_->UpdateCTLogList(base::Time::Now(), disqualified_logs,
-                                    log_operator_history);
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(disqualified_logs, log_operator_history);
 
   // SCTs should comply since retirement date is in the future.
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest,
@@ -569,13 +624,13 @@
 
   FillOperatorHistoryWithDiverseOperators(scts, &log_operator_history);
 
-  policy_enforcer_->UpdateCTLogList(base::Time::Now(), disqualified_logs,
-                                    log_operator_history);
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(disqualified_logs, log_operator_history);
 
   // SCTs should not comply since retirement date is in the past.
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, UpdatedSCTRequirements) {
@@ -637,7 +692,10 @@
       // Add different operators to the logs so the SCTs comply with operator
       // diversity.
       FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-      policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
+
+      std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+          MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
       CTPolicyCompliance expected;
       if (j == scts_required) {
         // If the scts provided are as many as are required, the cert should be
@@ -650,8 +708,8 @@
         // In any other case, the 'not enough' check should trip.
         expected = CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS;
       }
-      EXPECT_EQ(expected, policy_enforcer_->CheckCompliance(cert.get(), scts,
-                                                            NetLogWithSource()))
+      EXPECT_EQ(expected, policy_enforcer->CheckCompliance(cert.get(), scts,
+                                                           NetLogWithSource()))
           << " for: " << (validity_end - validity_start).InDays() << " and "
           << scts_required << " scts=" << scts.size() << " j=" << j;
     }
@@ -669,11 +727,13 @@
     entry.current_operator_ = "Operator";
     operator_history[sct->log_id] = entry;
   }
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, ConformsToCTPolicyDifferentOperators) {
@@ -682,11 +742,13 @@
                            2, std::vector<std::string>(), &scts);
   std::map<std::string, OperatorHistoryEntry> operator_history;
   FillOperatorHistoryWithDiverseOperators(scts, &operator_history);
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, ConformsToPolicyDueToOperatorSwitch) {
@@ -704,11 +766,13 @@
   // end time after the SCT timestamp.
   operator_history[scts[1]->log_id].previous_operators_.emplace_back(
       "Different Operator", scts[1]->timestamp + base::Seconds(1));
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, DoesNotConformToPolicyDueToOperatorSwitch) {
@@ -723,11 +787,13 @@
   // with an end time after the SCT timestamp.
   operator_history[scts[1]->log_id].previous_operators_.emplace_back(
       "Operator 0", scts[1]->timestamp + base::Seconds(1));
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, MultipleOperatorSwitches) {
@@ -743,11 +809,13 @@
       "Different Operator", scts[1]->timestamp - base::Seconds(1));
   operator_history[scts[1]->log_id].previous_operators_.emplace_back(
       "Operator 0", scts[1]->timestamp + base::Seconds(1));
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 TEST_F(ChromeCTPolicyEnforcerTest, MultipleOperatorSwitchesBeforeSCTTimestamp) {
@@ -767,11 +835,13 @@
       "Different Operator", scts[1]->timestamp - base::Seconds(2));
   operator_history[scts[1]->log_id].previous_operators_.emplace_back(
       "Yet Another Different Operator", scts[1]->timestamp - base::Seconds(1));
-  policy_enforcer_->SetOperatorHistoryForTesting(operator_history);
 
-  EXPECT_EQ(CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
-            policy_enforcer_->CheckCompliance(chain_.get(), scts,
-                                              NetLogWithSource()));
+  std::unique_ptr<ChromeCTPolicyEnforcer> policy_enforcer =
+      MakeChromeCTPolicyEnforcer(GetDisqualifiedLogs(), operator_history);
+
+  EXPECT_EQ(
+      CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS,
+      policy_enforcer->CheckCompliance(chain_.get(), scts, NetLogWithSource()));
 }
 
 }  // namespace certificate_transparency
diff --git a/components/commerce/core/metrics/scheduled_metrics_manager_unittest.cc b/components/commerce/core/metrics/scheduled_metrics_manager_unittest.cc
index 4ac9c589..da692b59 100644
--- a/components/commerce/core/metrics/scheduled_metrics_manager_unittest.cc
+++ b/components/commerce/core/metrics/scheduled_metrics_manager_unittest.cc
@@ -12,6 +12,7 @@
 #include "components/commerce/core/metrics/scheduled_metrics_manager.h"
 #include "components/commerce/core/mock_shopping_service.h"
 #include "components/commerce/core/pref_names.h"
+#include "components/commerce/core/price_tracking_utils.h"
 #include "components/commerce/core/test_utils.h"
 #include "components/power_bookmarks/core/power_bookmark_utils.h"
 #include "components/power_bookmarks/core/proto/power_bookmark_meta.pb.h"
@@ -56,8 +57,8 @@
 
   // Add two tracked products.
   shopping_service_->SetGetAllSubscriptionsCallbackValue(
-      {CreateUserTrackedSubscription(123L),
-       CreateUserTrackedSubscription(456L)});
+      {BuildUserSubscriptionForClusterId(123L),
+       BuildUserSubscriptionForClusterId(456L)});
 
   CreateUpdateManagerAndWait();
 
@@ -71,7 +72,7 @@
   base::HistogramTester histogram_tester;
 
   shopping_service_->SetGetAllSubscriptionsCallbackValue(
-      {CreateUserTrackedSubscription(123L)});
+      {BuildUserSubscriptionForClusterId(123L)});
 
   CreateUpdateManagerAndWait();
 
@@ -106,7 +107,7 @@
 
   // Have at least one tracked product.
   shopping_service_->SetGetAllSubscriptionsCallbackValue(
-      {CreateUserTrackedSubscription(123L)});
+      {BuildUserSubscriptionForClusterId(123L)});
 
   CreateUpdateManagerAndWait();
 
@@ -127,7 +128,7 @@
 
   // Have at least one tracked product.
   shopping_service_->SetGetAllSubscriptionsCallbackValue(
-      {CreateUserTrackedSubscription(123L)});
+      {BuildUserSubscriptionForClusterId(123L)});
 
   CreateUpdateManagerAndWait();
 
diff --git a/components/commerce/core/mock_shopping_service.cc b/components/commerce/core/mock_shopping_service.cc
index 32e1cebb..f0e2f05b 100644
--- a/components/commerce/core/mock_shopping_service.cc
+++ b/components/commerce/core/mock_shopping_service.cc
@@ -45,7 +45,6 @@
   SetIsSubscribedCallbackValue(true);
   SetGetAllSubscriptionsCallbackValue(std::vector<CommerceSubscription>());
   SetIsShoppingListEligible(true);
-  SetIsClusterIdTrackedByUserResponse(true);
   SetIsMerchantViewerEnabled(true);
   SetGetAllPriceTrackedBookmarksCallbackValue(
       std::vector<const bookmarks::BookmarkNode*>());
@@ -190,15 +189,6 @@
           });
 }
 
-void MockShoppingService::SetIsClusterIdTrackedByUserResponse(bool is_tracked) {
-  ON_CALL(*this, IsClusterIdTrackedByUser)
-      .WillByDefault([is_tracked](uint64_t cluster_id,
-                                  base::OnceCallback<void(bool)> callback) {
-        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-            FROM_HERE, base::BindOnce(std::move(callback), is_tracked));
-      });
-}
-
 void MockShoppingService::SetIsMerchantViewerEnabled(bool is_enabled) {
   ON_CALL(*this, IsMerchantViewerEnabled)
       .WillByDefault(testing::Return(is_enabled));
diff --git a/components/commerce/core/mock_shopping_service.h b/components/commerce/core/mock_shopping_service.h
index d26c085e..52b649a0 100644
--- a/components/commerce/core/mock_shopping_service.h
+++ b/components/commerce/core/mock_shopping_service.h
@@ -96,10 +96,6 @@
               WaitForReady,
               (base::OnceCallback<void(ShoppingService*)>),
               (override));
-  MOCK_METHOD(void,
-              IsClusterIdTrackedByUser,
-              (uint64_t cluster_id, base::OnceCallback<void(bool)> callback),
-              (override));
   MOCK_METHOD(bool, IsMerchantViewerEnabled, (), (override));
   MOCK_METHOD(bool, IsPriceInsightsEligible, (), (override));
   MOCK_METHOD(bool, IsDiscountEligibleToShowOnNavigation, (), (override));
@@ -138,7 +134,6 @@
       std::vector<CommerceSubscription> subscriptions);
   void SetIsShoppingListEligible(bool enabled);
   void SetIsReady(bool ready);
-  void SetIsClusterIdTrackedByUserResponse(bool is_tracked);
   void SetIsMerchantViewerEnabled(bool is_enabled);
   void SetGetAllPriceTrackedBookmarksCallbackValue(
       std::vector<const bookmarks::BookmarkNode*> bookmarks);
diff --git a/components/commerce/core/price_tracking_utils.h b/components/commerce/core/price_tracking_utils.h
index 18f974c7..1dfeb78 100644
--- a/components/commerce/core/price_tracking_utils.h
+++ b/components/commerce/core/price_tracking_utils.h
@@ -120,8 +120,9 @@
 // set by the user or is still in the default state.
 bool IsEmailNotificationPrefSetByUser(PrefService* pref_service);
 
-// Build a user-tracked price tracking subscription object for the provided
-// cluster ID.
+// Builds a user-managed price tracking subscription object for the provided
+// cluster ID. This does not change the state of the subscription, it only
+// creates the object representing the subscription.
 CommerceSubscription BuildUserSubscriptionForClusterId(uint64_t cluster_id);
 
 // Returns whether price tracking can be initiated given either a ProductInfo
diff --git a/components/commerce/core/price_tracking_utils_unittest.cc b/components/commerce/core/price_tracking_utils_unittest.cc
index ecf552b..c547806 100644
--- a/components/commerce/core/price_tracking_utils_unittest.cc
+++ b/components/commerce/core/price_tracking_utils_unittest.cc
@@ -373,7 +373,7 @@
   ASSERT_EQ(2U, bookmark_model_->other_node()->children().size());
 
   shopping_service_->SetGetAllSubscriptionsCallbackValue(
-      {CreateUserTrackedSubscription(12345L)});
+      {BuildUserSubscriptionForClusterId(12345L)});
 
   base::RunLoop run_loop;
   GetAllPriceTrackedBookmarks(
diff --git a/components/commerce/core/shopping_service.cc b/components/commerce/core/shopping_service.cc
index 3e21b77..33859737 100644
--- a/components/commerce/core/shopping_service.cc
+++ b/components/commerce/core/shopping_service.cc
@@ -1527,21 +1527,6 @@
   return true;
 }
 
-void ShoppingService::IsClusterIdTrackedByUser(
-    uint64_t cluster_id,
-    base::OnceCallback<void(bool)> callback) {
-  if (!subscriptions_manager_) {
-    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, base::BindOnce(std::move(callback), false));
-    return;
-  }
-
-  CommerceSubscription sub(
-      SubscriptionType::kPriceTrack, IdentifierType::kProductClusterId,
-      base::NumberToString(cluster_id), ManagementType::kUserManaged);
-  subscriptions_manager_->IsSubscribed(std::move(sub), std::move(callback));
-}
-
 void ShoppingService::StartTrackingParcels(
     const std::vector<std::pair<ParcelIdentifier::Carrier, std::string>>&
         parcel_identifiers,
diff --git a/components/commerce/core/shopping_service.h b/components/commerce/core/shopping_service.h
index 214f60c..20a8e572 100644
--- a/components/commerce/core/shopping_service.h
+++ b/components/commerce/core/shopping_service.h
@@ -377,12 +377,6 @@
   virtual void WaitForReady(
       base::OnceCallback<void(ShoppingService*)> callback);
 
-  // Check whether a product (based on cluster ID) is explicitly price tracked
-  // by the user.
-  virtual void IsClusterIdTrackedByUser(
-      uint64_t cluster_id,
-      base::OnceCallback<void(bool)> callback);
-
   // This is a feature check for the "merchant viewer", which will return true
   // if the user has the feature flag enabled or (if applicable) is in an
   // enabled country and locale.
diff --git a/components/commerce/core/test_utils.cc b/components/commerce/core/test_utils.cc
index 06dd18c..0ff7ddb6a 100644
--- a/components/commerce/core/test_utils.cc
+++ b/components/commerce/core/test_utils.cc
@@ -72,12 +72,6 @@
                                             std::move(meta));
 }
 
-CommerceSubscription CreateUserTrackedSubscription(uint64_t cluster_id) {
-  return CommerceSubscription(
-      SubscriptionType::kPriceTrack, IdentifierType::kProductClusterId,
-      base::NumberToString(cluster_id), ManagementType::kUserManaged);
-}
-
 void SetShoppingListEnterprisePolicyPref(PrefService* prefs, bool enabled) {
   prefs->SetBoolean(kShoppingListEnabledPrefName, enabled);
 }
diff --git a/components/commerce/core/test_utils.h b/components/commerce/core/test_utils.h
index a28c2b6..a1864ff 100644
--- a/components/commerce/core/test_utils.h
+++ b/components/commerce/core/test_utils.h
@@ -72,8 +72,6 @@
     const absl::optional<int64_t>& last_subscription_change_time =
         absl::nullopt);
 
-CommerceSubscription CreateUserTrackedSubscription(uint64_t cluster_id);
-
 // Sets the state of the enterprise policy for the shopping list feature for
 // testing.
 void SetShoppingListEnterprisePolicyPref(PrefService* prefs, bool enabled);
diff --git a/components/commerce/core/webui/shopping_list_handler_unittest.cc b/components/commerce/core/webui/shopping_list_handler_unittest.cc
index e1d922e..a0a210eb 100644
--- a/components/commerce/core/webui/shopping_list_handler_unittest.cc
+++ b/components/commerce/core/webui/shopping_list_handler_unittest.cc
@@ -230,7 +230,7 @@
   handler_->TrackPriceForBookmark(product->id());
 
   // Assume the subscription callback fires with a success.
-  handler_->OnSubscribe(CreateUserTrackedSubscription(cluster_id), true);
+  handler_->OnSubscribe(BuildUserSubscriptionForClusterId(cluster_id), true);
 
   task_environment_.RunUntilIdle();
 }
@@ -253,7 +253,7 @@
   handler_->UntrackPriceForBookmark(product->id());
 
   // Assume the subscription callback fires with a success.
-  handler_->OnUnsubscribe(CreateUserTrackedSubscription(cluster_id), true);
+  handler_->OnUnsubscribe(BuildUserSubscriptionForClusterId(cluster_id), true);
 
   task_environment_.RunUntilIdle();
 }
@@ -280,7 +280,7 @@
   handler_->TrackPriceForBookmark(product->id());
 
   // Assume the subscription callback fires with a failure.
-  handler_->OnUnsubscribe(CreateUserTrackedSubscription(cluster_id), false);
+  handler_->OnUnsubscribe(BuildUserSubscriptionForClusterId(cluster_id), false);
 
   task_environment_.RunUntilIdle();
 }
@@ -307,7 +307,7 @@
   handler_->UntrackPriceForBookmark(product->id());
 
   // Assume the subscription callback fires with a failure.
-  handler_->OnUnsubscribe(CreateUserTrackedSubscription(cluster_id), false);
+  handler_->OnUnsubscribe(BuildUserSubscriptionForClusterId(cluster_id), false);
 
   task_environment_.RunUntilIdle();
 }
@@ -321,7 +321,7 @@
               PriceUntrackedForBookmark(MojoBookmarkInfoWithId(product->id())));
 
   // Assume the plumbing for subscriptions works and fake an unsubscribe event.
-  handler_->OnUnsubscribe(CreateUserTrackedSubscription(123L), true);
+  handler_->OnUnsubscribe(BuildUserSubscriptionForClusterId(123L), true);
 
   task_environment_.RunUntilIdle();
 }
@@ -332,7 +332,7 @@
                          MojoBookmarkInfoWithClusterId(cluster_id)))
       .Times(1);
 
-  handler_->OnUnsubscribe(CreateUserTrackedSubscription(cluster_id), true);
+  handler_->OnUnsubscribe(BuildUserSubscriptionForClusterId(cluster_id), true);
 
   task_environment_.RunUntilIdle();
 }
@@ -346,7 +346,7 @@
   AddProductBookmark(bookmark_model_.get(), u"product 2",
                      GURL("http://example.com/2"), 456L, false, 4560000, "usd");
   shopping_service_->SetGetAllSubscriptionsCallbackValue(
-      {CreateUserTrackedSubscription(123L)});
+      {BuildUserSubscriptionForClusterId(123L)});
 
   std::vector<const bookmarks::BookmarkNode*> bookmark_list;
   bookmark_list.push_back(product);
diff --git a/components/component_updater/installer_policies/BUILD.gn b/components/component_updater/installer_policies/BUILD.gn
index 39fb776..0948f0f 100644
--- a/components/component_updater/installer_policies/BUILD.gn
+++ b/components/component_updater/installer_policies/BUILD.gn
@@ -16,8 +16,6 @@
   sources = [
     "autofill_states_component_installer.cc",
     "autofill_states_component_installer.h",
-    "client_side_phishing_component_installer_policy.cc",
-    "client_side_phishing_component_installer_policy.h",
     "masked_domain_list_component_installer_policy.cc",
     "masked_domain_list_component_installer_policy.h",
     "origin_trials_component_installer.cc",
diff --git a/components/component_updater/installer_policies/client_side_phishing_component_installer_policy.cc b/components/component_updater/installer_policies/client_side_phishing_component_installer_policy.cc
deleted file mode 100644
index c7a7b190..0000000
--- a/components/component_updater/installer_policies/client_side_phishing_component_installer_policy.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/component_updater/installer_policies/client_side_phishing_component_installer_policy.h"
-
-#include <stdint.h>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/files/file_path.h"
-#include "base/files/file_util.h"
-#include "base/functional/bind.h"
-#include "base/functional/callback.h"
-#include "base/values.h"
-#include "base/version.h"
-#include "components/component_updater/component_installer.h"
-
-namespace component_updater {
-
-namespace {
-const char kClientSidePhishingManifestName[] = "Client Side Phishing Detection";
-
-// The SHA256 of the SubjectPublicKeyInfo used to sign the extension.
-// The extension id is: imefjhfbkmcmebodilednhmaccmincoa
-const uint8_t kClientSidePhishingPublicKeySHA256[32] = {
-    0x8c, 0x45, 0x97, 0x51, 0xac, 0x2c, 0x41, 0xe3, 0x8b, 0x43, 0xd7,
-    0xc0, 0x22, 0xc8, 0xd2, 0xe0, 0xe3, 0xe2, 0x33, 0x88, 0x1f, 0x09,
-    0x6d, 0xde, 0x65, 0x6a, 0x83, 0x32, 0x71, 0x52, 0x6e, 0x77};
-
-}  // namespace
-
-const base::FilePath::CharType kClientModelBinaryPbFileName[] =
-    FILE_PATH_LITERAL("client_model.pb");
-const base::FilePath::CharType kVisualTfLiteModelFileName[] =
-    FILE_PATH_LITERAL("visual_model.tflite");
-
-ClientSidePhishingComponentInstallerPolicy::
-    ClientSidePhishingComponentInstallerPolicy(
-        const ReadFilesCallback& read_files_callback,
-        const InstallerAttributesCallback& installer_attributes_callback)
-    : read_files_callback_(std::move(read_files_callback)),
-      installer_attributes_callback_(std::move(installer_attributes_callback)) {
-}
-
-ClientSidePhishingComponentInstallerPolicy::
-    ~ClientSidePhishingComponentInstallerPolicy() = default;
-
-// static
-void ClientSidePhishingComponentInstallerPolicy::GetPublicHash(
-    std::vector<uint8_t>* hash) {
-  hash->assign(kClientSidePhishingPublicKeySHA256,
-               kClientSidePhishingPublicKeySHA256 +
-                   std::size(kClientSidePhishingPublicKeySHA256));
-}
-
-bool ClientSidePhishingComponentInstallerPolicy::
-    SupportsGroupPolicyEnabledComponentUpdates() const {
-  return true;
-}
-
-bool ClientSidePhishingComponentInstallerPolicy::RequiresNetworkEncryption()
-    const {
-  return false;
-}
-
-update_client::CrxInstaller::Result
-ClientSidePhishingComponentInstallerPolicy::OnCustomInstall(
-    const base::Value::Dict& manifest,
-    const base::FilePath& install_dir) {
-  return update_client::CrxInstaller::Result(0);  // Nothing custom here.
-}
-
-void ClientSidePhishingComponentInstallerPolicy::OnCustomUninstall() {}
-
-void ClientSidePhishingComponentInstallerPolicy::ComponentReady(
-    const base::Version& version,
-    const base::FilePath& install_dir,
-    base::Value::Dict manifest) {
-  read_files_callback_.Run(install_dir);
-}
-
-// Called during startup and installation before ComponentReady().
-bool ClientSidePhishingComponentInstallerPolicy::VerifyInstallation(
-    const base::Value::Dict& manifest,
-    const base::FilePath& install_dir) const {
-  // No need to actually validate the proto here, since we'll do the checking
-  // in PopulateFromDynamicUpdate().
-  return base::PathExists(install_dir.Append(kClientModelBinaryPbFileName)) ||
-         base::PathExists(install_dir.Append(kVisualTfLiteModelFileName));
-}
-
-base::FilePath
-ClientSidePhishingComponentInstallerPolicy::GetRelativeInstallDir() const {
-  return base::FilePath(FILE_PATH_LITERAL("ClientSidePhishing"));
-}
-
-void ClientSidePhishingComponentInstallerPolicy::GetHash(
-    std::vector<uint8_t>* hash) const {
-  GetPublicHash(hash);
-}
-
-std::string ClientSidePhishingComponentInstallerPolicy::GetName() const {
-  return kClientSidePhishingManifestName;
-}
-
-update_client::InstallerAttributes
-ClientSidePhishingComponentInstallerPolicy::GetInstallerAttributes() const {
-  return installer_attributes_callback_.Run();
-}
-
-}  // namespace component_updater
diff --git a/components/component_updater/installer_policies/client_side_phishing_component_installer_policy.h b/components/component_updater/installer_policies/client_side_phishing_component_installer_policy.h
deleted file mode 100644
index 8b71e2d..0000000
--- a/components/component_updater/installer_policies/client_side_phishing_component_installer_policy.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2021 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_COMPONENT_UPDATER_INSTALLER_POLICIES_CLIENT_SIDE_PHISHING_COMPONENT_INSTALLER_POLICY_H_
-#define COMPONENTS_COMPONENT_UPDATER_INSTALLER_POLICIES_CLIENT_SIDE_PHISHING_COMPONENT_INSTALLER_POLICY_H_
-
-#include <stdint.h>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/functional/callback.h"
-#include "base/values.h"
-#include "components/component_updater/component_installer.h"
-
-namespace base {
-class FilePath;
-class Version;
-}  // namespace base
-
-namespace component_updater {
-
-extern const base::FilePath::CharType kClientModelBinaryPbFileName[];
-extern const base::FilePath::CharType kVisualTfLiteModelFileName[];
-
-class ClientSidePhishingComponentInstallerPolicy
-    : public ComponentInstallerPolicy {
- public:
-  // A callback to read model files from the given install path and populate the
-  // model appropriately, used to customize the behaviour of `ComponentReady`.
-  using ReadFilesCallback =
-      base::RepeatingCallback<void(const base::FilePath&)>;
-  // A callback that returns the appoperiate installer attributes, used to
-  // customize the behaviour of `GetInstallerAttributes`.
-  using InstallerAttributesCallback =
-      base::RepeatingCallback<update_client::InstallerAttributes()>;
-
-  ClientSidePhishingComponentInstallerPolicy(
-      const ReadFilesCallback& read_files_callback,
-      const InstallerAttributesCallback& installer_attributes_callback);
-  ClientSidePhishingComponentInstallerPolicy(
-      const ClientSidePhishingComponentInstallerPolicy&) = delete;
-  ClientSidePhishingComponentInstallerPolicy& operator=(
-      const ClientSidePhishingComponentInstallerPolicy&) = delete;
-  ~ClientSidePhishingComponentInstallerPolicy() override;
-
-  static void GetPublicHash(std::vector<uint8_t>* hash);
-
- private:
-  // The following methods override ComponentInstallerPolicy.
-  bool SupportsGroupPolicyEnabledComponentUpdates() const override;
-  bool RequiresNetworkEncryption() const override;
-  update_client::CrxInstaller::Result OnCustomInstall(
-      const base::Value::Dict& manifest,
-      const base::FilePath& install_dir) override;
-  void OnCustomUninstall() override;
-  bool VerifyInstallation(const base::Value::Dict& manifest,
-                          const base::FilePath& install_dir) const override;
-  void ComponentReady(const base::Version& version,
-                      const base::FilePath& install_dir,
-                      base::Value::Dict manifest) override;
-  base::FilePath GetRelativeInstallDir() const override;
-  void GetHash(std::vector<uint8_t>* hash) const override;
-  std::string GetName() const override;
-  update_client::InstallerAttributes GetInstallerAttributes() const override;
-
-  static base::FilePath GetInstalledPath(const base::FilePath& base);
-
-  ReadFilesCallback read_files_callback_;
-  InstallerAttributesCallback installer_attributes_callback_;
-};
-
-}  // namespace component_updater
-
-#endif  // COMPONENTS_COMPONENT_UPDATER_INSTALLER_POLICIES_CLIENT_SIDE_PHISHING_COMPONENT_INSTALLER_POLICY_H_
diff --git a/components/exo/keyboard_unittest.cc b/components/exo/keyboard_unittest.cc
index be5e6210..a6bade9 100644
--- a/components/exo/keyboard_unittest.cc
+++ b/components/exo/keyboard_unittest.cc
@@ -883,7 +883,7 @@
   EXPECT_TRUE(keyboard.HasDeviceConfigurationDelegate());
   testing::Mock::VerifyAndClearExpectations(&configuration_delegate);
 
-  ash::AccessibilityControllerImpl* accessibility_controller =
+  ash::AccessibilityController* accessibility_controller =
       ash::Shell::Get()->accessibility_controller();
 
   // Enable a11y keyboard calls OnKeyboardTypeChanged() with false.
diff --git a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
index bbe4b8c..87def57 100644
--- a/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
+++ b/components/feature_engagement/public/android/java/src/org/chromium/components/feature_engagement/EventConstants.java
@@ -150,10 +150,6 @@
     /** Shared Highlighting button event */
     public static final String IPH_SHARED_HIGHLIGHTING_USED = "iph_shared_highlighting_used";
 
-    /** Webnotes Stylize feature used from Sharing Hub */
-    public static final String SHARING_HUB_WEBNOTES_STYLIZE_USED =
-            "sharing_hub_webnotes_stylize_used";
-
     /** AutoDark disabled from app menu events. */
     public static final String AUTO_DARK_DISABLED_IN_APP_MENU = "auto_dark_disabled_in_app_menu";
 
diff --git a/components/history/core/browser/history_database.cc b/components/history/core/browser/history_database.cc
index 9a3d586..11b81c3 100644
--- a/components/history/core/browser/history_database.cc
+++ b/components/history/core/browser/history_database.cc
@@ -293,7 +293,6 @@
 }
 
 int HistoryDatabase::CountUniqueHostsVisitedLastMonth() {
-  base::TimeTicks start_time = base::TimeTicks::Now();
   // Collect all URLs visited within the last month.
   base::Time one_month_ago = base::Time::Now() - base::Days(30);
 
@@ -310,8 +309,6 @@
     hosts.insert(url.host());
   }
 
-  UMA_HISTOGRAM_TIMES("History.DatabaseMonthlyHostCountTime",
-                      base::TimeTicks::Now() - start_time);
   return hosts.size();
 }
 
diff --git a/components/history/core/browser/history_service_unittest.cc b/components/history/core/browser/history_service_unittest.cc
index ed229500..a0a1677 100644
--- a/components/history/core/browser/history_service_unittest.cc
+++ b/components/history/core/browser/history_service_unittest.cc
@@ -876,7 +876,6 @@
 
 // Counts hosts visited in the last month.
 TEST_F(HistoryServiceTest, CountMonthlyVisitedHosts) {
-  base::HistogramTester histogram_tester;
   HistoryService* history = history_service_.get();
   ASSERT_TRUE(history);
 
@@ -896,8 +895,6 @@
   AddPageInThePast(history, "https://www.yahoo.com/foo", 29);
   EXPECT_EQ(3, GetMonthlyHostCountHelper(history, &tracker_));
 
-  // The time required to compute host count is reported on each computation.
-  histogram_tester.ExpectTotalCount("History.DatabaseMonthlyHostCountTime", 4);
 }
 
 TEST_F(HistoryServiceTest, GetDomainDiversityShortBasetimeRange) {
diff --git a/components/history/core/browser/top_sites_backend.cc b/components/history/core/browser/top_sites_backend.cc
index 30618b6b..6e6b06f 100644
--- a/components/history/core/browser/top_sites_backend.cc
+++ b/components/history/core/browser/top_sites_backend.cc
@@ -55,11 +55,10 @@
       std::move(callback));
 }
 
-void TopSitesBackend::UpdateTopSites(const TopSitesDelta& delta,
-                                     const RecordHistogram record_or_not) {
+void TopSitesBackend::UpdateTopSites(const TopSitesDelta& delta) {
   db_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&TopSitesBackend::UpdateTopSitesOnDBThread,
-                                this, delta, record_or_not));
+      FROM_HERE,
+      base::BindOnce(&TopSitesBackend::UpdateTopSitesOnDBThread, this, delta));
 }
 
 void TopSitesBackend::ResetDatabase() {
@@ -91,21 +90,13 @@
   return db_ ? db_->GetSites() : MostVisitedURLList();
 }
 
-void TopSitesBackend::UpdateTopSitesOnDBThread(
-    const TopSitesDelta& delta, const RecordHistogram record_or_not) {
+void TopSitesBackend::UpdateTopSitesOnDBThread(const TopSitesDelta& delta) {
   TRACE_EVENT0("startup", "history::TopSitesBackend::UpdateTopSitesOnDBThread");
 
   if (!db_)
     return;
 
-  base::TimeTicks begin_time = base::TimeTicks::Now();
-
   db_->ApplyDelta(delta);
-
-  if (record_or_not == RECORD_HISTOGRAM_YES) {
-    UMA_HISTOGRAM_TIMES("History.FirstUpdateTime",
-                        base::TimeTicks::Now() - begin_time);
-  }
 }
 
 void TopSitesBackend::ResetDatabaseOnDBThread(const base::FilePath& file_path) {
diff --git a/components/history/core/browser/top_sites_backend.h b/components/history/core/browser/top_sites_backend.h
index db4ca4a..8fae8a01 100644
--- a/components/history/core/browser/top_sites_backend.h
+++ b/components/history/core/browser/top_sites_backend.h
@@ -27,15 +27,6 @@
 // thread.
 class TopSitesBackend : public base::RefCountedThreadSafe<TopSitesBackend> {
  public:
-  // TODO(yiyaoliu): Remove the enums and related code when crbug/223430 is
-  // fixed.
-  // An enum representing whether the UpdateTopSites execution time related
-  // histogram should be recorded.
-  enum RecordHistogram {
-    RECORD_HISTOGRAM_YES,
-    RECORD_HISTOGRAM_NO
-  };
-
   using GetMostVisitedSitesCallback =
       base::OnceCallback<void(MostVisitedURLList)>;
 
@@ -54,8 +45,7 @@
                            base::CancelableTaskTracker* tracker);
 
   // Updates top sites database from the specified delta.
-  void UpdateTopSites(const TopSitesDelta& delta,
-                      const RecordHistogram record_or_not);
+  void UpdateTopSites(const TopSitesDelta& delta);
 
   // Deletes the database and recreates it.
   void ResetDatabase();
@@ -75,8 +65,7 @@
   MostVisitedURLList GetMostVisitedSitesOnDBThread();
 
   // Updates top sites.
-  void UpdateTopSitesOnDBThread(const TopSitesDelta& delta,
-                                const RecordHistogram record_or_not);
+  void UpdateTopSitesOnDBThread(const TopSitesDelta& delta);
 
   // Resets the database.
   void ResetDatabaseOnDBThread(const base::FilePath& file_path);
diff --git a/components/history/core/browser/top_sites_impl.cc b/components/history/core/browser/top_sites_impl.cc
index d3d6085..0e53f261 100644
--- a/components/history/core/browser/top_sites_impl.cc
+++ b/components/history/core/browser/top_sites_impl.cc
@@ -18,7 +18,6 @@
 #include "base/location.h"
 #include "base/memory/ref_counted.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
@@ -125,8 +124,6 @@
 };
 
 // Initially, histogram is not recorded.
-bool TopSitesImpl::histogram_recorded_ = false;
-
 TopSitesImpl::TopSitesImpl(PrefService* pref_service,
                            HistoryService* history_service,
                            TemplateURLService* template_url_service,
@@ -376,21 +373,10 @@
   TopSitesDelta delta;
   DiffMostVisited(top_sites_, top_sites, &delta);
 
-  TopSitesBackend::RecordHistogram record_or_not =
-      TopSitesBackend::RECORD_HISTOGRAM_NO;
-
-  if (location == CALL_LOCATION_FROM_ON_GOT_MOST_VISITED_URLS &&
-      !histogram_recorded_) {
-    // Will be passed to TopSitesBackend to let it record the histogram.
-    record_or_not = TopSitesBackend::RECORD_HISTOGRAM_YES;
-    // Change it to true so that the histogram will not be recorded any more.
-    histogram_recorded_ = true;
-  }
-
   bool should_notify_observers = false;
   // If there is a change in urls, update the db and notify observers.
   if (!delta.deleted.empty() || !delta.added.empty() || !delta.moved.empty()) {
-    backend_->UpdateTopSites(delta, record_or_not);
+    backend_->UpdateTopSites(delta);
     should_notify_observers = true;
   }
   // If there is no url change in top sites, check if the titles have changes.
diff --git a/components/history/core/browser/top_sites_impl.h b/components/history/core/browser/top_sites_impl.h
index 5bc47e7..257afcc 100644
--- a/components/history/core/browser/top_sites_impl.h
+++ b/components/history/core/browser/top_sites_impl.h
@@ -234,10 +234,6 @@
   // Are we loaded?
   bool loaded_;
 
-  // Have the SetTopSites execution time related histograms been recorded?
-  // The histogram should only be recorded once for each Chrome execution.
-  static bool histogram_recorded_;
-
   base::ScopedObservation<HistoryService, HistoryServiceObserver>
       history_service_observation_{this};
 };
diff --git a/components/metrics/generate_expired_histograms_array.gni b/components/metrics/generate_expired_histograms_array.gni
index 2b121757..72a5de8 100644
--- a/components/metrics/generate_expired_histograms_array.gni
+++ b/components/metrics/generate_expired_histograms_array.gni
@@ -199,6 +199,7 @@
       "//tools/metrics/histograms/metadata/quickstart/enums.xml",
       "//tools/metrics/histograms/metadata/quickstart/histograms.xml",
       "//tools/metrics/histograms/metadata/quota/histograms.xml",
+      "//tools/metrics/histograms/metadata/readaloud/histograms.xml",
       "//tools/metrics/histograms/metadata/renderer/histograms.xml",
       "//tools/metrics/histograms/metadata/renderer4/histograms.xml",
       "//tools/metrics/histograms/metadata/safe_browsing/enums.xml",
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 4b731bc..f26cc320 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -141,6 +141,7 @@
     "insertion_ordered_set.h",
     "optimization_guide_constants.cc",
     "optimization_guide_constants.h",
+    "optimization_guide_enums.h",
     "optimization_guide_features.cc",
     "optimization_guide_features.h",
     "optimization_guide_prefs.cc",
@@ -203,7 +204,6 @@
     "optimization_filter.h",
     "optimization_guide_decider.h",
     "optimization_guide_decision.h",
-    "optimization_guide_enums.h",
     "optimization_guide_logger.cc",
     "optimization_guide_logger.h",
     "optimization_guide_model_executor.h",
@@ -254,6 +254,8 @@
       "model_execution/model_execution_util.h",
       "model_execution/on_device_model_access_controller.cc",
       "model_execution/on_device_model_access_controller.h",
+      "model_execution/on_device_model_component.cc",
+      "model_execution/on_device_model_component.h",
       "model_execution/on_device_model_execution_config_interpreter.cc",
       "model_execution/on_device_model_execution_config_interpreter.h",
       "model_execution/on_device_model_execution_proto_descriptors.h",
@@ -496,10 +498,13 @@
       "model_execution/model_execution_features_unittest.cc",
       "model_execution/model_execution_fetcher_unittest.cc",
       "model_execution/model_execution_manager_unittest.cc",
+      "model_execution/on_device_model_component_unittest.cc",
       "model_execution/on_device_model_execution_config_interpreter_unittest.cc",
       "model_execution/on_device_model_execution_proto_value_utils_unittest.cc",
       "model_execution/on_device_model_service_controller_unittest.cc",
       "model_execution/optimization_guide_model_execution_error_unittest.cc",
+      "model_execution/test_on_device_model_component.cc",
+      "model_execution/test_on_device_model_component.h",
     ]
   }
   if (build_with_tflite_lib) {
diff --git a/components/optimization_guide/core/model_execution/on_device_model_component.cc b/components/optimization_guide/core/model_execution/on_device_model_component.cc
new file mode 100644
index 0000000..a3134c70
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/on_device_model_component.cc
@@ -0,0 +1,208 @@
+// 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/optimization_guide/core/model_execution/on_device_model_component.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/no_destructor.h"
+#include "base/time/time.h"
+#include "base/types/cxx23_to_underlying.h"
+#include "components/optimization_guide/core/optimization_guide_enums.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_prefs.h"
+#include "components/prefs/pref_service.h"
+
+namespace optimization_guide {
+namespace {
+
+base::WeakPtr<OnDeviceModelComponentStateManager>& GetInstance() {
+  static base::NoDestructor<base::WeakPtr<OnDeviceModelComponentStateManager>>
+      state_manager_instance;
+  return *state_manager_instance.get();
+}
+
+bool WasAnOnDeviceFeatureRecentlyUsed(const PrefService& local_state) {
+  base::Time last_use = local_state.GetTime(
+      prefs::localstate::kLastTimeOnDeviceEligibleFeatureWasUsed);
+  constexpr base::TimeDelta grace_period = base::Days(7);
+  auto time_since_use = base::Time::Now() - last_use;
+  // Note: Since we're storing a base::Time, we need to consider the possibility
+  // of clock changes.
+  return time_since_use < grace_period && time_since_use > -grace_period;
+}
+
+bool IsDeviceCapable(const PrefService& local_state) {
+  int value =
+      local_state.GetInteger(prefs::localstate::kOnDevicePerformanceClass);
+  if (value < 0 ||
+      value > static_cast<int>(OnDeviceModelPerformanceClass::kMaxValue)) {
+    return false;
+  }
+  return features::IsPerformanceClassCompatibleWithOnDeviceModel(
+      static_cast<OnDeviceModelPerformanceClass>(value));
+}
+
+bool IsModelNeeded(const PrefService& local_state) {
+  return WasAnOnDeviceFeatureRecentlyUsed(local_state) &&
+         features::IsOnDeviceExecutionEnabled() && IsDeviceCapable(local_state);
+}
+
+}  // namespace
+
+void OnDeviceModelComponentStateManager::UninstallComplete() {
+  local_state_->ClearPref(
+      prefs::localstate::kLastTimeEligibleForOnDeviceModelDownload);
+
+  component_installer_registered_ = false;
+}
+
+void OnDeviceModelComponentStateManager::OnDeviceEligibleFeatureUsed() {
+  local_state_->SetTime(
+      prefs::localstate::kLastTimeOnDeviceEligibleFeatureWasUsed,
+      base::Time::Now());
+
+  UpdateRegistration();
+}
+
+void OnDeviceModelComponentStateManager::DevicePerformanceClassChanged(
+    OnDeviceModelPerformanceClass performance_class) {
+  local_state_->SetInteger(prefs::localstate::kOnDevicePerformanceClass,
+                           base::to_underlying(performance_class));
+
+  UpdateRegistration();
+}
+
+void OnDeviceModelComponentStateManager::OnStartup() {
+  // TODO(b/302327114): Add UMA.
+  UpdateRegistration();
+}
+
+void OnDeviceModelComponentStateManager::UpdateRegistration() {
+  // After the installer is registered, don't do anything until after a chrome
+  // restart.
+  if (component_installer_registered_) {
+    return;
+  }
+
+  switch (GetRegistrationDecision()) {
+    case OnDeviceRegistrationDecision::kDoNotInstall:
+      break;
+    case OnDeviceRegistrationDecision::kInstall:
+      component_installer_registered_ = true;
+      delegate_->RegisterInstaller(this);
+      break;
+    case OnDeviceRegistrationDecision::kUninstall:
+      // Don't allow UpdateRegistration to do anything until after
+      // UninstallComplete.
+      component_installer_registered_ = true;
+      delegate_->Uninstall(this);
+      break;
+  }
+}
+
+OnDeviceModelComponentStateManager::OnDeviceRegistrationDecision
+OnDeviceModelComponentStateManager::GetRegistrationDecision() {
+  if (IsModelNeeded(*local_state_)) {
+    local_state_->SetTime(
+        prefs::localstate::kLastTimeEligibleForOnDeviceModelDownload,
+        base::Time::Now());
+    return OnDeviceRegistrationDecision::kInstall;
+  }
+
+  auto last_time_eligible = local_state_->GetTime(
+      prefs::localstate::kLastTimeEligibleForOnDeviceModelDownload);
+  if (last_time_eligible == base::Time::Min()) {
+    return OnDeviceRegistrationDecision::kDoNotInstall;
+  }
+
+  const base::TimeDelta retention_time =
+      features::GetOnDeviceModelRetentionTime();
+  auto time_since_eligible = base::Time::Now() - last_time_eligible;
+  if (time_since_eligible < retention_time &&
+      time_since_eligible > -retention_time) {
+    return OnDeviceRegistrationDecision::kInstall;
+  }
+  return OnDeviceRegistrationDecision::kUninstall;
+}
+
+OnDeviceModelComponentStateManager::OnDeviceModelComponentStateManager(
+    PrefService* local_state,
+    std::unique_ptr<Delegate> delegate)
+    : local_state_(local_state), delegate_(std::move(delegate)) {
+  CHECK(local_state);  // Useful to catch poor test setup.
+}
+
+OnDeviceModelComponentStateManager::~OnDeviceModelComponentStateManager() =
+    default;
+
+const OnDeviceModelComponentState*
+OnDeviceModelComponentStateManager::GetState() {
+  return state_.get();
+}
+
+scoped_refptr<OnDeviceModelComponentStateManager>
+OnDeviceModelComponentStateManager::CreateOrGet(
+    PrefService* local_state,
+    std::unique_ptr<Delegate> delegate) {
+  base::WeakPtr<OnDeviceModelComponentStateManager>& instance = GetInstance();
+  if (!instance) {
+    auto state_manager =
+        base::WrapRefCounted(new OnDeviceModelComponentStateManager(
+            local_state, std::move(delegate)));
+    instance = state_manager->GetWeakPtr();
+    return state_manager;
+  }
+  return scoped_refptr<OnDeviceModelComponentStateManager>(instance.get());
+}
+
+void OnDeviceModelComponentStateManager::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void OnDeviceModelComponentStateManager::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+// static
+OnDeviceModelComponentStateManager*
+OnDeviceModelComponentStateManager::GetInstanceForTesting() {
+  return GetInstance().get();
+}
+
+bool OnDeviceModelComponentStateManager::VerifyInstallation(
+    const base::FilePath& install_dir,
+    const base::Value::Dict& manifest) {
+  // TODO(b/302327114): Avoid having these file names duplicated. Either
+  // have OnDeviceModelComponentState provide the full path to each file,
+  // or add these names as constants in a shared place.
+  for (const base::FilePath::CharType* file_name :
+       {FILE_PATH_LITERAL("spm.model"), FILE_PATH_LITERAL("weights.bin"),
+        FILE_PATH_LITERAL("model.pb"),
+        FILE_PATH_LITERAL("on_device_model_execution_config.pb")}) {
+    if (!base::PathExists(install_dir.Append(file_name))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void OnDeviceModelComponentStateManager::SetReady(
+    const base::Version& version,
+    const base::FilePath& install_dir,
+    const base::Value::Dict& manifest) {
+  state_ = base::WrapUnique(new OnDeviceModelComponentState);
+  state_->install_dir_ = install_dir;
+  state_->version_ = version;
+  for (auto& o : observers_) {
+    o.StateChanged(state_.get());
+  }
+}
+
+OnDeviceModelComponentState::OnDeviceModelComponentState() = default;
+OnDeviceModelComponentState::~OnDeviceModelComponentState() = default;
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/on_device_model_component.h b/components/optimization_guide/core/model_execution/on_device_model_component.h
new file mode 100644
index 0000000..b83cd78
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/on_device_model_component.h
@@ -0,0 +1,157 @@
+// 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_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_MODEL_COMPONENT_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_MODEL_COMPONENT_H_
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/functional/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/observer_list_types.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/optimization_guide/core/optimization_guide_enums.h"
+
+class PrefService;
+
+namespace optimization_guide {
+
+inline constexpr std::string_view kOnDeviceModelCrxId =
+    "fklghjjljmnfjoepjmlobpekiapffcja";
+
+class OnDeviceModelComponentState;
+
+// Manages the state of the on-device component.
+// This object needs to have lifetime equal to the browser process. This is
+// achieved by holding a scoped_refptr on KeyedServices which need it, and on
+// the installer (which is owned by ComponentUpdaterService).
+class OnDeviceModelComponentStateManager
+    : public base::RefCounted<OnDeviceModelComponentStateManager> {
+ public:
+  class Delegate {
+   public:
+    virtual ~Delegate() = default;
+
+    // Registers the component installer. Calls
+    // `OnDeviceModelComponentStateManager::SetReady` when the component is
+    // ready to use.
+    virtual void RegisterInstaller(
+        scoped_refptr<OnDeviceModelComponentStateManager> state_manager) = 0;
+
+    // Uninstall the component. Calls
+    // `OnDeviceModelComponentStateManager::UninstallComplete()` when uninstall
+    // completes.
+    virtual void Uninstall(
+        scoped_refptr<OnDeviceModelComponentStateManager> state_manager) = 0;
+  };
+
+  class Observer : public base::CheckedObserver {
+   public:
+    // Called whenever the on-device component state changes. `state` is null if
+    // the component is not available.
+    virtual void StateChanged(const OnDeviceModelComponentState* state) = 0;
+  };
+
+  // Creates the instance if one does not already exist. Returns an existing
+  // instance otherwise.
+  static scoped_refptr<OnDeviceModelComponentStateManager> CreateOrGet(
+      PrefService* local_state,
+      std::unique_ptr<Delegate> delegate);
+
+  // Called at startup. Triggers install or uninstall of the component if
+  // necessary.
+  void OnStartup();
+
+  // Should be called whenever an on-device eligible feature was used.
+  void OnDeviceEligibleFeatureUsed();
+
+  // Should be called whenever the device performance class changes.
+  void DevicePerformanceClassChanged(
+      OnDeviceModelPerformanceClass performance_class);
+
+  // Returns the current state. Null if the component is not available.
+  const OnDeviceModelComponentState* GetState();
+
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  // Functions called by the component installer:
+
+  // Called when the on-device component has been uninstalled.
+  void UninstallComplete();
+
+  // Returns whether the component installation is valid.
+  bool VerifyInstallation(const base::FilePath& install_dir,
+                          const base::Value::Dict& manifest);
+
+  // Creates the on-device component state, only called after VerifyInstallation
+  // returns true.
+  void SetReady(const base::Version& version,
+                const base::FilePath& install_dir,
+                const base::Value::Dict& manifest);
+
+  base::WeakPtr<OnDeviceModelComponentStateManager> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+  // Testing functionality:
+  static OnDeviceModelComponentStateManager* GetInstanceForTesting();
+
+ private:
+  friend class base::RefCounted<OnDeviceModelComponentStateManager>;
+
+  enum class OnDeviceRegistrationDecision {
+    // The component should be installed.
+    kInstall,
+    // The component should not be installed, and should be removed.
+    kUninstall,
+    // The component should not be installed, and does not need removed.
+    kDoNotInstall,
+  };
+
+  OnDeviceModelComponentStateManager(PrefService* local_state,
+                                     std::unique_ptr<Delegate> delegate);
+  ~OnDeviceModelComponentStateManager();
+
+  // Called at startup to determine whether to install or uninstall the on
+  // device component.
+  OnDeviceRegistrationDecision GetRegistrationDecision();
+
+  // Installs the component installer if it needs installed.
+  void UpdateRegistration();
+
+  raw_ptr<PrefService> local_state_;
+  std::unique_ptr<Delegate> delegate_;
+  base::ObserverList<Observer> observers_;
+  bool component_installer_registered_ = false;
+  std::unique_ptr<OnDeviceModelComponentState> state_;
+
+  base::WeakPtrFactory<OnDeviceModelComponentStateManager> weak_ptr_factory_{
+      this};
+};
+
+// State of the on-device model component.
+class OnDeviceModelComponentState {
+ public:
+  ~OnDeviceModelComponentState();
+
+  const base::FilePath& GetInstallDirectory() const { return install_dir_; }
+  const base::Version& GetVersion() const { return version_; }
+
+ private:
+  OnDeviceModelComponentState();
+  friend class OnDeviceModelComponentStateManager;
+
+  base::FilePath install_dir_;
+  base::Version version_;
+  // Note that we'll need to read the manifest and expose additional
+  // information for b/310740288.
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_ON_DEVICE_MODEL_COMPONENT_H_
diff --git a/components/optimization_guide/core/model_execution/on_device_model_component_unittest.cc b/components/optimization_guide/core/model_execution/on_device_model_component_unittest.cc
new file mode 100644
index 0000000..efcb8cea
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/on_device_model_component_unittest.cc
@@ -0,0 +1,210 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/optimization_guide/core/model_execution/on_device_model_component.h"
+
+#include <memory>
+
+#include "base/scoped_add_feature_flags.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/thread_annotations.h"
+#include "base/types/cxx23_to_underlying.h"
+#include "components/optimization_guide/core/model_execution/test_on_device_model_component.h"
+#include "components/optimization_guide/core/optimization_guide_enums.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_prefs.h"
+#include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace optimization_guide {
+namespace {
+
+class StubObserver : public OnDeviceModelComponentStateManager::Observer {
+ public:
+  void StateChanged(const OnDeviceModelComponentState* state) override {
+    state_ = state;
+  }
+
+  const OnDeviceModelComponentState* GetState() { return state_; }
+
+ private:
+  raw_ptr<const OnDeviceModelComponentState> state_;
+};
+
+class OnDeviceModelComponentTest : public testing::Test {
+ public:
+  void SetUp() override {
+    prefs::RegisterLocalStatePrefs(local_state_.registry());
+
+    local_state_.SetInteger(
+        prefs::localstate::kOnDevicePerformanceClass,
+        base::to_underlying(OnDeviceModelPerformanceClass::kLow));
+    local_state_.SetTime(
+        prefs::localstate::kLastTimeOnDeviceEligibleFeatureWasUsed,
+        base::Time::Now());
+
+    feature_list_.InitWithFeatures({features::kOptimizationGuideModelExecution,
+                                    features::kOptimizationGuideOnDeviceModel,
+                                    features::kLogOnDeviceMetricsOnStartup},
+                                   {});
+  }
+
+  scoped_refptr<OnDeviceModelComponentStateManager> manager() {
+    return on_device_component_state_manager_.get();
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  TestingPrefServiceSimple local_state_;
+  base::test::ScopedFeatureList feature_list_;
+  TestOnDeviceModelComponentStateManager on_device_component_state_manager_{
+      &local_state_};
+};
+
+TEST_F(OnDeviceModelComponentTest, InstallsWhenEligible) {
+  manager()->OnStartup();
+
+  EXPECT_TRUE(on_device_component_state_manager_.IsInstallerRegistered());
+  EXPECT_EQ(local_state_.GetTime(
+                prefs::localstate::kLastTimeEligibleForOnDeviceModelDownload),
+            base::Time::Now());
+}
+
+TEST_F(OnDeviceModelComponentTest, DoesNotInstallWhenFeatureNotEnabled) {
+  // It should not install if any of these features are disabled.
+  for (const base::Feature* feature :
+       {&features::kOptimizationGuideModelExecution,
+        &features::kOptimizationGuideOnDeviceModel,
+        &features::kLogOnDeviceMetricsOnStartup}) {
+    SCOPED_TRACE(feature->name);
+    on_device_component_state_manager_.Reset();
+    base::test::ScopedFeatureList features;
+    features.InitAndDisableFeature(*feature);
+
+    manager()->OnStartup();
+    EXPECT_FALSE(on_device_component_state_manager_.IsInstallerRegistered());
+  }
+}
+
+TEST_F(OnDeviceModelComponentTest, NoEligibleFeatureUse) {
+  local_state_.ClearPref(
+      prefs::localstate::kLastTimeOnDeviceEligibleFeatureWasUsed);
+
+  manager()->OnStartup();
+  EXPECT_FALSE(on_device_component_state_manager_.IsInstallerRegistered());
+}
+
+TEST_F(OnDeviceModelComponentTest, EligibleFeatureUseTooOld) {
+  local_state_.SetTime(
+      prefs::localstate::kLastTimeOnDeviceEligibleFeatureWasUsed,
+      base::Time::Now() - base::Days(31));
+
+  manager()->OnStartup();
+  EXPECT_FALSE(on_device_component_state_manager_.IsInstallerRegistered());
+}
+
+TEST_F(OnDeviceModelComponentTest, NoPerformanceClass) {
+  local_state_.ClearPref(prefs::localstate::kOnDevicePerformanceClass);
+
+  manager()->OnStartup();
+  EXPECT_FALSE(on_device_component_state_manager_.IsInstallerRegistered());
+}
+
+TEST_F(OnDeviceModelComponentTest, PerformanceClassTooLow) {
+  local_state_.SetInteger(
+      prefs::localstate::kOnDevicePerformanceClass,
+      base::to_underlying(OnDeviceModelPerformanceClass::kVeryLow));
+
+  manager()->OnStartup();
+  EXPECT_FALSE(on_device_component_state_manager_.IsInstallerRegistered());
+}
+
+TEST_F(OnDeviceModelComponentTest, UninstallNeeded) {
+  // This pref records that the model was eligible for download previously,
+  // and hasn't been cleaned up yet.
+  local_state_.SetTime(
+      prefs::localstate::kLastTimeEligibleForOnDeviceModelDownload,
+      base::Time::Now() - base::Minutes(1) -
+          features::GetOnDeviceModelRetentionTime());
+  local_state_.ClearPref(
+      prefs::localstate::kLastTimeOnDeviceEligibleFeatureWasUsed);
+
+  // Should uninstall the first time, and skip uninstallation the next time.
+  manager()->OnStartup();
+  EXPECT_TRUE(on_device_component_state_manager_.WasComponentUninstalled());
+
+  manager()->UninstallComplete();
+
+  manager()->OnStartup();
+  EXPECT_FALSE(on_device_component_state_manager_.IsInstallerRegistered());
+}
+
+TEST_F(OnDeviceModelComponentTest, InstallWhileNotEligible) {
+  // If the model is already installed, we don't uninstall right away.
+  local_state_.SetTime(
+      prefs::localstate::kLastTimeEligibleForOnDeviceModelDownload,
+      base::Time::Now() - base::Days(1));
+  local_state_.ClearPref(
+      prefs::localstate::kLastTimeOnDeviceEligibleFeatureWasUsed);
+
+  manager()->OnStartup();
+  EXPECT_TRUE(on_device_component_state_manager_.IsInstallerRegistered());
+}
+
+TEST_F(OnDeviceModelComponentTest, GetStateInitiallyNull) {
+  EXPECT_EQ(manager()->GetState(), nullptr);
+}
+
+TEST_F(OnDeviceModelComponentTest, SetReady) {
+  StubObserver observer;
+  manager()->AddObserver(&observer);
+  manager()->SetReady(base::Version("0.1.1"),
+                      base::FilePath(FILE_PATH_LITERAL("/some/path")),
+                      base::Value::Dict());
+
+  const OnDeviceModelComponentState* state = manager()->GetState();
+  ASSERT_TRUE(state);
+
+  EXPECT_EQ(state->GetInstallDirectory(),
+            base::FilePath(FILE_PATH_LITERAL("/some/path")));
+  EXPECT_EQ(state->GetVersion(), base::Version("0.1.1"));
+  ASSERT_EQ(observer.GetState(), state);
+}
+
+TEST_F(OnDeviceModelComponentTest, InstallAfterPerformanceClassChanges) {
+  // This sequence would happen on first run.
+  local_state_.ClearPref(prefs::localstate::kOnDevicePerformanceClass);
+  manager()->OnStartup();
+  ASSERT_FALSE(on_device_component_state_manager_.IsInstallerRegistered());
+
+  manager()->DevicePerformanceClassChanged(OnDeviceModelPerformanceClass::kLow);
+
+  EXPECT_TRUE(on_device_component_state_manager_.IsInstallerRegistered());
+}
+
+TEST_F(OnDeviceModelComponentTest, DontUninstallAfterPerformanceClassChanges) {
+  manager()->OnStartup();
+  ASSERT_TRUE(on_device_component_state_manager_.IsInstallerRegistered());
+
+  manager()->DevicePerformanceClassChanged(OnDeviceModelPerformanceClass::kLow);
+
+  EXPECT_TRUE(on_device_component_state_manager_.IsInstallerRegistered());
+  EXPECT_FALSE(on_device_component_state_manager_.WasComponentUninstalled());
+}
+
+TEST_F(OnDeviceModelComponentTest, InstallAfterEligibleFeatureWasUsed) {
+  local_state_.ClearPref(
+      prefs::localstate::kLastTimeOnDeviceEligibleFeatureWasUsed);
+  manager()->OnStartup();
+  ASSERT_FALSE(on_device_component_state_manager_.IsInstallerRegistered());
+
+  manager()->OnDeviceEligibleFeatureUsed();
+  EXPECT_TRUE(on_device_component_state_manager_.IsInstallerRegistered());
+}
+
+}  // namespace
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
index 3e1388b..ed96c55 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.cc
@@ -8,6 +8,7 @@
 #include "base/strings/strcat.h"
 #include "base/task/thread_pool.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_access_controller.h"
+#include "components/optimization_guide/core/model_execution/on_device_model_component.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_execution_config_interpreter.h"
 #include "components/optimization_guide/core/model_execution/session_impl.h"
 #include "components/optimization_guide/core/model_util.h"
@@ -61,10 +62,22 @@
 }  // namespace
 
 OnDeviceModelServiceController::OnDeviceModelServiceController(
-    std::unique_ptr<OnDeviceModelAccessController> access_controller)
-    : access_controller_(std::move(access_controller)) {}
+    std::unique_ptr<OnDeviceModelAccessController> access_controller,
+    base::WeakPtr<optimization_guide::OnDeviceModelComponentStateManager>
+        on_device_component_state_manager)
+    : access_controller_(std::move(access_controller)),
+      on_device_component_state_manager_(
+          std::move(on_device_component_state_manager)) {
+  if (on_device_component_state_manager_) {
+    on_device_component_state_manager_->AddObserver(this);
+  }
+}
 
-OnDeviceModelServiceController::~OnDeviceModelServiceController() = default;
+OnDeviceModelServiceController::~OnDeviceModelServiceController() {
+  if (on_device_component_state_manager_) {
+    on_device_component_state_manager_->RemoveObserver(this);
+  }
+}
 
 void OnDeviceModelServiceController::Init(
     const base::FilePath& model_path,
@@ -79,13 +92,21 @@
 void OnDeviceModelServiceController::Init() {
   auto model_path_override_switch =
       switches::GetOnDeviceModelExecutionOverride();
+  std::optional<base::FilePath> model_path;
   if (model_path_override_switch) {
-    auto file_path = StringToFilePath(*model_path_override_switch);
-    if (file_path) {
-      Init(*file_path,
-           std::make_unique<OnDeviceModelExecutionConfigInterpreter>());
+    model_path = StringToFilePath(*model_path_override_switch);
+  } else if (on_device_component_state_manager_) {
+    const OnDeviceModelComponentState* state =
+        on_device_component_state_manager_->GetState();
+    if (state) {
+      model_path = state->GetInstallDirectory();
     }
   }
+
+  if (model_path) {
+    Init(*model_path,
+         std::make_unique<OnDeviceModelExecutionConfigInterpreter>());
+  }
 }
 
 std::unique_ptr<OptimizationGuideModelExecutor::Session>
@@ -93,6 +114,7 @@
     proto::ModelExecutionFeature feature,
     ExecuteRemoteFn execute_remote_fn,
     OptimizationGuideLogger* optimization_guide_logger) {
+  on_device_component_state_manager_->OnDeviceEligibleFeatureUsed();
   ScopedEligibilityReasonLogger logger(feature);
   if (!base::FeatureList::IsEnabled(
           features::kOptimizationGuideOnDeviceModel)) {
@@ -179,6 +201,19 @@
                      weak_ptr_factory_.GetWeakPtr()));
 }
 
+void OnDeviceModelServiceController::StateChanged(
+    const OnDeviceModelComponentState* state) {
+  if (state && model_path_.empty()) {
+    Init();
+  } else {
+    // TODO(b/302327114): Support other cases. Decide how to handle:
+    // * If state is null and Init() has already been called. We should prevent
+    // future requests from being handled, and maybe kill in-flight tasks.
+    // * If state is non-null and Init() has already been called. We should
+    // probably re-load any files as they may have changed.
+  }
+}
+
 void OnDeviceModelServiceController::OnLoadModelResult(
     on_device_model::mojom::LoadModelResult result) {
   base::UmaHistogramEnumeration(
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller.h b/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
index 6bf191bc..60a3d95 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller.h
@@ -10,9 +10,11 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ref.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/types/pass_key.h"
+#include "components/optimization_guide/core/model_execution/on_device_model_component.h"
 #include "components/optimization_guide/core/model_execution/session_impl.h"
 #include "components/optimization_guide/core/optimization_guide_model_executor.h"
 #include "components/optimization_guide/proto/model_execution.pb.h"
@@ -29,6 +31,7 @@
 
 namespace optimization_guide {
 class OnDeviceModelAccessController;
+class OnDeviceModelComponentStateManager;
 class OnDeviceModelExecutionConfigInterpreter;
 
 // Controls the lifetime of the on-device model service, loading and unloading
@@ -43,10 +46,13 @@
 // this. Also handle multiple requests gracefully and fail the subsequent
 // requests, while handling the first one.
 class OnDeviceModelServiceController
-    : public base::RefCounted<OnDeviceModelServiceController> {
+    : public base::RefCounted<OnDeviceModelServiceController>,
+      public OnDeviceModelComponentStateManager::Observer {
  public:
   explicit OnDeviceModelServiceController(
-      std::unique_ptr<OnDeviceModelAccessController> access_controller);
+      std::unique_ptr<OnDeviceModelAccessController> access_controller,
+      base::WeakPtr<OnDeviceModelComponentStateManager>
+          on_device_component_state_manager);
 
   // Initializes the on-device model controller with the parameters, to be ready
   // to load models and execute.
@@ -88,13 +94,16 @@
     return model_remote_.is_bound() || service_remote_.is_bound();
   }
 
+  // OnDeviceModelComponentStateManager::Observer.
+  void StateChanged(const OnDeviceModelComponentState* state) override;
+
  private:
   friend class base::RefCounted<OnDeviceModelServiceController>;
   friend class ChromeOnDeviceModelServiceController;
   friend class OnDeviceModelServiceControllerTest;
   friend class FakeOnDeviceModelServiceController;
 
-  virtual ~OnDeviceModelServiceController();
+  ~OnDeviceModelServiceController() override;
 
   // Makes sure the service is running and starts a mojo session.
   void StartMojoSession(
@@ -118,6 +127,8 @@
 
   // This may be null in the destructor, otherwise non-null.
   std::unique_ptr<OnDeviceModelAccessController> access_controller_;
+  base::WeakPtr<OnDeviceModelComponentStateManager>
+      on_device_component_state_manager_;
   base::FilePath model_path_;
   std::unique_ptr<OnDeviceModelExecutionConfigInterpreter> config_interpreter_;
   mojo::Remote<on_device_model::mojom::OnDeviceModelService> service_remote_;
diff --git a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
index 1fe87ea..33811f4 100644
--- a/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
+++ b/components/optimization_guide/core/model_execution/on_device_model_service_controller_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/test/task_environment.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_access_controller.h"
 #include "components/optimization_guide/core/model_execution/on_device_model_execution_config_interpreter.h"
+#include "components/optimization_guide/core/model_execution/test_on_device_model_component.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
 #include "components/optimization_guide/core/optimization_guide_logger.h"
 #include "components/optimization_guide/core/optimization_guide_prefs.h"
@@ -180,8 +181,12 @@
     : public OnDeviceModelServiceController {
  public:
   explicit FakeOnDeviceModelServiceController(
-      std::unique_ptr<OnDeviceModelAccessController> access_controller)
-      : OnDeviceModelServiceController(std::move(access_controller)) {}
+      std::unique_ptr<OnDeviceModelAccessController> access_controller,
+      base::WeakPtr<OnDeviceModelComponentStateManager>
+          on_device_component_state_manager)
+      : OnDeviceModelServiceController(
+            std::move(access_controller),
+            std::move(on_device_component_state_manager)) {}
 
   void LaunchService() override {
     did_launch_service_ = true;
@@ -225,6 +230,11 @@
     RecreateServiceController();
   }
 
+  void TearDown() override {
+    access_controller_ = nullptr;
+    test_controller_ = nullptr;
+  }
+
   ExecuteRemoteFn CreateExecuteRemoteFn() {
     return base::BindLambdaForTesting(
         [=](proto::ModelExecutionFeature feature,
@@ -239,14 +249,16 @@
   }
 
   void RecreateServiceController() {
-    test_controller_ = nullptr;
     access_controller_ = nullptr;
+    test_controller_ = nullptr;
 
     auto access_controller =
         std::make_unique<OnDeviceModelAccessController>(pref_service_);
     access_controller_ = access_controller.get();
     test_controller_ = base::MakeRefCounted<FakeOnDeviceModelServiceController>(
-        std::move(access_controller));
+        std::move(access_controller),
+        on_device_component_state_manager_.get()->GetWeakPtr());
+
     proto::OnDeviceModelExecutionFeatureConfig config;
     config.set_feature(kFeature);
     auto& input_config = *config.mutable_input_config();
@@ -327,6 +339,8 @@
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   TestingPrefServiceSimple pref_service_;
+  TestOnDeviceModelComponentStateManager on_device_component_state_manager_{
+      &pref_service_};
   scoped_refptr<FakeOnDeviceModelServiceController> test_controller_;
   // Owned by FakeOnDeviceModelServiceController.
   raw_ptr<OnDeviceModelAccessController> access_controller_ = nullptr;
@@ -801,7 +815,8 @@
       std::make_unique<OnDeviceModelAccessController>(pref_service_);
   access_controller_ = access_controller.get();
   test_controller_ = base::MakeRefCounted<FakeOnDeviceModelServiceController>(
-      std::move(access_controller));
+      std::move(access_controller),
+      on_device_component_state_manager_.get()->GetWeakPtr());
 
   proto::OnDeviceModelExecutionFeatureConfig config;
   config.set_feature(kFeature);
@@ -844,7 +859,8 @@
       std::make_unique<OnDeviceModelAccessController>(pref_service_);
   access_controller_ = access_controller.get();
   test_controller_ = base::MakeRefCounted<FakeOnDeviceModelServiceController>(
-      std::move(access_controller));
+      std::move(access_controller),
+      on_device_component_state_manager_.get()->GetWeakPtr());
 
   proto::OnDeviceModelExecutionFeatureConfig config;
   config.set_feature(kFeature);
diff --git a/components/optimization_guide/core/model_execution/test_on_device_model_component.cc b/components/optimization_guide/core/model_execution/test_on_device_model_component.cc
new file mode 100644
index 0000000..1dba212
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/test_on_device_model_component.cc
@@ -0,0 +1,71 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/optimization_guide/core/model_execution/test_on_device_model_component.h"
+
+#include "base/check.h"
+
+namespace optimization_guide {
+
+class FakeOnDeviceModelComponentStateManagerDelegate
+    : public OnDeviceModelComponentStateManager::Delegate {
+ public:
+  ~FakeOnDeviceModelComponentStateManagerDelegate() override = default;
+  void RegisterInstaller(scoped_refptr<OnDeviceModelComponentStateManager>
+                             state_manager) override {
+    installer_registered_ = true;
+  }
+  void Uninstall(scoped_refptr<OnDeviceModelComponentStateManager>
+                     state_manager) override {
+    uninstall_called_ = true;
+  }
+
+ private:
+  friend class TestOnDeviceModelComponentStateManager;
+  bool installer_registered_ = false;
+  bool uninstall_called_ = false;
+};
+
+TestOnDeviceModelComponentStateManager::TestOnDeviceModelComponentStateManager(
+    PrefService* local_state)
+    : local_state_(local_state) {}
+
+TestOnDeviceModelComponentStateManager::
+    ~TestOnDeviceModelComponentStateManager() {
+  Reset();
+}
+
+scoped_refptr<OnDeviceModelComponentStateManager>
+TestOnDeviceModelComponentStateManager::get() {
+  // Note that we create the instance lazily to allow tests time to register
+  // prefs.
+  if (!manager_) {
+    CHECK(!OnDeviceModelComponentStateManager::GetInstanceForTesting())
+        << "Instance already exists";
+    auto delegate =
+        std::make_unique<FakeOnDeviceModelComponentStateManagerDelegate>();
+    delegate_ = delegate.get();
+    manager_ = OnDeviceModelComponentStateManager::CreateOrGet(
+        local_state_, std::move(delegate));
+    CHECK(manager_);
+  }
+  return manager_;
+}
+
+void TestOnDeviceModelComponentStateManager::Reset() {
+  if (manager_) {
+    delegate_ = nullptr;
+    manager_ = nullptr;
+  }
+}
+
+bool TestOnDeviceModelComponentStateManager::IsInstallerRegistered() const {
+  return delegate_ && delegate_->installer_registered_;
+}
+
+bool TestOnDeviceModelComponentStateManager::WasComponentUninstalled() const {
+  return delegate_ && delegate_->uninstall_called_;
+}
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/model_execution/test_on_device_model_component.h b/components/optimization_guide/core/model_execution/test_on_device_model_component.h
new file mode 100644
index 0000000..abf7f4e0
--- /dev/null
+++ b/components/optimization_guide/core/model_execution/test_on_device_model_component.h
@@ -0,0 +1,38 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TEST_ON_DEVICE_MODEL_COMPONENT_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TEST_ON_DEVICE_MODEL_COMPONENT_H_
+#include "components/optimization_guide/core/model_execution/on_device_model_component.h"
+
+class PrefService;
+
+namespace optimization_guide {
+
+class FakeOnDeviceModelComponentStateManagerDelegate;
+
+// Provides scoped creation and destruction of
+// OnDeviceModelComponentStateManager. Checks to make sure only one instance is
+// used at a time.
+class TestOnDeviceModelComponentStateManager {
+ public:
+  explicit TestOnDeviceModelComponentStateManager(PrefService* local_state);
+  ~TestOnDeviceModelComponentStateManager();
+
+  scoped_refptr<OnDeviceModelComponentStateManager> get();
+
+  void Reset();
+
+  bool IsInstallerRegistered() const;
+  bool WasComponentUninstalled() const;
+
+ private:
+  raw_ptr<PrefService> local_state_;
+  raw_ptr<FakeOnDeviceModelComponentStateManagerDelegate> delegate_;
+  scoped_refptr<OnDeviceModelComponentStateManager> manager_;
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_MODEL_EXECUTION_TEST_ON_DEVICE_MODEL_COMPONENT_H_
diff --git a/components/optimization_guide/core/optimization_guide_enums.h b/components/optimization_guide/core/optimization_guide_enums.h
index fbeefc3..2ad52cc 100644
--- a/components/optimization_guide/core/optimization_guide_enums.h
+++ b/components/optimization_guide/core/optimization_guide_enums.h
@@ -268,9 +268,9 @@
 
 // Performance class of this device.
 //
-// These values are persisted to logs. Entries should not be renumbered and
-// numeric values should never be reused.
-enum class OnDeviceModelPerformanceClass {
+// These values are persisted to logs and prefs. Entries should not be
+// renumbered and numeric values should never be reused.
+enum class OnDeviceModelPerformanceClass : int {
   kUnknown = 0,
 
   // See on_device_model::mojom::PerformanceClass for explanation of these.
@@ -281,6 +281,9 @@
   kHigh = 5,
   kVeryHigh = 6,
 
+  // WARNING!: If you add a new performance class, please be aware of
+  // `IsPerformanceClassCompatibleWithOnDeviceModel`.
+
   // The service crashed, so a valid value was not returned.
   kServiceCrash = 7,
 
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index ad91e7f8..8b652006 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -15,10 +15,12 @@
 #include "base/metrics/field_trial_params.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "base/strings/to_string.h"
 #include "base/system/sys_info.h"
 #include "build/build_config.h"
 #include "components/optimization_guide/core/insertion_ordered_set.h"
 #include "components/optimization_guide/core/optimization_guide_constants.h"
+#include "components/optimization_guide/core/optimization_guide_enums.h"
 #include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "components/optimization_guide/machine_learning_tflite_buildflags.h"
 #include "components/optimization_guide/proto/common_types.pb.h"
@@ -990,10 +992,38 @@
   return kOnDeviceModelFallbackToServerOnDisconnect.Get();
 }
 
+bool IsPerformanceClassCompatibleWithOnDeviceModel(
+    OnDeviceModelPerformanceClass performance_class) {
+  std::string perf_classes_string = base::GetFieldTrialParamValueByFeature(
+      kOptimizationGuideOnDeviceModel,
+      "compatible_on_device_performance_classes");
+  if (perf_classes_string.empty()) {
+    perf_classes_string = "3,4,5,6";
+  }
+  std::vector<std::string_view> perf_classes_list = base::SplitStringPiece(
+      perf_classes_string, ",", base::WhitespaceHandling::TRIM_WHITESPACE,
+      base::SplitResult::SPLIT_WANT_NONEMPTY);
+  return base::Contains(perf_classes_list,
+                        base::ToString(static_cast<int>(performance_class)));
+}
+
 bool CanLaunchOnDeviceModelService() {
   return base::FeatureList::IsEnabled(kOptimizationGuideOnDeviceModel) ||
          base::FeatureList::IsEnabled(kLogOnDeviceMetricsOnStartup);
 }
 
+bool IsOnDeviceExecutionEnabled() {
+  return base::FeatureList::IsEnabled(
+             features::kOptimizationGuideModelExecution) &&
+         base::FeatureList::IsEnabled(kOptimizationGuideOnDeviceModel) &&
+         base::FeatureList::IsEnabled(kLogOnDeviceMetricsOnStartup);
+}
+
+base::TimeDelta GetOnDeviceModelRetentionTime() {
+  return base::GetFieldTrialParamByFeatureAsTimeDelta(
+      kOptimizationGuideOnDeviceModel, "on_device_model_retention_time",
+      base::Days(30));
+}
+
 }  // namespace features
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index 679b210..ccfc377 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -13,6 +13,7 @@
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/time/time.h"
+#include "components/optimization_guide/core/optimization_guide_enums.h"
 #include "components/optimization_guide/core/page_content_annotation_type.h"
 #include "components/optimization_guide/proto/hints.pb.h"
 #include "components/optimization_guide/proto/model_execution.pb.h"
@@ -530,10 +531,31 @@
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 bool GetOnDeviceFallbackToServerOnDisconnect();
 
-// Whether any features are enabled that allow launching the on-device service.
+// Returns whether the performance class is compatible with executing the
+// on-device model. Used to determine whether or not to fetch the on-device
+// model.
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+bool IsPerformanceClassCompatibleWithOnDeviceModel(
+    OnDeviceModelPerformanceClass performance_class);
+
+// Whether any features are enabled that allow launching the on-device
+// service.
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 bool CanLaunchOnDeviceModelService();
 
+// Whether on-device execution is enabled.
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+bool IsOnDeviceExecutionEnabled();
+
+// The on-device model is fetched when the device is considered eligible for
+// on-device execution. When the device stops being eligible, the model is
+// retained for this amount of time. This protects the user from repeatedly
+// downloading the model in the event eligibility fluctuates. for on-device
+// evaluation
+// See on_device_model_component.cc for how eligibility is computed.
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+base::TimeDelta GetOnDeviceModelRetentionTime();
+
 }  // namespace features
 }  // namespace optimization_guide
 
diff --git a/components/optimization_guide/core/optimization_guide_features_unittest.cc b/components/optimization_guide/core/optimization_guide_features_unittest.cc
index 05fdba91..3717453 100644
--- a/components/optimization_guide/core/optimization_guide_features_unittest.cc
+++ b/components/optimization_guide/core/optimization_guide_features_unittest.cc
@@ -402,6 +402,23 @@
   }
 }
 
+TEST(OptimizationGuideFeaturesTest,
+     IsPerformanceClassCompatibleWithOnDeviceModel) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      features::kOptimizationGuideOnDeviceModel,
+      {{"compatible_on_device_performance_classes", "4,6"}});
+
+  EXPECT_FALSE(features::IsPerformanceClassCompatibleWithOnDeviceModel(
+      OnDeviceModelPerformanceClass::kError));
+  EXPECT_TRUE(features::IsPerformanceClassCompatibleWithOnDeviceModel(
+      OnDeviceModelPerformanceClass::kMedium));
+  EXPECT_FALSE(features::IsPerformanceClassCompatibleWithOnDeviceModel(
+      OnDeviceModelPerformanceClass::kHigh));
+  EXPECT_TRUE(features::IsPerformanceClassCompatibleWithOnDeviceModel(
+      OnDeviceModelPerformanceClass::kVeryHigh));
+}
+
 }  // namespace
 
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/optimization_guide_prefs.cc b/components/optimization_guide/core/optimization_guide_prefs.cc
index 5d0acf3..93dcd09 100644
--- a/components/optimization_guide/core/optimization_guide_prefs.cc
+++ b/components/optimization_guide/core/optimization_guide_prefs.cc
@@ -125,11 +125,25 @@
 const char kOnDeviceModelTimeoutCount[] =
     "optimization_guide.on_device.timeout_count";
 
+// Stores the last computed `OnDeviceModelPerformanceClass` of the device.
+const char kOnDevicePerformanceClass[] =
+    "optimization_guide.on_device.performance_class";
+
 // A dictionary pref that stores the file paths that need to be deleted as keys.
 // The value will not be used.
 const char kStoreFilePathsToDelete[] =
     "optimization_guide.store_file_paths_to_delete";
 
+// A timestamp for the last time a feature was used which could have benefited
+// from the on-device model. We will use this to help decide whether to acquire
+// the on device model.
+const char kLastTimeOnDeviceEligibleFeatureWasUsed[] =
+    "optimization_guide.last_time_on_device_eligible_feature_used";
+
+// A timestamp for the last time the on-device model was eligible for download.
+const char kLastTimeEligibleForOnDeviceModelDownload[] =
+    "optimization_guide.on_device.last_time_eligible_for_download";
+
 }  // namespace localstate
 
 void RegisterProfilePrefs(PrefRegistrySimple* registry) {
@@ -165,7 +179,12 @@
   registry->RegisterDictionaryPref(localstate::kModelCacheKeyMapping);
   registry->RegisterIntegerPref(localstate::kOnDeviceModelCrashCount, 0);
   registry->RegisterIntegerPref(localstate::kOnDeviceModelTimeoutCount, 0);
+  registry->RegisterIntegerPref(localstate::kOnDevicePerformanceClass, 0);
   registry->RegisterDictionaryPref(localstate::kStoreFilePathsToDelete);
+  registry->RegisterTimePref(
+      localstate::kLastTimeOnDeviceEligibleFeatureWasUsed, base::Time::Min());
+  registry->RegisterTimePref(
+      localstate::kLastTimeEligibleForOnDeviceModelDownload, base::Time::Min());
 }
 
 }  // namespace prefs
diff --git a/components/optimization_guide/core/optimization_guide_prefs.h b/components/optimization_guide/core/optimization_guide_prefs.h
index ccb3fc7..8d2da7b1 100644
--- a/components/optimization_guide/core/optimization_guide_prefs.h
+++ b/components/optimization_guide/core/optimization_guide_prefs.h
@@ -64,7 +64,13 @@
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 extern const char kOnDeviceModelTimeoutCount[];
 COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+extern const char kOnDevicePerformanceClass[];
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
 extern const char kStoreFilePathsToDelete[];
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+extern const char kLastTimeOnDeviceEligibleFeatureWasUsed[];
+COMPONENT_EXPORT(OPTIMIZATION_GUIDE_FEATURES)
+extern const char kLastTimeEligibleForOnDeviceModelDownload[];
 
 }  // namespace localstate
 
diff --git a/components/permissions/permission_manager.cc b/components/permissions/permission_manager.cc
index 357c6ed..1a0311a7 100644
--- a/components/permissions/permission_manager.cc
+++ b/components/permissions/permission_manager.cc
@@ -411,7 +411,7 @@
 }
 
 PermissionManager::SubscriptionId
-PermissionManager::SubscribePermissionStatusChange(
+PermissionManager::SubscribeToPermissionStatusChange(
     PermissionType permission,
     content::RenderProcessHost* render_process_host,
     content::RenderFrameHost* render_frame_host,
@@ -474,7 +474,7 @@
   return id;
 }
 
-void PermissionManager::UnsubscribePermissionStatusChange(
+void PermissionManager::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (is_shutting_down_)
diff --git a/components/permissions/permission_manager.h b/components/permissions/permission_manager.h
index 1dd1f82..1354e754 100644
--- a/components/permissions/permission_manager.h
+++ b/components/permissions/permission_manager.h
@@ -141,13 +141,13 @@
   bool IsPermissionOverridable(
       blink::PermissionType permission,
       const absl::optional<url::Origin>& origin) override;
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       base::RepeatingCallback<void(PermissionStatus)> callback) override;
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
   absl::optional<gfx::Rect> GetExclusionAreaBoundsInScreen(
       content::WebContents* web_contents) const override;
diff --git a/components/permissions/permission_manager_unittest.cc b/components/permissions/permission_manager_unittest.cc
index f04abc71..af0a3632 100644
--- a/components/permissions/permission_manager_unittest.cc
+++ b/components/permissions/permission_manager_unittest.cc
@@ -194,20 +194,21 @@
   }
 
   content::PermissionControllerDelegate::SubscriptionId
-  SubscribePermissionStatusChange(
+  SubscribeToPermissionStatusChange(
       PermissionType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       base::RepeatingCallback<void(PermissionStatus)> callback) {
-    return GetPermissionManager()->SubscribePermissionStatusChange(
+    return GetPermissionManager()->SubscribeToPermissionStatusChange(
         permission, render_process_host, render_frame_host, requesting_origin,
         std::move(callback));
   }
 
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       content::PermissionControllerDelegate::SubscriptionId subscription_id) {
-    GetPermissionManager()->UnsubscribePermissionStatusChange(subscription_id);
+    GetPermissionManager()->UnsubscribeFromPermissionStatusChange(
+        subscription_id);
   }
 
   bool IsPermissionOverridable(PermissionType permission,
@@ -406,7 +407,7 @@
 TEST_F(PermissionManagerTest, SubscriptionDestroyedCleanlyWithoutUnsubscribe) {
   // Test that the PermissionManager shuts down cleanly with subscriptions that
   // haven't been removed, crbug.com/720071.
-  SubscribePermissionStatusChange(
+  SubscribeToPermissionStatusChange(
       PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, main_rfh(),
       url(),
       base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -415,7 +416,7 @@
 
 TEST_F(PermissionManagerTest, SubscribeUnsubscribeAfterShutdown) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -426,22 +427,22 @@
   // reenterant.
   GetPermissionManager()->Shutdown();
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 
   // Check that subscribe/unsubscribe after shutdown don't crash.
   content::PermissionControllerDelegate::SubscriptionId subscription2_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  UnsubscribePermissionStatusChange(subscription2_id);
+  UnsubscribeFromPermissionStatusChange(subscription2_id);
 }
 
 TEST_F(PermissionManagerTest, SameTypeChangeNotifies) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -452,12 +453,12 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, DifferentTypeChangeDoesNotNotify) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -468,18 +469,18 @@
 
   EXPECT_FALSE(callback_called());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, ChangeAfterUnsubscribeDoesNotNotify) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 
   SetPermission(url(), url(), PermissionType::GEOLOCATION,
                 PermissionStatus::GRANTED);
@@ -490,19 +491,19 @@
 TEST_F(PermissionManagerTest,
        ChangeAfterUnsubscribeOnlyNotifiesActiveSubscribers) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                               base::Unretained(this)));
 
-  SubscribePermissionStatusChange(
+  SubscribeToPermissionStatusChange(
       PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, main_rfh(),
       url(),
       base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
                           base::Unretained(this)));
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 
   SetPermission(url(), url(), PermissionType::GEOLOCATION,
                 PermissionStatus::GRANTED);
@@ -512,7 +513,7 @@
 
 TEST_F(PermissionManagerTest, DifferentPrimaryUrlDoesNotNotify) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -523,12 +524,12 @@
 
   EXPECT_FALSE(callback_called());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, DifferentSecondaryUrlDoesNotNotify) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::STORAGE_ACCESS_GRANT, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -539,12 +540,12 @@
 
   EXPECT_FALSE(callback_called());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, WildCardPatternNotifies) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -556,7 +557,7 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, ClearSettingsNotifies) {
@@ -564,7 +565,7 @@
                 PermissionStatus::GRANTED);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -576,12 +577,12 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::ASK, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, NewValueCorrectlyPassed) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -592,14 +593,14 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::DENIED, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, ChangeWithoutPermissionChangeDoesNotNotify) {
   SetPermission(PermissionType::GEOLOCATION, PermissionStatus::GRANTED);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -610,7 +611,7 @@
 
   EXPECT_FALSE(callback_called());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, ChangesBackAndForth) {
@@ -618,7 +619,7 @@
                 PermissionStatus::ASK);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -637,14 +638,14 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::ASK, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, ChangesBackAndForthWorker) {
   SetPermission(PermissionType::GEOLOCATION, PermissionStatus::ASK);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, process(), /*render_frame_host=*/nullptr,
           url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -662,12 +663,12 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::ASK, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, SubscribeMIDIPermission) {
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::MIDI, /*render_process_host=*/nullptr, main_rfh(),
           url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -679,7 +680,7 @@
 
   EXPECT_FALSE(callback_called());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, PermissionIgnoredCleanup) {
@@ -877,7 +878,7 @@
   content::RenderFrameHost* child = AddChildRFH(parent, GURL(kOrigin2));
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, child,
           GURL(kOrigin2),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -904,7 +905,7 @@
   EXPECT_EQ(PermissionStatus::GRANTED, GetPermissionStatusForCurrentDocument(
                                            PermissionType::GEOLOCATION, child));
 
-  subscription_id = SubscribePermissionStatusChange(
+  subscription_id = SubscribeToPermissionStatusChange(
       PermissionType::GEOLOCATION, /*render_process_host=*/nullptr, child,
       GURL(kOrigin2),
       base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -921,7 +922,7 @@
   EXPECT_EQ(PermissionStatus::DENIED, GetPermissionStatusForCurrentDocument(
                                           PermissionType::GEOLOCATION, child));
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, SubscribeUnsubscribeAndResubscribe) {
@@ -929,7 +930,7 @@
   NavigateAndCommit(GURL(kOrigin1));
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), GURL(kOrigin1),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -941,7 +942,7 @@
   EXPECT_EQ(callback_count(), 1);
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 
   // ensure no callbacks are received when unsubscribed.
   SetPermission(PermissionType::GEOLOCATION, PermissionStatus::DENIED);
@@ -950,7 +951,7 @@
   EXPECT_EQ(callback_count(), 1);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id_2 =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), GURL(kOrigin1),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -962,7 +963,7 @@
   EXPECT_EQ(callback_count(), 2);
   EXPECT_EQ(PermissionStatus::DENIED, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id_2);
+  UnsubscribeFromPermissionStatusChange(subscription_id_2);
 }
 
 TEST_F(PermissionManagerTest, GetCanonicalOrigin) {
@@ -1052,7 +1053,7 @@
   NavigateAndCommit(GURL(kOrigin1));
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), GURL(kOrigin1),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -1078,7 +1079,7 @@
                                        false /* dismissed_prompt_was_quiet */);
   EXPECT_EQ(callback_count(), 1);
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest, UpdatePermissionStatusWithDeviceStatus) {
@@ -1136,7 +1137,7 @@
   permissions_client().SetCanRequestDevicePermission(true);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -1194,7 +1195,7 @@
   EXPECT_TRUE(callback_called());
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 TEST_F(PermissionManagerTest,
@@ -1205,7 +1206,7 @@
   permissions_client().SetCanRequestDevicePermission(false);
 
   content::PermissionControllerDelegate::SubscriptionId subscription_id =
-      SubscribePermissionStatusChange(
+      SubscribeToPermissionStatusChange(
           PermissionType::GEOLOCATION, /*render_process_host=*/nullptr,
           main_rfh(), url(),
           base::BindRepeating(&PermissionManagerTest::OnPermissionChange,
@@ -1247,7 +1248,7 @@
   EXPECT_EQ(PermissionStatus::GRANTED, callback_result());
   Reset();
 
-  UnsubscribePermissionStatusChange(subscription_id);
+  UnsubscribeFromPermissionStatusChange(subscription_id);
 }
 
 }  // namespace permissions
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/DeviceExtendedFkeysModifier.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/DeviceExtendedFkeysModifier.yaml
index f46fc27..8b67810 100644
--- a/components/policy/resources/templates/policy_definitions/Miscellaneous/DeviceExtendedFkeysModifier.yaml
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/DeviceExtendedFkeysModifier.yaml
@@ -9,8 +9,6 @@
 features:
   dynamic_refresh: true
   per_profile: false
-future_on:
-- chrome_os
 items:
 - caption: F11/F12 settings are disabled
   name: Disabled
@@ -34,5 +32,7 @@
   - 2
   - 3
   type: integer
+supported_on:
+- chrome_os:122-
 tags: []
 type: int-enum
diff --git a/components/power_bookmarks/core/BUILD.gn b/components/power_bookmarks/core/BUILD.gn
index 1728432e..cb55b72 100644
--- a/components/power_bookmarks/core/BUILD.gn
+++ b/components/power_bookmarks/core/BUILD.gn
@@ -43,8 +43,6 @@
 
 static_library("features") {
   sources = [
-    "flag_descriptions.cc",
-    "flag_descriptions.h",
     "power_bookmark_features.cc",
     "power_bookmark_features.h",
   ]
diff --git a/components/power_bookmarks/core/flag_descriptions.cc b/components/power_bookmarks/core/flag_descriptions.cc
deleted file mode 100644
index f35c9483..0000000
--- a/components/power_bookmarks/core/flag_descriptions.cc
+++ /dev/null
@@ -1,14 +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 "components/power_bookmarks/core/flag_descriptions.h"
-
-namespace power_bookmarks::flag_descriptions {
-
-const char kSimplifiedBookmarkSaveFlowName[] = "Simplified Bookmark Save Flow";
-const char kSimplifiedBookmarkSaveFlowDescription[] =
-    "Show a simplified version of the bookmark save flow without rename and "
-    "folder options.";
-
-}  // namespace power_bookmarks::flag_descriptions
diff --git a/components/power_bookmarks/core/flag_descriptions.h b/components/power_bookmarks/core/flag_descriptions.h
deleted file mode 100644
index cee6dac8..0000000
--- a/components/power_bookmarks/core/flag_descriptions.h
+++ /dev/null
@@ -1,15 +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 COMPONENTS_POWER_BOOKMARKS_CORE_FLAG_DESCRIPTIONS_H_
-#define COMPONENTS_POWER_BOOKMARKS_CORE_FLAG_DESCRIPTIONS_H_
-
-namespace power_bookmarks::flag_descriptions {
-
-extern const char kSimplifiedBookmarkSaveFlowName[];
-extern const char kSimplifiedBookmarkSaveFlowDescription[];
-
-}  // namespace power_bookmarks::flag_descriptions
-
-#endif  // COMPONENTS_POWER_BOOKMARKS_CORE_FLAG_DESCRIPTIONS_H_
diff --git a/components/power_bookmarks/core/power_bookmark_features.cc b/components/power_bookmarks/core/power_bookmark_features.cc
index 678d58e..a2cc053 100644
--- a/components/power_bookmarks/core/power_bookmark_features.cc
+++ b/components/power_bookmarks/core/power_bookmark_features.cc
@@ -11,8 +11,4 @@
              "PowerBookmarkBackend",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kSimplifiedBookmarkSaveFlow,
-             "SimplifiedBookmarkSaveFlow",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 }  // namespace power_bookmarks
diff --git a/components/power_bookmarks/core/power_bookmark_features.h b/components/power_bookmarks/core/power_bookmark_features.h
index 6f8448c..665b8a0 100644
--- a/components/power_bookmarks/core/power_bookmark_features.h
+++ b/components/power_bookmarks/core/power_bookmark_features.h
@@ -11,8 +11,6 @@
 // Controls the power bookmarks backend.
 BASE_DECLARE_FEATURE(kPowerBookmarkBackend);
 
-BASE_DECLARE_FEATURE(kSimplifiedBookmarkSaveFlow);
-
 }  // namespace power_bookmarks
 
 #endif  // COMPONENTS_POWER_BOOKMARKS_CORE_POWER_BOOKMARK_FEATURES_H_
diff --git a/components/reading_list/core/dual_reading_list_model.cc b/components/reading_list/core/dual_reading_list_model.cc
index 48f7540..773bef0 100644
--- a/components/reading_list/core/dual_reading_list_model.cc
+++ b/components/reading_list/core/dual_reading_list_model.cc
@@ -609,9 +609,6 @@
     return;
   }
 
-  // Only expected for changes received via sync.
-  DCHECK(ToReadingListModelImpl(model)->IsTrackingSyncMetadata());
-
   NotifyObserversWithWillMoveEntry(url);
   UpdateEntryStateCountersOnEntryRemoval(*GetEntryByURL(url));
 }
@@ -623,9 +620,6 @@
     return;
   }
 
-  // Only expected for changes received via sync.
-  DCHECK(ToReadingListModelImpl(model)->IsTrackingSyncMetadata());
-
   UpdateEntryStateCountersOnEntryInsertion(*GetEntryByURL(url));
   NotifyObserversWithDidMoveEntry(url);
 }
@@ -812,4 +806,13 @@
   return local_or_syncable_model_->GetKeys();
 }
 
+ReadingListModel* DualReadingListModel::GetLocalOrSyncableModel() {
+  return local_or_syncable_model_.get();
+}
+
+ReadingListModel* DualReadingListModel::GetAccountModelIfSyncing() {
+  return account_model_->IsTrackingSyncMetadata() ? account_model_.get()
+                                                  : nullptr;
+}
+
 }  // namespace reading_list
diff --git a/components/reading_list/core/dual_reading_list_model.h b/components/reading_list/core/dual_reading_list_model.h
index 4505eabf..47f070f2 100644
--- a/components/reading_list/core/dual_reading_list_model.h
+++ b/components/reading_list/core/dual_reading_list_model.h
@@ -138,6 +138,13 @@
 
   StorageStateForTesting GetStorageStateForURLForTesting(const GURL& url);
 
+  // Returns the model responsible for the local/syncable reading list.
+  ReadingListModel* GetLocalOrSyncableModel();
+  // Returns the model responsible for the account-bound reading list. This can
+  // toggle between null and non-null at runtime depending on the sync/signin
+  // state.
+  ReadingListModel* GetAccountModelIfSyncing();
+
  private:
   void NotifyObserversWithWillRemoveEntry(const GURL& url);
   void NotifyObserversWithDidRemoveEntry(const GURL& url);
diff --git a/components/reading_list/core/dual_reading_list_model_unittest.cc b/components/reading_list/core/dual_reading_list_model_unittest.cc
index b9f3a20..5ab8aea 100644
--- a/components/reading_list/core/dual_reading_list_model_unittest.cc
+++ b/components/reading_list/core/dual_reading_list_model_unittest.cc
@@ -130,6 +130,7 @@
   absl::optional<base::FilePath> distilation_path_;
 };
 
+// TODO(crbug.com/1510547): Add test coverage for GetAccountModelIfSyncing.
 class DualReadingListModelTest : public testing::Test {
  public:
   DualReadingListModelTest() = default;
diff --git a/components/reporting/storage/storage_unittest.cc b/components/reporting/storage/storage_unittest.cc
index 6a315ea..b267e45 100644
--- a/components/reporting/storage/storage_unittest.cc
+++ b/components/reporting/storage/storage_unittest.cc
@@ -762,7 +762,7 @@
     const raw_ptr<base::flat_map<Priority, int64_t>> last_upload_generation_id_;
     const raw_ptr<LastRecordDigestMap> last_record_digest_map_;
 
-    const raw_ptr<const MockUpload, DanglingUntriaged> mock_upload_;
+    const raw_ptr<const MockUpload> mock_upload_;
     const base::SequenceBound<SequenceBoundUpload> sequence_bound_upload_;
 
     const scoped_refptr<test::Decryptor> decryptor_;
diff --git a/components/safe_browsing/core/browser/db/v4_local_database_manager.cc b/components/safe_browsing/core/browser/db/v4_local_database_manager.cc
index fde6764..f13b28b 100644
--- a/components/safe_browsing/core/browser/db/v4_local_database_manager.cc
+++ b/components/safe_browsing/core/browser/db/v4_local_database_manager.cc
@@ -935,19 +935,21 @@
     }
   }
 
+  bool did_match_allowlist = *match == AsyncMatch::MATCH;
   if (check->client_callback_type == ClientCallbackType::CHECK_OTHER) {
-    bool result = *match == AsyncMatch::MATCH;
     if (GetPrefixMatchesIsAsync()) {
       // This is already asynchronous so no need for another PostTask.
-      std::move(callback).Run(result);
+      std::move(callback).Run(did_match_allowlist);
     } else {
-      sb_task_runner()->PostTask(FROM_HERE,
-                                 base::BindOnce(std::move(callback), result));
+      sb_task_runner()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(callback), did_match_allowlist));
     }
   } else if (check->client_callback_type ==
              ClientCallbackType::CHECK_CSD_ALLOWLIST) {
     if (GetPrefixMatchesIsAsync()) {
-      check->most_severe_threat_type = SB_THREAT_TYPE_CSD_ALLOWLIST;
+      check->most_severe_threat_type = did_match_allowlist
+                                           ? SB_THREAT_TYPE_CSD_ALLOWLIST
+                                           : SB_THREAT_TYPE_SAFE;
       RespondToClient(std::move(check));
     }
   } else {
diff --git a/components/safe_browsing/core/browser/db/v4_local_database_manager_unittest.cc b/components/safe_browsing/core/browser/db/v4_local_database_manager_unittest.cc
index 636f685..493fb0a 100644
--- a/components/safe_browsing/core/browser/db/v4_local_database_manager_unittest.cc
+++ b/components/safe_browsing/core/browser/db/v4_local_database_manager_unittest.cc
@@ -740,7 +740,7 @@
   ReplaceV4Database(store_and_hash_prefixes, /* stores_available= */ true);
 
   TestAllowlistClient client(
-      /* match_expected= */ true,
+      /* match_expected= */ false,
       /* expected_sb_threat_type= */ SB_THREAT_TYPE_CSD_ALLOWLIST);
   const GURL url_check("https://other.com/");
   auto result =
diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
index c165b8b7..3e5217b 100644
--- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc
@@ -3729,9 +3729,12 @@
   RunRegressionTest(FILE_PATH_LITERAL("title-in-shadow.html"));
 }
 
-// TODO(https://crbug.com/1175562): Flaky
-IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
-                       DISABLED_ReusedMapChangeMapName) {
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, ReusedMapChangeMapName) {
+  RunRegressionTest(FILE_PATH_LITERAL("reused-map-change-map-name.html"));
+}
+
+IN_PROC_BROWSER_TEST_P(YieldingParserDumpAccessibilityTreeTest,
+                       ReusedMapChangeMapName) {
   RunRegressionTest(FILE_PATH_LITERAL("reused-map-change-map-name.html"));
 }
 
diff --git a/content/browser/android/nfc_host.cc b/content/browser/android/nfc_host.cc
index 1696f3c..98ee802 100644
--- a/content/browser/android/nfc_host.cc
+++ b/content/browser/android/nfc_host.cc
@@ -68,14 +68,17 @@
   if (!subscription_id_) {
     // base::Unretained() is safe here because the subscription is canceled when
     // this object is destroyed.
-    // TODO(crbug.com/1271543) : Move `SubscribePermissionStatusChange` to
+    // TODO(crbug.com/1271543) : Move `SubscribeToPermissionStatusChange` to
     // `PermissionController`.
-    subscription_id_ = permission_controller_->SubscribePermissionStatusChange(
-        blink::PermissionType::NFC, /*render_process_host=*/nullptr,
-        render_frame_host,
-        render_frame_host->GetMainFrame()->GetLastCommittedOrigin().GetURL(),
-        base::BindRepeating(&NFCHost::OnPermissionStatusChange,
-                            base::Unretained(this)));
+    subscription_id_ =
+        permission_controller_->SubscribeToPermissionStatusChange(
+            blink::PermissionType::NFC, /*render_process_host=*/nullptr,
+            render_frame_host,
+            render_frame_host->GetMainFrame()
+                ->GetLastCommittedOrigin()
+                .GetURL(),
+            base::BindRepeating(&NFCHost::OnPermissionStatusChange,
+                                base::Unretained(this)));
   }
 
   if (!nfc_provider_) {
@@ -125,7 +128,8 @@
 
 void NFCHost::Close() {
   nfc_provider_.reset();
-  permission_controller_->UnsubscribePermissionStatusChange(subscription_id_);
+  permission_controller_->UnsubscribeFromPermissionStatusChange(
+      subscription_id_);
   subscription_id_ = PermissionController::SubscriptionId();
 }
 
diff --git a/content/browser/android/nfc_host_unittest.cc b/content/browser/android/nfc_host_unittest.cc
index 743e9b42..261ce33 100644
--- a/content/browser/android/nfc_host_unittest.cc
+++ b/content/browser/android/nfc_host_unittest.cc
@@ -60,9 +60,9 @@
       .WillOnce(Return(blink::mojom::PermissionStatus::GRANTED))
       .WillOnce(Return(blink::mojom::PermissionStatus::GRANTED));
   EXPECT_CALL(mock_permission_manager(),
-              SubscribePermissionStatusChange(blink::PermissionType::NFC,
-                                              /*render_process_host=*/nullptr,
-                                              main_rfh(), GURL(kTestUrl), _))
+              SubscribeToPermissionStatusChange(blink::PermissionType::NFC,
+                                                /*render_process_host=*/nullptr,
+                                                main_rfh(), GURL(kTestUrl), _))
       .WillOnce(Return(kSubscriptionId));
 
   mojo::Remote<device::mojom::NFC> nfc1, nfc2;
@@ -75,7 +75,7 @@
   EXPECT_TRUE(nfc2.is_bound());
 
   EXPECT_CALL(mock_permission_manager(),
-              UnsubscribePermissionStatusChange(kSubscriptionId));
+              UnsubscribeFromPermissionStatusChange(kSubscriptionId));
 
   DeleteContents();
 }
diff --git a/content/browser/attribution_reporting/attribution_host_unittest.cc b/content/browser/attribution_reporting/attribution_host_unittest.cc
index 154d362..24e017e7 100644
--- a/content/browser/attribution_reporting/attribution_host_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_host_unittest.cc
@@ -122,10 +122,10 @@
     // permissions policy set.
     FrameTreeNode* fenced_frame_node =
         static_cast<RenderFrameHostImpl*>(fenced_frame)->frame_tree_node();
-    absl::optional<FencedFrameProperties> new_props =
-        fenced_frame_node->GetFencedFrameProperties();
-    new_props->effective_enabled_permissions.push_back(
+    FencedFrameConfig new_config = FencedFrameConfig(GURL("about:blank"));
+    new_config.effective_enabled_permissions.push_back(
         blink::mojom::PermissionsPolicyFeature::kAttributionReporting);
+    FencedFrameProperties new_props = FencedFrameProperties(new_config);
     fenced_frame_node->set_fenced_frame_properties(new_props);
   }
 
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index 58d971a..40e39f6 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -21,6 +21,7 @@
 #include "base/check_op.h"
 #include "base/containers/enum_set.h"
 #include "base/containers/flat_set.h"
+#include "base/containers/span.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/functional/overloaded.h"
@@ -831,14 +832,10 @@
   int64_t min_priority;
 
   while (min_priority_statement.Step()) {
-    std::string metadata;
-    if (!min_priority_statement.ColumnBlobAsString(0, &metadata)) {
-      continue;
-    }
-
     uint32_t trigger_data;
     int64_t priority;
-    if (!DeserializeReportMetadata(metadata, trigger_data, priority)) {
+    if (base::span<const uint8_t> blob = min_priority_statement.ColumnBlob(0);
+        !DeserializeReportMetadata(blob, trigger_data, priority)) {
       continue;
     }
 
@@ -1542,13 +1539,9 @@
     corruption_causes.Put(ReportCorruptionStatus::kReportingOriginMismatch);
   }
 
-  std::string metadata;
-  if (!statement.ColumnBlobAsString(col++, &metadata)) {
-    corruption_causes.Put(ReportCorruptionStatus::kMetadataAsStringFailed);
-  }
-
   absl::optional<AttributionReport::Data> data;
-  switch (*report_type) {
+  switch (base::span<const uint8_t> metadata = statement.ColumnBlob(col++);
+          *report_type) {
     case AttributionReport::Type::kEventLevel: {
       if (!source_data.has_value()) {
         corruption_causes.Put(
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.h b/content/browser/attribution_reporting/attribution_storage_sql.h
index 8d50665..7c1c5d8 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.h
+++ b/content/browser/attribution_reporting/attribution_storage_sql.h
@@ -93,7 +93,7 @@
     kInvalidReportingOrigin = 4,
     kInvalidReportType = 5,
     kReportingOriginMismatch = 6,
-    kMetadataAsStringFailed = 7,
+    // Obsolete: kMetadataAsStringFailed = 7,
     kSourceDataMissingEventLevel = 8,
     kSourceDataMissingAggregatable = 9,
     kSourceDataFoundNullAggregatable = 10,
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
index aca04ee..f05c74b 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_migrations_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/attribution_reporting/attribution_storage_sql_migrations.h"
 
+#include "base/containers/span.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
@@ -362,9 +363,10 @@
         db.GetUniqueStatement("SELECT read_only_source_data FROM sources"));
     ASSERT_TRUE(s.Step());
     proto::AttributionReadOnlySourceData msg;
-    std::string blob;
-    ASSERT_TRUE(s.ColumnBlobAsString(0, &blob));
-    ASSERT_TRUE(msg.ParseFromString(blob));
+    {
+      base::span<const uint8_t> blob = s.ColumnBlob(0);
+      ASSERT_TRUE(msg.ParseFromArray(blob.data(), blob.size()));
+    }
     EXPECT_EQ(3, msg.max_event_level_reports());
     EXPECT_FALSE(msg.has_randomized_response_rate());
     EXPECT_EQ(0, msg.event_level_report_window_start_time());
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
index 61a8763..43af75e 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
@@ -15,6 +15,7 @@
 #include <vector>
 
 #include "base/check.h"
+#include "base/containers/span.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/bind.h"
@@ -1761,11 +1762,11 @@
     while (get_statement.Step()) {
       int64_t id = get_statement.ColumnInt64(0);
 
-      std::string blob;
-      ASSERT_TRUE(get_statement.ColumnBlobAsString(1, &blob));
-
       proto::AttributionReadOnlySourceData msg;
-      ASSERT_TRUE(msg.ParseFromString(blob));
+      {
+        base::span<const uint8_t> blob = get_statement.ColumnBlob(1);
+        ASSERT_TRUE(msg.ParseFromArray(blob.data(), blob.size()));
+      }
 
       msg.clear_randomized_response_rate();
 
@@ -1805,11 +1806,11 @@
     while (get_statement.Step()) {
       int64_t id = get_statement.ColumnInt64(0);
 
-      std::string blob;
-      ASSERT_TRUE(get_statement.ColumnBlobAsString(1, &blob));
-
       proto::AttributionReadOnlySourceData msg;
-      ASSERT_TRUE(msg.ParseFromString(blob));
+      {
+        base::span<const uint8_t> blob = get_statement.ColumnBlob(1);
+        ASSERT_TRUE(msg.ParseFromArray(blob.data(), blob.size()));
+      }
 
       msg.clear_event_level_epsilon();
 
diff --git a/content/browser/attribution_reporting/sql_utils.cc b/content/browser/attribution_reporting/sql_utils.cc
index 2bd970b..6bf9fa4 100644
--- a/content/browser/attribution_reporting/sql_utils.cc
+++ b/content/browser/attribution_reporting/sql_utils.cc
@@ -11,6 +11,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/containers/span.h"
 #include "base/feature_list.h"
 #include "base/time/time.h"
 #include "components/aggregation_service/features.h"
@@ -195,13 +196,10 @@
 
 absl::optional<proto::AttributionReadOnlySourceData>
 DeserializeReadOnlySourceDataAsProto(sql::Statement& stmt, int col) {
-  std::string str;
-  if (!stmt.ColumnBlobAsString(col, &str)) {
-    return absl::nullopt;
-  }
 
   proto::AttributionReadOnlySourceData msg;
-  if (!msg.ParseFromString(str)) {
+  if (base::span<const uint8_t> blob = stmt.ColumnBlob(col);
+      !msg.ParseFromArray(blob.data(), blob.size())) {
     return absl::nullopt;
   }
   return msg;
@@ -223,13 +221,9 @@
 absl::optional<attribution_reporting::FilterData> DeserializeFilterData(
     sql::Statement& stmt,
     int col) {
-  std::string string;
-  if (!stmt.ColumnBlobAsString(col, &string)) {
-    return absl::nullopt;
-  }
-
   proto::AttributionFilterData msg;
-  if (!msg.ParseFromString(string)) {
+  if (base::span<const uint8_t> blob = stmt.ColumnBlob(col);
+      !msg.ParseFromArray(blob.data(), blob.size())) {
     return absl::nullopt;
   }
 
@@ -273,13 +267,9 @@
 
 absl::optional<attribution_reporting::AggregationKeys>
 DeserializeAggregationKeys(sql::Statement& stmt, int col) {
-  std::string str;
-  if (!stmt.ColumnBlobAsString(col, &str)) {
-    return absl::nullopt;
-  }
-
   proto::AttributionAggregatableSource msg;
-  if (!msg.ParseFromString(str)) {
+  if (base::span<const uint8_t> blob = stmt.ColumnBlob(col);
+      !msg.ParseFromArray(blob.data(), blob.size())) {
     return absl::nullopt;
   }
 
@@ -305,12 +295,12 @@
   return msg.SerializeAsString();
 }
 
-bool DeserializeReportMetadata(const std::string& str,
+bool DeserializeReportMetadata(base::span<const uint8_t> blob,
                                uint32_t& trigger_data,
                                int64_t& priority) {
   proto::AttributionEventLevelMetadata msg;
-  if (!msg.ParseFromString(str) || !msg.has_trigger_data() ||
-      !msg.has_priority()) {
+  if (!msg.ParseFromArray(blob.data(), blob.size()) ||
+      !msg.has_trigger_data() || !msg.has_priority()) {
     return false;
   }
 
@@ -340,11 +330,11 @@
 }
 
 bool DeserializeReportMetadata(
-    const std::string& str,
+    base::span<const uint8_t> blob,
     AttributionReport::AggregatableAttributionData& data) {
   proto::AttributionAggregatableMetadata msg;
-  if (!msg.ParseFromString(str) || msg.contributions().empty() ||
-      !msg.has_common_data() ||
+  if (!msg.ParseFromArray(blob.data(), blob.size()) ||
+      msg.contributions().empty() || !msg.has_common_data() ||
       !DeserializeCommonAggregatableData(msg.common_data(), data.common_data)) {
     return false;
   }
@@ -378,11 +368,11 @@
   return msg.SerializeAsString();
 }
 
-bool DeserializeReportMetadata(const std::string& str,
+bool DeserializeReportMetadata(base::span<const uint8_t> blob,
                                AttributionReport::NullAggregatableData& data) {
   proto::AttributionNullAggregatableMetadata msg;
-  if (!msg.ParseFromString(str) || !msg.has_fake_source_time() ||
-      !msg.has_common_data() ||
+  if (!msg.ParseFromArray(blob.data(), blob.size()) ||
+      !msg.has_fake_source_time() || !msg.has_common_data() ||
       !DeserializeCommonAggregatableData(msg.common_data(), data.common_data)) {
     return false;
   }
diff --git a/content/browser/attribution_reporting/sql_utils.h b/content/browser/attribution_reporting/sql_utils.h
index 04e9a00..64f767cb 100644
--- a/content/browser/attribution_reporting/sql_utils.h
+++ b/content/browser/attribution_reporting/sql_utils.h
@@ -9,6 +9,7 @@
 
 #include <string>
 
+#include "base/containers/span.h"
 #include "components/attribution_reporting/source_type.mojom-forward.h"
 #include "components/attribution_reporting/trigger_data_matching.mojom-forward.h"
 #include "content/browser/attribution_reporting/attribution_report.h"
@@ -80,16 +81,16 @@
 std::string SerializeReportMetadata(
     const AttributionReport::NullAggregatableData&);
 
-[[nodiscard]] bool DeserializeReportMetadata(const std::string&,
+[[nodiscard]] bool DeserializeReportMetadata(base::span<const uint8_t>,
                                              uint32_t& trigger_data,
                                              int64_t& priority);
 
 [[nodiscard]] bool DeserializeReportMetadata(
-    const std::string&,
+    base::span<const uint8_t>,
     AttributionReport::AggregatableAttributionData&);
 
 [[nodiscard]] bool DeserializeReportMetadata(
-    const std::string&,
+    base::span<const uint8_t>,
     AttributionReport::NullAggregatableData&);
 
 }  // namespace content
diff --git a/content/browser/client_hints/client_hints.cc b/content/browser/client_hints/client_hints.cc
index adf593b..92a3236 100644
--- a/content/browser/client_hints/client_hints.cc
+++ b/content/browser/client_hints/client_hints.cc
@@ -574,7 +574,7 @@
           frame_tree_node->GetFencedFrameProperties();
       base::span<const blink::mojom::PermissionsPolicyFeature> permissions;
       if (fenced_frame_properties) {
-        permissions = fenced_frame_properties->effective_enabled_permissions;
+        permissions = fenced_frame_properties->effective_enabled_permissions();
       }
       permissions_policy = blink::PermissionsPolicy::CreateForFencedFrame(
           resource_origin, permissions);
diff --git a/content/browser/fenced_frame/fenced_frame_config.cc b/content/browser/fenced_frame/fenced_frame_config.cc
index f8a5f4ac..b31b8661 100644
--- a/content/browser/fenced_frame/fenced_frame_config.cc
+++ b/content/browser/fenced_frame/fenced_frame_config.cc
@@ -238,7 +238,7 @@
                        VisibilityToContent::kOpaque),
       mode_(config.mode_),
       is_ad_component_(config.is_ad_component_),
-      effective_enabled_permissions(config.effective_enabled_permissions) {
+      effective_enabled_permissions_(config.effective_enabled_permissions) {
   if (config.shared_storage_budget_metadata_) {
     shared_storage_budget_metadata_.emplace(
         &config.shared_storage_budget_metadata_->GetValueIgnoringVisibility(),
@@ -322,7 +322,7 @@
   redacted_properties.mode_ = mode_;
 
   redacted_properties.effective_enabled_permissions_ =
-      effective_enabled_permissions;
+      effective_enabled_permissions_;
 
   return redacted_properties;
 }
diff --git a/content/browser/fenced_frame/fenced_frame_config.h b/content/browser/fenced_frame/fenced_frame_config.h
index 9fa9503..7bb767b 100644
--- a/content/browser/fenced_frame/fenced_frame_config.h
+++ b/content/browser/fenced_frame/fenced_frame_config.h
@@ -40,9 +40,6 @@
 //   `FencedFrameConfig` and the `kEmbedder` entity. The constructor
 //   automatically performs the redaction process.
 //
-//   TODO(crbug.com/1347953): Remove this disclaimer.
-//   (Note: the following two steps aren't implemented yet, and are currently
-//    accomplished with urns.)
 // * FLEDGE returns the redacted config to the embedder's renderer.
 //   `RedactedFencedFrameConfig` supports mojom type mappings for
 //   `blink::mojom::FencedFrameConfig`.
@@ -200,7 +197,7 @@
  private:
   friend class content::FencedFrameURLMapping;
   friend struct FencedFrameConfig;
-  friend struct FencedFrameProperties;
+  friend class FencedFrameProperties;
 
   T value_;
   VisibilityToEmbedder visibility_to_embedder_;
@@ -333,8 +330,8 @@
 // These `FencedFrameProperties` are stored in the fenced frame root
 // `FrameTreeNode`, and live between embedder-initiated fenced frame
 // navigations.
-// TODO(crbug.com/1417871): Turn this into a class and make its fields private.
-struct CONTENT_EXPORT FencedFrameProperties {
+class CONTENT_EXPORT FencedFrameProperties {
+ public:
   // The empty constructor is used for:
   // * pre-navigation fenced frames
   // * embedder-initiated non-opaque url navigations
@@ -354,8 +351,6 @@
   blink::FencedFrame::RedactedFencedFrameProperties RedactFor(
       FencedFrameEntity entity) const;
 
-  absl::optional<FencedFrameProperty<GURL>> mapped_url_;
-
   // Update the stored mapped URL to a new one given by `url`.
   // `this` must have a value for `mapped_url_` when the function is called.
   // We use this method when an embedder-initiated fenced frame root navigation
@@ -380,6 +375,90 @@
   const absl::optional<AutomaticBeaconInfo> GetAutomaticBeaconInfo(
       blink::mojom::AutomaticBeaconType event_type) const;
 
+  const absl::optional<FencedFrameProperty<GURL>>& mapped_url() const {
+    return mapped_url_;
+  }
+
+  const absl::optional<FencedFrameProperty<AdAuctionData>>& ad_auction_data()
+      const {
+    return ad_auction_data_;
+  }
+
+  const base::RepeatingClosure& on_navigate_callback() const {
+    return on_navigate_callback_;
+  }
+
+  const absl::optional<
+      FencedFrameProperty<std::vector<std::pair<GURL, FencedFrameConfig>>>>
+  nested_urn_config_pairs() const {
+    return nested_urn_config_pairs_;
+  }
+
+  const absl::optional<
+      FencedFrameProperty<raw_ptr<const SharedStorageBudgetMetadata>>>&
+  shared_storage_budget_metadata() const {
+    return shared_storage_budget_metadata_;
+  }
+
+  const absl::optional<std::u16string>& embedder_shared_storage_context()
+      const {
+    return embedder_shared_storage_context_;
+  }
+
+  // Used to store the shared storage context passed from the embedder
+  // (navigation initiator)'s renderer into the new FencedFrameProperties.
+  // TODO(crbug.com/1417871): Refactor this to be part of the
+  // FencedFrameProperties constructor rather than
+  // OnFencedFrameURLMappingComplete.
+  void SetEmbedderSharedStorageContext(
+      const absl::optional<std::u16string>& embedder_shared_storage_context) {
+    embedder_shared_storage_context_ = embedder_shared_storage_context;
+  }
+
+  const scoped_refptr<FencedFrameReporter>& fenced_frame_reporter() const {
+    return fenced_frame_reporter_;
+  }
+
+  const absl::optional<FencedFrameProperty<base::UnguessableToken>>&
+  partition_nonce() const {
+    return partition_nonce_;
+  }
+
+  // Used for urn iframes, which should not have a separate storage/network
+  // partition.
+  // TODO(crbug.com/1417871): Refactor this to be part of the
+  // FencedFrameProperties constructor rather than
+  // OnFencedFrameURLMappingComplete.
+  void ClearPartitionNonce() { partition_nonce_ = absl::nullopt; }
+
+  const DeprecatedFencedFrameMode& mode() const { return mode_; }
+
+  bool is_ad_component() const { return is_ad_component_; }
+
+  const std::vector<blink::mojom::PermissionsPolicyFeature>&
+  effective_enabled_permissions() const {
+    return effective_enabled_permissions_;
+  }
+
+  // Set the current FencedFrameProperties to have "opaque ads mode".
+  // This should only be used during tests, when the proper embedder-initiated
+  // fenced frame root urn/config navigation flow isn't available.
+  // TODO(crbug.com/1347953): Refactor and expand use of test utils so there is
+  // a consistent way to do this properly everywhere. Consider removing
+  // arbitrary restrictions in "default mode" so that using opaque ads mode is
+  // less necessary.
+  void SetFencedFramePropertiesOpaqueAdsModeForTesting() {
+    mode_ = blink::FencedFrame::DeprecatedFencedFrameMode::kOpaqueAds;
+  }
+
+ private:
+  FRIEND_TEST_ALL_PREFIXES(FencedFrameConfigMojomTraitsTest,
+                           ConfigMojomTraitsTest);
+  FRIEND_TEST_ALL_PREFIXES(FencedFrameConfigMojomTraitsTest,
+                           PropertiesHasFencedFrameReportingTest);
+
+  absl::optional<FencedFrameProperty<GURL>> mapped_url_;
+
   absl::optional<FencedFrameProperty<gfx::Size>> container_size_;
 
   // TODO(crbug.com/1420638): The representation of size in fenced frame config
@@ -452,7 +531,7 @@
   // See entry in spec:
   // https://wicg.github.io/fenced-frame/#fenced-frame-config-effective-enabled-permissions
   std::vector<blink::mojom::PermissionsPolicyFeature>
-      effective_enabled_permissions;
+      effective_enabled_permissions_;
 };
 
 }  // namespace content
diff --git a/content/browser/fenced_frame/redacted_fenced_frame_config_mojom_traits_unittest.cc b/content/browser/fenced_frame/redacted_fenced_frame_config_mojom_traits_unittest.cc
index 2808737..16175e8 100644
--- a/content/browser/fenced_frame/redacted_fenced_frame_config_mojom_traits_unittest.cc
+++ b/content/browser/fenced_frame/redacted_fenced_frame_config_mojom_traits_unittest.cc
@@ -301,7 +301,7 @@
 
       RedactedFencedFrameProperties input_properties =
           browser_properties.RedactFor(entity);
-      ASSERT_TRUE(browser_properties.mode_ == input_properties.mode());
+      ASSERT_TRUE(browser_properties.mode() == input_properties.mode());
 
       RedactedFencedFrameProperties output_properties;
       mojo::test::SerializeAndDeserialize<blink::mojom::FencedFrameProperties>(
diff --git a/content/browser/geolocation/geolocation_service_impl.cc b/content/browser/geolocation/geolocation_service_impl.cc
index c8accdc..bf104fc 100644
--- a/content/browser/geolocation/geolocation_service_impl.cc
+++ b/content/browser/geolocation/geolocation_service_impl.cc
@@ -126,7 +126,7 @@
   subscription_id_ =
       PermissionControllerImpl::FromBrowserContext(
           render_frame_host_->GetBrowserContext())
-          ->SubscribePermissionStatusChange(
+          ->SubscribeToPermissionStatusChange(
               blink::PermissionType::GEOLOCATION,
               /*render_process_host=*/nullptr, render_frame_host_,
               requesting_url,
@@ -141,7 +141,7 @@
       subscription_id_.value()) {
     PermissionControllerImpl::FromBrowserContext(
         render_frame_host_->GetBrowserContext())
-        ->UnsubscribePermissionStatusChange(subscription_id_);
+        ->UnsubscribeFromPermissionStatusChange(subscription_id_);
     geolocation_context_->OnPermissionRevoked(requesting_origin_);
   }
 }
diff --git a/content/browser/interest_group/ad_auction_service_impl.cc b/content/browser/interest_group/ad_auction_service_impl.cc
index 832f4af..706b4199 100644
--- a/content/browser/interest_group/ad_auction_service_impl.cc
+++ b/content/browser/interest_group/ad_auction_service_impl.cc
@@ -246,11 +246,11 @@
     return;
   }
 
-  if (!fenced_frame_properties->ad_auction_data_.has_value()) {
+  if (!fenced_frame_properties->ad_auction_data().has_value()) {
     return;
   }
 
-  if (fenced_frame_properties->is_ad_component_ &&
+  if (fenced_frame_properties->is_ad_component() &&
       !base::FeatureList::IsEnabled(
           blink::features::kFencedFramesM120FeaturesPart2)) {
     // The ability to leave interest group from an ad component is not supported
@@ -259,7 +259,7 @@
   }
 
   const blink::FencedFrame::AdAuctionData& auction_data =
-      fenced_frame_properties->ad_auction_data_->GetValueIgnoringVisibility();
+      fenced_frame_properties->ad_auction_data()->GetValueIgnoringVisibility();
 
   if (auction_data.interest_group_owner != origin()) {
     // The ad page calling LeaveAdInterestGroup is not the owner of the group.
@@ -447,11 +447,11 @@
   void OnFencedFrameURLMappingComplete(
       const absl::optional<FencedFrameProperties>& properties) override {
     if (properties) {
-      if (properties->mapped_url_) {
-        *mapped_url_ = properties->mapped_url_->GetValueIgnoringVisibility();
+      if (properties->mapped_url()) {
+        *mapped_url_ = properties->mapped_url()->GetValueIgnoringVisibility();
       }
-      if (send_reports_ && properties->on_navigate_callback_) {
-        properties->on_navigate_callback_.Run();
+      if (send_reports_ && properties->on_navigate_callback()) {
+        properties->on_navigate_callback().Run();
       }
     }
     called_ = true;
diff --git a/content/browser/interest_group/ad_auction_service_impl_unittest.cc b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
index 9bba82d..935c233 100644
--- a/content/browser/interest_group/ad_auction_service_impl_unittest.cc
+++ b/content/browser/interest_group/ad_auction_service_impl_unittest.cc
@@ -833,8 +833,8 @@
 
   absl::optional<GURL> ConvertFencedFrameURNToURL(const GURL& urn_url) {
     auto properties = GetFencedFramePropertiesForURN(urn_url);
-    if (properties && properties->mapped_url_.has_value()) {
-      return properties->mapped_url_->GetValueIgnoringVisibility();
+    if (properties && properties->mapped_url().has_value()) {
+      return properties->mapped_url()->GetValueIgnoringVisibility();
     }
     return absl::nullopt;
   }
@@ -846,7 +846,7 @@
   void InvokeCallbackForURN(const GURL& urn_url) {
     auto properties = GetFencedFramePropertiesForURN(urn_url);
     ASSERT_TRUE(properties);
-    properties->on_navigate_callback_.Run();
+    properties->on_navigate_callback().Run();
   }
 
   // Creates a new AdAuctionServiceImpl and use it to try and join
@@ -10798,7 +10798,7 @@
       GetFencedFramePropertiesForURN(*result);
   ASSERT_TRUE(properties);
   EXPECT_THAT(
-      properties->fenced_frame_reporter_->GetAdBeaconMapForTesting(),
+      properties->fenced_frame_reporter()->GetAdBeaconMapForTesting(),
       testing::UnorderedElementsAre(
           testing::Pair(
               blink::FencedFrame::ReportingDestination::kBuyer,
@@ -11082,7 +11082,7 @@
       GetFencedFramePropertiesForURN(*result);
   ASSERT_TRUE(properties);
   EXPECT_THAT(
-      properties->fenced_frame_reporter_->GetAdBeaconMapForTesting(),
+      properties->fenced_frame_reporter()->GetAdBeaconMapForTesting(),
       testing::UnorderedElementsAre(
           testing::Pair(
               blink::FencedFrame::ReportingDestination::kBuyer,
@@ -11707,7 +11707,7 @@
       GetFencedFramePropertiesForURN(*result);
   ASSERT_TRUE(properties);
   EXPECT_THAT(
-      properties->fenced_frame_reporter_->GetAdBeaconMapForTesting(),
+      properties->fenced_frame_reporter()->GetAdBeaconMapForTesting(),
       testing::UnorderedElementsAre(
           testing::Pair(
               blink::FencedFrame::ReportingDestination::kBuyer,
@@ -11913,7 +11913,7 @@
       GetFencedFramePropertiesForURN(*result);
   ASSERT_TRUE(properties);
   EXPECT_THAT(
-      properties->fenced_frame_reporter_->GetAdBeaconMapForTesting(),
+      properties->fenced_frame_reporter()->GetAdBeaconMapForTesting(),
       testing::UnorderedElementsAre(
           testing::Pair(
               blink::FencedFrame::ReportingDestination::kBuyer,
@@ -12121,7 +12121,7 @@
       GetFencedFramePropertiesForURN(*result);
   ASSERT_TRUE(properties);
   EXPECT_THAT(
-      properties->fenced_frame_reporter_->GetAdBeaconMapForTesting(),
+      properties->fenced_frame_reporter()->GetAdBeaconMapForTesting(),
       testing::UnorderedElementsAre(
           testing::Pair(
               blink::FencedFrame::ReportingDestination::kBuyer,
@@ -12291,7 +12291,7 @@
       GetFencedFramePropertiesForURN(*result);
   ASSERT_TRUE(properties);
   EXPECT_THAT(
-      properties->fenced_frame_reporter_->GetAdBeaconMapForTesting(),
+      properties->fenced_frame_reporter()->GetAdBeaconMapForTesting(),
       testing::UnorderedElementsAre(
           testing::Pair(
               blink::FencedFrame::ReportingDestination::kBuyer,
@@ -12471,7 +12471,7 @@
       GetFencedFramePropertiesForURN(*result);
   ASSERT_TRUE(properties);
   EXPECT_THAT(
-      properties->fenced_frame_reporter_->GetAdBeaconMapForTesting(),
+      properties->fenced_frame_reporter()->GetAdBeaconMapForTesting(),
       testing::UnorderedElementsAre(
           testing::Pair(blink::FencedFrame::ReportingDestination::kBuyer,
                         testing::ElementsAre()),
diff --git a/content/browser/permissions/permission_controller_impl.cc b/content/browser/permissions/permission_controller_impl.cc
index 88ba1a3..b94bf6f 100644
--- a/content/browser/permissions/permission_controller_impl.cc
+++ b/content/browser/permissions/permission_controller_impl.cc
@@ -651,7 +651,7 @@
 }
 
 PermissionControllerImpl::SubscriptionId
-PermissionControllerImpl::SubscribePermissionStatusChange(
+PermissionControllerImpl::SubscribeToPermissionStatusChange(
     PermissionType permission,
     RenderProcessHost* render_process_host,
     RenderFrameHost* render_frame_host,
@@ -682,7 +682,7 @@
       browser_context_->GetPermissionControllerDelegate();
   if (delegate) {
     subscription->delegate_subscription_id =
-        delegate->SubscribePermissionStatusChange(
+        delegate->SubscribeToPermissionStatusChange(
             permission, render_process_host, render_frame_host,
             requesting_origin,
             base::BindRepeating(
@@ -694,17 +694,17 @@
 }
 
 PermissionControllerImpl::SubscriptionId
-PermissionControllerImpl::SubscribePermissionStatusChange(
+PermissionControllerImpl::SubscribeToPermissionStatusChange(
     PermissionType permission,
     RenderProcessHost* render_process_host,
     const url::Origin& requesting_origin,
     const base::RepeatingCallback<void(PermissionStatus)>& callback) {
-  return SubscribePermissionStatusChange(permission, render_process_host,
-                                         /*render_frame_host=*/nullptr,
-                                         requesting_origin.GetURL(), callback);
+  return SubscribeToPermissionStatusChange(
+      permission, render_process_host,
+      /*render_frame_host=*/nullptr, requesting_origin.GetURL(), callback);
 }
 
-void PermissionControllerImpl::UnsubscribePermissionStatusChange(
+void PermissionControllerImpl::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {
   Subscription* subscription = subscriptions_.Lookup(subscription_id);
   if (!subscription)
@@ -712,7 +712,7 @@
   PermissionControllerDelegate* delegate =
       browser_context_->GetPermissionControllerDelegate();
   if (delegate) {
-    delegate->UnsubscribePermissionStatusChange(
+    delegate->UnsubscribeFromPermissionStatusChange(
         subscription->delegate_subscription_id);
   }
   subscriptions_.Remove(subscription_id);
diff --git a/content/browser/permissions/permission_controller_impl.h b/content/browser/permissions/permission_controller_impl.h
index 8eb13dd..9d41d85c 100644
--- a/content/browser/permissions/permission_controller_impl.h
+++ b/content/browser/permissions/permission_controller_impl.h
@@ -81,19 +81,19 @@
 
   // Only one of |render_process_host| and |render_frame_host| should be set,
   // or neither. RenderProcessHost will be inferred from |render_frame_host|.
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       PermissionType permission,
       RenderProcessHost* render_process_host,
       RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       const base::RepeatingCallback<void(PermissionStatus)>& callback);
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       PermissionType permission,
       RenderProcessHost* render_process_host,
       const url::Origin& requesting_origin,
       const base::RepeatingCallback<void(PermissionStatus)>& callback) override;
 
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
 
   // If there's currently a permission prompt bubble for the given WebContents,
diff --git a/content/browser/permissions/permission_controller_impl_unittest.cc b/content/browser/permissions/permission_controller_impl_unittest.cc
index 611c7fc8..8e4f52a 100644
--- a/content/browser/permissions/permission_controller_impl_unittest.cc
+++ b/content/browser/permissions/permission_controller_impl_unittest.cc
@@ -381,11 +381,11 @@
       kTestOrigin, PermissionType::GEOLOCATION, PermissionStatus::DENIED);
 
   base::MockCallback<PermissionStatusCallback> geo_callback;
-  permission_controller()->SubscribePermissionStatusChange(
+  permission_controller()->SubscribeToPermissionStatusChange(
       PermissionType::GEOLOCATION, nullptr, nullptr, kUrl, geo_callback.Get());
 
   base::MockCallback<PermissionStatusCallback> sync_callback;
-  permission_controller()->SubscribePermissionStatusChange(
+  permission_controller()->SubscribeToPermissionStatusChange(
       PermissionType::BACKGROUND_SYNC, nullptr, nullptr, kUrl,
       sync_callback.Get());
 
diff --git a/content/browser/permissions/permission_service_context.cc b/content/browser/permissions/permission_service_context.cc
index 3d4cd259..8c335a7 100644
--- a/content/browser/permissions/permission_service_context.cc
+++ b/content/browser/permissions/permission_service_context.cc
@@ -63,7 +63,7 @@
     BrowserContext* browser_context = context_->GetBrowserContext();
     if (browser_context) {
       PermissionControllerImpl::FromBrowserContext(browser_context)
-          ->UnsubscribePermissionStatusChange(id_);
+          ->UnsubscribeFromPermissionStatusChange(id_);
     }
   }
 
@@ -197,7 +197,7 @@
   GURL requesting_origin(origin.Serialize());
   auto subscription_id =
       PermissionControllerImpl::FromBrowserContext(browser_context)
-          ->SubscribePermissionStatusChange(
+          ->SubscribeToPermissionStatusChange(
               permission_type, render_process_host_, render_frame_host_,
               requesting_origin,
               base::BindRepeating(
diff --git a/content/browser/renderer_host/frame_tree_node.cc b/content/browser/renderer_host/frame_tree_node.cc
index 39443f5b8..bda0a796 100644
--- a/content/browser/renderer_host/frame_tree_node.cc
+++ b/content/browser/renderer_host/frame_tree_node.cc
@@ -958,7 +958,7 @@
   // `properties` will exist for both fenced frames as well as iframes loaded
   // with a urn:uuid. This allows URN iframes to call this function without
   // getting bad-messaged.
-  if (!properties || !properties->fenced_frame_reporter_) {
+  if (!properties || !properties->fenced_frame_reporter()) {
     mojo::ReportBadMessage(
         "Automatic beacon data can only be set in fenced frames or iframes "
         "loaded from a config with a fenced frame reporter.");
@@ -966,9 +966,9 @@
   }
   // This metadata should only be present in the renderer in frames that are
   // same-origin to the mapped url.
-  if (!properties->mapped_url_.has_value() ||
+  if (!properties->mapped_url().has_value() ||
       !current_origin().IsSameOriginWith(url::Origin::Create(
-          properties->mapped_url_->GetValueIgnoringVisibility()))) {
+          properties->mapped_url()->GetValueIgnoringVisibility()))) {
     mojo::ReportBadMessage(
         "Automatic beacon data can only be set from documents that are same-"
         "origin to the mapped url from the fenced frame config.");
@@ -992,7 +992,7 @@
 
       // This implies the fenced frame is from shared storage.
       if (node->fenced_frame_properties_ &&
-          node->fenced_frame_properties_->shared_storage_budget_metadata_) {
+          node->fenced_frame_properties_->shared_storage_budget_metadata()) {
         shared_storage_fenced_frame_root_count += 1;
       }
     } else {
@@ -1015,8 +1015,8 @@
   if (!root_fenced_frame_properties.has_value()) {
     return absl::nullopt;
   }
-  if (root_fenced_frame_properties->partition_nonce_.has_value()) {
-    return root_fenced_frame_properties->partition_nonce_
+  if (root_fenced_frame_properties->partition_nonce().has_value()) {
+    return root_fenced_frame_properties->partition_nonce()
         ->GetValueIgnoringVisibility();
   }
   // It is only possible for there to be `FencedFrameProperties` but no
@@ -1056,7 +1056,7 @@
     return blink::FencedFrame::DeprecatedFencedFrameMode::kDefault;
   }
 
-  return root_fenced_frame_properties->mode_;
+  return root_fenced_frame_properties->mode();
 }
 
 bool FrameTreeNode::IsErrorPageIsolationEnabled() const {
@@ -1075,9 +1075,9 @@
 
   while (true) {
     if (node->fenced_frame_properties_ &&
-        node->fenced_frame_properties_->shared_storage_budget_metadata_) {
+        node->fenced_frame_properties_->shared_storage_budget_metadata()) {
       result.emplace_back(
-          node->fenced_frame_properties_->shared_storage_budget_metadata_
+          node->fenced_frame_properties_->shared_storage_budget_metadata()
               ->GetValueIgnoringVisibility());
     }
 
@@ -1097,12 +1097,12 @@
       GetFencedFramePropertiesForEditing();
   // We only return embedder context for frames that are same origin with the
   // fenced frame root or ancestor URN iframe.
-  if (!properties || !properties->mapped_url_.has_value() ||
+  if (!properties || !properties->mapped_url().has_value() ||
       !current_origin().IsSameOriginWith(url::Origin::Create(
-          properties->mapped_url_->GetValueIgnoringVisibility()))) {
+          properties->mapped_url()->GetValueIgnoringVisibility()))) {
     return absl::nullopt;
   }
-  return properties->embedder_shared_storage_context_;
+  return properties->embedder_shared_storage_context();
 }
 
 const scoped_refptr<BrowsingContextState>&
diff --git a/content/browser/renderer_host/frame_tree_node.h b/content/browser/renderer_host/frame_tree_node.h
index 7c8ac46a..215a374 100644
--- a/content/browser/renderer_host/frame_tree_node.h
+++ b/content/browser/renderer_host/frame_tree_node.h
@@ -526,8 +526,8 @@
   // less necessary.
   void SetFencedFramePropertiesOpaqueAdsModeForTesting() {
     if (fenced_frame_properties_.has_value()) {
-      fenced_frame_properties_->mode_ =
-          blink::FencedFrame::DeprecatedFencedFrameMode::kOpaqueAds;
+      fenced_frame_properties_
+          ->SetFencedFramePropertiesOpaqueAdsModeForTesting();
     }
   }
 
diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc
index ad844d2b..38175174 100644
--- a/content/browser/renderer_host/media/media_stream_manager.cc
+++ b/content/browser/renderer_host/media/media_stream_manager.cc
@@ -4031,7 +4031,7 @@
   if (is_audio_request) {
     // It is safe to bind base::Unretained(this) because MediaStreamManager is
     // owned by BrowserMainLoop.
-    audio_subscription_id = controller->SubscribePermissionStatusChange(
+    audio_subscription_id = controller->SubscribeToPermissionStatusChange(
         blink::PermissionType::AUDIO_CAPTURE,
         /*render_process_host=*/nullptr,
         RenderFrameHost::FromID(requesting_render_frame_host_id), origin,
@@ -4044,7 +4044,7 @@
   if (is_video_request) {
     // It is safe to bind base::Unretained(this) because MediaStreamManager is
     // owned by BrowserMainLoop.
-    video_subscription_id = controller->SubscribePermissionStatusChange(
+    video_subscription_id = controller->SubscribeToPermissionStatusChange(
         blink::PermissionType::VIDEO_CAPTURE,
         /*render_process_host=*/nullptr,
         RenderFrameHost::FromID(requesting_render_frame_host_id), origin,
@@ -4104,8 +4104,8 @@
     return;
   }
 
-  controller->UnsubscribePermissionStatusChange(audio_subscription_id);
-  controller->UnsubscribePermissionStatusChange(video_subscription_id);
+  controller->UnsubscribeFromPermissionStatusChange(audio_subscription_id);
+  controller->UnsubscribeFromPermissionStatusChange(video_subscription_id);
 }
 
 void MediaStreamManager::PermissionChangedCallback(
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 10177bba..1b5fd36 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -2416,16 +2416,16 @@
     return;
   }
 
-  if (properties->on_navigate_callback_) {
-    properties->on_navigate_callback_.Run();
+  if (properties->on_navigate_callback()) {
+    properties->on_navigate_callback().Run();
   }
 
   // Currently, all fenced frame use cases include mapped urls. Patch up
   // url-related fields to use the underlying mapped url, rather than the
   // original urn.
-  CHECK(properties->mapped_url_.has_value());
+  CHECK(properties->mapped_url().has_value());
   const GURL& mapped_url_value =
-      properties->mapped_url_->GetValueIgnoringVisibility();
+      properties->mapped_url()->GetValueIgnoringVisibility();
   common_params_->url = mapped_url_value;
   commit_params_->original_url = mapped_url_value;
 
@@ -2436,19 +2436,19 @@
 
   // Set the shared storage context in the fenced frame properties.
   DCHECK(fenced_frame_properties_);
-  fenced_frame_properties_->embedder_shared_storage_context_ =
-      embedder_shared_storage_context_;
+  fenced_frame_properties_->SetEmbedderSharedStorageContext(
+      embedder_shared_storage_context_);
   embedder_shared_storage_context_ = absl::nullopt;
 
   // For urns loaded into iframes for FLEDGE OT, for compatibility we don't
   // want to use a fenced frame nonce.
   if (!frame_tree_node_->IsFencedFrameRoot()) {
     CHECK(blink::features::IsAllowURNsInIframeEnabled());
-    fenced_frame_properties_->partition_nonce_ = absl::nullopt;
+    fenced_frame_properties_->ClearPartitionNonce();
   }
 
   // This implies the URN is created from shared storage.
-  if (fenced_frame_properties_->shared_storage_budget_metadata_) {
+  if (fenced_frame_properties_->shared_storage_budget_metadata()) {
     base::TimeDelta time_spent_in_fenced_frame_url_mapping =
         base::TimeTicks::Now() - fenced_frame_url_mapping_start_time_;
 
@@ -7509,7 +7509,7 @@
     // * Embedder-initiated FF root navigations to transparent (non-urn) urls.
     // In those cases, we skip this step.
     if (fenced_frame_properties_.has_value() &&
-        fenced_frame_properties_->mapped_url_.has_value()) {
+        fenced_frame_properties_->mapped_url().has_value()) {
       fenced_frame_properties_->UpdateMappedURL(GetURL());
     }
   }
@@ -7529,9 +7529,9 @@
   if (computed_fenced_frame_properties.has_value()) {
     content::FencedFrameEntity entity =
         content::FencedFrameEntity::kSameOriginContent;
-    if (computed_fenced_frame_properties->mapped_url_.has_value() &&
+    if (computed_fenced_frame_properties->mapped_url().has_value() &&
         !url::Origin::Create(common_params_->url)
-             .IsSameOriginWith(computed_fenced_frame_properties->mapped_url_
+             .IsSameOriginWith(computed_fenced_frame_properties->mapped_url()
                                    ->GetValueIgnoringVisibility())) {
       entity = content::FencedFrameEntity::kCrossOriginContent;
     }
@@ -8766,7 +8766,7 @@
   // extra policies defined in the outer document/"allow" attribute won't have
   // any effect.
   for (const blink::mojom::PermissionsPolicyFeature feature :
-       computed_fenced_frame_properties->effective_enabled_permissions) {
+       computed_fenced_frame_properties->effective_enabled_permissions()) {
     if (!IsFencedFrameRequiredPolicyFeatureAllowed(origin, feature)) {
       const blink::PermissionsPolicyFeatureToNameMap& feature_to_name_map =
           blink::GetPermissionsPolicyFeatureToNameMap();
@@ -9422,14 +9422,14 @@
   if (!computed_fenced_frame_properties.has_value()) {
     return absl::nullopt;
   }
-  if (!computed_fenced_frame_properties->partition_nonce_.has_value()) {
+  if (!computed_fenced_frame_properties->partition_nonce().has_value()) {
     // It is only possible for there to be `FencedFrameProperties` but no
     // partition nonce in urn iframes (which could indeed be nested inside a
     // fenced frame).
     CHECK(blink::features::IsAllowURNsInIframeEnabled());
     return absl::nullopt;
   }
-  return computed_fenced_frame_properties->partition_nonce_
+  return computed_fenced_frame_properties->partition_nonce()
       ->GetValueIgnoringVisibility();
 }
 
diff --git a/content/browser/renderer_host/navigation_request_unittest.cc b/content/browser/renderer_host/navigation_request_unittest.cc
index 33b1514a..bf4471e 100644
--- a/content/browser/renderer_host/navigation_request_unittest.cc
+++ b/content/browser/renderer_host/navigation_request_unittest.cc
@@ -528,10 +528,10 @@
       content::RenderFrameHostTester::For(main_rfh())->AppendFencedFrame());
   FrameTreeNode* fenced_frame_node =
       static_cast<RenderFrameHostImpl*>(fenced_frame_root)->frame_tree_node();
-  absl::optional<FencedFrameProperties> new_props =
-      fenced_frame_node->GetFencedFrameProperties();
-  new_props->effective_enabled_permissions.push_back(
+  FencedFrameConfig new_config = FencedFrameConfig(GURL("about:blank"));
+  new_config.effective_enabled_permissions.push_back(
       blink::mojom::PermissionsPolicyFeature::kSharedStorage);
+  FencedFrameProperties new_props = FencedFrameProperties(new_config);
   fenced_frame_node->set_fenced_frame_properties(new_props);
   fenced_frame_root->ResetPermissionsPolicy();
 
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 2f60f2b..6bad112 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -642,7 +642,7 @@
         frame->frame_tree_node()->GetFencedFrameProperties();
     base::span<const blink::mojom::PermissionsPolicyFeature> permissions;
     if (fenced_frame_properties) {
-      permissions = fenced_frame_properties->effective_enabled_permissions;
+      permissions = fenced_frame_properties->effective_enabled_permissions();
     }
     subframe_policy = blink::PermissionsPolicy::CreateForFencedFrame(
         subframe_origin, permissions);
@@ -8492,7 +8492,7 @@
   const absl::optional<FencedFrameProperties>& fenced_frame_properties =
       frame_tree_node_->GetFencedFrameProperties();
   if (!fenced_frame_properties.has_value() ||
-      !fenced_frame_properties->fenced_frame_reporter_) {
+      !fenced_frame_properties->fenced_frame_reporter()) {
     // No associated fenced frame reporter. This should have been captured
     // in the renderer process at `Fence::reportEvent`.
     // This implies there is an inconsistency between the browser and the
@@ -8503,9 +8503,9 @@
         "consistent between the two.");
     return;
   }
-  if (!fenced_frame_properties->mapped_url_.has_value() ||
+  if (!fenced_frame_properties->mapped_url().has_value() ||
       !GetLastCommittedOrigin().IsSameOriginWith(
-          url::Origin::Create(fenced_frame_properties->mapped_url_
+          url::Origin::Create(fenced_frame_properties->mapped_url()
                                   ->GetValueIgnoringVisibility()))) {
     mojo::ReportBadMessage(
         "This frame is cross-origin to the mapped url of its fenced frame "
@@ -8513,7 +8513,7 @@
     return;
   }
 
-  fenced_frame_properties->fenced_frame_reporter_
+  fenced_frame_properties->fenced_frame_reporter()
       ->SendPrivateAggregationRequestsForEvent(event_type);
 }
 
@@ -8691,7 +8691,7 @@
     base::UmaHistogramEnumeration(blink::kFencedFrameTopNavigationHistogram,
                                   blink::FencedFrameNavigationState::kCommit);
   }
-  if (!properties.has_value() || !properties->fenced_frame_reporter_) {
+  if (!properties.has_value() || !properties->fenced_frame_reporter()) {
     return;
   }
   FencedDocumentData* fenced_document_data = nullptr;
@@ -8752,10 +8752,10 @@
   // when the initiator document is cross-origin with the fenced frame config's
   // mapped url, but only if the document opts in through a header.
   bool is_same_origin =
-      properties->mapped_url_.has_value() &&
+      properties->mapped_url().has_value() &&
       initiator_rfh->GetLastCommittedOrigin().IsSameOriginWith(
           url::Origin::Create(
-              properties->mapped_url_->GetValueIgnoringVisibility()));
+              properties->mapped_url()->GetValueIgnoringVisibility()));
   if (!is_same_origin && !initiator_allows_fenced_frame_automatic_beacons) {
     RecordAutomaticBeaconOutcome(
         blink::AutomaticBeaconOutcome::kNotSameOriginNotOptedIn);
@@ -8772,7 +8772,7 @@
     RecordAutomaticBeaconOutcome(blink::AutomaticBeaconOutcome::kSuccess);
 
     for (const auto& destination :
-         properties->fenced_frame_reporter_->ReportingDestinations()) {
+         properties->fenced_frame_reporter()->ReportingDestinations()) {
       std::string data;
       // For data to be sent in the automatic beacon, it must be specified in
       // the event's "destination" for setReportEventDataForAutomaticBeacons().
@@ -8828,7 +8828,7 @@
       frame_tree_node_->GetFencedFrameProperties();
 
   if (!fenced_frame_properties.has_value() ||
-      !fenced_frame_properties->fenced_frame_reporter_) {
+      !fenced_frame_properties->fenced_frame_reporter()) {
     // No associated fenced frame reporter. This should have been captured
     // in the renderer process at `Fence::reportEvent`.
     // This implies there is an inconsistency between the browser and the
@@ -8838,7 +8838,7 @@
     return false;
   }
 
-  if (fenced_frame_properties->is_ad_component_) {
+  if (fenced_frame_properties->is_ad_component()) {
     // Direct invocation of fence.reportEvent from an ad component is
     // disallowed.
     AddMessageToConsole(
@@ -8848,9 +8848,9 @@
     return false;
   }
 
-  if (!fenced_frame_properties->mapped_url_.has_value() ||
+  if (!fenced_frame_properties->mapped_url().has_value() ||
       !GetLastCommittedOrigin().IsSameOriginWith(
-          url::Origin::Create(fenced_frame_properties->mapped_url_
+          url::Origin::Create(fenced_frame_properties->mapped_url()
                                   ->GetValueIgnoringVisibility()))) {
     mojo::ReportBadMessage(
         "This frame is cross-origin to the mapped url of its fenced frame "
@@ -8896,7 +8896,8 @@
   }
 
   if (!frame_tree_node_->GetFencedFrameProperties()
-           ->fenced_frame_reporter_->SendReport(
+           ->fenced_frame_reporter()
+           ->SendReport(
                event_variant, destination, /*request_initiator_frame=*/this,
                fenced_frame_data->features(), error_message,
                console_message_level, GetFrameTreeNodeId(), navigation_id)) {
@@ -8945,7 +8946,7 @@
     // iframes loaded with a urn:uuid. This allows URN iframes to call this
     // function without getting bad-messaged.
     if (!fenced_frame_properties ||
-        !fenced_frame_properties->fenced_frame_reporter_) {
+        !fenced_frame_properties->fenced_frame_reporter()) {
       mojo::ReportBadMessage(
           "Automatic beacon data can only be set in fenced frames or iframes "
           "loaded from a config with a fenced frame reporter.");
@@ -8953,9 +8954,9 @@
     }
     // This metadata should only be present in the renderer in frames that are
     // same-origin to the mapped url.
-    if (!fenced_frame_properties->mapped_url_.has_value() ||
+    if (!fenced_frame_properties->mapped_url().has_value() ||
         !GetLastCommittedOrigin().IsSameOriginWith(
-            url::Origin::Create(fenced_frame_properties->mapped_url_
+            url::Origin::Create(fenced_frame_properties->mapped_url()
                                     ->GetValueIgnoringVisibility()))) {
       mojo::ReportBadMessage(
           "Automatic beacon data can only be set from documents that are same-"
@@ -8965,7 +8966,7 @@
 
     // Ad components cannot set event data for automatic beacons.
     std::string event_data_to_use =
-        fenced_frame_properties->is_ad_component_ ? std::string{} : event_data;
+        fenced_frame_properties->is_ad_component() ? std::string{} : event_data;
 
     auto* fenced_document_data =
         FencedDocumentData::GetOrCreateForCurrentDocument(this);
@@ -11695,7 +11696,7 @@
         frame_tree_node()->GetFencedFrameProperties();
     base::span<const blink::mojom::PermissionsPolicyFeature> permissions;
     if (fenced_frame_properties) {
-      permissions = fenced_frame_properties->effective_enabled_permissions;
+      permissions = fenced_frame_properties->effective_enabled_permissions();
     }
     permissions_policy_ = blink::PermissionsPolicy::CreateForFencedFrame(
         last_committed_origin_, permissions);
@@ -13229,7 +13230,7 @@
     if (fenced_frame_properties &&
         (frame_tree_node()->IsFencedFrameRoot() ||
          !frame_tree_node()->IsInFencedFrameTree())) {
-      if (fenced_frame_properties->nested_urn_config_pairs_.has_value()) {
+      if (fenced_frame_properties->nested_urn_config_pairs().has_value()) {
         // Store nested ad components in the fenced frame's url map.
         // This may only be done after creating the DocumentAssociatedData for
         // the new document, if appropriate, since `fenced_frame_urls_map` hangs
@@ -13237,17 +13238,17 @@
         // the urn iframe root don't create a new Page (because the root of the
         // Page is the top-level frame). So this operation is a no-op.
         GetPage().fenced_frame_urls_map().ImportPendingAdComponents(
-            fenced_frame_properties->nested_urn_config_pairs_
+            fenced_frame_properties->nested_urn_config_pairs()
                 ->GetValueIgnoringVisibility());
       }
 
-      if (fenced_frame_properties->ad_auction_data_.has_value()) {
+      if (fenced_frame_properties->ad_auction_data().has_value()) {
         AdAuctionDocumentData::CreateForCurrentDocument(
             this,
-            fenced_frame_properties->ad_auction_data_
+            fenced_frame_properties->ad_auction_data()
                 ->GetValueIgnoringVisibility()
                 .interest_group_owner,
-            fenced_frame_properties->ad_auction_data_
+            fenced_frame_properties->ad_auction_data()
                 ->GetValueIgnoringVisibility()
                 .interest_group_name);
       }
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index c66c7eb..01807b1 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -3468,7 +3468,6 @@
     switches::kV,
     switches::kVideoCaptureUseGpuMemoryBuffer,
     switches::kVideoThreads,
-    switches::kVideoUnderflowThresholdMs,
     switches::kVModule,
     switches::kWaitForDebuggerOnNavigation,
     switches::kWebAuthRemoteDesktopSupport,
diff --git a/content/browser/web_package/signed_exchange_handler_unittest.cc b/content/browser/web_package/signed_exchange_handler_unittest.cc
index a1ae7284..9bb64510 100644
--- a/content/browser/web_package/signed_exchange_handler_unittest.cc
+++ b/content/browser/web_package/signed_exchange_handler_unittest.cc
@@ -161,7 +161,7 @@
 
 class MockCTPolicyEnforcer : public net::CTPolicyEnforcer {
  public:
-  MOCK_METHOD3(
+  MOCK_CONST_METHOD3(
       CheckCompliance,
       net::ct::CTPolicyCompliance(net::X509Certificate* cert,
                                   const net::ct::SCTList& verified_scts,
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index d04a708..114f7860 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -331,6 +331,17 @@
       render_frame_host->GetPage());
 }
 
+FedCmMetrics::NumAccounts ComputeNumMatchingAccounts(
+    const IdpNetworkRequestManager::AccountList& accounts) {
+  if (accounts.empty()) {
+    return FedCmMetrics::NumAccounts::kZero;
+  }
+  if (accounts.size() == 1u) {
+    return FedCmMetrics::NumAccounts::kOne;
+  }
+  return FedCmMetrics::NumAccounts::kMultiple;
+}
+
 void FilterAccountsWithLoginHint(
     const std::string& login_hint,
     IdpNetworkRequestManager::AccountList& accounts) {
@@ -346,12 +357,7 @@
     return !base::Contains(account.login_hints, login_hint);
   };
   base::EraseIf(accounts, filter);
-  FedCmMetrics::NumAccounts num_matching = FedCmMetrics::NumAccounts::kZero;
-  if (accounts.size() == 1u) {
-    num_matching = FedCmMetrics::NumAccounts::kOne;
-  } else if (accounts.size() > 1u) {
-    num_matching = FedCmMetrics::NumAccounts::kMultiple;
-  }
+  FedCmMetrics::NumAccounts num_matching = ComputeNumMatchingAccounts(accounts);
   base::UmaHistogramEnumeration("Blink.FedCm.LoginHint.NumMatchingAccounts",
                                 num_matching);
 }
@@ -374,6 +380,9 @@
     };
     base::EraseIf(accounts, filter);
   }
+  FedCmMetrics::NumAccounts num_matching = ComputeNumMatchingAccounts(accounts);
+  base::UmaHistogramEnumeration("Blink.FedCm.DomainHint.NumMatchingAccounts",
+                                num_matching);
 }
 
 std::unique_ptr<FedCmMetrics> CreateFedCmMetrics(
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index e8af0cf..2369273 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -4458,6 +4458,9 @@
   RunAuthTest(parameters, kExpectationSuccess, configuration);
   ASSERT_EQ(displayed_accounts().size(), 1u);
   EXPECT_EQ(displayed_accounts()[0].id, kAccountId);
+
+  histogram_tester_.ExpectTotalCount(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts", 0);
 }
 
 TEST_F(FederatedAuthRequestImplTest, DomainHintSingleAccountMatch) {
@@ -4474,6 +4477,10 @@
   RunAuthTest(parameters, kExpectationSuccess, configuration);
   ASSERT_EQ(displayed_accounts().size(), 1u);
   EXPECT_EQ(displayed_accounts()[0].id, kAccountId);
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kOne, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest, DomainHintSingleAccountStarMatch) {
@@ -4491,6 +4498,10 @@
   RunAuthTest(parameters, kExpectationSuccess, configuration);
   ASSERT_EQ(displayed_accounts().size(), 1u);
   EXPECT_EQ(displayed_accounts()[0].id, kAccountId);
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kOne, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest, DomainHintSingleAccountStarNoMatch) {
@@ -4512,6 +4523,10 @@
   RunAuthTest(parameters, expectations, configuration);
   EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
   EXPECT_FALSE(did_show_accounts_dialog());
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kZero, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest, DomainHintSingleAccountNoMatch) {
@@ -4533,6 +4548,10 @@
   RunAuthTest(parameters, expectations, configuration);
   EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
   EXPECT_FALSE(did_show_accounts_dialog());
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kZero, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest, DomainHintNoMatch) {
@@ -4550,6 +4569,10 @@
   RunAuthTest(parameters, expectations, kConfigurationValid);
   EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
   EXPECT_FALSE(did_show_accounts_dialog());
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kZero, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest, DomainHintMultipleAccountsSingleMatch) {
@@ -4566,6 +4589,10 @@
   RunAuthTest(parameters, kExpectationSuccess, configuration);
   ASSERT_EQ(displayed_accounts().size(), 1u);
   EXPECT_EQ(displayed_accounts()[0].id, kAccountIdZach);
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kOne, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest,
@@ -4584,6 +4611,10 @@
   ASSERT_EQ(displayed_accounts().size(), 2u);
   EXPECT_EQ(displayed_accounts()[0].id, kAccountIdNicolas);
   EXPECT_EQ(displayed_accounts()[1].id, kAccountIdZach);
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kMultiple, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest, DomainHintMultipleAccountsStar) {
@@ -4602,6 +4633,10 @@
   ASSERT_EQ(displayed_accounts().size(), 2u);
   EXPECT_EQ(displayed_accounts()[0].id, kAccountIdNicolas);
   EXPECT_EQ(displayed_accounts()[1].id, kAccountIdZach);
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kMultiple, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest, DomainHintMultipleAccountsNoMatch) {
@@ -4623,6 +4658,10 @@
   RunAuthTest(parameters, expectations, configuration);
   EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
   EXPECT_FALSE(did_show_accounts_dialog());
+
+  histogram_tester_.ExpectUniqueSample(
+      "Blink.FedCm.DomainHint.NumMatchingAccounts",
+      FedCmMetrics::NumAccounts::kZero, 1);
 }
 
 // Test that when FedCmRpContext flag is enabled and rp_context is specified,
diff --git a/content/public/browser/permission_controller.h b/content/public/browser/permission_controller.h
index 4fb5df93..05afd6e 100644
--- a/content/public/browser/permission_controller.h
+++ b/content/public/browser/permission_controller.h
@@ -100,13 +100,13 @@
   virtual void ResetPermission(blink::PermissionType permission,
                                const url::Origin& origin) = 0;
 
-  virtual SubscriptionId SubscribePermissionStatusChange(
+  virtual SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       RenderProcessHost* render_process_host,
       const url::Origin& requesting_origin,
       const base::RepeatingCallback<void(PermissionStatus)>& callback) = 0;
 
-  virtual void UnsubscribePermissionStatusChange(
+  virtual void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) = 0;
 
   // Returns `true` if a document subscribed to
diff --git a/content/public/browser/permission_controller_delegate.h b/content/public/browser/permission_controller_delegate.h
index 68c0d50..2c23867 100644
--- a/content/public/browser/permission_controller_delegate.h
+++ b/content/public/browser/permission_controller_delegate.h
@@ -121,7 +121,7 @@
   // unsubscribe, which can be `is_null()` if the subscribe was not successful.
   // Exactly one of |render_process_host| and |render_frame_host| should be
   // set, RenderProcessHost will be inferred from |render_frame_host|.
-  virtual SubscriptionId SubscribePermissionStatusChange(
+  virtual SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
@@ -130,9 +130,9 @@
 
   // Unregisters from permission status change notifications. The
   // |subscription_id| must match the value returned by the
-  // SubscribePermissionStatusChange call. Unsubscribing an already
+  // SubscribeToPermissionStatusChange call. Unsubscribing an already
   // unsubscribed |subscription_id| or an `is_null()` ID is a no-op.
-  virtual void UnsubscribePermissionStatusChange(
+  virtual void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) = 0;
 
   // If there's currently a permission UI presenting for the given WebContents,
diff --git a/content/public/test/fenced_frame_reporter_observer.cc b/content/public/test/fenced_frame_reporter_observer.cc
index d7e809d..44a397d7 100644
--- a/content/public/test/fenced_frame_reporter_observer.cc
+++ b/content/public/test/fenced_frame_reporter_observer.cc
@@ -58,7 +58,7 @@
   CHECK(fenced_frame_properties.has_value());
 
   scoped_refptr<content::FencedFrameReporter> fenced_frame_reporter =
-      fenced_frame_properties->fenced_frame_reporter_;
+      fenced_frame_properties->fenced_frame_reporter();
   CHECK(fenced_frame_reporter);
 
   return std::make_unique<FencedFrameReporterObserverForTesting>(
diff --git a/content/public/test/mock_permission_controller.cc b/content/public/test/mock_permission_controller.cc
index 5137c4e..11dbf76 100644
--- a/content/public/test/mock_permission_controller.cc
+++ b/content/public/test/mock_permission_controller.cc
@@ -24,7 +24,7 @@
 void MockPermissionController::ResetPermission(blink::PermissionType permission,
                                                const url::Origin& origin) {}
 
-void MockPermissionController::UnsubscribePermissionStatusChange(
+void MockPermissionController::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {}
 
 }  // namespace content
diff --git a/content/public/test/mock_permission_controller.h b/content/public/test/mock_permission_controller.h
index d23a742c..dc5fb3d 100644
--- a/content/public/test/mock_permission_controller.h
+++ b/content/public/test/mock_permission_controller.h
@@ -71,14 +71,14 @@
   void ResetPermission(blink::PermissionType permission,
                        const url::Origin& origin) override;
 
-  MOCK_METHOD4(SubscribePermissionStatusChange,
+  MOCK_METHOD4(SubscribeToPermissionStatusChange,
                SubscriptionId(blink::PermissionType permission,
                               RenderProcessHost* render_process_host,
                               const url::Origin& requesting_origin,
                               const base::RepeatingCallback<void(
                                   blink::mojom::PermissionStatus)>& callback));
 
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
 };
 
diff --git a/content/public/test/mock_permission_manager.h b/content/public/test/mock_permission_manager.h
index aeba0e7f..aedb7fa 100644
--- a/content/public/test/mock_permission_manager.h
+++ b/content/public/test/mock_permission_manager.h
@@ -65,7 +65,7 @@
       base::OnceCallback<
           void(const std::vector<blink::mojom::PermissionStatus>&)> callback)
       override;
-  MOCK_METHOD5(SubscribePermissionStatusChange,
+  MOCK_METHOD5(SubscribeToPermissionStatusChange,
                SubscriptionId(
                    blink::PermissionType permission,
                    RenderProcessHost* render_process_host,
@@ -73,7 +73,7 @@
                    const GURL& requesting_origin,
                    base::RepeatingCallback<void(blink::mojom::PermissionStatus)>
                        callback));
-  MOCK_METHOD1(UnsubscribePermissionStatusChange,
+  MOCK_METHOD1(UnsubscribeFromPermissionStatusChange,
                void(SubscriptionId subscription_id));
 };
 
diff --git a/content/shell/browser/shell_permission_manager.cc b/content/shell/browser/shell_permission_manager.cc
index f0a1197..8724e953 100644
--- a/content/shell/browser/shell_permission_manager.cc
+++ b/content/shell/browser/shell_permission_manager.cc
@@ -197,7 +197,7 @@
 }
 
 ShellPermissionManager::SubscriptionId
-ShellPermissionManager::SubscribePermissionStatusChange(
+ShellPermissionManager::SubscribeToPermissionStatusChange(
     PermissionType permission,
     RenderProcessHost* render_process_host,
     RenderFrameHost* render_frame_host,
@@ -206,7 +206,7 @@
   return SubscriptionId();
 }
 
-void ShellPermissionManager::UnsubscribePermissionStatusChange(
+void ShellPermissionManager::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {}
 
 }  // namespace content
diff --git a/content/shell/browser/shell_permission_manager.h b/content/shell/browser/shell_permission_manager.h
index 8ed8407..8ee39ca 100644
--- a/content/shell/browser/shell_permission_manager.h
+++ b/content/shell/browser/shell_permission_manager.h
@@ -59,14 +59,14 @@
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host,
       const url::Origin& overridden_origin) override;
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       RenderProcessHost* render_process_host,
       RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback)
       override;
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
 };
 
diff --git a/content/test/content_test_bundle_data.filelist b/content/test/content_test_bundle_data.filelist
index 4afa6061..51c1b55 100644
--- a/content/test/content_test_bundle_data.filelist
+++ b/content/test/content_test_bundle_data.filelist
@@ -5439,6 +5439,7 @@
 data/accessibility/regression/slot-creation-crash.html
 data/accessibility/regression/title-in-shadow-expected-blink.txt
 data/accessibility/regression/title-in-shadow.html
+data/accessibility/regression/workplace.jpg
 data/accessibility/regression/xml-in-iframe-crash.html
 data/accessibility/relations/ignore-duplicate-relation-ids-expected-auralinux.txt
 data/accessibility/relations/ignore-duplicate-relation-ids-expected-win.txt
diff --git a/content/test/data/accessibility/regression/reused-map-change-map-name-expected-blink.txt b/content/test/data/accessibility/regression/reused-map-change-map-name-expected-blink.txt
index 0903328..142c849 100644
--- a/content/test/data/accessibility/regression/reused-map-change-map-name-expected-blink.txt
+++ b/content/test/data/accessibility/regression/reused-map-change-map-name-expected-blink.txt
@@ -1,15 +1,17 @@
 rootWebArea focusable name='done'
 ++genericContainer ignored
 ++++genericContainer ignored
-++++++image name='star1'
+++++++heading name='Image Maps'
+++++++++staticText name='Image Maps'
+++++++++++inlineTextBox name='Image Maps'
+++++++paragraph
+++++++++staticText name='After map1 is renamed map2, the third picture should have only the coffee mapped'
+++++++++++inlineTextBox name='After map1 is renamed map2, the third picture should have only the coffee mapped'
+++++++image name='Workplace pic 1'
 ++++++staticText name=' '
-++++++++inlineTextBox name=' '
-++++++image name='star2'
+++++++image name='Workplace pic 2'
 ++++++staticText name=' '
-++++++image name='star3'
-++++++++link focusable name='Area'
-++++++++link focusable name='Area2'
+++++++image name='Workplace pic 3'
+++++++++link focusable name='Coffee only (origin map1)'
 ++++++staticText name=' '
-++++++++inlineTextBox name=' '
-++++++image name='star4'
-++++++genericContainer
+++++++image name='Workplace pic 4'
diff --git a/content/test/data/accessibility/regression/reused-map-change-map-name.html b/content/test/data/accessibility/regression/reused-map-change-map-name.html
index b7e4fcb3..f6c118eb 100644
--- a/content/test/data/accessibility/regression/reused-map-change-map-name.html
+++ b/content/test/data/accessibility/regression/reused-map-change-map-name.html
@@ -3,24 +3,26 @@
 @AURALINUX-ALLOW:focus*
 @WAIT-FOR:done
 -->
-<!-- Only the first image that uses a given map gets its area children. -->
-<img id="img1" alt="star1" usemap="#map1"
-     src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E">
-<img id="img2" alt="star2" usemap="#map1"
-     src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E">
-<img id="img3" alt="star3" usemap="#map2"
-     src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E">
-<img id="img4" alt="star4" usemap="#map2"
-     src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E">
+<!DOCTYPE html>
+<html>
+<body>
+
+<h2>Image Maps</h2>
+<p>After map1 is renamed map2, the third picture should have only the coffee mapped</p>
+
+<img src="workplace.jpg" alt="Workplace pic 1" usemap="#map1" width="400" height="379">
+<img src="workplace.jpg" alt="Workplace pic 2" usemap="#map1" width="400" height="379">
+<img src="workplace.jpg" alt="Workplace pic 3" usemap="#map2" width="400" height="379">
+<img src="workplace.jpg" alt="Workplace pic 4" usemap="#map2" width="400" height="379">
+
 <map name="map1">
-  <area shape="rect" coords="0,0,5,5" href="about:blank" alt="Area">
+  <area shape="circle" coords="337,300,44" alt="Coffee only (origin map1)" href="coffee.htm">
 </map>
 <map name="map2">
-  <area shape="rect" coords="0,0,5,5" href="about:blank" alt="Area">
-  <area shape="rect" coords="3,3,8,8" href="about:blank" alt="Area2" autofocus>
+  <area shape="rect" coords="34,44,270,350" alt="Computer" href="computer.htm">
+  <area shape="rect" coords="290,172,333,250" alt="Phone" href="phone.htm">
+  <area shape="circle" coords="337,300,44" alt="Cup of coffee" href="coffee.htm">
 </map>
-<div id="container">
-</div>
 
 <script>
   document.addEventListener('DOMContentLoaded', () => {
@@ -33,3 +35,6 @@
     }, 250);
   });
 </script>
+
+</body>
+</html>
diff --git a/content/test/data/accessibility/regression/workplace.jpg b/content/test/data/accessibility/regression/workplace.jpg
new file mode 100644
index 0000000..4e95fba
--- /dev/null
+++ b/content/test/data/accessibility/regression/workplace.jpg
Binary files differ
diff --git a/content/test/fenced_frame_test_utils.h b/content/test/fenced_frame_test_utils.h
index c092cf6e..59be56e 100644
--- a/content/test/fenced_frame_test_utils.h
+++ b/content/test/fenced_frame_test_utils.h
@@ -49,41 +49,41 @@
 
   absl::optional<GURL> mapped_url() const {
     if (!observed_fenced_frame_properties_ ||
-        !observed_fenced_frame_properties_->mapped_url_) {
+        !observed_fenced_frame_properties_->mapped_url()) {
       return absl::nullopt;
     }
-    return observed_fenced_frame_properties_->mapped_url_
+    return observed_fenced_frame_properties_->mapped_url()
         ->GetValueIgnoringVisibility();
   }
 
   absl::optional<std::vector<std::pair<GURL, FencedFrameConfig>>>
   nested_urn_config_pairs() const {
     if (!observed_fenced_frame_properties_ ||
-        !observed_fenced_frame_properties_->nested_urn_config_pairs_) {
+        !observed_fenced_frame_properties_->nested_urn_config_pairs()) {
       return absl::nullopt;
     }
-    return observed_fenced_frame_properties_->nested_urn_config_pairs_
+    return observed_fenced_frame_properties_->nested_urn_config_pairs()
         ->GetValueIgnoringVisibility();
   }
 
   absl::optional<AdAuctionData> ad_auction_data() const {
     if (!observed_fenced_frame_properties_ ||
-        !observed_fenced_frame_properties_->ad_auction_data_) {
+        !observed_fenced_frame_properties_->ad_auction_data()) {
       return absl::nullopt;
     }
-    return observed_fenced_frame_properties_->ad_auction_data_
+    return observed_fenced_frame_properties_->ad_auction_data()
         ->GetValueIgnoringVisibility();
   }
 
   const base::RepeatingClosure& on_navigate_callback() const {
-    return observed_fenced_frame_properties_->on_navigate_callback_;
+    return observed_fenced_frame_properties_->on_navigate_callback();
   }
 
   FencedFrameReporter* fenced_frame_reporter() {
     if (!observed_fenced_frame_properties_) {
       return nullptr;
     }
-    return observed_fenced_frame_properties_->fenced_frame_reporter_.get();
+    return observed_fenced_frame_properties_->fenced_frame_reporter().get();
   }
 
  private:
diff --git a/content/web_test/browser/web_test_permission_manager.cc b/content/web_test/browser/web_test_permission_manager.cc
index 2e24569..70cbd184 100644
--- a/content/web_test/browser/web_test_permission_manager.cc
+++ b/content/web_test/browser/web_test_permission_manager.cc
@@ -296,7 +296,7 @@
 }
 
 WebTestPermissionManager::SubscriptionId
-WebTestPermissionManager::SubscribePermissionStatusChange(
+WebTestPermissionManager::SubscribeToPermissionStatusChange(
     blink::PermissionType permission,
     RenderProcessHost* render_process_host,
     RenderFrameHost* render_frame_host,
@@ -324,7 +324,7 @@
   return id;
 }
 
-void WebTestPermissionManager::UnsubscribePermissionStatusChange(
+void WebTestPermissionManager::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
diff --git a/content/web_test/browser/web_test_permission_manager.h b/content/web_test/browser/web_test_permission_manager.h
index e9cb9ab..1da5251 100644
--- a/content/web_test/browser/web_test_permission_manager.h
+++ b/content/web_test/browser/web_test_permission_manager.h
@@ -73,14 +73,14 @@
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host,
       const url::Origin& overridden_origin) override;
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       RenderProcessHost* render_process_host,
       RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback)
       override;
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
 
   void SetPermission(
diff --git a/docs/testing/android_instrumentation_tests.md b/docs/testing/android_instrumentation_tests.md
index acce6469..db702f7 100644
--- a/docs/testing/android_instrumentation_tests.md
+++ b/docs/testing/android_instrumentation_tests.md
@@ -1,57 +1,11 @@
 # Android Instrumentation Tests
 
-Instrumentation tests are Java tests based on
-[`android.app.Instrumentation`](https://developer.android.com/reference/android/app/Instrumentation.html).
-They run on a device.
+Instrumentation tests are JUnit 4 tests that run on devices or emulators. They
+can be either unit tests or integration test.
 
 [TOC]
 
-## Writing an instrumentation test
-
-Currently, an instrumentation test is just a JUnit3-style test based on
-[android.test.InstrumentationTestCase](https://developer.android.com/reference/android/test/InstrumentationTestCase.html).
-(This will change starting in [Android N](https://en.wikipedia.org/wiki/Android_Nougat).)
-
-Writing an instrumentation test case can be simple, e.g.
-
-```java
-package org.chromium.sample.test;
-
-import android.test.InstrumentationTestCase;
-
-public class MyInstrumentationTest extends InstrumentationTestCase {
-
-    // Note that, because it's a JUnit3-style test, the test method *must*
-    // start with "test".
-    public void testTheFirst() {
-        bool writingInstrumentationTestsCanBeEasy = true;
-
-        // InstrumentationTestCase inherits the assert* methods through
-        // junit.framework.TestCase.
-        assertTrue(writingInstrumentationTestsCanBeEasy);
-    }
-
-    public void testTheSecond() {
-        bool writingInstrumentationTestsIsAlwaysEasy = false;
-        assertFalse(writingInstrumentationTestsIsAlwaysEasy);
-    }
-}
-```
-
-After writing a test, you can run it by:
-
- - Adding the file to the relevant gn target if the entire file is new.
-Typically, the "relevant gn target" is simply the target containing the
-other files in the same directory.
- - Rebuild.
- - Run the test
-
-## Instrumentation test features
-
-In many cases, Chromium has extended the instrumentation test framework
-classes to implement additional features.
-
-### Tracing
+## Tracing
 
 Enabling tracing during a test run allows all the function calls involved to be
 observed in a visual display (using Chrome's built-in chrome://tracing feature).
@@ -66,13 +20,7 @@
 the device. For a more detailed look, add the (no-argument) `--trace-all` flag.
 This causes every function called on the Python side to be added to the trace.
 
-### Annotations
-
-Instrumentation tests in Chromium use a wide variety of annotations to control
-and manipulate test execution. Some of these are implemented in Chromium, while
-others are pulled in from outside. They include:
-
-#### Test Batching
+## Test Batching Annotations
 
 The [`@Batch("group_name")`](https://chromium.googlesource.com/chromium/src/+/main/base/test/android/javatests/src/org/chromium/base/test/util/Batch.java)
 annotation is used to run all tests with the same batch group name in the same
@@ -84,23 +32,18 @@
 seconds), and that doesn't count the cost of starting an Activity like
 ChromeTabbedActivity.
 
-#### Size annotations
+## Size Annotations
 
-Size annotations are used primarily by the test runner to determine the length
-of time to wait before considering a test hung (i.e., its timeout duration).
+Size annotations are [used by the test runner] to determine the length of time
+to wait before considering a test hung (i.e., its timeout duration).
 
-Several of the annotations are Android APIs from
-[android.test.suitebuilder.annotation](https://developer.android.com/reference/android/test/suitebuilder/annotation/package-summary.html)
-(prior to [Android N](https://en.wikipedia.org/wiki/Android_Nougat)) or
-[androidx.test.filters](https://developer.android.com/reference/androidx/test/filters/package-summary.html)
-(starting in Android N). These are all fairly self-explanatory:
+Annotations from `androidx.test.filters`:
 
  - [`@SmallTest`](https://developer.android.com/reference/androidx/test/filters/SmallTest.html) (timeout: **10 seconds**)
  - [`@MediumTest`](https://developer.android.com/reference/androidx/test/filters/MediumTest.html) (timeout: **30 seconds**)
  - [`@LargeTest`](https://developer.android.com/reference/androidx/test/filters/LargeTest.html) (timeout: **2 minutes**)
 
-A few additional size annotations are provided in
-[//base](https://chromium.googlesource.com/chromium/src/+/main/base):
+Annotations from `//base`:
 
  - [`@EnormousTest`](https://chromium.googlesource.com/chromium/src/+/main/base/test/android/javatests/src/org/chromium/base/test/util/EnormousTest.java)
 (timeout: **5 minutes**) Typically used for tests that require WiFi.
@@ -109,16 +52,14 @@
  - [`@Manual`](https://chromium.googlesource.com/chromium/src/+/main/base/test/android/javatests/src/org/chromium/base/test/util/Manual.java)
 (timeout: **10 hours**) Used for manual tests.
 
-Beware that the timeout durations for these annotations are subject to
-change, though they rarely do. These values are defined
-[here](https://chromium.googlesource.com/chromium/src/+/main/build/android/pylib/local/device/local_device_instrumentation_test_run.py#20).
+[used by the test runner]: https://source.chromium.org/search?q=file:local_device_instrumentation_test_run.py%20symbol:TIMEOUT_ANNOTATIONS&sq=&ss=chromium
 
-#### Annotations that disable tests
+## Annotations that Disable Tests
 
 There are several annotations that control whether or not a test runs.
 Some are conditional, others are not.
 
-##### Unconditional disabling
+### Unconditional Disabling
 
 [**@DisabledTest**](https://chromium.googlesource.com/chromium/src/+/main/base/test/android/javatests/src/org/chromium/base/test/util/DisabledTest.java)
 unconditionally disables a test.
@@ -129,7 +70,7 @@
 )
 ```
 
-##### Conditional disabling
+### Conditional Disabling
 
 There are two primary annotation categories that conditionally disable tests:
 **@DisableIf** and **@Restriction**. The **@DisableIf** annotations are intended
@@ -235,7 +176,7 @@
 )
 ```
 
-#### Annotations that affect how a test is run
+## Command-Line Flags Annotations
 
 Several annotations affect how a test is run in interesting or nontrivial ways.
 
@@ -263,7 +204,7 @@
 )
 ```
 
-#### Feature annotations
+## Feature Annotations
 
 [**@Feature**](https://chromium.googlesource.com/chromium/src/+/main/base/test/android/javatests/src/org/chromium/base/test/util/Feature.java)
 has been used inconsistently in Chromium to group tests across
diff --git a/extensions/docs/testing_api.md b/extensions/docs/testing_api.md
index 13b469e6..2156410 100644
--- a/extensions/docs/testing_api.md
+++ b/extensions/docs/testing_api.md
@@ -305,7 +305,12 @@
 <verify state>
 ```
 
-This, too, will be even more readable with Promise-based APIs.
+For Promise-based calls in MV3 tests this can be simplified even more:
+
+```js
+const tab = await chrome.tabs.create({url: url});
+<verify state>
+```
 
 ### listenOnce() and listenForever()
 `chrome.test.listenOnce()` and `chrome.test.listenForever()` are utility
diff --git a/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc b/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
index 4db99fd..e3a60fe 100644
--- a/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
+++ b/fuchsia_web/webengine/browser/web_engine_permission_delegate.cc
@@ -109,7 +109,7 @@
 }
 
 WebEnginePermissionDelegate::SubscriptionId
-WebEnginePermissionDelegate::SubscribePermissionStatusChange(
+WebEnginePermissionDelegate::SubscribeToPermissionStatusChange(
     blink::PermissionType permission,
     content::RenderProcessHost* render_process_host,
     content::RenderFrameHost* render_frame_host,
@@ -121,7 +121,7 @@
   return SubscriptionId();
 }
 
-void WebEnginePermissionDelegate::UnsubscribePermissionStatusChange(
+void WebEnginePermissionDelegate::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {
   // TODO(crbug.com/1063094): Implement permission status subscription. It's
   // used in blink to emit PermissionStatus.onchange notifications.
diff --git a/fuchsia_web/webengine/browser/web_engine_permission_delegate.h b/fuchsia_web/webengine/browser/web_engine_permission_delegate.h
index 1096b145a3..487ff98 100644
--- a/fuchsia_web/webengine/browser/web_engine_permission_delegate.h
+++ b/fuchsia_web/webengine/browser/web_engine_permission_delegate.h
@@ -59,14 +59,14 @@
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host,
       const url::Origin& overridden_origin) override;
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback)
       override;
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
 };
 
diff --git a/headless/lib/browser/headless_permission_manager.cc b/headless/lib/browser/headless_permission_manager.cc
index 285f5aa..8eddd09 100644
--- a/headless/lib/browser/headless_permission_manager.cc
+++ b/headless/lib/browser/headless_permission_manager.cc
@@ -94,7 +94,7 @@
 }
 
 HeadlessPermissionManager::SubscriptionId
-HeadlessPermissionManager::SubscribePermissionStatusChange(
+HeadlessPermissionManager::SubscribeToPermissionStatusChange(
     blink::PermissionType permission,
     content::RenderProcessHost* render_process_host,
     content::RenderFrameHost* render_frame_host,
@@ -103,7 +103,7 @@
   return SubscriptionId();
 }
 
-void HeadlessPermissionManager::UnsubscribePermissionStatusChange(
+void HeadlessPermissionManager::UnsubscribeFromPermissionStatusChange(
     SubscriptionId subscription_id) {}
 
 }  // namespace headless
diff --git a/headless/lib/browser/headless_permission_manager.h b/headless/lib/browser/headless_permission_manager.h
index 1481295..926b8a5 100644
--- a/headless/lib/browser/headless_permission_manager.h
+++ b/headless/lib/browser/headless_permission_manager.h
@@ -65,14 +65,14 @@
       blink::PermissionType permission,
       content::RenderFrameHost* render_frame_host,
       const url::Origin& overridden_origin) override;
-  SubscriptionId SubscribePermissionStatusChange(
+  SubscriptionId SubscribeToPermissionStatusChange(
       blink::PermissionType permission,
       content::RenderProcessHost* render_process_host,
       content::RenderFrameHost* render_frame_host,
       const GURL& requesting_origin,
       base::RepeatingCallback<void(blink::mojom::PermissionStatus)> callback)
       override;
-  void UnsubscribePermissionStatusChange(
+  void UnsubscribeFromPermissionStatusChange(
       SubscriptionId subscription_id) override;
 
  private:
diff --git a/infra/config/generated/builders/ci/GPU FYI Win x64 Builder/gn-args.json b/infra/config/generated/builders/ci/GPU FYI Win x64 Builder/gn-args.json
index a8bb27ba..da74d64 100644
--- a/infra/config/generated/builders/ci/GPU FYI Win x64 Builder/gn-args.json
+++ b/infra/config/generated/builders/ci/GPU FYI Win x64 Builder/gn-args.json
@@ -7,6 +7,7 @@
     "is_debug": false,
     "proprietary_codecs": true,
     "symbol_level": 1,
+    "target_os": "win",
     "use_remoteexec": true
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/gpu-fyi-try-win10-amd-rel-64/gn-args.json b/infra/config/generated/builders/try/gpu-fyi-try-win10-amd-rel-64/gn-args.json
index a8bb27ba..da74d64 100644
--- a/infra/config/generated/builders/try/gpu-fyi-try-win10-amd-rel-64/gn-args.json
+++ b/infra/config/generated/builders/try/gpu-fyi-try-win10-amd-rel-64/gn-args.json
@@ -7,6 +7,7 @@
     "is_debug": false,
     "proprietary_codecs": true,
     "symbol_level": 1,
+    "target_os": "win",
     "use_remoteexec": true
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/gpu-fyi-try-win10-intel-exp-64/gn-args.json b/infra/config/generated/builders/try/gpu-fyi-try-win10-intel-exp-64/gn-args.json
index a8bb27ba..da74d64 100644
--- a/infra/config/generated/builders/try/gpu-fyi-try-win10-intel-exp-64/gn-args.json
+++ b/infra/config/generated/builders/try/gpu-fyi-try-win10-intel-exp-64/gn-args.json
@@ -7,6 +7,7 @@
     "is_debug": false,
     "proprietary_codecs": true,
     "symbol_level": 1,
+    "target_os": "win",
     "use_remoteexec": true
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/gpu-fyi-try-win10-intel-rel-64/gn-args.json b/infra/config/generated/builders/try/gpu-fyi-try-win10-intel-rel-64/gn-args.json
index a8bb27ba..da74d64 100644
--- a/infra/config/generated/builders/try/gpu-fyi-try-win10-intel-rel-64/gn-args.json
+++ b/infra/config/generated/builders/try/gpu-fyi-try-win10-intel-rel-64/gn-args.json
@@ -7,6 +7,7 @@
     "is_debug": false,
     "proprietary_codecs": true,
     "symbol_level": 1,
+    "target_os": "win",
     "use_remoteexec": true
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-exp-64/gn-args.json b/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-exp-64/gn-args.json
index a8bb27ba..da74d64 100644
--- a/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-exp-64/gn-args.json
+++ b/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-exp-64/gn-args.json
@@ -7,6 +7,7 @@
     "is_debug": false,
     "proprietary_codecs": true,
     "symbol_level": 1,
+    "target_os": "win",
     "use_remoteexec": true
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-rel-64/gn-args.json b/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-rel-64/gn-args.json
index a8bb27ba..da74d64 100644
--- a/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-rel-64/gn-args.json
+++ b/infra/config/generated/builders/try/gpu-fyi-try-win10-nvidia-rel-64/gn-args.json
@@ -7,6 +7,7 @@
     "is_debug": false,
     "proprietary_codecs": true,
     "symbol_level": 1,
+    "target_os": "win",
     "use_remoteexec": true
   }
 }
\ No newline at end of file
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 194fe92..774f7b4 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -67691,12 +67691,11 @@
     builders {
       name: "android-official"
       swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builderless:1"
+      dimensions: "builder:android-official"
       dimensions: "cores:32"
       dimensions: "cpu:x86-64"
       dimensions: "os:Ubuntu-22.04"
       dimensions: "pool:luci.chromium.try"
-      dimensions: "ssd:1"
       exe {
         cipd_package: "infra/chromium/bootstrapper/${platform}"
         cipd_version: "latest"
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 33978b9..f644df0 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -337,16 +337,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 122.0.6180.0',
+    'description': 'Run with ash-chrome version 122.0.6181.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6180.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v122.0.6180.0',
-          'revision': 'version:122.0.6180.0',
+          'location': 'lacros_version_skew_tests_v122.0.6181.0',
+          'revision': 'version:122.0.6181.0',
         },
       ],
     },
diff --git a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
index 1460f67..266a6ff 100644
--- a/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.gpu.fyi.star
@@ -1566,6 +1566,9 @@
             "try_builder",
             "reclient",
             "disable_nacl",
+            # Remove this once the decision to use cross-compilation or not in
+            # crbug.com/1510985 is made.
+            "win_cross",
         ],
     ),
     console_view_entry = consoles.console_view_entry(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.star b/infra/config/subprojects/chromium/try/tryserver.chromium.star
index 380632bf..7c850ef 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.star
@@ -43,7 +43,7 @@
             "dcheck_always_on",
         ],
     ),
-    ssd = True,
+    builderless = False,
     contact_team_email = "clank-engprod@google.com",
 )
 
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index 6e30c798..b8fee8b 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,16 +1,16 @@
 {
   "LACROS_VERSION_SKEW_CANARY": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6180.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 122.0.6180.0",
+    "description": "Run with ash-chrome version 122.0.6181.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_v122.0.6180.0",
-          "revision": "version:122.0.6180.0"
+          "location": "lacros_version_skew_tests_v122.0.6181.0",
+          "revision": "version:122.0.6181.0"
         }
       ]
     }
diff --git a/ios/chrome/browser/push_notification/model/BUILD.gn b/ios/chrome/browser/push_notification/model/BUILD.gn
index ee781e2..2db0cce 100644
--- a/ios/chrome/browser/push_notification/model/BUILD.gn
+++ b/ios/chrome/browser/push_notification/model/BUILD.gn
@@ -56,6 +56,7 @@
     "push_notification_util.mm",
   ]
   deps = [
+    ":constants",
     ":push_notification_client",
     "//base",
     "//components/optimization_guide/core:features",
@@ -84,6 +85,13 @@
   sources = [ "notifications_alert_presenter.h" ]
 }
 
+source_set("constants") {
+  sources = [
+    "constants.h",
+    "constants.mm",
+  ]
+}
+
 source_set("unit_tests") {
   testonly = true
   sources = [
diff --git a/ios/chrome/browser/push_notification/model/constants.h b/ios/chrome/browser/push_notification/model/constants.h
new file mode 100644
index 0000000..80669c9
--- /dev/null
+++ b/ios/chrome/browser/push_notification/model/constants.h
@@ -0,0 +1,17 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IOS_CHROME_BROWSER_PUSH_NOTIFICATION_MODEL_CONSTANTS_H_
+#define IOS_CHROME_BROWSER_PUSH_NOTIFICATION_MODEL_CONSTANTS_H_
+
+#import <string>
+
+// Key of commerce notification used in pref
+// kFeaturePushNotificationPermissions.
+extern std::string const kCommerceNotificationKey;
+
+// Key of content notification used in pref kFeaturePushNotificationPermissions.
+extern std::string const kContentNotificationKey;
+
+#endif  // IOS_CHROME_BROWSER_PUSH_NOTIFICATION_MODEL_CONSTANTS_H_
diff --git a/ios/chrome/browser/push_notification/model/constants.mm b/ios/chrome/browser/push_notification/model/constants.mm
new file mode 100644
index 0000000..21d5f3f
--- /dev/null
+++ b/ios/chrome/browser/push_notification/model/constants.mm
@@ -0,0 +1,8 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/push_notification/model/constants.h"
+
+std::string const kCommerceNotificationKey = "PRICE_DROP";
+std::string const kContentNotificationKey = "CONTENT";
diff --git a/ios/chrome/browser/push_notification/model/push_notification_client_manager.mm b/ios/chrome/browser/push_notification/model/push_notification_client_manager.mm
index 8457890..86caabf 100644
--- a/ios/chrome/browser/push_notification/model/push_notification_client_manager.mm
+++ b/ios/chrome/browser/push_notification/model/push_notification_client_manager.mm
@@ -10,6 +10,7 @@
 #import "components/optimization_guide/core/optimization_guide_features.h"
 #import "ios/chrome/browser/commerce/model/push_notification/commerce_push_notification_client.h"
 #import "ios/chrome/browser/commerce/model/push_notification/push_notification_feature.h"
+#import "ios/chrome/browser/push_notification/model/constants.h"
 #import "ios/chrome/browser/push_notification/model/push_notification_util.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 
@@ -101,10 +102,10 @@
     PushNotificationClientId client_id) {
   switch (client_id) {
     case PushNotificationClientId::kCommerce: {
-      return "PRICE_DROP";
+      return kCommerceNotificationKey;
     }
     case PushNotificationClientId::kContent: {
-      return "CONTENT";
+      return kContentNotificationKey;
     }
   }
 }
diff --git a/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm b/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
index afa986a..02336bc0 100644
--- a/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/signin/signin_coordinator.mm
@@ -265,8 +265,7 @@
 - (void)dealloc {
   // -[SigninCoordinator runCompletionCallbackWithSigninResult:completionInfo:]
   // has to be called by the subclass before the coordinator is deallocated.
-  DUMP_WILL_BE_CHECK(!self.signinCompletion)
-      << base::SysNSStringToUTF8([self description]);
+  DCHECK(!self.signinCompletion) << base::SysNSStringToUTF8([self description]);
 }
 
 - (void)interruptWithAction:(SigninCoordinatorInterrupt)action
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index 2f22df6..bfbec5d 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -167,7 +167,7 @@
   if ([self isRunningTest:@selector(testLargeFakeboxFocus)]) {
     config.features_enabled.push_back(kIOSLargeFakebox);
   }
-  if ([self isRunningTest:@selector(testMinimumHeight)]) {
+  if ([self isRunningTest:@selector(DISABLED_testMinimumHeight)]) {
     config.features_enabled.push_back(kMagicStack);
   }
   return config;
@@ -999,7 +999,8 @@
       performAction:grey_tap()];
 }
 
-- (void)testMinimumHeight {
+// TODO(crbug.com/1510926): This test is flaky.
+- (void)DISABLED_testMinimumHeight {
   [ChromeEarlGreyAppInterface
       setBoolValue:NO
        forUserPref:base::SysUTF8ToNSString(prefs::kArticlesForYouEnabled)];
diff --git a/ios/chrome/browser/ui/price_notifications/price_notifications_price_tracking_mediator.mm b/ios/chrome/browser/ui/price_notifications/price_notifications_price_tracking_mediator.mm
index fb2dc585..4986138 100644
--- a/ios/chrome/browser/ui/price_notifications/price_notifications_price_tracking_mediator.mm
+++ b/ios/chrome/browser/ui/price_notifications/price_notifications_price_tracking_mediator.mm
@@ -229,8 +229,9 @@
       [self createPriceNotificationTableViewItem:NO
                                  fromProductInfo:productInfo
                                            atURL:URL];
-  self.shoppingService->IsClusterIdTrackedByUser(
-      productInfo->product_cluster_id.value(),
+  self.shoppingService->IsSubscribed(
+      commerce::BuildUserSubscriptionForClusterId(
+          productInfo->product_cluster_id.value()),
       base::BindOnce(^(bool isTracked) {
         [weakSelf.consumer setTrackableItem:item currentlyTracking:isTracked];
       }));
diff --git a/ios/chrome/browser/ui/settings/notifications/BUILD.gn b/ios/chrome/browser/ui/settings/notifications/BUILD.gn
index 02e3340..6790ea5 100644
--- a/ios/chrome/browser/ui/settings/notifications/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/notifications/BUILD.gn
@@ -75,6 +75,7 @@
     "//components/commerce/core:pref_names",
     "//components/prefs",
     "//components/prefs/ios:ios",
+    "//ios/chrome/browser/push_notification/model:constants",
     "//ios/chrome/browser/push_notification/model:push_notification_client",
     "//ios/chrome/browser/push_notification/model:push_notification_service",
     "//ios/chrome/browser/shared/model/application_context",
@@ -83,6 +84,24 @@
   ]
 }
 
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "notifications_settings_observer_unittest.mm" ]
+  deps = [
+    ":notifications_ui",
+    ":utils",
+    "//base/test:test_support",
+    "//components/commerce/core:pref_names",
+    "//components/prefs",
+    "//components/prefs:test_support",
+    "//ios/chrome/browser/push_notification/model:constants",
+    "//ios/chrome/browser/push_notification/model:push_notification_client",
+    "//ios/chrome/browser/shared/model/prefs:pref_names",
+    "//third_party/ocmock",
+  ]
+  frameworks = [ "UIKit.framework" ]
+}
+
 source_set("eg2_tests") {
   configs += [ "//build/config/ios:xctest_config" ]
   testonly = true
diff --git a/ios/chrome/browser/ui/settings/notifications/notifications_coordinator.mm b/ios/chrome/browser/ui/settings/notifications/notifications_coordinator.mm
index f727542..8b68d88 100644
--- a/ios/chrome/browser/ui/settings/notifications/notifications_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/notifications/notifications_coordinator.mm
@@ -81,6 +81,11 @@
                                            animated:YES];
 }
 
+- (void)stop {
+  _notificationsObserver.delegate = nil;
+  _notificationsObserver = nil;
+}
+
 #pragma mark - NotificationsAlertPresenter
 
 - (void)presentPushNotificationPermissionAlert {
diff --git a/ios/chrome/browser/ui/settings/notifications/notifications_mediator.mm b/ios/chrome/browser/ui/settings/notifications/notifications_mediator.mm
index 602db0a..5d0037a 100644
--- a/ios/chrome/browser/ui/settings/notifications/notifications_mediator.mm
+++ b/ios/chrome/browser/ui/settings/notifications/notifications_mediator.mm
@@ -254,14 +254,14 @@
       [self updateDetailTextForItem:_priceTrackingItem withClientID:clientID];
       break;
     }
-    // TODO(b/307593022): Move Notification popup logic here when the pref is
-    // ready.
     case PushNotificationClientId::kContent: {
       break;
     }
   }
 }
 
+#pragma mark - private
+
 // Updates the current user's permission preference for the given `client_id`.
 - (void)setPreferenceFor:(PushNotificationClientId)clientID to:(BOOL)enabled {
   PushNotificationService* service =
diff --git a/ios/chrome/browser/ui/settings/notifications/notifications_settings_observer.mm b/ios/chrome/browser/ui/settings/notifications/notifications_settings_observer.mm
index 19821af..9cbcb3a 100644
--- a/ios/chrome/browser/ui/settings/notifications/notifications_settings_observer.mm
+++ b/ios/chrome/browser/ui/settings/notifications/notifications_settings_observer.mm
@@ -9,6 +9,7 @@
 #import "components/commerce/core/pref_names.h"
 #import "components/prefs/pref_change_registrar.h"
 #import "components/prefs/pref_service.h"
+#import "ios/chrome/browser/push_notification/model/constants.h"
 #import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
 
@@ -18,6 +19,15 @@
 
   // Registrar for pref changes notifications.
   PrefChangeRegistrar _prefChangeRegistrar;
+
+  // Pref Service.
+  raw_ptr<PrefService> _prefService;
+
+  // YES if price tracing notification is enabled.
+  BOOL _priceTrackingNotificationEnabled;
+
+  // YES if content notification is enabled.
+  BOOL _contentNotificationEnabled;
 }
 
 - (instancetype)initWithPrefService:(PrefService*)prefService {
@@ -31,6 +41,16 @@
         commerce::kPriceEmailNotificationsEnabled, &_prefChangeRegistrar);
     _prefObserverBridge->ObserveChangesForPreference(
         prefs::kFeaturePushNotificationPermissions, &_prefChangeRegistrar);
+
+    _prefService = prefService;
+    _priceTrackingNotificationEnabled =
+        _prefService->GetDict(prefs::kFeaturePushNotificationPermissions)
+            .FindBool(kCommerceNotificationKey)
+            .value_or(false);
+    _contentNotificationEnabled =
+        _prefService->GetDict(prefs::kFeaturePushNotificationPermissions)
+            .FindBool(kContentNotificationKey)
+            .value_or(false);
   }
 
   return self;
@@ -38,16 +58,38 @@
 
 #pragma mark - PrefObserverDelegate
 
-// TODO(b/304830588) Decouple kFeaturePushNotificationPermissions from Price
-// Tracking to make it universally usable. Add two separate prefs for Content
-// and Price Tracking, and keep the original one which is updated if at least
-// one of the other prefs is True, and becomes false when both are False.
 - (void)onPreferenceChanged:(const std::string&)preferenceName {
-  if (preferenceName == commerce::kPriceEmailNotificationsEnabled ||
-      preferenceName == prefs::kFeaturePushNotificationPermissions) {
+  if (preferenceName == commerce::kPriceEmailNotificationsEnabled) {
     [self.delegate notificationsSettingsDidChangeForClient:
                        PushNotificationClientId::kCommerce];
+  } else if (preferenceName == prefs::kFeaturePushNotificationPermissions) {
+    if (_priceTrackingNotificationEnabled !=
+        [self isPriceTrackingNotificationEnabled]) {
+      _priceTrackingNotificationEnabled =
+          [self isPriceTrackingNotificationEnabled];
+      [self.delegate notificationsSettingsDidChangeForClient:
+                         PushNotificationClientId::kCommerce];
+    } else if (_contentNotificationEnabled !=
+               [self isContentNotificationEnabled]) {
+      _contentNotificationEnabled = [self isContentNotificationEnabled];
+      [self.delegate notificationsSettingsDidChangeForClient:
+                         PushNotificationClientId::kContent];
+    }
   }
 }
 
+#pragma mark - private
+
+- (BOOL)isPriceTrackingNotificationEnabled {
+  return _prefService->GetDict(prefs::kFeaturePushNotificationPermissions)
+      .FindBool(kCommerceNotificationKey)
+      .value_or(false);
+}
+
+- (BOOL)isContentNotificationEnabled {
+  return _prefService->GetDict(prefs::kFeaturePushNotificationPermissions)
+      .FindBool(kContentNotificationKey)
+      .value_or(false);
+}
+
 @end
diff --git a/ios/chrome/browser/ui/settings/notifications/notifications_settings_observer_unittest.mm b/ios/chrome/browser/ui/settings/notifications/notifications_settings_observer_unittest.mm
new file mode 100644
index 0000000..95467ac
--- /dev/null
+++ b/ios/chrome/browser/ui/settings/notifications/notifications_settings_observer_unittest.mm
@@ -0,0 +1,74 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "ios/chrome/browser/ui/settings/notifications/notifications_settings_observer.h"
+
+#import "base/test/task_environment.h"
+#import "components/commerce/core/pref_names.h"
+#import "components/prefs/pref_registry_simple.h"
+#import "components/prefs/scoped_user_pref_update.h"
+#import "components/prefs/testing_pref_service.h"
+#import "ios/chrome/browser/push_notification/model/constants.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
+#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
+#import "ios/chrome/browser/ui/settings/notifications/notifications_settings_observer.h"
+#import "testing/platform_test.h"
+#import "third_party/ocmock/OCMock/OCMock.h"
+#import "third_party/ocmock/gtest_support.h"
+
+// Tests NotificationsSettingsObserver functionality.
+class NotificationsSettingsObserverTest : public PlatformTest {
+ protected:
+  void SetUp() override {
+    PlatformTest::SetUp();
+
+    pref_service_ = std::make_unique<TestingPrefServiceSimple>();
+
+    pref_service_->registry()->RegisterDictionaryPref(
+        prefs::kFeaturePushNotificationPermissions);
+    pref_service_->registry()->RegisterBooleanPref(
+        commerce::kPriceEmailNotificationsEnabled, false);
+
+    observer_ = [[NotificationsSettingsObserver alloc]
+        initWithPrefService:pref_service_.get()];
+    observer_.delegate = mock_delegate_;
+  }
+
+  void TurnOnNotificationForKey(const std::string key) {
+    ScopedDictPrefUpdate update(pref_service_.get(),
+                                prefs::kFeaturePushNotificationPermissions);
+    update->Set(key, true);
+  }
+
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
+  NotificationsSettingsObserver* observer_;
+  id<NotificationsSettingsObserverDelegate> mock_delegate_ =
+      OCMProtocolMock(@protocol(NotificationsSettingsObserverDelegate));
+};
+
+// Tests the observer delegate is notified with correct client id when pref
+// kPriceEmailNotificationsEnabled changed.
+TEST_F(NotificationsSettingsObserverTest,
+       PrefkPriceEmailNotificationsEnabledChanged) {
+  OCMExpect([mock_delegate_ notificationsSettingsDidChangeForClient:
+                                PushNotificationClientId::kCommerce]);
+  pref_service_->SetBoolean(commerce::kPriceEmailNotificationsEnabled, true);
+  EXPECT_OCMOCK_VERIFY(mock_delegate_);
+}
+
+// Tests the observer delegate is notified with correct client id when
+// kFeaturePushNotificationPermissions pref changed.
+TEST_F(NotificationsSettingsObserverTest,
+       PrefkFeaturePushNotificationPermissionsChanged) {
+  OCMExpect([mock_delegate_ notificationsSettingsDidChangeForClient:
+                                PushNotificationClientId::kCommerce]);
+  TurnOnNotificationForKey(kCommerceNotificationKey);
+  EXPECT_OCMOCK_VERIFY(mock_delegate_);
+
+  OCMExpect([mock_delegate_ notificationsSettingsDidChangeForClient:
+                                PushNotificationClientId::kContent]);
+  TurnOnNotificationForKey(kContentNotificationKey);
+  EXPECT_OCMOCK_VERIFY(mock_delegate_);
+}
diff --git a/ios/chrome/test/BUILD.gn b/ios/chrome/test/BUILD.gn
index 3209cab..dcbe10b 100644
--- a/ios/chrome/test/BUILD.gn
+++ b/ios/chrome/test/BUILD.gn
@@ -407,6 +407,7 @@
     "//ios/chrome/browser/ui/settings/downloads/save_to_photos:unit_tests",
     "//ios/chrome/browser/ui/settings/google_services:unit_tests",
     "//ios/chrome/browser/ui/settings/language:unit_tests",
+    "//ios/chrome/browser/ui/settings/notifications:unit_tests",
     "//ios/chrome/browser/ui/settings/password:unit_tests",
     "//ios/chrome/browser/ui/settings/password/password_checkup:unit_tests",
     "//ios/chrome/browser/ui/settings/password/password_details:unit_tests",
diff --git a/ios/web/shell/BUILD.gn b/ios/web/shell/BUILD.gn
index 0463438a..0cce78b0 100644
--- a/ios/web/shell/BUILD.gn
+++ b/ios/web/shell/BUILD.gn
@@ -9,6 +9,7 @@
 
 ios_app_bundle("ios_web_shell") {
   info_plist = "Info.plist"
+  bundle_identifier = shared_bundle_id_for_test_apps
 
   deps = [ ":shell" ]
 
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index f9e9d363..514edfef 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -145,11 +145,6 @@
 // automated testing.
 const char kMuteAudio[] = "mute-audio";
 
-// Allows clients to override the threshold for when the media renderer will
-// declare the underflow state for the video stream when audio is present.
-// TODO(dalecurtis): Remove once experiments for http://crbug.com/470940 finish.
-const char kVideoUnderflowThresholdMs[] = "video-underflow-threshold-ms";
-
 // Disables the new rendering algorithm for webrtc, which is designed to improve
 // the rendering smoothness.
 const char kDisableRTCSmoothnessAlgorithm[] =
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index 04f5528f..cbb5753 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -71,8 +71,6 @@
 
 MEDIA_EXPORT extern const char kMuteAudio[];
 
-MEDIA_EXPORT extern const char kVideoUnderflowThresholdMs[];
-
 MEDIA_EXPORT extern const char kDisableRTCSmoothnessAlgorithm[];
 
 MEDIA_EXPORT extern const char kForceVideoOverlays[];
diff --git a/net/cert/ct_policy_enforcer.cc b/net/cert/ct_policy_enforcer.cc
index 717d022a..5f8ddf1 100644
--- a/net/cert/ct_policy_enforcer.cc
+++ b/net/cert/ct_policy_enforcer.cc
@@ -11,7 +11,7 @@
 ct::CTPolicyCompliance DefaultCTPolicyEnforcer::CheckCompliance(
     X509Certificate* cert,
     const ct::SCTList& verified_scts,
-    const NetLogWithSource& net_log) {
+    const NetLogWithSource& net_log) const {
   return ct::CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY;
 }
 
diff --git a/net/cert/ct_policy_enforcer.h b/net/cert/ct_policy_enforcer.h
index 47be4b7..9721e61b 100644
--- a/net/cert/ct_policy_enforcer.h
+++ b/net/cert/ct_policy_enforcer.h
@@ -38,7 +38,7 @@
   virtual ct::CTPolicyCompliance CheckCompliance(
       X509Certificate* cert,
       const ct::SCTList& verified_scts,
-      const NetLogWithSource& net_log) = 0;
+      const NetLogWithSource& net_log) const = 0;
 };
 
 // A default implementation of Certificate Transparency policies that is
@@ -53,7 +53,7 @@
   ct::CTPolicyCompliance CheckCompliance(
       X509Certificate* cert,
       const ct::SCTList& verified_scts,
-      const NetLogWithSource& net_log) override;
+      const NetLogWithSource& net_log) const override;
 };
 
 }  // namespace net
diff --git a/net/quic/crypto/proof_verifier_chromium_test.cc b/net/quic/crypto/proof_verifier_chromium_test.cc
index 1ef3097..2f63604 100644
--- a/net/quic/crypto/proof_verifier_chromium_test.cc
+++ b/net/quic/crypto/proof_verifier_chromium_test.cc
@@ -70,10 +70,10 @@
 // A mock CTPolicyEnforcer that returns a custom verification result.
 class MockCTPolicyEnforcer : public CTPolicyEnforcer {
  public:
-  MOCK_METHOD3(CheckCompliance,
-               ct::CTPolicyCompliance(X509Certificate* cert,
-                                      const ct::SCTList&,
-                                      const NetLogWithSource&));
+  MOCK_CONST_METHOD3(CheckCompliance,
+                     ct::CTPolicyCompliance(X509Certificate* cert,
+                                            const ct::SCTList&,
+                                            const NetLogWithSource&));
 };
 
 class MockRequireCTDelegate : public TransportSecurityState::RequireCTDelegate {
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index 6cde27e2..9f11df6 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -585,10 +585,10 @@
 // A mock CTPolicyEnforcer that returns a custom verification result.
 class MockCTPolicyEnforcer : public CTPolicyEnforcer {
  public:
-  MOCK_METHOD3(CheckCompliance,
-               ct::CTPolicyCompliance(X509Certificate* cert,
-                                      const ct::SCTList&,
-                                      const NetLogWithSource&));
+  MOCK_CONST_METHOD3(CheckCompliance,
+                     ct::CTPolicyCompliance(X509Certificate* cert,
+                                            const ct::SCTList&,
+                                            const NetLogWithSource&));
 };
 
 class MockRequireCTDelegate : public TransportSecurityState::RequireCTDelegate {
diff --git a/net/socket/ssl_server_socket_unittest.cc b/net/socket/ssl_server_socket_unittest.cc
index 7dc2434..265e8cfe 100644
--- a/net/socket/ssl_server_socket_unittest.cc
+++ b/net/socket/ssl_server_socket_unittest.cc
@@ -115,7 +115,7 @@
   ct::CTPolicyCompliance CheckCompliance(
       X509Certificate* cert,
       const ct::SCTList& verified_scts,
-      const NetLogWithSource& net_log) override {
+      const NetLogWithSource& net_log) const override {
     return ct::CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS;
   }
 };
diff --git a/net/url_request/url_request_quic_unittest.cc b/net/url_request/url_request_quic_unittest.cc
index c823354..b6ef5e9 100644
--- a/net/url_request/url_request_quic_unittest.cc
+++ b/net/url_request/url_request_quic_unittest.cc
@@ -66,7 +66,7 @@
   ct::CTPolicyCompliance CheckCompliance(
       X509Certificate* cert,
       const ct::SCTList& verified_scts,
-      const NetLogWithSource& net_log) override {
+      const NetLogWithSource& net_log) const override {
     return ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS;
   }
 };
diff --git a/services/tracing/perfetto/privacy_filtered_fields-inl.h b/services/tracing/perfetto/privacy_filtered_fields-inl.h
index 68b1c10..a2b403181 100644
--- a/services/tracing/perfetto/privacy_filtered_fields-inl.h
+++ b/services/tracing/perfetto/privacy_filtered_fields-inl.h
@@ -460,6 +460,19 @@
 constexpr MessageInfo kScrollPredictorMetrics = {
     kScrollPredictorMetricsIndices, kScrollPredictorMetricsComplexMessages};
 
+// Proto Message: PageLoad
+constexpr int kPageLoadIndices[] = {1, -1};
+constexpr MessageInfo kPageLoad = {kPageLoadIndices, nullptr};
+
+// Proto Message: StartUp
+constexpr int kStartUpIndices[] = {1, 3, -1};
+constexpr MessageInfo kStartUp = {kStartUpIndices, nullptr};
+
+// Proto Message: WebContentInteraction
+constexpr int kWebContentInteractionIndices[] = {1, 2, -1};
+constexpr MessageInfo kWebContentInteraction = {kWebContentInteractionIndices,
+                                                nullptr};
+
 // Proto Message: TrackEvent
 constexpr int kTrackEventIndices[] = {
     1,    2,    3,    5,    6,    9,    10,   11,   12,   16,   17,   22,
@@ -468,7 +481,7 @@
     1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014,
     1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1028,
     1031, 1032, 1033, 1034, 1036, 1038, 1039, 1040, 1041, 1042, 1046, 1047,
-    1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, -1};
+    1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, -1};
 constexpr MessageInfo const* kTrackEventComplexMessages[] = {
     nullptr,
     nullptr,
@@ -549,7 +562,10 @@
     &kChromeGraphicsPipeline,
     &kCrasUnified,
     &kLibunwindstackUnwinder,
-    &kScrollPredictorMetrics};
+    &kScrollPredictorMetrics,
+    &kPageLoad,
+    &kStartUp,
+    &kWebContentInteraction};
 constexpr MessageInfo kTrackEvent = {kTrackEventIndices,
                                      kTrackEventComplexMessages};
 
@@ -632,9 +648,9 @@
 constexpr MessageInfo kNamedRule = {kNamedRuleIndices, nullptr};
 
 // Proto Message: TriggerRule
-constexpr int kTriggerRuleIndices[] = {1, 2, 3, -1};
+constexpr int kTriggerRuleIndices[] = {1, 2, 3, 4, -1};
 constexpr MessageInfo const* kTriggerRuleComplexMessages[] = {
-    nullptr, &kHistogramRule, &kNamedRule};
+    nullptr, &kHistogramRule, &kNamedRule, nullptr};
 constexpr MessageInfo kTriggerRule = {kTriggerRuleIndices,
                                       kTriggerRuleComplexMessages};
 
@@ -724,9 +740,14 @@
 constexpr MessageInfo kTrackDescriptor = {kTrackDescriptorIndices,
                                           kTrackDescriptorComplexMessages};
 
+// Proto Message: TraceUuid
+constexpr int kTraceUuidIndices[] = {1, 2, -1};
+constexpr MessageInfo kTraceUuid = {kTraceUuidIndices, nullptr};
+
 // Proto Message: TracePacket
-constexpr int kTracePacketIndices[] = {6,  8,  10, 11, 12, 13, 35, 36, 41, 42,
-                                       43, 44, 51, 54, 56, 58, 59, 60, 87, -1};
+constexpr int kTracePacketIndices[] = {6,  8,  10, 11, 12, 13, 35,
+                                       36, 41, 42, 43, 44, 51, 54,
+                                       56, 58, 59, 60, 87, 89, -1};
 constexpr MessageInfo const* kTracePacketComplexMessages[] = {
     &kClockSnapshot,
     nullptr,
@@ -746,7 +767,8 @@
     nullptr,
     &kTracePacketDefaults,
     &kTrackDescriptor,
-    nullptr};
+    nullptr,
+    &kTraceUuid};
 constexpr MessageInfo kTracePacket = {kTracePacketIndices,
                                       kTracePacketComplexMessages};
 
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 9839362..7d6900c 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -596,18 +596,14 @@
   }
 
   if (skia_support_pdf) {
+    # Blink includes Skia's JPEG encoder and decoder which pdf uses.
+    assert(use_blink)
     deps += [
       "//third_party:freetype_harfbuzz",
       "//third_party/zlib",
     ]
     public += skia_pdf_public
     sources += skia_pdf_sources
-    if (use_blink) {
-      # Blink includes Skia's JPEG decoder
-      sources += skia_pdf_jpeginfo_lib
-    } else {
-      sources += skia_pdf_jpeginfo_none
-    }
   } else {
     sources += [ "//third_party/skia/src/pdf/SkDocument_PDF_None.cpp" ]
   }
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index cd4c6f9..8e2eef0 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -6193,9 +6193,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_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6205,8 +6205,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
@@ -6343,9 +6343,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -6355,8 +6355,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index a47178cf..461ad43 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -20593,9 +20593,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_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20605,8 +20605,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
@@ -20743,9 +20743,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20755,8 +20755,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index dadbf733..d233607 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -43432,9 +43432,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_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43443,8 +43443,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
@@ -43582,9 +43582,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43593,8 +43593,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
@@ -44906,9 +44906,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_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44917,8 +44917,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
@@ -45056,9 +45056,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -45067,8 +45067,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
@@ -45766,9 +45766,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_v122.0.6180.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -45777,8 +45777,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index a645e4fd..4e08cee 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -16313,12 +16313,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_v122.0.6180.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16328,8 +16328,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
@@ -16483,12 +16483,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6180.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 122.0.6180.0",
+        "description": "Run with ash-chrome version 122.0.6181.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16498,8 +16498,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v122.0.6180.0",
-              "revision": "version:122.0.6180.0"
+              "location": "lacros_version_skew_tests_v122.0.6181.0",
+              "revision": "version:122.0.6181.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/filters/android.emulator_11.content_shell_test_apk.filter b/testing/buildbot/filters/android.emulator_11.content_shell_test_apk.filter
index f42e467..d4fe188 100644
--- a/testing/buildbot/filters/android.emulator_11.content_shell_test_apk.filter
+++ b/testing/buildbot/filters/android.emulator_11.content_shell_test_apk.filter
@@ -5,9 +5,6 @@
 -org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputMode
 -org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputModeWithPhysicalKeyboard
 
-# crbug.com/1220151
--org.chromium.base.task.AsyncTaskTest.testChromeThreadPoolExecutorOsAsyncTask
-
 # crbug.com/1220155
 -org.chromium.content.browser.ContentTextSelectionTest.testSelectionClearedAfterLossOfFocus
 -org.chromium.content.browser.ContentTextSelectionTest.testSelectionPreservedAfterLossOfFocusIfRequested
diff --git a/testing/buildbot/filters/android.emulator_12.content_shell_test_apk.filter b/testing/buildbot/filters/android.emulator_12.content_shell_test_apk.filter
index 9e73461..ab91c383 100644
--- a/testing/buildbot/filters/android.emulator_12.content_shell_test_apk.filter
+++ b/testing/buildbot/filters/android.emulator_12.content_shell_test_apk.filter
@@ -6,9 +6,6 @@
 -org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputMode
 -org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputModeWithPhysicalKeyboard
 
-# https://crbug.com/1220151
--org.chromium.base.task.AsyncTaskTest.testChromeThreadPoolExecutorOsAsyncTask
-
 # https://crbug.com/1233309
 -org.chromium.content.browser.GestureDetectorResetTest.testSeparateClicksAreRegisteredOnReload
 
diff --git a/testing/buildbot/filters/android.emulator_12l.content_shell_test_apk.filter b/testing/buildbot/filters/android.emulator_12l.content_shell_test_apk.filter
index 11b66342..21cb3c5 100644
--- a/testing/buildbot/filters/android.emulator_12l.content_shell_test_apk.filter
+++ b/testing/buildbot/filters/android.emulator_12l.content_shell_test_apk.filter
@@ -6,9 +6,6 @@
 -org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputMode
 -org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputModeWithPhysicalKeyboard
 
-# https://crbug.com/1220151
--org.chromium.base.task.AsyncTaskTest.testChromeThreadPoolExecutorOsAsyncTask
-
 # https://crbug.com/1233309
 -org.chromium.content.browser.GestureDetectorResetTest.testSeparateClicksAreRegisteredOnReload
 
diff --git a/testing/buildbot/filters/android.emulator_13.content_shell_test_apk.filter b/testing/buildbot/filters/android.emulator_13.content_shell_test_apk.filter
index 6c4983a..bb7e871 100644
--- a/testing/buildbot/filters/android.emulator_13.content_shell_test_apk.filter
+++ b/testing/buildbot/filters/android.emulator_13.content_shell_test_apk.filter
@@ -6,9 +6,6 @@
 -org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputMode
 -org.chromium.content.browser.input.ImeInputModeTest.testShowAndHideInputModeWithPhysicalKeyboard
 
-# https://crbug.com/1220151
--org.chromium.base.task.AsyncTaskTest.testChromeThreadPoolExecutorOsAsyncTask
-
 # https://crbug.com/1233309
 -org.chromium.content.browser.GestureDetectorResetTest.testSeparateClicksAreRegisteredOnReload
 
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 33978b9..f644df0 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -337,16 +337,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 122.0.6180.0',
+    'description': 'Run with ash-chrome version 122.0.6181.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6180.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v122.0.6181.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v122.0.6180.0',
-          'revision': 'version:122.0.6180.0',
+          'location': 'lacros_version_skew_tests_v122.0.6181.0',
+          'revision': 'version:122.0.6181.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index fdbbdbe..3d52c7bf 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -2921,21 +2921,6 @@
             ]
         }
     ],
-    "CCTRealTimeEngagementSignalsAlternativeImpl": [
-        {
-            "platforms": [
-                "android"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "CCTRealTimeEngagementSignalsAlternativeImpl"
-                    ]
-                }
-            ]
-        }
-    ],
     "CPSS": [
         {
             "platforms": [
@@ -5601,6 +5586,37 @@
             ]
         }
     ],
+    "DesktopLinkCapturingPWAExperiment": [
+        {
+            "platforms": [
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_DefaultOn",
+                    "params": {
+                        "link_capturing_guardrail_storage_duration": "60",
+                        "on_by_default": "true"
+                    },
+                    "enable_features": [
+                        "DesktopPWAsLinkCapturing"
+                    ]
+                },
+                {
+                    "name": "Enabled_DefaultOff",
+                    "params": {
+                        "link_capturing_guardrail_storage_duration": "60",
+                        "on_by_default": "false"
+                    },
+                    "enable_features": [
+                        "DesktopPWAsLinkCapturing"
+                    ]
+                }
+            ]
+        }
+    ],
     "DesktopNtpDriveCache": [
         {
             "platforms": [
@@ -13783,6 +13799,25 @@
             ]
         }
     ],
+    "PriceTrackingIconColors": [
+        {
+            "platforms": [
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "PriceTrackingIconColors"
+                    ]
+                }
+            ]
+        }
+    ],
     "PriceTrackingPageActionIconLabelFeatureIPH": [
         {
             "platforms": [
@@ -15625,9 +15660,7 @@
                     "name": "Enabled",
                     "enable_features": [
                         "ShoppingCollection",
-                        "ShoppingListTrackByDefault",
-                        "ShoppingListWAARestrictionRemoval",
-                        "SimplifiedBookmarkSaveFlow"
+                        "ShoppingListTrackByDefault"
                     ]
                 }
             ]
diff --git a/third_party/angle b/third_party/angle
index 398cfb4..f8fae1f 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 398cfb4b1421901b8c18be07faf5f54bf14c7633
+Subproject commit f8fae1ff4fae14fc6ba0aa1dc3af2e13a6e9a597
diff --git a/third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h b/third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h
index aa26830..9cfbd705 100644
--- a/third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h
+++ b/third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h
@@ -23,7 +23,7 @@
 
 namespace content {
 struct FencedFrameConfig;
-struct FencedFrameProperties;
+class FencedFrameProperties;
 }  // namespace content
 
 namespace blink::FencedFrame {
@@ -208,7 +208,7 @@
   }
 
  private:
-  friend struct content::FencedFrameProperties;
+  friend class content::FencedFrameProperties;
   friend struct mojo::StructTraits<
       blink::mojom::FencedFramePropertiesDataView,
       blink::FencedFrame::RedactedFencedFrameProperties>;
diff --git a/third_party/blink/public/web/web_autofill_client.h b/third_party/blink/public/web/web_autofill_client.h
index 78d02a88..98e6356 100644
--- a/third_party/blink/public/web/web_autofill_client.h
+++ b/third_party/blink/public/web/web_autofill_client.h
@@ -66,6 +66,9 @@
   virtual void TextFieldDidChange(const WebFormControlElement&) {}
   virtual void TextFieldDidReceiveKeyDown(const WebInputElement&,
                                           const WebKeyboardEvent&) {}
+  // This is called once per-character when a user edits a contenteditable
+  // element by typing.
+  virtual void ContentEditableDidChange(const WebElement&) {}
   // This is called when a datalist indicator is clicked.
   virtual void OpenTextDataListChooser(const WebInputElement&) {}
   // This is called when the datalist for an input has changed.
diff --git a/third_party/blink/public/web/web_element.h b/third_party/blink/public/web/web_element.h
index 02fa305..9cd08e8 100644
--- a/third_party/blink/public/web/web_element.h
+++ b/third_party/blink/public/web/web_element.h
@@ -80,6 +80,7 @@
   WebString GetAttribute(const WebString&) const;
   void SetAttribute(const WebString& name, const WebString& value);
   WebString TextContent() const;
+  WebString TextContentAbridged(unsigned int max_length) const;
   WebString InnerHTML() const;
 
   // Returns true if the element's contenteditable attribute is in the true
diff --git a/third_party/blink/renderer/core/css/css_style_declaration.cc b/third_party/blink/renderer/core/css/css_style_declaration.cc
index 044b1fc..ce913fe1 100644
--- a/third_party/blink/renderer/core/css/css_style_declaration.cc
+++ b/third_party/blink/renderer/core/css/css_style_declaration.cc
@@ -42,6 +42,7 @@
 #include "third_party/blink/renderer/core/css/properties/css_property.h"
 #include "third_party/blink/renderer/core/css/property_bitsets.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -224,6 +225,9 @@
       "CSSStyleDeclaration",
       CSSProperty::Get(ResolveCSSPropertyID(unresolved_property))
           .GetPropertyName());
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidSetStyle();
   if (value->IsNumber()) {
     double double_value = NativeValueTraits<IDLUnrestrictedDouble>::NativeValue(
         script_state->GetIsolate(), value, exception_state);
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 67c4e53a..3610b6b 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -185,6 +185,7 @@
 #include "third_party/blink/renderer/core/page/page_animator.h"
 #include "third_party/blink/renderer/core/page/pointer_lock_controller.h"
 #include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h"
+#include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h"
 #include "third_party/blink/renderer/core/page/spatial_navigation.h"
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
@@ -1556,6 +1557,10 @@
     return 0;
   }
 
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
+
   GetDocument().UpdateStyleAndLayoutForNode(this,
                                             DocumentUpdateReason::kJavaScript);
 
@@ -1592,6 +1597,10 @@
     return 0;
   }
 
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
+
   GetDocument().UpdateStyleAndLayoutForNode(this,
                                             DocumentUpdateReason::kJavaScript);
 
@@ -2150,6 +2159,9 @@
 }
 
 DOMRectList* Element::getClientRects() {
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
   GetDocument().EnsurePaintLocationDataValidForNode(
       this, DocumentUpdateReason::kJavaScript);
   Vector<gfx::QuadF> quads;
@@ -2199,6 +2211,9 @@
 }
 
 DOMRect* Element::GetBoundingClientRectForBinding() {
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
   return GetBoundingClientRect();
 }
 
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 4e5cd001..b8ee4e9 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -412,7 +412,7 @@
   DOMRectList* getClientRects();
   // Returns a rectangle in zoomed pixel units.
   gfx::RectF GetBoundingClientRectNoLifecycleUpdateNoAdjustment() const;
-  // Returns a rectangle in CSS pixel units.  i.e. ignorign zoom.
+  // Returns a rectangle in CSS pixel units.  i.e. ignoring zoom.
   gfx::RectF GetBoundingClientRectNoLifecycleUpdate() const;
   DOMRect* GetBoundingClientRect();
   DOMRect* GetBoundingClientRectForBinding();
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index c3621aa..d63585e 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -1940,7 +1940,8 @@
 }
 
 String Node::textContent(bool convert_brs_to_newlines,
-                         TextVisitor* visitor) const {
+                         TextVisitor* visitor,
+                         unsigned int max_length) const {
   // This covers ProcessingInstruction and Comment that should return their
   // value when .textContent is accessed on them, but should be ignored when
   // iterated over as a descendant of a ContainerNode.
@@ -1965,8 +1966,14 @@
       content.Append('\n');
     } else if (auto* text_node = DynamicTo<Text>(node)) {
       content.Append(text_node->data());
+      // Only abridge text content when max_length is explicitly set.
+      if (max_length < UINT_MAX && content.length() > max_length) {
+        content.Resize(max_length);
+        break;
+      }
     }
   }
+
   return content.ReleaseString();
 }
 
diff --git a/third_party/blink/renderer/core/dom/node.h b/third_party/blink/renderer/core/dom/node.h
index bea7a0a..8ce0445d 100644
--- a/third_party/blink/renderer/core/dom/node.h
+++ b/third_party/blink/renderer/core/dom/node.h
@@ -26,6 +26,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_NODE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_NODE_H_
 
+#include <climits>
+
 #include "base/dcheck_is_on.h"
 #include "base/notreached.h"
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink-forward.h"
@@ -305,7 +307,8 @@
   const AtomicString& lookupNamespaceURI(const String& prefix) const;
 
   String textContent(bool convert_brs_to_newlines = false,
-                     TextVisitor* visitor = nullptr) const;
+                     TextVisitor* visitor = nullptr,
+                     unsigned int max_length = UINT_MAX) const;
   virtual void setTextContent(const String&);
   V8UnionStringOrTrustedScript* textContentForBinding() const;
   virtual void setTextContentForBinding(
diff --git a/third_party/blink/renderer/core/dom/range.cc b/third_party/blink/renderer/core/dom/range.cc
index 7df78ca..0c86ea1 100644
--- a/third_party/blink/renderer/core/dom/range.cc
+++ b/third_party/blink/renderer/core/dom/range.cc
@@ -57,6 +57,7 @@
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/layout/layout_text.h"
 #include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
+#include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h"
 #include "third_party/blink/renderer/core/svg/svg_svg_element.h"
 #include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
@@ -1611,6 +1612,9 @@
 }
 
 DOMRectList* Range::getClientRects() const {
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
   DisplayLockUtilities::ScopedForcedUpdate force_locks(
       this, DisplayLockContext::ForcedPhase::kLayout);
   owner_document_->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
@@ -1622,6 +1626,9 @@
 }
 
 DOMRect* Range::getBoundingClientRect() const {
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
   return DOMRect::FromRectF(BoundingRect());
 }
 
diff --git a/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc b/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc
index cee73ba..d2718143 100644
--- a/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc
+++ b/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc
@@ -72,6 +72,7 @@
 #include "third_party/blink/renderer/core/editing/visible_selection.h"
 #include "third_party/blink/renderer/core/editing/visible_units.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
 #include "third_party/blink/renderer/core/html/html_br_element.h"
 #include "third_party/blink/renderer/core/html/html_div_element.h"
 #include "third_party/blink/renderer/core/html/html_element.h"
@@ -82,6 +83,8 @@
 #include "third_party/blink/renderer/core/html_names.h"
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
 #include "third_party/blink/renderer/core/layout/layout_text.h"
+#include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/clear_collection_scope.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -2101,6 +2104,12 @@
   // Command will be equal to last edit command only in the case of typing
   if (last_edit_command == this) {
     DCHECK(IsTypingCommand());
+    if (Element* element = undo_step.StartingRootEditableElement()) {
+      element->GetDocument()
+          .GetPage()
+          ->GetChromeClient()
+          .DidUserChangeContentEditableContent(*element);
+    }
   } else if (last_edit_command && last_edit_command->IsDragAndDropCommand() &&
              (GetInputType() == InputEvent::InputType::kDeleteByDrag ||
               GetInputType() == InputEvent::InputType::kInsertFromDrop)) {
diff --git a/third_party/blink/renderer/core/exported/web_element.cc b/third_party/blink/renderer/core/exported/web_element.cc
index e8c7cb3..0639dd6 100644
--- a/third_party/blink/renderer/core/exported/web_element.cc
+++ b/third_party/blink/renderer/core/exported/web_element.cc
@@ -119,6 +119,9 @@
 WebString WebElement::TextContent() const {
   return ConstUnwrap<Element>()->textContent();
 }
+WebString WebElement::TextContentAbridged(const unsigned int max_length) const {
+  return ConstUnwrap<Element>()->textContent(false, nullptr, max_length);
+}
 
 WebString WebElement::InnerHTML() const {
   return ConstUnwrap<Element>()->innerHTML();
diff --git a/third_party/blink/renderer/core/frame/dom_visual_viewport.cc b/third_party/blink/renderer/core/frame/dom_visual_viewport.cc
index a53763d..c7de067 100644
--- a/third_party/blink/renderer/core/frame/dom_visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/dom_visual_viewport.cc
@@ -35,6 +35,7 @@
 #include "third_party/blink/renderer/core/geometry/dom_rect.h"
 #include "third_party/blink/renderer/core/layout/adjust_for_absolute_zoom.h"
 #include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/widget/frame_widget.h"
@@ -94,6 +95,10 @@
   if (!view || !view->LayoutViewport())
     return 0;
 
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
+
   frame->GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
   float viewport_x = view->LayoutViewport()->GetScrollOffset().x();
 
@@ -117,6 +122,10 @@
   if (!view || !view->LayoutViewport())
     return 0;
 
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
+
   frame->GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
   float viewport_y = view->LayoutViewport()->GetScrollOffset().y();
 
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index f9b31ca0..b2ccfc6 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -127,6 +127,7 @@
 #include "third_party/blink/renderer/core/page/create_window.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
+#include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/probe/core_probes.h"
 #include "third_party/blink/renderer/core/script/modulator.h"
@@ -186,6 +187,9 @@
 int RequestAnimationFrame(Document* document,
                           V8FrameRequestCallback* callback,
                           bool legacy) {
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidRequestAnimationFrame();
   SetCurrentTaskAsCallbackParent(callback);
   auto* frame_callback = MakeGarbageCollected<V8FrameCallback>(callback);
   frame_callback->SetUseLegacyTimeBase(legacy);
@@ -1646,6 +1650,10 @@
   if (!view)
     return 0;
 
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
+
   document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
 
   // TODO(bokan): This is wrong when the document.rootScroller is non-default.
@@ -1663,6 +1671,10 @@
   if (!view)
     return 0;
 
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic::DidAccessScrollOffset();
+
   document()->UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
 
   // TODO(bokan): This is wrong when the document.rootScroller is non-default.
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
index c124404..a5dde98 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.cc
@@ -615,6 +615,7 @@
   RECORD_METRIC(JavascriptDocumentUpdate);
   RECORD_METRIC(ParseStyleSheet);
   RECORD_METRIC(Accessibility);
+  RECORD_METRIC(PossibleSynchronizedScrollCount);
 
   builder.Record(recorder);
 #undef RECORD_METRIC
@@ -672,6 +673,7 @@
   RECORD_METRIC(JavascriptDocumentUpdate);
   RECORD_METRIC(ParseStyleSheet);
   RECORD_METRIC(Accessibility);
+  RECORD_METRIC(PossibleSynchronizedScrollCount);
 
   builder.Record(recorder);
 #undef RECORD_METRIC
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
index 75030b4..fb483c8 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h
@@ -150,8 +150,9 @@
     kUserDrivenDocumentUpdate,
     kParseStyleSheet,
     kAccessibility,
+    kPossibleSynchronizedScrollCount,
     kCount,
-    kMainFrame
+    kMainFrame,
   };
 
   // For metrics that require it, this converts the input value to use
@@ -201,7 +202,8 @@
         {"Blink.ServiceDocumentUpdate.UpdateTime", true},
         {"Blink.UserDrivenDocumentUpdate.UpdateTime", true},
         {"Blink.ParseStyleSheet.UpdateTime", true},
-        {"Blink.Accessibility.UpdateTime", true}};
+        {"Blink.Accessibility.UpdateTime", true},
+        {"Blink.PossibleSynchronizedScrollCount.UpdateTime", true}};
     static_assert(std::size(data) == kCount, "Metrics data mismatch");
     return data;
   }
diff --git a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
index 60c4326..c096fba 100644
--- a/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_ukm_aggregator_test.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
 
 #include "base/metrics/statistics_recorder.h"
+#include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/test_mock_time_task_runner.h"
 #include "cc/metrics/begin_main_frame_metrics.h"
@@ -18,6 +19,7 @@
 #include "third_party/blink/renderer/core/testing/intersection_observer_test_helper.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
+#include "third_party/blink/renderer/platform/testing/testing_platform_support_with_mock_scheduler.h"
 
 namespace blink {
 
@@ -1038,4 +1040,286 @@
   histogram_tester.ExpectTotalCount("Blink.MainFrame.UpdateTime.PreFCP", 1);
 }
 
+enum SyncScrollMutation {
+  kSyncScrollMutatesPosition,
+  kSyncScrollMutatesTransform,
+  kSyncScrollMutatesPositionBeforeAccess,
+  kSyncScrollMutatesNothing,
+};
+
+enum SyncScrollPositionAccess {
+  kSyncScrollAccessScrollOffset,
+  kSyncScrollDoesNotAccessScrollOffset,
+};
+
+enum SyncScrollHandlerStrategy {
+  kSyncScrollWithEventHandler,
+  kSyncScrollWithEventHandlerSchedulingRAF,
+  kSyncScrollNoEventHandlerWithRAF,
+  kSyncScrollNoEventHandler,
+};
+
+using SyncScrollHeuristicTestConfig =
+    ::testing::tuple<SyncScrollMutation,
+                     SyncScrollPositionAccess,
+                     SyncScrollHandlerStrategy>;
+
+class LocalFrameUkmAggregatorSyncScrollTest
+    : public LocalFrameUkmAggregatorSimTest,
+      public ::testing::WithParamInterface<SyncScrollHeuristicTestConfig> {
+ public:
+  static std::string PrintTestName(
+      const ::testing::TestParamInfo<SyncScrollHeuristicTestConfig>& info) {
+    std::stringstream ss;
+    switch (GetSyncScrollMutation(info.param)) {
+      case SyncScrollMutation::kSyncScrollMutatesPosition:
+        ss << "MutatesPosition";
+        break;
+      case SyncScrollMutation::kSyncScrollMutatesPositionBeforeAccess:
+        ss << "MutatesPositionBeforeAccess";
+        break;
+      case SyncScrollMutation::kSyncScrollMutatesTransform:
+        ss << "MutatesTransform";
+        break;
+      case SyncScrollMutation::kSyncScrollMutatesNothing:
+        ss << "MutatesNothing";
+        break;
+    }
+    ss << "_";
+    switch (GetSyncScrollPositionAccess(info.param)) {
+      case SyncScrollPositionAccess::kSyncScrollAccessScrollOffset:
+        ss << "AccessScrollOffset";
+        break;
+      case SyncScrollPositionAccess::kSyncScrollDoesNotAccessScrollOffset:
+        ss << "DoesNotAccessScrollOffset";
+        break;
+    }
+    ss << "_";
+    switch (GetSyncScrollHandlerStrategy(info.param)) {
+      case SyncScrollHandlerStrategy::kSyncScrollWithEventHandler:
+        ss << "WithEventHandler";
+        break;
+      case SyncScrollHandlerStrategy::kSyncScrollWithEventHandlerSchedulingRAF:
+        ss << "WithEventHandlerSchedulingRAF";
+        break;
+      case SyncScrollHandlerStrategy::kSyncScrollNoEventHandler:
+        ss << "NoEventHandler";
+        break;
+      case SyncScrollHandlerStrategy::kSyncScrollNoEventHandlerWithRAF:
+        ss << "NoEventHandlerWithRAF";
+        break;
+    }
+    return ss.str();
+  }
+
+ protected:
+  static SyncScrollMutation GetSyncScrollMutation(
+      const SyncScrollHeuristicTestConfig& config) {
+    return ::testing::get<0>(config);
+  }
+
+  static SyncScrollPositionAccess GetSyncScrollPositionAccess(
+      const SyncScrollHeuristicTestConfig& config) {
+    return ::testing::get<1>(config);
+  }
+
+  static SyncScrollHandlerStrategy GetSyncScrollHandlerStrategy(
+      const SyncScrollHeuristicTestConfig& config) {
+    return ::testing::get<2>(config);
+  }
+
+  bool ShouldTriggerSyncScrollHeuristic() const {
+    // We would only attempt to synchronize scrolling if we had a scroll handler
+    // and, provided this is the case, we look for both mutating a property and
+    // accessing scroll offset. Note: it's also ok to mutate via rAF, provided
+    // that rAF was scheduled during the scroll handler.
+    return GetSyncScrollMutation(GetParam()) !=
+               SyncScrollMutation::kSyncScrollMutatesNothing &&
+           GetSyncScrollMutation(GetParam()) !=
+               SyncScrollMutation::kSyncScrollMutatesPositionBeforeAccess &&
+           GetSyncScrollPositionAccess(GetParam()) ==
+               SyncScrollPositionAccess::kSyncScrollAccessScrollOffset &&
+           (GetSyncScrollHandlerStrategy(GetParam()) ==
+                SyncScrollHandlerStrategy::kSyncScrollWithEventHandler ||
+            GetSyncScrollHandlerStrategy(GetParam()) ==
+                SyncScrollHandlerStrategy::
+                    kSyncScrollWithEventHandlerSchedulingRAF);
+  }
+
+  std::string GenerateNewScrollPosition() {
+    switch (GetSyncScrollPositionAccess(GetParam())) {
+      case SyncScrollPositionAccess::kSyncScrollAccessScrollOffset:
+        return "document.scrollingElement.scrollTop";
+      case SyncScrollPositionAccess::kSyncScrollDoesNotAccessScrollOffset:
+        return "100";
+    }
+    NOTREACHED();
+  }
+
+  std::string GenerateMutation() {
+    std::string pos = GenerateNewScrollPosition();
+    switch (GetSyncScrollMutation(GetParam())) {
+      case SyncScrollMutation::kSyncScrollMutatesPosition:
+        return base::StringPrintf("card.style.top = %s + 'px'", pos.c_str());
+      case SyncScrollMutation::kSyncScrollMutatesTransform:
+        return base::StringPrintf(
+            "card.style.transform = 'translateY(' + %s + 'px)'", pos.c_str());
+      case SyncScrollMutation::kSyncScrollMutatesPositionBeforeAccess:
+        return base::StringPrintf(
+            "card.style.top = Math.floor(Math.random() * 100) + 'px'; var "
+            "unused = %s",
+            pos.c_str());
+      case SyncScrollMutation::kSyncScrollMutatesNothing:
+        return "";
+    }
+    NOTREACHED();
+  }
+
+  std::string GenerateScrollHandler() {
+    switch (GetSyncScrollHandlerStrategy(GetParam())) {
+      case SyncScrollHandlerStrategy::kSyncScrollWithEventHandler:
+        return base::StringPrintf(R"HTML(
+          document.addEventListener('scroll', (e) => {
+            %s;
+          });
+        )HTML",
+                                  GenerateMutation().c_str());
+      case SyncScrollHandlerStrategy::kSyncScrollWithEventHandlerSchedulingRAF:
+        return base::StringPrintf(R"HTML(
+          document.addEventListener('scroll', (e) => {
+            window.requestAnimationFrame((t) => { %s; });
+          });
+        )HTML",
+                                  GenerateMutation().c_str());
+      case SyncScrollHandlerStrategy::kSyncScrollNoEventHandlerWithRAF:
+        return base::StringPrintf(R"HTML(
+          function doSyncEffect(t) {
+            %s;
+            window.requestAnimationFrame(doSyncEffect);
+          }
+          window.requestAnimationFrame(doSyncEffect);
+        )HTML",
+                                  GenerateMutation().c_str());
+      case SyncScrollHandlerStrategy::kSyncScrollNoEventHandler:
+        return "";
+    }
+    NOTREACHED();
+  }
+
+  ScopedTestingPlatformSupport<TestingPlatformSupportWithMockScheduler>
+      platform_;
+};
+
+TEST_P(LocalFrameUkmAggregatorSyncScrollTest, SyncScrollHeuristicRAFSetTop) {
+  base::HistogramTester histogram_tester;
+  const bool should_trigger = ShouldTriggerSyncScrollHeuristic();
+
+  WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
+  SimRequest main_resource("https://example.com/", "text/html");
+  LoadURL("https://example.com/");
+  std::string html = base::StringPrintf(R"HTML(
+    <!DOCTYPE html>
+    <style>
+      #card {
+        background: green;
+        width: 100px;
+        height: 100px;
+        position: absolute;
+      }
+    </style>
+    <div id='card'></div>
+    <div style='background:orange;width:100px;height:10000px'></div>
+    <script>
+      %s
+    </script>
+  )HTML",
+                                        GenerateScrollHandler().c_str());
+  main_resource.Complete(html.c_str());
+
+  // Wait until the script has had time to run.
+  platform_->RunForPeriodSeconds(5.);
+  base::RunLoop().RunUntilIdle();
+
+  // Do a pre-FCP frame.
+  Compositor().BeginFrame();
+
+  // We haven't scrolled at this point, so we should never have a count.
+  histogram_tester.ExpectTotalCount(
+      "Blink.PossibleSynchronizedScrollCount.UpdateTime.PreFCP", 0);
+
+  // Cause a pre-FCP scroll.
+  auto* scrolling_element =
+      LocalFrameRoot().GetFrame()->GetDocument()->scrollingElement();
+  scrolling_element->setScrollTop(100.0);
+
+  // Do another pre-FCP frame.
+  Compositor().BeginFrame();
+
+  // Now that we'ev scrolled, we should have an update if triggering conditions
+  // are met.
+  histogram_tester.ExpectTotalCount(
+      "Blink.PossibleSynchronizedScrollCount.UpdateTime.PreFCP",
+      should_trigger ? 1 : 0);
+
+  // Cause FCP on the next frame.
+  Element* target = GetDocument().getElementById(AtomicString("card"));
+  target->setInnerHTML("hello world");
+
+  Compositor().BeginFrame();
+
+  EXPECT_FALSE(IsBeforeFCPForTesting());
+
+  scrolling_element =
+      LocalFrameRoot().GetFrame()->GetDocument()->scrollingElement();
+  scrolling_element->setScrollTop(200.0);
+
+  // Do another post-FCP frame.
+  Compositor().BeginFrame();
+
+  if (should_trigger) {
+    // Should only have triggered for the one pre FCP scroll.
+    EXPECT_THAT(
+        histogram_tester.GetAllSamples("Blink.PossibleSynchronizedScrollCount."
+                                       "UpdateTime.AggregatedPreFCP"),
+        base::BucketsAre(base::Bucket(1, 1)));
+    // Should only have triggered for the one post FCP scroll.
+    histogram_tester.ExpectTotalCount(
+        "Blink.PossibleSynchronizedScrollCount.UpdateTime.PostFCP", 1);
+    // Should only trigger for the two scroll top adjustments.
+    EXPECT_THAT(
+        histogram_tester.GetAllSamples("Renderer.PossibleSynchronizedScroll"),
+        base::BucketsInclude(base::Bucket(0, 2), base::Bucket(1, 2)));
+  } else {
+    // Should never trigger.
+    EXPECT_THAT(
+        histogram_tester.GetAllSamples("Blink.PossibleSynchronizedScrollCount."
+                                       "UpdateTime.AggregatedPreFCP"),
+        base::BucketsAre(base::Bucket(0, 1)));
+    histogram_tester.ExpectTotalCount(
+        "Blink.PossibleSynchronizedScrollCount.UpdateTime.PostFCP", 0);
+    EXPECT_THAT(
+        histogram_tester.GetAllSamples("Renderer.PossibleSynchronizedScroll"),
+        base::BucketsInclude(base::Bucket(0, 4)));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    P,
+    LocalFrameUkmAggregatorSyncScrollTest,
+    ::testing::Combine(
+        ::testing::Values(
+            SyncScrollMutation::kSyncScrollMutatesPosition,
+            SyncScrollMutation::kSyncScrollMutatesTransform,
+            SyncScrollMutation::kSyncScrollMutatesPositionBeforeAccess,
+            SyncScrollMutation::kSyncScrollMutatesNothing),
+        ::testing::Values(
+            SyncScrollPositionAccess::kSyncScrollAccessScrollOffset,
+            SyncScrollPositionAccess::kSyncScrollDoesNotAccessScrollOffset),
+        ::testing::Values(
+            SyncScrollHandlerStrategy::kSyncScrollWithEventHandler,
+            SyncScrollHandlerStrategy::kSyncScrollWithEventHandlerSchedulingRAF,
+            SyncScrollHandlerStrategy::kSyncScrollNoEventHandlerWithRAF,
+            SyncScrollHandlerStrategy::kSyncScrollNoEventHandler)),
+    LocalFrameUkmAggregatorSyncScrollTest::PrintTestName);
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/absolute_utils.cc b/third_party/blink/renderer/core/layout/absolute_utils.cc
index ed7bffb..b460303 100644
--- a/third_party/blink/renderer/core/layout/absolute_utils.cc
+++ b/third_party/blink/renderer/core/layout/absolute_utils.cc
@@ -22,6 +22,20 @@
 
 using InsetBias = InsetModifiedContainingBlock::InsetBias;
 
+StyleSelfAlignmentData AlignSelf(const ComputedStyle& style) {
+  return RuntimeEnabledFeatures::LayoutAlignForPositionedEnabled()
+             ? style.ResolvedAlignSelf(ItemPosition::kNormal)
+             : StyleSelfAlignmentData(ItemPosition::kNormal,
+                                      OverflowAlignment::kDefault);
+}
+
+StyleSelfAlignmentData JustifySelf(const ComputedStyle& style) {
+  return RuntimeEnabledFeatures::LayoutAlignForPositionedEnabled()
+             ? style.ResolvedJustifySelf(ItemPosition::kNormal)
+             : StyleSelfAlignmentData(ItemPosition::kNormal,
+                                      OverflowAlignment::kDefault);
+}
+
 inline InsetBias GetStaticPositionInsetBias(
     LogicalStaticPosition::InlineEdge inline_edge) {
   switch (inline_edge) {
@@ -46,6 +60,56 @@
   }
 }
 
+InsetBias GetAlignmentInsetBias(
+    const StyleSelfAlignmentData& alignment,
+    WritingDirectionMode container_writing_direction,
+    WritingDirectionMode self_writing_direction,
+    bool is_justify_axis,
+    absl::optional<InsetBias>* out_safe_inset_bias) {
+  // `alignment` is in the writing-direction of the containing-block, vs. the
+  // inset-bias which is relative to the writing-direction of the candidate.
+  const LogicalToLogical bias(
+      self_writing_direction, container_writing_direction, InsetBias::kStart,
+      InsetBias::kEnd, InsetBias::kStart, InsetBias::kEnd);
+
+  if (alignment.Overflow() == OverflowAlignment::kSafe) {
+    *out_safe_inset_bias =
+        is_justify_axis ? bias.InlineStart() : bias.BlockStart();
+  }
+
+  switch (alignment.GetPosition()) {
+    case ItemPosition::kStart:
+    case ItemPosition::kFlexStart:
+    case ItemPosition::kBaseline:
+    case ItemPosition::kStretch:
+    case ItemPosition::kNormal:
+    case ItemPosition::kAnchorCenter:
+      return is_justify_axis ? bias.InlineStart() : bias.BlockStart();
+    case ItemPosition::kCenter:
+      return InsetBias::kEqual;
+    case ItemPosition::kEnd:
+    case ItemPosition::kFlexEnd:
+    case ItemPosition::kLastBaseline:
+      return is_justify_axis ? bias.InlineEnd() : bias.BlockEnd();
+    case ItemPosition::kSelfStart:
+      return InsetBias::kStart;
+    case ItemPosition::kSelfEnd:
+      return InsetBias::kEnd;
+    case ItemPosition::kLeft:
+      DCHECK(is_justify_axis);
+      return container_writing_direction.IsLtr() ? bias.InlineStart()
+                                                 : bias.InlineEnd();
+    case ItemPosition::kRight:
+      DCHECK(is_justify_axis);
+      return container_writing_direction.IsRtl() ? bias.InlineStart()
+                                                 : bias.InlineEnd();
+    case ItemPosition::kLegacy:
+    case ItemPosition::kAuto:
+      NOTREACHED();
+      return InsetBias::kStart;
+  }
+}
+
 // Computes the inset modified containing block in one axis, accounting for
 // insets and the static-position.
 void ComputeUnclampedIMCBInOneAxis(
@@ -54,10 +118,12 @@
     const absl::optional<LayoutUnit>& inset_end,
     const LayoutUnit static_position_offset,
     InsetBias static_position_inset_bias,
-    bool start_side_matches_containing_block,
+    InsetBias alignment_inset_bias,
+    const absl::optional<InsetBias>& safe_alignment_inset_bias,
     LayoutUnit* imcb_start_out,
     LayoutUnit* imcb_end_out,
-    InsetBias* imcb_inset_bias_out) {
+    InsetBias* imcb_inset_bias_out,
+    absl::optional<InsetBias>* imcb_safe_inset_bias_out) {
   DCHECK_NE(available_size, kIndefiniteSize);
   if (!inset_start && !inset_end) {
     // If both our insets are auto, the available-space is defined by the
@@ -100,11 +166,10 @@
       *imcb_inset_bias_out =
           inset_start.has_value() ? InsetBias::kStart : InsetBias::kEnd;
     } else {
-      // Otherwise the weaker inset is the inset of the end edge (where end is
-      // interpreted relative to the writing mode of the containing block).
-      *imcb_inset_bias_out = start_side_matches_containing_block
-                                 ? InsetBias::kStart
-                                 : InsetBias::kEnd;
+      // Both insets were set - use the alignment bias (defaults to the "start"
+      // edge of the containing block if we have normal alignment).
+      *imcb_inset_bias_out = alignment_inset_bias;
+      *imcb_safe_inset_bias_out = safe_alignment_inset_bias;
     }
   }
 }
@@ -113,29 +178,43 @@
     const LogicalSize& available_size,
     const LogicalOofInsets& insets,
     const LogicalStaticPosition& static_position,
+    const ComputedStyle& style,
     WritingDirectionMode container_writing_direction,
     WritingDirectionMode self_writing_direction) {
   InsetModifiedContainingBlock imcb;
   imcb.available_size = available_size;
 
-  // Determines if the "start" sides of margins match.
-  const LogicalToLogical start_sides_match(
-      container_writing_direction, self_writing_direction,
-      /* inline_start */ true, /* inline_end */ false,
-      /* block_start */ true, /* block_end */ false);
+  const bool is_parallel =
+      IsParallelWritingMode(container_writing_direction.GetWritingMode(),
+                            self_writing_direction.GetWritingMode());
+  const auto inline_alignment =
+      is_parallel ? JustifySelf(style) : AlignSelf(style);
+  const auto block_alignment =
+      is_parallel ? AlignSelf(style) : JustifySelf(style);
+
+  absl::optional<InsetBias> safe_inline_alignment_inset_bias;
+  const auto inline_alignment_inset_bias = GetAlignmentInsetBias(
+      inline_alignment, container_writing_direction, self_writing_direction,
+      /* is_justify_axis */ is_parallel, &safe_inline_alignment_inset_bias);
+  absl::optional<InsetBias> safe_block_alignment_inset_bias;
+  const auto block_alignment_inset_bias = GetAlignmentInsetBias(
+      block_alignment, container_writing_direction, self_writing_direction,
+      /* is_justify_axis */ !is_parallel, &safe_block_alignment_inset_bias);
 
   ComputeUnclampedIMCBInOneAxis(
       available_size.inline_size, insets.inline_start, insets.inline_end,
       static_position.offset.inline_offset,
       GetStaticPositionInsetBias(static_position.inline_edge),
-      start_sides_match.InlineStart(), &imcb.inline_start, &imcb.inline_end,
-      &imcb.inline_inset_bias);
+      inline_alignment_inset_bias, safe_inline_alignment_inset_bias,
+      &imcb.inline_start, &imcb.inline_end, &imcb.inline_inset_bias,
+      &imcb.safe_inline_inset_bias);
   ComputeUnclampedIMCBInOneAxis(
       available_size.block_size, insets.block_start, insets.block_end,
       static_position.offset.block_offset,
       GetStaticPositionInsetBias(static_position.block_edge),
-      start_sides_match.BlockStart(), &imcb.block_start, &imcb.block_end,
-      &imcb.block_inset_bias);
+      block_alignment_inset_bias, safe_block_alignment_inset_bias,
+      &imcb.block_start, &imcb.block_end, &imcb.block_inset_bias,
+      &imcb.safe_block_inset_bias);
   return imcb;
 }
 
@@ -223,24 +302,67 @@
                    LayoutUnit imcb_start,
                    LayoutUnit imcb_end,
                    const InsetBias imcb_inset_bias,
+                   const absl::optional<InsetBias>& imcb_safe_inset_bias,
                    const LayoutUnit margin_start,
                    const LayoutUnit margin_end,
                    const LayoutUnit size,
                    LayoutUnit* inset_start_out,
                    LayoutUnit* inset_end_out) {
   DCHECK_NE(available_size, kIndefiniteSize);
-  const LayoutUnit free_space =
+  LayoutUnit free_space =
       available_size - imcb_start - imcb_end - margin_start - margin_end - size;
+  InsetBias bias = imcb_inset_bias;
+  if (imcb_safe_inset_bias && free_space < LayoutUnit()) {
+    free_space = LayoutUnit();
+    bias = *imcb_safe_inset_bias;
+  }
 
   // Move the weaker inset edge to consume all the free space, so that:
   // `imcb_start` + `margin_start` + `size` + `margin_end` + `imcb_end` =
   // `available_size`
-  ResizeIMCBInOneAxis(imcb_inset_bias, free_space, &imcb_start, &imcb_end);
+  ResizeIMCBInOneAxis(bias, free_space, &imcb_start, &imcb_end);
 
   *inset_start_out = imcb_start + margin_start;
   *inset_end_out = imcb_end + margin_end;
 }
 
+bool CanComputeBlockSizeWithoutLayout(
+    const BlockNode& node,
+    WritingDirectionMode container_writing_direction,
+    ItemPosition block_alignment_position,
+    bool has_auto_block_inset) {
+  // Tables (even with an explicit size) apply a min-content constraint.
+  if (node.IsTable()) {
+    return false;
+  }
+  // Replaced elements always have their size computed ahead of time.
+  if (node.IsReplaced()) {
+    return true;
+  }
+  const auto& style = node.Style();
+  if (style.LogicalHeight().IsContentOrIntrinsic() ||
+      style.LogicalMinHeight().IsContentOrIntrinsic() ||
+      style.LogicalMaxHeight().IsContentOrIntrinsic()) {
+    return false;
+  }
+  if (style.LogicalHeight().IsAuto()) {
+    // Any 'auto' inset will trigger shink-to-fit sizing.
+    if (has_auto_block_inset) {
+      return false;
+    }
+    if (block_alignment_position == ItemPosition::kStretch) {
+      return true;
+    }
+    // Non-normal alignment will trigger shrink-to-fit sizing.
+    if (block_alignment_position != ItemPosition::kNormal) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
 bool IsInsetAutoForAxis(const Length& side1,
                         const Length& side2,
                         const ComputedStyle& style,
@@ -264,36 +386,6 @@
       .IsNone();
 }
 
-bool CanComputeBlockSizeWithoutLayout(
-    const BlockNode& node,
-    WritingDirectionMode container_writing_direction,
-    const AnchorEvaluatorImpl* anchor_evaluator) {
-  // Tables (even with an explicit size) apply a min-content constraint.
-  if (node.IsTable()) {
-    return false;
-  }
-  // Replaced elements always have their size computed ahead of time.
-  if (node.IsReplaced()) {
-    return true;
-  }
-  const auto& style = node.Style();
-  if (style.LogicalHeight().IsContentOrIntrinsic() ||
-      style.LogicalMinHeight().IsContentOrIntrinsic() ||
-      style.LogicalMaxHeight().IsContentOrIntrinsic()) {
-    return false;
-  }
-  if (style.LogicalHeight().IsAuto()) {
-    // Any 'auto' inset will trigger shink-to-fit sizing.
-    if (IsInsetAutoForAxis(style.LogicalTop(), style.LogicalBottom(), style,
-                           container_writing_direction, anchor_evaluator)) {
-      return false;
-    }
-  }
-  return true;
-}
-
-}  // namespace
-
 LogicalOofInsets ComputeOutOfFlowInsets(
     const ComputedStyle& style,
     const LogicalSize& available_logical_size,
@@ -366,9 +458,9 @@
     const LogicalStaticPosition& static_position,
     WritingDirectionMode container_writing_direction,
     WritingDirectionMode self_writing_direction) {
-  InsetModifiedContainingBlock imcb =
-      ComputeUnclampedIMCB(available_size, insets, static_position,
-                           container_writing_direction, self_writing_direction);
+  InsetModifiedContainingBlock imcb = ComputeUnclampedIMCB(
+      available_size, insets, static_position, node.Style(),
+      container_writing_direction, self_writing_direction);
   // Clamp any negative size to 0.
   if (imcb.InlineSize() < LayoutUnit()) {
     ResizeIMCBInOneAxis(imcb.inline_inset_bias, imcb.InlineSize(),
@@ -398,9 +490,10 @@
     const LogicalSize& available_size,
     const LogicalOofInsets& insets,
     const LogicalStaticPosition& static_position,
+    const ComputedStyle& style,
     WritingDirectionMode container_writing_direction,
     WritingDirectionMode self_writing_direction) {
-  return ComputeUnclampedIMCB(available_size, insets, static_position,
+  return ComputeUnclampedIMCB(available_size, insets, static_position, style,
                               container_writing_direction,
                               self_writing_direction);
 }
@@ -418,10 +511,25 @@
   DCHECK(dimensions);
   DCHECK_GE(imcb.InlineSize(), LayoutUnit());
 
+  const bool is_justify_axis = IsParallelWritingMode(
+      container_writing_direction.GetWritingMode(), style.GetWritingMode());
+  const auto alignment_position =
+      (is_justify_axis ? JustifySelf(style) : AlignSelf(style)).GetPosition();
+  const auto block_alignment_position =
+      (is_justify_axis ? AlignSelf(style) : JustifySelf(style)).GetPosition();
+
+  const bool has_auto_inline_inset =
+      IsInsetAutoForAxis(style.LogicalInlineStart(), style.LogicalInlineEnd(),
+                         style, container_writing_direction, anchor_evaluator);
+  const bool has_auto_block_inset =
+      IsInsetAutoForAxis(style.LogicalTop(), style.LogicalBottom(), style,
+                         container_writing_direction, anchor_evaluator);
+
   bool depends_on_min_max_sizes = false;
   const bool can_compute_block_size_without_layout =
       CanComputeBlockSizeWithoutLayout(node, container_writing_direction,
-                                       anchor_evaluator);
+                                       block_alignment_position,
+                                       has_auto_block_inset);
 
   auto MinMaxSizesFunc = [&](MinMaxSizesType type) -> MinMaxSizesResult {
     DCHECK(!node.IsReplaced());
@@ -454,10 +562,6 @@
                                    builder.ToConstraintSpace());
   };
 
-  const bool has_auto_inline_inset =
-      IsInsetAutoForAxis(style.LogicalInlineStart(), style.LogicalInlineEnd(),
-                         style, container_writing_direction, anchor_evaluator);
-
   LayoutUnit inline_size;
   if (replaced_size) {
     DCHECK(node.IsReplaced());
@@ -466,22 +570,34 @@
     Length main_inline_length = style.LogicalWidth();
     Length min_inline_length = style.LogicalMinWidth();
 
-    const bool stretch_inline_size = !has_auto_inline_inset;
+    const bool is_implicit_stretch =
+        !has_auto_inline_inset && alignment_position == ItemPosition::kNormal;
+    const bool is_explicit_stretch =
+        !has_auto_inline_inset && alignment_position == ItemPosition::kStretch;
+    const bool is_stretch = is_implicit_stretch || is_explicit_stretch;
+
+    // If our block constraint is strong/explicit.
+    const bool is_block_explicit =
+        !style.LogicalHeight().IsAuto() ||
+        (!has_auto_block_inset &&
+         block_alignment_position == ItemPosition::kStretch);
 
     // Determine how "auto" should resolve.
     if (main_inline_length.IsAuto()) {
       if (node.IsTable()) {
-        // Tables always shrink-to-fit.
-        main_inline_length = Length::FitContent();
+        // Tables always shrink-to-fit unless explicitly asked to stretch.
+        main_inline_length = is_explicit_stretch ? Length::FillAvailable()
+                                                 : Length::FitContent();
       } else if (!style.AspectRatio().IsAuto() &&
                  can_compute_block_size_without_layout &&
-                 (!stretch_inline_size || !style.LogicalHeight().IsAuto())) {
+                 (!is_stretch || (is_implicit_stretch && is_block_explicit))) {
         // We'd like to apply the aspect-ratio.
         // The aspect-ratio applies from the block-axis if we can compute our
         // block-size without invoking layout, and either:
         //  - We aren't stretching our auto inline-size.
-        //  - We are stretching our auto inline-size, but the block-size isn't
-        //  auto.
+        //  - We are stretching our auto inline-size, but the block-size has a
+        //    stronger (explicit) constraint, e.g:
+        //    "height:10px" or "align-self:stretch".
         main_inline_length = Length::FitContent();
 
         // Apply the automatic minimum size.
@@ -489,8 +605,8 @@
             min_inline_length.IsAuto())
           min_inline_length = Length::MinIntrinsic();
       } else {
-        main_inline_length = stretch_inline_size ? Length::FillAvailable()
-                                                 : Length::FitContent();
+        main_inline_length =
+            is_stretch ? Length::FillAvailable() : Length::FitContent();
       }
     }
 
@@ -526,7 +642,7 @@
 
   ComputeInsets(space.AvailableSize().inline_size, imcb.inline_start,
                 imcb.inline_end, imcb.inline_inset_bias,
-                dimensions->margins.inline_start,
+                imcb.safe_inline_inset_bias, dimensions->margins.inline_start,
                 dimensions->margins.inline_end, inline_size,
                 &dimensions->inset.inline_start, &dimensions->inset.inline_end);
 
@@ -546,12 +662,19 @@
   DCHECK(dimensions);
   DCHECK_GE(imcb.BlockSize(), LayoutUnit());
 
-  const bool is_table = node.IsTable();
+  const bool is_justify_axis = !IsParallelWritingMode(
+      container_writing_direction.GetWritingMode(), style.GetWritingMode());
+  const auto alignment_position =
+      (is_justify_axis ? JustifySelf(style) : AlignSelf(style)).GetPosition();
+
+  const bool has_auto_block_inset =
+      IsInsetAutoForAxis(style.LogicalTop(), style.LogicalBottom(), style,
+                         container_writing_direction, anchor_evaluator);
 
   const LayoutResult* result = nullptr;
 
   MinMaxSizes min_max_block_sizes = ComputeMinMaxBlockSizes(
-      space, style, border_padding, imcb.Size().block_size, anchor_evaluator);
+      space, style, border_padding, imcb.BlockSize(), anchor_evaluator);
 
   auto IntrinsicBlockSizeFunc = [&]() -> LayoutUnit {
     DCHECK(!node.IsReplaced());
@@ -563,13 +686,21 @@
                                      style.GetWritingDirection(),
                                      /* is_new_fc */ true);
       builder.SetAvailableSize(
-          {dimensions->size.inline_size, space.AvailableSize().block_size});
+          {dimensions->size.inline_size, imcb.BlockSize()});
       builder.SetIsFixedInlineSize(true);
       builder.SetPercentageResolutionSize(space.PercentageResolutionSize());
+
       // Use the computed |MinMaxSizes| because |node.Layout()| can't resolve
       // the `anchor-size()` function.
       builder.SetOverrideMinMaxBlockSizes(min_max_block_sizes);
 
+      // Tables need to know about the explicit stretch constraint to produce
+      // the correct result.
+      if (!has_auto_block_inset &&
+          alignment_position == ItemPosition::kStretch) {
+        builder.SetBlockAutoBehavior(AutoSizeBehavior::kStretchExplicit);
+      }
+
       if (space.IsInitialColumnBalancingPass()) {
         // The |fragmentainer_offset_delta| will not make a difference in the
         // initial column balancing pass.
@@ -586,10 +717,6 @@
         .BlockSize();
   };
 
-  const bool has_auto_block_inset =
-      IsInsetAutoForAxis(style.LogicalTop(), style.LogicalBottom(), style,
-                         container_writing_direction, anchor_evaluator);
-
   LayoutUnit block_size;
   if (replaced_size) {
     DCHECK(node.IsReplaced());
@@ -597,19 +724,27 @@
   } else {
     Length main_block_length = style.LogicalHeight();
 
-    const bool stretch_block_size = !has_auto_block_inset;
+    const bool is_table = node.IsTable();
+
+    const bool is_implicit_stretch =
+        !has_auto_block_inset && alignment_position == ItemPosition::kNormal;
+    const bool is_explicit_stretch =
+        !has_auto_block_inset && alignment_position == ItemPosition::kStretch;
+    const bool is_stretch = is_implicit_stretch || is_explicit_stretch;
 
     // Determine how "auto" should resolve.
     if (main_block_length.IsAuto()) {
       if (is_table) {
-        // Tables always shrink-to-fit.
-        main_block_length = Length::FitContent();
+        // Tables always shrink-to-fit unless explicitly asked to stretch.
+        main_block_length = is_explicit_stretch ? Length::FillAvailable()
+                                                : Length::FitContent();
       } else if (!style.AspectRatio().IsAuto() &&
-                 dimensions->size.inline_size != kIndefiniteSize) {
+                 dimensions->size.inline_size != kIndefiniteSize &&
+                 !is_explicit_stretch) {
         main_block_length = Length::FitContent();
       } else {
         main_block_length =
-            stretch_block_size ? Length::FillAvailable() : Length::FitContent();
+            is_stretch ? Length::FillAvailable() : Length::FitContent();
       }
     }
 
@@ -655,9 +790,9 @@
 
   ComputeInsets(space.AvailableSize().block_size, imcb.block_start,
                 imcb.block_end, imcb.block_inset_bias,
-                dimensions->margins.block_start, dimensions->margins.block_end,
-                block_size, &dimensions->inset.block_start,
-                &dimensions->inset.block_end);
+                imcb.safe_block_inset_bias, dimensions->margins.block_start,
+                dimensions->margins.block_end, block_size,
+                &dimensions->inset.block_start, &dimensions->inset.block_end);
 
   return result;
 }
diff --git a/third_party/blink/renderer/core/layout/absolute_utils.h b/third_party/blink/renderer/core/layout/absolute_utils.h
index 781d124..845a4e9 100644
--- a/third_party/blink/renderer/core/layout/absolute_utils.h
+++ b/third_party/blink/renderer/core/layout/absolute_utils.h
@@ -47,6 +47,12 @@
   absl::optional<LayoutUnit> block_end;
 };
 
+bool IsInsetAutoForAxis(const Length& side1,
+                        const Length& side2,
+                        const ComputedStyle& style,
+                        WritingDirectionMode container_writing_direction,
+                        const AnchorEvaluatorImpl*);
+
 CORE_EXPORT LogicalOofInsets
 ComputeOutOfFlowInsets(const ComputedStyle& style,
                        const LogicalSize& available_size,
@@ -72,6 +78,12 @@
   InsetBias inline_inset_bias = InsetBias::kStart;
   InsetBias block_inset_bias = InsetBias::kStart;
 
+  // If safe alignment is specified (e.g. "align-self: safe end") and the
+  // object overflows its containing block it'll become start aligned instead.
+  // This field indicates the "start" edge of the containing block.
+  absl::optional<InsetBias> safe_inline_inset_bias;
+  absl::optional<InsetBias> safe_block_inset_bias;
+
   LayoutUnit InlineEndOffset() const {
     return available_size.inline_size - inline_end;
   }
@@ -106,6 +118,7 @@
 ComputeIMCBForPositionFallback(const LogicalSize& available_size,
                                const LogicalOofInsets&,
                                const LogicalStaticPosition&,
+                               const ComputedStyle&,
                                WritingDirectionMode container_writing_direction,
                                WritingDirectionMode self_writing_direction);
 
diff --git a/third_party/blink/renderer/core/layout/inline/inline_item.cc b/third_party/blink/renderer/core/layout/inline/inline_item.cc
index 4a5b7bb..2fb92a1 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_item.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_item.cc
@@ -178,7 +178,7 @@
     case kInitialLetterBox:
       return "InitialLetterBox";
     case kListMarker:
-      return "ListMerker";
+      return "ListMarker";
     case kBidiControl:
       return "BidiControl";
   }
diff --git a/third_party/blink/renderer/core/layout/length_utils.cc b/third_party/blink/renderer/core/layout/length_utils.cc
index ecd5387..a7477516 100644
--- a/third_party/blink/renderer/core/layout/length_utils.cc
+++ b/third_party/blink/renderer/core/layout/length_utils.cc
@@ -765,9 +765,6 @@
                 space.AvailableSize().block_size != kIndefiniteSize)) {
       Length block_length_to_resolve = block_length;
       if (block_length_to_resolve.IsAuto()) {
-        // TODO(dgrogan): This code block (and its corresponding inline version
-        // below) didn't make any tests pass when written so it may be
-        // unnecessary or untested. Check again when launching ReplacedNG.
         DCHECK(space.IsBlockAutoBehaviorStretch());
         block_length_to_resolve = Length::FillAvailable();
       }
diff --git a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
index 5133f20..9d3ffff 100644
--- a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
@@ -1970,7 +1970,7 @@
 
   absl::optional<LogicalSize> replaced_size;
   if (node_info.node.IsReplaced()) {
-    // Create a new space with the IMCB size.
+    // Create a new space with the IMCB size, and stretch constraints.
     ConstraintSpaceBuilder builder(candidate_style.GetWritingMode(),
                                    candidate_style.GetWritingDirection(),
                                    /* is_new_fc */ true);
@@ -1980,6 +1980,37 @@
     builder.SetReplacedPercentageResolutionSize(
         node_info.constraint_space.PercentageResolutionSize());
 
+    if (RuntimeEnabledFeatures::LayoutAlignForPositionedEnabled()) {
+      const bool is_parallel =
+          IsParallelWritingMode(container_writing_direction.GetWritingMode(),
+                                candidate_writing_direction.GetWritingMode());
+      const ItemPosition inline_position =
+          (is_parallel ? candidate_style.JustifySelf()
+                       : candidate_style.AlignSelf())
+              .GetPosition();
+      const bool is_inline_stretch =
+          !IsInsetAutoForAxis(candidate_style.LogicalInlineStart(),
+                              candidate_style.LogicalInlineEnd(),
+                              candidate_style, container_writing_direction,
+                              anchor_evaluator) &&
+          inline_position == ItemPosition::kStretch;
+      if (is_inline_stretch) {
+        builder.SetInlineAutoBehavior(AutoSizeBehavior::kStretchExplicit);
+      }
+      const ItemPosition block_position =
+          (is_parallel ? candidate_style.AlignSelf()
+                       : candidate_style.JustifySelf())
+              .GetPosition();
+      const bool is_block_stretch =
+          !IsInsetAutoForAxis(candidate_style.LogicalTop(),
+                              candidate_style.LogicalBottom(), candidate_style,
+                              container_writing_direction, anchor_evaluator) &&
+          block_position == ItemPosition::kStretch;
+      if (is_block_stretch) {
+        builder.SetBlockAutoBehavior(AutoSizeBehavior::kStretchExplicit);
+      }
+    }
+
     replaced_size = ComputeReplacedSize(
         node_info.node, builder.ToConstraintSpace(), border_padding,
         ReplacedSizeMode::kNormal, anchor_evaluator);
@@ -2006,7 +2037,7 @@
   if (try_fit_available_space) {
     imcb_for_position_fallback = ComputeIMCBForPositionFallback(
         node_info.constraint_space.AvailableSize(), insets,
-        node_info.static_position, container_writing_direction,
+        node_info.static_position, candidate_style, container_writing_direction,
         candidate_writing_direction);
     if (!CalculateNonOverflowingRangeInOneAxis(
             insets.inline_start, insets.inline_end,
diff --git a/third_party/blink/renderer/core/page/build.gni b/third_party/blink/renderer/core/page/build.gni
index 4ff67a5..9521de6d 100644
--- a/third_party/blink/renderer/core/page/build.gni
+++ b/third_party/blink/renderer/core/page/build.gni
@@ -74,6 +74,8 @@
   "scrolling/root_scroller_controller.h",
   "scrolling/scrolling_coordinator.cc",
   "scrolling/scrolling_coordinator.h",
+  "scrolling/sync_scroll_attempt_heuristic.cc",
+  "scrolling/sync_scroll_attempt_heuristic.h",
   "scrolling/snap_coordinator.cc",
   "scrolling/snap_coordinator.h",
   "scrolling/sticky_position_scrolling_constraints.cc",
diff --git a/third_party/blink/renderer/core/page/chrome_client.h b/third_party/blink/renderer/core/page/chrome_client.h
index b6ab6cb..cb3d2d6 100644
--- a/third_party/blink/renderer/core/page/chrome_client.h
+++ b/third_party/blink/renderer/core/page/chrome_client.h
@@ -498,6 +498,7 @@
       HTMLElement*,
       WebFormRelatedChangeType) {}
   virtual void DidChangeValueInTextField(HTMLFormControlElement&) {}
+  virtual void DidUserChangeContentEditableContent(Element&) {}
   virtual void DidEndEditingOnTextField(HTMLInputElement&) {}
   virtual void HandleKeyboardEventOnTextField(HTMLInputElement&,
                                               KeyboardEvent&) {}
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.cc b/third_party/blink/renderer/core/page/chrome_client_impl.cc
index 4d1c8dc..7b362ff0 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.cc
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.cc
@@ -49,6 +49,7 @@
 #include "third_party/blink/public/web/blink.h"
 #include "third_party/blink/public/web/web_autofill_client.h"
 #include "third_party/blink/public/web/web_console_message.h"
+#include "third_party/blink/public/web/web_form_element.h"
 #include "third_party/blink/public/web/web_input_element.h"
 #include "third_party/blink/public/web/web_local_frame_client.h"
 #include "third_party/blink/public/web/web_node.h"
@@ -59,6 +60,7 @@
 #include "third_party/blink/public/web/web_window_features.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/node.h"
 #include "third_party/blink/renderer/core/events/web_input_event_conversion.h"
 #include "third_party/blink/renderer/core/exported/web_dev_tools_agent_impl.h"
@@ -1245,6 +1247,23 @@
   }
 }
 
+void ChromeClientImpl::DidUserChangeContentEditableContent(Element& element) {
+  Document& doc = element.GetDocument();
+  // Selecting the focused element as we are only interested in changes made by
+  // the user. We assume the user must focus the field to type into it.
+  WebElement focused_element = doc.FocusedElement();
+  // If element argument is not the focused element we can assume the user
+  // was not typing (this covers cases like element.innerText = 'foo').
+  // Value changes caused by |document.execCommand| calls should not be
+  // interpreted as a user action. See https://crbug.com/764760.
+  if (!element.IsFocusedElementInDocument() || doc.IsRunningExecCommand()) {
+    return;
+  }
+  if (auto* fill_client = AutofillClientFromFrame(doc.GetFrame())) {
+    fill_client->ContentEditableDidChange(focused_element);
+  }
+}
+
 void ChromeClientImpl::DidEndEditingOnTextField(
     HTMLInputElement& input_element) {
   if (auto* fill_client =
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.h b/third_party/blink/renderer/core/page/chrome_client_impl.h
index d0459b32..d86a685 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.h
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.h
@@ -260,6 +260,7 @@
   void HandleKeyboardEventOnTextField(HTMLInputElement&,
                                       KeyboardEvent&) override;
   void DidChangeValueInTextField(HTMLFormControlElement&) override;
+  void DidUserChangeContentEditableContent(Element&) override;
   void DidEndEditingOnTextField(HTMLInputElement&) override;
   void OpenTextDataListChooser(HTMLInputElement&) override;
   void TextFieldDataListChanged(HTMLInputElement&) override;
diff --git a/third_party/blink/renderer/core/page/page_animator.cc b/third_party/blink/renderer/core/page/page_animator.cc
index 2219e73..e156b6633 100644
--- a/third_party/blink/renderer/core/page/page_animator.cc
+++ b/third_party/blink/renderer/core/page/page_animator.cc
@@ -20,6 +20,7 @@
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
 #include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h"
 #include "third_party/blink/renderer/core/page/validation_message_client.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
 #include "third_party/blink/renderer/core/svg/svg_document_extensions.h"
@@ -103,6 +104,9 @@
     controllers.emplace_back(document.first->GetScriptedAnimationController(),
                              document.second);
   }
+  // TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+  // impact is understood.
+  SyncScrollAttemptHeuristic heuristic(page_->MainFrame());
   ServiceScriptedAnimations(monotonic_animation_start_time, controllers);
   page_->GetValidationMessageClient().LayoutOverlay();
 }
@@ -249,6 +253,7 @@
   // 8. For each fully active Document in docs, run the scroll steps
   // for that Document, passing in now as the timestamp.
   run_for_all_active_controllers_with_timing([&](wtf_size_t i) {
+    auto scope = SyncScrollAttemptHeuristic::GetScrollHandlerScope();
     active_controllers[i]->DispatchEvents(WTF::BindRepeating([](Event* event) {
       return event->type() == event_type_names::kScroll ||
              event->type() == event_type_names::kSnapchanged ||
@@ -283,6 +288,7 @@
   // 13. For each fully active Document in docs, run the animation
   // frame callbacks for that Document, passing in now as the timestamp.
   run_for_all_active_controllers_with_timing([&](wtf_size_t i) {
+    auto scope = SyncScrollAttemptHeuristic::GetRequestAnimationFrameScope();
     active_controllers[i]->ExecuteFrameCallbacks();
     if (!active_controllers[i]->GetExecutionContext()) {
       return;
diff --git a/third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.cc b/third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.cc
new file mode 100644
index 0000000..2418588
--- /dev/null
+++ b/third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.cc
@@ -0,0 +1,119 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h"
+
+#include "base/check_op.h"
+#include "base/metrics/histogram_functions.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+
+namespace blink {
+
+namespace {
+
+SyncScrollAttemptHeuristic* g_sync_scroll_attempt_heuristic = nullptr;
+
+}  // namespace
+
+SyncScrollAttemptHeuristic::SyncScrollAttemptHeuristic(Frame* frame)
+    : frame_(frame), last_instance_(g_sync_scroll_attempt_heuristic) {
+  if (frame_ && frame_->IsOutermostMainFrame()) {
+    g_sync_scroll_attempt_heuristic = this;
+  } else {
+    g_sync_scroll_attempt_heuristic = nullptr;
+  }
+}
+
+SyncScrollAttemptHeuristic::~SyncScrollAttemptHeuristic() {
+  if (frame_ && frame_->IsOutermostMainFrame()) {
+    CHECK_EQ(g_sync_scroll_attempt_heuristic, this);
+  }
+  g_sync_scroll_attempt_heuristic = last_instance_;
+  const bool saw_possible_sync_scrolling_attempt =
+      did_access_scroll_offset_ && did_set_style_;
+  if (saw_possible_sync_scrolling_attempt && frame_ &&
+      frame_->IsOutermostMainFrame() && !frame_->IsDetached()) {
+    // This will not cover cases where |frame_| is remote.
+    if (LocalFrame* local_frame = DynamicTo<LocalFrame>(frame_)) {
+      if (local_frame->View()) {
+        if (LocalFrameUkmAggregator* ukm_aggregator =
+                local_frame->View()->GetUkmAggregator()) {
+          ukm_aggregator->RecordCountSample(
+              LocalFrameUkmAggregator::kPossibleSynchronizedScrollCount, 1);
+        }
+      }
+    }
+  }
+  base::UmaHistogramBoolean("Renderer.PossibleSynchronizedScroll",
+                            saw_possible_sync_scrolling_attempt);
+}
+
+SyncScrollAttemptHeuristic::Scope::Scope(bool enable_observation)
+    : enable_observation_(enable_observation) {
+  if (enable_observation_) {
+    SyncScrollAttemptHeuristic::EnableObservation();
+  }
+}
+
+SyncScrollAttemptHeuristic::Scope::~Scope() {
+  if (enable_observation_) {
+    SyncScrollAttemptHeuristic::DisableObservation();
+  }
+}
+
+SyncScrollAttemptHeuristic::Scope
+SyncScrollAttemptHeuristic::GetScrollHandlerScope() {
+  return Scope(g_sync_scroll_attempt_heuristic);
+}
+
+SyncScrollAttemptHeuristic::Scope
+SyncScrollAttemptHeuristic::GetRequestAnimationFrameScope() {
+  // We only want to observe rAF if one was requested during a scroll
+  // handler. If that's the case, |did_request_animation_frame_| should be
+  // true.
+  return Scope(g_sync_scroll_attempt_heuristic &&
+               g_sync_scroll_attempt_heuristic->did_request_animation_frame_);
+}
+
+void SyncScrollAttemptHeuristic::DidAccessScrollOffset() {
+  if (UNLIKELY(g_sync_scroll_attempt_heuristic &&
+               g_sync_scroll_attempt_heuristic->is_observing_)) {
+    g_sync_scroll_attempt_heuristic->did_access_scroll_offset_ = true;
+  }
+}
+
+void SyncScrollAttemptHeuristic::DidSetStyle() {
+  // We only want to record a mutation if we've already accessed the scroll
+  // offset.
+  if (UNLIKELY(g_sync_scroll_attempt_heuristic &&
+               g_sync_scroll_attempt_heuristic->is_observing_ &&
+               g_sync_scroll_attempt_heuristic->did_access_scroll_offset_)) {
+    g_sync_scroll_attempt_heuristic->did_set_style_ = true;
+  }
+}
+
+void SyncScrollAttemptHeuristic::DidRequestAnimationFrame() {
+  if (UNLIKELY(g_sync_scroll_attempt_heuristic &&
+               g_sync_scroll_attempt_heuristic->is_observing_)) {
+    g_sync_scroll_attempt_heuristic->did_request_animation_frame_ = true;
+  }
+}
+
+void SyncScrollAttemptHeuristic::EnableObservation() {
+  if (UNLIKELY(g_sync_scroll_attempt_heuristic)) {
+    CHECK(!g_sync_scroll_attempt_heuristic->is_observing_);
+    g_sync_scroll_attempt_heuristic->is_observing_ = true;
+  }
+}
+
+void SyncScrollAttemptHeuristic::DisableObservation() {
+  if (UNLIKELY(g_sync_scroll_attempt_heuristic)) {
+    CHECK(g_sync_scroll_attempt_heuristic->is_observing_);
+    g_sync_scroll_attempt_heuristic->is_observing_ = false;
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h b/third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h
new file mode 100644
index 0000000..dce3c67
--- /dev/null
+++ b/third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h
@@ -0,0 +1,67 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_SYNC_SCROLL_ATTEMPT_HEURISTIC_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_SYNC_SCROLL_ATTEMPT_HEURISTIC_H_
+
+#include "base/memory/stack_allocated.h"
+#include "third_party/blink/renderer/core/core_export.h"
+
+namespace blink {
+
+class Frame;
+
+// SyncScrollAttemptHeuristic handles detecting cases where a page attempts to
+// synchronize an effect with scrolling. While a vended Scope instance exists,
+// the heuristic observes when script accesses scroll position and then also
+// modifies inline style in order to estimate when script is attempting to
+// synchronize content with scrolling. This is complicated somewhat by rAF. It
+// can happen that a scroll handler will use rAF to realize the update. We only
+// attempt to detect sync scroll attempts if the given frame is the outermost
+// main frame and  UKM is not recorded when the given frame is remote.
+//
+// TODO(crbug.com/1499981): This should be removed once synchronized scrolling
+// impact is understood.
+class CORE_EXPORT SyncScrollAttemptHeuristic final {
+  STACK_ALLOCATED();
+
+ public:
+  explicit SyncScrollAttemptHeuristic(Frame* frame);
+  SyncScrollAttemptHeuristic(const SyncScrollAttemptHeuristic&) = delete;
+  SyncScrollAttemptHeuristic& operator=(const SyncScrollAttemptHeuristic&) =
+      delete;
+  ~SyncScrollAttemptHeuristic();
+
+  class Scope {
+    STACK_ALLOCATED();
+
+   public:
+    explicit Scope(bool enable_observation);
+    ~Scope();
+
+   private:
+    bool enable_observation_ = false;
+  };
+
+  [[nodiscard]] static Scope GetScrollHandlerScope();
+  [[nodiscard]] static Scope GetRequestAnimationFrameScope();
+  static void DidAccessScrollOffset();
+  static void DidSetStyle();
+  static void DidRequestAnimationFrame();
+
+ private:
+  static void EnableObservation();
+  static void DisableObservation();
+
+  Frame* frame_ = nullptr;
+  SyncScrollAttemptHeuristic* last_instance_ = nullptr;
+  bool did_access_scroll_offset_ = false;
+  bool did_set_style_ = false;
+  bool did_request_animation_frame_ = false;
+  bool is_observing_ = false;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAGE_SCROLLING_SYNC_SCROLL_ATTEMPT_HEURISTIC_H_
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 6826317..303f06d 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -3509,9 +3509,6 @@
     case TreeUpdateReason::kMarkDirtyFromRemove:
       EnsureMarkDirtyWithCleanLayout(node);
       break;
-    case TreeUpdateReason::kNameAttributeChanged:
-      HandleNameAttributeChangedWithCleanLayout(node);
-      break;
     case TreeUpdateReason::kNodeGainedFocus:
       HandleNodeGainedFocusWithCleanLayout(node);
       break;
@@ -3527,6 +3524,7 @@
       RemoveValidationMessageObjectWithCleanLayout(node);
       break;
     case TreeUpdateReason::kRoleChangeFromAriaHasPopup:
+    case TreeUpdateReason::kRoleChangeFromImageMapName:
     case TreeUpdateReason::kRoleChangeFromRoleOrType:
       HandleRoleChangeWithCleanLayout(node);
       break;
@@ -4059,7 +4057,7 @@
   } else if (attr_name == html_names::kUsemapAttr) {
     DeferTreeUpdate(TreeUpdateReason::kUseMapAttributeChanged, element);
   } else if (attr_name == html_names::kNameAttr) {
-    DeferTreeUpdate(TreeUpdateReason::kNameAttributeChanged, element);
+    HandleNameAttributeChanged(element);
   } else if (attr_name == html_names::kControlsAttr) {
     ChildrenChanged(element);
   } else if (attr_name == html_names::kHrefAttr) {
@@ -4086,16 +4084,26 @@
     HandleRoleChangeWithCleanLayout(previous_map->ImageElement());
 }
 
-void AXObjectCacheImpl::HandleNameAttributeChangedWithCleanLayout(Node* node) {
+void AXObjectCacheImpl::HandleNameAttributeChanged(Node* node) {
+  HTMLMapElement* map = DynamicTo<HTMLMapElement>(node);
+  if (!map) {
+    return;
+  }
+
   // Changing a map name can alter an image's role and children.
-  // The name has already changed, so we can no longer find the primary image
-  // via the DOM. Use an area child's parent to find the old image.
-  // If the old image was treated as a map, and now isn't, it will take care
-  // of updating any other image that is newly associated with the map,
-  // via AXNodeObject::AddImageMapChildren().
-  if (HTMLMapElement* map = DynamicTo<HTMLMapElement>(node)) {
-    if (AXObject* ax_previous_image = GetAXImageForMap(*map))
-      HandleRoleChangeWithCleanLayout(ax_previous_image->GetNode());
+  // First update any image that may have used the old map name.
+  if (AXObject* ax_previous_image = GetAXImageForMap(*map)) {
+    DeferTreeUpdate(TreeUpdateReason::kRoleChangeFromImageMapName,
+                    ax_previous_image->GetElement());
+  }
+
+  // Then, update any image which may use the new map name.
+  HTMLImageElement* new_image = map->ImageElement();
+  if (new_image) {
+    if (AXObject* obj = Get(new_image)) {
+      DeferTreeUpdate(TreeUpdateReason::kRoleChangeFromImageMapName,
+                      obj->GetElement());
+    }
   }
 }
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
index bee9e2b..7b42ad95 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
@@ -774,15 +774,15 @@
     kMarkDirtyFromHandleLayout = 11,
     kMarkDirtyFromHandleScroll = 12,
     kMarkDirtyFromRemove = 13,
-    kNameAttributeChanged = 14,
-    kNodeGainedFocus = 15,
-    kNodeLostFocus = 16,
-    kPostNotificationFromHandleLoadComplete = 17,
-    kPostNotificationFromHandleLoadStart = 18,
-    kPostNotificationFromHandleScrolledToAnchor = 19,
-    kRemoveValidationMessageObjectFromFocusedUIElement = 20,
-    kRemoveValidationMessageObjectFromValidationMessageObject = 21,
-    kRoleChangeFromAriaHasPopup = 22,
+    kNodeGainedFocus = 14,
+    kNodeLostFocus = 15,
+    kPostNotificationFromHandleLoadComplete = 16,
+    kPostNotificationFromHandleLoadStart = 17,
+    kPostNotificationFromHandleScrolledToAnchor = 18,
+    kRemoveValidationMessageObjectFromFocusedUIElement = 19,
+    kRemoveValidationMessageObjectFromValidationMessageObject = 20,
+    kRoleChangeFromAriaHasPopup = 21,
+    kRoleChangeFromImageMapName = 22,
     kRoleChangeFromRoleOrType = 23,
     kRoleMaybeChangedFromEventListener = 24,
     kRoleMaybeChangedFromHref = 25,
@@ -989,7 +989,7 @@
   // call children changed.
   void HandleTextMarkerDataAddedWithCleanLayout(Node*);
   void HandleUseMapAttributeChangedWithCleanLayout(Node*);
-  void HandleNameAttributeChangedWithCleanLayout(Node*);
+  void HandleNameAttributeChanged(Node*);
 
   bool DoesEventListenerImpactIgnoredState(const AtomicString& event_type,
                                            const Node& node) const;
diff --git a/third_party/blink/renderer/modules/indexeddb/BUILD.gn b/third_party/blink/renderer/modules/indexeddb/BUILD.gn
index 8406d6a..9bd9485 100644
--- a/third_party/blink/renderer/modules/indexeddb/BUILD.gn
+++ b/third_party/blink/renderer/modules/indexeddb/BUILD.gn
@@ -60,8 +60,6 @@
     "inspector_indexed_db_agent.h",
     "web_idb_cursor.cc",
     "web_idb_cursor.h",
-    "web_idb_database.cc",
-    "web_idb_database.h",
   ]
 
   public_deps = [
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_cursor.cc b/third_party/blink/renderer/modules/indexeddb/idb_cursor.cc
index 2bef174..e450fffc 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_cursor.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_cursor.cc
@@ -40,7 +40,6 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_database.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_object_store.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_transaction.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
@@ -356,7 +355,7 @@
                                       IDBDatabase::kIsKeyCursorErrorMessage);
     return nullptr;
   }
-  if (!transaction_->BackendDB()) {
+  if (!transaction_->db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -364,7 +363,7 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  transaction_->BackendDB()->Delete(
+  transaction_->db()->Delete(
       transaction_->Id(), EffectiveObjectStore()->Id(), IdbPrimaryKey(),
       WTF::BindOnce(&IDBRequest::OnDelete, WrapPersistent(request)));
   return request;
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_database.cc b/third_party/blink/renderer/modules/indexeddb/idb_database.cc
index 02b22b7..03dc8c65 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_database.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_database.cc
@@ -47,12 +47,16 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_event_dispatcher.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_index.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_key_path.h"
+#include "third_party/blink/renderer/modules/indexeddb/idb_key_range.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_version_change_event.h"
+#include "third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits.h"
+#include "third_party/blink/renderer/modules/indexeddb/indexed_db_dispatcher.h"
 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
+#include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
 
 namespace blink {
@@ -94,20 +98,23 @@
 
 IDBDatabase::IDBDatabase(
     ExecutionContext* context,
-    std::unique_ptr<WebIDBDatabase> backend,
     mojo::PendingAssociatedReceiver<mojom::blink::IDBDatabaseCallbacks>
         callbacks_receiver,
-    mojo::PendingRemote<mojom::blink::ObservedFeature> connection_lifetime)
+    mojo::PendingRemote<mojom::blink::ObservedFeature> connection_lifetime,
+    mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase> pending_database)
     : ActiveScriptWrappable<IDBDatabase>({}),
       ExecutionContextLifecycleObserver(context),
-      backend_(std::move(backend)),
+      database_remote_(context),
       connection_lifetime_(std::move(connection_lifetime)),
       callbacks_receiver_(this, context) {
+  database_remote_.Bind(std::move(pending_database),
+                        context->GetTaskRunner(TaskType::kDatabaseAccess));
   callbacks_receiver_.Bind(std::move(callbacks_receiver),
                            context->GetTaskRunner(TaskType::kDatabaseAccess));
 }
 
 void IDBDatabase::Trace(Visitor* visitor) const {
+  visitor->Trace(database_remote_);
   visitor->Trace(version_change_transaction_);
   visitor->Trace(transactions_);
   visitor->Trace(callbacks_receiver_);
@@ -157,27 +164,30 @@
 
   TransactionWillFinish(transaction);
 
-  if (close_pending_ && transactions_.empty())
+  if (close_pending_ && transactions_.empty()) {
     CloseConnection();
+  }
 }
 
 void IDBDatabase::ForcedClose() {
-  for (const auto& it : transactions_)
+  for (const auto& it : transactions_) {
     it.value->StartAborting(nullptr);
+  }
   this->close();
   DispatchEvent(*Event::Create(event_type_names::kClose));
 }
 
 void IDBDatabase::VersionChange(int64_t old_version, int64_t new_version) {
   TRACE_EVENT0("IndexedDB", "IDBDatabase::onVersionChange");
-  if (!GetExecutionContext())
+  if (!GetExecutionContext()) {
     return;
+  }
 
   if (close_pending_) {
     // If we're pending, that means there's a busy transaction. We won't
     // fire 'versionchange' but since we're not closing immediately the
     // back-end should still send out 'blocked'.
-    backend_->VersionChangeIgnored();
+    VersionChangeIgnored();
     return;
   }
 
@@ -205,8 +215,9 @@
 
 DOMStringList* IDBDatabase::objectStoreNames() const {
   auto* object_store_names = MakeGarbageCollected<DOMStringList>();
-  for (const auto& it : metadata_.object_stores)
+  for (const auto& it : metadata_.object_stores) {
     object_store_names->Append(it.value->name);
+  }
   object_store_names->Sort();
   return object_store_names;
 }
@@ -255,7 +266,7 @@
     return nullptr;
   }
 
-  if (!backend_) {
+  if (!database_remote_.is_bound()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -266,10 +277,9 @@
   version_change_transaction_->CreateObjectStore(object_store_id, name,
                                                  key_path, auto_increment);
 
-  scoped_refptr<IDBObjectStoreMetadata> store_metadata =
-      base::AdoptRef(new IDBObjectStoreMetadata(
-          name, object_store_id, key_path, auto_increment,
-          WebIDBDatabase::kMinimumIndexId));
+  scoped_refptr<IDBObjectStoreMetadata> store_metadata = base::AdoptRef(
+      new IDBObjectStoreMetadata(name, object_store_id, key_path,
+                                 auto_increment, IDBDatabase::kMinimumIndexId));
   auto* object_store = MakeGarbageCollected<IDBObjectStore>(
       store_metadata, version_change_transaction_.Get());
   version_change_transaction_->ObjectStoreCreated(name, object_store);
@@ -279,41 +289,6 @@
   return object_store;
 }
 
-void IDBDatabase::deleteObjectStore(const String& name,
-                                    ExceptionState& exception_state) {
-  TRACE_EVENT0("IndexedDB", "IDBDatabase::deleteObjectStore");
-  if (!version_change_transaction_) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kInvalidStateError,
-        IDBDatabase::kNotVersionChangeTransactionErrorMessage);
-    return;
-  }
-  if (!version_change_transaction_->IsActive()) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kTransactionInactiveError,
-        version_change_transaction_->InactiveErrorMessage());
-    return;
-  }
-
-  int64_t object_store_id = FindObjectStoreId(name);
-  if (object_store_id == IDBObjectStoreMetadata::kInvalidId) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kNotFoundError,
-        "The specified object store was not found.");
-    return;
-  }
-
-  if (!backend_) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
-                                      IDBDatabase::kDatabaseClosedErrorMessage);
-    return;
-  }
-
-  version_change_transaction_->DeleteObjectStore(object_store_id);
-  version_change_transaction_->ObjectStoreDeleted(object_store_id, name);
-  metadata_.object_stores.erase(object_store_id);
-}
-
 IDBTransaction* IDBDatabase::transaction(
     ScriptState* script_state,
     const V8UnionStringOrStringSequence* store_names,
@@ -329,8 +304,9 @@
       scope.insert(store_names->GetAsString());
       break;
     case V8UnionStringOrStringSequence::ContentType::kStringSequence:
-      for (const String& name : store_names->GetAsStringSequence())
+      for (const String& name : store_names->GetAsStringSequence()) {
         scope.insert(name);
+      }
       break;
   }
 
@@ -347,7 +323,7 @@
     return nullptr;
   }
 
-  if (!backend_) {
+  if (!database_remote_.is_bound()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -396,36 +372,74 @@
   mojo::PendingAssociatedReceiver<mojom::blink::IDBTransaction> receiver =
       transaction_remote.BindNewEndpointAndPassReceiver(
           execution_context->GetTaskRunner(TaskType::kDatabaseAccess));
-  backend_->CreateTransaction(std::move(receiver), transaction_id,
-                              object_store_ids, mode, durability);
+  CreateTransaction(std::move(receiver), transaction_id, object_store_ids, mode,
+                    durability);
 
   return IDBTransaction::CreateNonVersionChange(
       script_state, std::move(transaction_remote), transaction_id, scope, mode,
       durability, this);
 }
 
+void IDBDatabase::deleteObjectStore(const String& name,
+                                    ExceptionState& exception_state) {
+  TRACE_EVENT0("IndexedDB", "IDBDatabase::deleteObjectStore");
+  if (!version_change_transaction_) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kInvalidStateError,
+        IDBDatabase::kNotVersionChangeTransactionErrorMessage);
+    return;
+  }
+  if (!version_change_transaction_->IsActive()) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kTransactionInactiveError,
+        version_change_transaction_->InactiveErrorMessage());
+    return;
+  }
+
+  int64_t object_store_id = FindObjectStoreId(name);
+  if (object_store_id == IDBObjectStoreMetadata::kInvalidId) {
+    exception_state.ThrowDOMException(
+        DOMExceptionCode::kNotFoundError,
+        "The specified object store was not found.");
+    return;
+  }
+
+  if (!database_remote_.is_bound()) {
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      IDBDatabase::kDatabaseClosedErrorMessage);
+    return;
+  }
+
+  version_change_transaction_->DeleteObjectStore(object_store_id);
+  version_change_transaction_->ObjectStoreDeleted(object_store_id, name);
+  metadata_.object_stores.erase(object_store_id);
+}
+
 void IDBDatabase::close() {
   TRACE_EVENT0("IndexedDB", "IDBDatabase::close");
-  if (close_pending_)
+  if (close_pending_) {
     return;
+  }
 
   connection_lifetime_.reset();
   close_pending_ = true;
 
-  if (transactions_.empty())
+  if (transactions_.empty()) {
     CloseConnection();
+  }
 }
 
 void IDBDatabase::CloseConnection() {
   DCHECK(close_pending_);
   DCHECK(transactions_.empty());
 
-  if (backend_) {
-    backend_.reset();
+  if (database_remote_.is_bound()) {
+    database_remote_.reset();
   }
 
-  if (callbacks_receiver_.is_bound())
+  if (callbacks_receiver_.is_bound()) {
     callbacks_receiver_.reset();
+  }
 }
 
 DispatchEventResult IDBDatabase::DispatchEventInternal(Event& event) {
@@ -434,19 +448,23 @@
   event.SetTarget(this);
 
   // If this event originated from script, it should have no side effects.
-  if (!event.isTrusted())
+  if (!event.isTrusted()) {
     return EventTarget::DispatchEventInternal(event);
+  }
   DCHECK(event.type() == event_type_names::kVersionchange ||
          event.type() == event_type_names::kClose);
 
-  if (!GetExecutionContext())
+  if (!GetExecutionContext()) {
     return DispatchEventResult::kCanceledBeforeDispatch;
+  }
 
   DispatchEventResult dispatch_result =
       EventTarget::DispatchEventInternal(event);
+
   if (event.type() == event_type_names::kVersionchange && !close_pending_ &&
-      backend_)
-    backend_->VersionChangeIgnored();
+      database_remote_.is_bound()) {
+    VersionChangeIgnored();
+  }
   return dispatch_result;
 }
 
@@ -463,14 +481,14 @@
 void IDBDatabase::RenameObjectStore(int64_t object_store_id,
                                     const String& new_name) {
   DCHECK(version_change_transaction_)
-      << "Object store renamed on database without a versionchange transaction";
+      << "Object store renamed on database without a versionchange "
+         "transaction";
   DCHECK(version_change_transaction_->IsActive())
       << "Object store renamed when versionchange transaction is not active";
-  DCHECK(backend_) << "Object store renamed after database connection closed";
   DCHECK(metadata_.object_stores.Contains(object_store_id));
 
-  backend_->RenameObjectStore(version_change_transaction_->Id(),
-                              object_store_id, new_name);
+  RenameObjectStore(version_change_transaction_->Id(), object_store_id,
+                    new_name);
 
   IDBObjectStoreMetadata* object_store_metadata =
       metadata_.object_stores.at(object_store_id);
@@ -513,16 +531,15 @@
   // Immediately close the connection to the back end. Don't attempt a
   // normal close() since that may wait on transactions which require a
   // round trip to the back-end to abort.
-  if (backend_) {
-    backend_.reset();
+  if (database_remote_.is_bound()) {
+    database_remote_.reset();
   }
-
   connection_lifetime_.reset();
 }
 
 void IDBDatabase::ContextEnteredBackForwardCache() {
-  if (backend_) {
-    backend_->DidBecomeInactive();
+  if (database_remote_.is_bound()) {
+    DidBecomeInactive();
   }
 }
 
@@ -551,4 +568,159 @@
 STATIC_ASSERT_ENUM(mojom::blink::IDBException::kTimeoutError,
                    DOMExceptionCode::kTimeoutError);
 
+void IDBDatabase::Get(
+    int64_t transaction_id,
+    int64_t object_store_id,
+    int64_t index_id,
+    const IDBKeyRange* key_range,
+    bool key_only,
+    base::OnceCallback<void(mojom::blink::IDBDatabaseGetResultPtr)>
+        result_callback) {
+  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
+
+  mojom::blink::IDBKeyRangePtr key_range_ptr =
+      mojom::blink::IDBKeyRange::From(key_range);
+  database_remote_->Get(transaction_id, object_store_id, index_id,
+                        std::move(key_range_ptr), key_only,
+                        std::move(result_callback));
+}
+
+void IDBDatabase::GetAll(int64_t transaction_id,
+                         int64_t object_store_id,
+                         int64_t index_id,
+                         const IDBKeyRange* key_range,
+                         int64_t max_count,
+                         bool key_only,
+                         IDBRequest* request) {
+  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
+
+  mojom::blink::IDBKeyRangePtr key_range_ptr =
+      mojom::blink::IDBKeyRange::From(key_range);
+  database_remote_->GetAll(
+      transaction_id, object_store_id, index_id, std::move(key_range_ptr),
+      key_only, max_count,
+      WTF::BindOnce(&IDBRequest::OnGetAll, WrapWeakPersistent(request),
+                    key_only));
+}
+
+void IDBDatabase::SetIndexKeys(int64_t transaction_id,
+                               int64_t object_store_id,
+                               std::unique_ptr<IDBKey> primary_key,
+                               Vector<IDBIndexKeys> index_keys) {
+  database_remote_->SetIndexKeys(transaction_id, object_store_id,
+                                 std::move(primary_key), std::move(index_keys));
+}
+
+void IDBDatabase::SetIndexesReady(int64_t transaction_id,
+                                  int64_t object_store_id,
+                                  const Vector<int64_t>& index_ids) {
+  database_remote_->SetIndexesReady(transaction_id, object_store_id,
+                                    std::move(index_ids));
+}
+
+void IDBDatabase::OpenCursor(int64_t object_store_id,
+                             int64_t index_id,
+                             const IDBKeyRange* key_range,
+                             mojom::blink::IDBCursorDirection direction,
+                             bool key_only,
+                             mojom::blink::IDBTaskType task_type,
+                             IDBRequest* request) {
+  IndexedDBDispatcher::ResetCursorPrefetchCaches(request->transaction()->Id(),
+                                                 nullptr);
+
+  mojom::blink::IDBKeyRangePtr key_range_ptr =
+      mojom::blink::IDBKeyRange::From(key_range);
+  database_remote_->OpenCursor(
+      request->transaction()->Id(), object_store_id, index_id,
+      std::move(key_range_ptr), direction, key_only, task_type,
+      WTF::BindOnce(&IDBRequest::OnOpenCursor, WrapWeakPersistent(request)));
+}
+
+void IDBDatabase::Count(int64_t transaction_id,
+                        int64_t object_store_id,
+                        int64_t index_id,
+                        const IDBKeyRange* key_range,
+                        mojom::blink::IDBDatabase::CountCallback callback) {
+  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
+
+  database_remote_->Count(transaction_id, object_store_id, index_id,
+                          mojom::blink::IDBKeyRange::From(key_range),
+                          std::move(callback));
+}
+
+void IDBDatabase::Delete(int64_t transaction_id,
+                         int64_t object_store_id,
+                         const IDBKey* primary_key,
+                         base::OnceCallback<void(bool)> success_callback) {
+  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
+
+  mojom::blink::IDBKeyRangePtr key_range_ptr =
+      mojom::blink::IDBKeyRange::From(IDBKeyRange::Create(primary_key));
+  database_remote_->DeleteRange(transaction_id, object_store_id,
+                                std::move(key_range_ptr),
+                                std::move(success_callback));
+}
+
+void IDBDatabase::DeleteRange(int64_t transaction_id,
+                              int64_t object_store_id,
+                              const IDBKeyRange* key_range,
+                              base::OnceCallback<void(bool)> success_callback) {
+  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
+
+  mojom::blink::IDBKeyRangePtr key_range_ptr =
+      mojom::blink::IDBKeyRange::From(key_range);
+  database_remote_->DeleteRange(transaction_id, object_store_id,
+                                std::move(key_range_ptr),
+                                std::move(success_callback));
+}
+
+void IDBDatabase::GetKeyGeneratorCurrentNumber(
+    int64_t transaction_id,
+    int64_t object_store_id,
+    mojom::blink::IDBDatabase::GetKeyGeneratorCurrentNumberCallback callback) {
+  database_remote_->GetKeyGeneratorCurrentNumber(
+      transaction_id, object_store_id, std::move(callback));
+}
+
+void IDBDatabase::Clear(
+    int64_t transaction_id,
+    int64_t object_store_id,
+    mojom::blink::IDBDatabase::ClearCallback success_callback) {
+  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
+  database_remote_->Clear(transaction_id, object_store_id,
+                          std::move(success_callback));
+}
+
+void IDBDatabase::CreateIndex(int64_t transaction_id,
+                              int64_t object_store_id,
+                              int64_t index_id,
+                              const String& name,
+                              const IDBKeyPath& key_path,
+                              bool unique,
+                              bool multi_entry) {
+  database_remote_->CreateIndex(transaction_id, object_store_id, index_id, name,
+                                key_path, unique, multi_entry);
+}
+
+void IDBDatabase::DeleteIndex(int64_t transaction_id,
+                              int64_t object_store_id,
+                              int64_t index_id) {
+  database_remote_->DeleteIndex(transaction_id, object_store_id, index_id);
+}
+
+void IDBDatabase::RenameIndex(int64_t transaction_id,
+                              int64_t object_store_id,
+                              int64_t index_id,
+                              const String& new_name) {
+  DCHECK(!new_name.IsNull());
+  database_remote_->RenameIndex(transaction_id, object_store_id, index_id,
+                                new_name);
+}
+
+void IDBDatabase::Abort(int64_t transaction_id) {
+  if (database_remote_.is_bound()) {
+    database_remote_->Abort(transaction_id);
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_database.h b/third_party/blink/renderer/modules/indexeddb/idb_database.h
index 5ae40a4d..852ea90 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_database.h
+++ b/third_party/blink/renderer/modules/indexeddb/idb_database.h
@@ -42,7 +42,7 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_object_store.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_transaction.h"
 #include "third_party/blink/renderer/modules/indexeddb/indexed_db.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
+#include "third_party/blink/renderer/modules/indexeddb/web_idb_cursor.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -66,10 +66,11 @@
  public:
   IDBDatabase(
       ExecutionContext*,
-      std::unique_ptr<WebIDBDatabase>,
       mojo::PendingAssociatedReceiver<mojom::blink::IDBDatabaseCallbacks>
           callbacks_receiver,
-      mojo::PendingRemote<mojom::blink::ObservedFeature> connection_lifetime);
+      mojo::PendingRemote<mojom::blink::ObservedFeature> connection_lifetime,
+      mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase>
+          pending_database);
 
   void Trace(Visitor*) const override;
 
@@ -145,9 +146,6 @@
   void RevertObjectStoreMetadata(
       scoped_refptr<IDBObjectStoreMetadata> old_metadata);
 
-  // Will return nullptr if this database is stopped.
-  WebIDBDatabase* Backend() const { return backend_.get(); }
-
   static int64_t NextTransactionId();
 
   static const char kIndexDeletedErrorMessage[];
@@ -168,6 +166,90 @@
   static const char kTransactionReadOnlyErrorMessage[];
   static const char kDatabaseClosedErrorMessage[];
 
+  static const int64_t kMinimumIndexId = 30;
+
+  void RenameObjectStore(int64_t transaction_id,
+                         int64_t object_store_id,
+                         const String& new_name) {
+    database_remote_->RenameObjectStore(transaction_id, object_store_id,
+                                        new_name);
+  }
+  void CreateTransaction(mojo::PendingAssociatedReceiver<
+                             mojom::blink::IDBTransaction> transaction_receiver,
+                         int64_t transaction_id,
+                         const Vector<int64_t>& object_store_ids,
+                         mojom::blink::IDBTransactionMode mode,
+                         mojom::blink::IDBTransactionDurability durability) {
+    database_remote_->CreateTransaction(std::move(transaction_receiver),
+                                        transaction_id, object_store_ids, mode,
+                                        durability);
+  }
+  void VersionChangeIgnored() { database_remote_->VersionChangeIgnored(); }
+  void Get(
+      int64_t transaction_id,
+      int64_t object_store_id,
+      int64_t index_id,
+      const IDBKeyRange*,
+      bool key_only,
+      base::OnceCallback<void(mojom::blink::IDBDatabaseGetResultPtr)> result);
+  void GetAll(int64_t transaction_id,
+              int64_t object_store_id,
+              int64_t index_id,
+              const IDBKeyRange*,
+              int64_t max_count,
+              bool key_only,
+              IDBRequest*);
+  void SetIndexKeys(int64_t transaction_id,
+                    int64_t object_store_id,
+                    std::unique_ptr<IDBKey> primary_key,
+                    Vector<IDBIndexKeys>);
+  void SetIndexesReady(int64_t transaction_id,
+                       int64_t object_store_id,
+                       const Vector<int64_t>& index_ids);
+  void OpenCursor(int64_t object_store_id,
+                  int64_t index_id,
+                  const IDBKeyRange*,
+                  mojom::blink::IDBCursorDirection direction,
+                  bool key_only,
+                  mojom::blink::IDBTaskType,
+                  IDBRequest*);
+  void Count(int64_t transaction_id,
+             int64_t object_store_id,
+             int64_t index_id,
+             const IDBKeyRange*,
+             mojom::blink::IDBDatabase::CountCallback callback);
+  void Delete(int64_t transaction_id,
+              int64_t object_store_id,
+              const IDBKey* primary_key,
+              mojom::blink::IDBDatabase::DeleteRangeCallback callback);
+  void DeleteRange(int64_t transaction_id,
+                   int64_t object_store_id,
+                   const IDBKeyRange*,
+                   mojom::blink::IDBDatabase::DeleteRangeCallback callback);
+  void GetKeyGeneratorCurrentNumber(
+      int64_t transaction_id,
+      int64_t object_store_id,
+      mojom::blink::IDBDatabase::GetKeyGeneratorCurrentNumberCallback callback);
+  void Clear(int64_t transaction_id,
+             int64_t object_store_id,
+             mojom::blink::IDBDatabase::ClearCallback callback);
+  void CreateIndex(int64_t transaction_id,
+                   int64_t object_store_id,
+                   int64_t index_id,
+                   const String& name,
+                   const IDBKeyPath&,
+                   bool unique,
+                   bool multi_entry);
+  void DeleteIndex(int64_t transaction_id,
+                   int64_t object_store_id,
+                   int64_t index_id);
+  void RenameIndex(int64_t transaction_id,
+                   int64_t object_store_id,
+                   int64_t index_id,
+                   const String& new_name);
+  void Abort(int64_t transaction_id);
+  void DidBecomeInactive() { database_remote_->DidBecomeInactive(); }
+
  protected:
   // EventTarget
   DispatchEventResult DispatchEventInternal(Event&) override;
@@ -180,7 +262,7 @@
   void CloseConnection();
 
   IDBDatabaseMetadata metadata_;
-  std::unique_ptr<WebIDBDatabase> backend_;
+  HeapMojoAssociatedRemote<mojom::blink::IDBDatabase> database_remote_;
   Member<IDBTransaction> version_change_transaction_;
   HeapHashMap<int64_t, Member<IDBTransaction>> transactions_;
   // No interface here, so no need to bind it.  This is only for
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_factory_client.cc b/third_party/blink/renderer/modules/indexeddb/idb_factory_client.cc
index 9df68e9..7576a81 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_factory_client.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_factory_client.cc
@@ -40,7 +40,6 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_open_db_request.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_value.h"
 #include "third_party/blink/renderer/modules/indexeddb/web_idb_cursor.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -96,20 +95,18 @@
 void IDBFactoryClient::OpenSuccess(
     mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase> pending_database,
     const IDBDatabaseMetadata& metadata) {
-  std::unique_ptr<WebIDBDatabase> db;
-  if (pending_database.is_valid()) {
-    db = std::make_unique<WebIDBDatabase>(std::move(pending_database),
-                                          task_runner_);
+  if (!request_) {
+    return;
   }
-  if (request_) {
+
 #if DCHECK_IS_ON()
     DCHECK(!request_->TransactionHasQueuedResults());
 #endif  // DCHECK_IS_ON()
     IDBOpenDBRequest* request = request_.Get();
     Detach();
-    request->OnOpenDBSuccess(std::move(db), IDBDatabaseMetadata(metadata));
+    request->OnOpenDBSuccess(std::move(pending_database), task_runner_,
+                             IDBDatabaseMetadata(metadata));
     // `this` may be deleted because event dispatch can run a nested loop.
-  }
 }
 
 void IDBFactoryClient::DeleteSuccess(int64_t old_version) {
@@ -144,23 +141,20 @@
     mojom::blink::IDBDataLoss data_loss,
     const String& data_loss_message,
     const IDBDatabaseMetadata& metadata) {
-  std::unique_ptr<WebIDBDatabase> db;
-  if (pending_database.is_valid()) {
-    db = std::make_unique<WebIDBDatabase>(std::move(pending_database),
-                                          task_runner_);
+  if (!request_) {
+    return;
   }
-  if (request_) {
+
 #if DCHECK_IS_ON()
     DCHECK(!request_->TransactionHasQueuedResults());
 #endif  // DCHECK_IS_ON()
-    request_->OnUpgradeNeeded(old_version, std::move(db),
-                              IDBDatabaseMetadata(metadata), data_loss,
-                              data_loss_message);
+    request_->OnUpgradeNeeded(old_version, std::move(pending_database),
+                              task_runner_, IDBDatabaseMetadata(metadata),
+                              data_loss, data_loss_message);
     // `this` may be deleted because event dispatch can run a nested loop.
     // Not resetting |request_|.  In this instance we will have to forward at
     // least one other call in the set UpgradeNeeded() / OpenSuccess() /
     // Error().
-  }
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_index.cc b/third_party/blink/renderer/modules/indexeddb/idb_index.cc
index 9d601ea..1b5e2f1 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_index.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_index.cc
@@ -92,7 +92,7 @@
                                       IDBDatabase::kIndexNameTakenErrorMessage);
     return;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return;
@@ -139,7 +139,7 @@
   if (exception_state.HadException())
     return nullptr;
 
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -155,8 +155,8 @@
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
   request->SetCursorDetails(indexed_db::kCursorKeyAndValue, direction);
-  BackendDB()->OpenCursor(object_store_->Id(), Id(), key_range, direction,
-                          false, mojom::IDBTaskType::Normal, request);
+  db()->OpenCursor(object_store_->Id(), Id(), key_range, direction, false,
+                   mojom::IDBTaskType::Normal, request);
   return request;
 }
 
@@ -183,7 +183,7 @@
   if (exception_state.HadException())
     return nullptr;
 
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -191,9 +191,8 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->Count(
-      transaction_->Id(), object_store_->Id(), Id(), key_range,
-      WTF::BindOnce(&IDBRequest::OnCount, WrapWeakPersistent(request)));
+  db()->Count(transaction_->Id(), object_store_->Id(), Id(), key_range,
+              WTF::BindOnce(&IDBRequest::OnCount, WrapWeakPersistent(request)));
   return request;
 }
 
@@ -222,7 +221,7 @@
       ExecutionContext::From(script_state), range, exception_state);
   if (exception_state.HadException())
     return nullptr;
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -231,8 +230,8 @@
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
   request->SetCursorDetails(indexed_db::kCursorKeyOnly, direction);
-  BackendDB()->OpenCursor(object_store_->Id(), Id(), key_range, direction, true,
-                          mojom::IDBTaskType::Normal, request);
+  db()->OpenCursor(object_store_->Id(), Id(), key_range, direction, true,
+                   mojom::IDBTaskType::Normal, request);
   return request;
 }
 
@@ -320,16 +319,15 @@
         IDBDatabase::kNoKeyOrKeyRangeErrorMessage);
     return nullptr;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
   }
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->Get(transaction_->Id(), object_store_->Id(), Id(), key_range,
-                   key_only,
-                   WTF::BindOnce(&IDBRequest::OnGet, WrapPersistent(request)));
+  db()->Get(transaction_->Id(), object_store_->Id(), Id(), key_range, key_only,
+            WTF::BindOnce(&IDBRequest::OnGet, WrapPersistent(request)));
   return request;
 }
 
@@ -358,7 +356,7 @@
       ExecutionContext::From(script_state), range, exception_state);
   if (exception_state.HadException())
     return nullptr;
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -366,13 +364,13 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->GetAll(transaction_->Id(), object_store_->Id(), Id(), key_range,
-                      max_count, key_only, request);
+  db()->GetAll(transaction_->Id(), object_store_->Id(), Id(), key_range,
+               max_count, key_only, request);
   return request;
 }
 
-WebIDBDatabase* IDBIndex::BackendDB() const {
-  return transaction_->BackendDB();
+IDBDatabase* IDBIndex::db() const {
+  return transaction_->db();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_index.h b/third_party/blink/renderer/modules/indexeddb/idb_index.h
index 2b30a54..a3d56f9 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_index.h
+++ b/third_party/blink/renderer/modules/indexeddb/idb_index.h
@@ -33,7 +33,6 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_metadata.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_request.h"
 #include "third_party/blink/renderer/modules/indexeddb/web_idb_cursor.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -120,7 +119,7 @@
       mojom::IDBCursorDirection,
       IDBRequest::AsyncTraceState = IDBRequest::AsyncTraceState());
 
-  WebIDBDatabase* BackendDB() const;
+  IDBDatabase* db() const;
 
  private:
   const IDBIndexMetadata& Metadata() const { return *metadata_; }
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc b/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
index a4a6df0..0b7a56c7 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_object_store.cc
@@ -51,7 +51,6 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_key_path.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.h"
 #include "third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
@@ -104,7 +103,7 @@
         IDBDatabase::kObjectStoreNameTakenErrorMessage);
     return;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return;
@@ -156,7 +155,7 @@
         IDBDatabase::kNoKeyOrKeyRangeErrorMessage);
     return nullptr;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -164,10 +163,9 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->Get(
-      transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId, key_range,
-      /*key_only=*/false,
-      WTF::BindOnce(&IDBRequest::OnGet, WrapWeakPersistent(request)));
+  db()->Get(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId, key_range,
+            /*key_only=*/false,
+            WTF::BindOnce(&IDBRequest::OnGet, WrapWeakPersistent(request)));
   return request;
 }
 
@@ -200,7 +198,7 @@
         IDBDatabase::kNoKeyOrKeyRangeErrorMessage);
     return nullptr;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -208,9 +206,9 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->Get(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId,
-                   key_range, /*key_only=*/true,
-                   WTF::BindOnce(&IDBRequest::OnGet, WrapPersistent(request)));
+  db()->Get(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId, key_range,
+            /*key_only=*/true,
+            WTF::BindOnce(&IDBRequest::OnGet, WrapPersistent(request)));
   return request;
 }
 
@@ -248,7 +246,7 @@
       ExecutionContext::From(script_state), key_range, exception_state);
   if (exception_state.HadException())
     return nullptr;
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -256,8 +254,8 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->GetAll(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId,
-                      range, max_count, false, request);
+  db()->GetAll(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId, range,
+               max_count, false, request);
   return request;
 }
 
@@ -295,7 +293,7 @@
       ExecutionContext::From(script_state), key_range, exception_state);
   if (exception_state.HadException())
     return nullptr;
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -303,8 +301,8 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->GetAll(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId,
-                      range, max_count, true, request);
+  db()->GetAll(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId, range,
+               max_count, true, request);
   return request;
 }
 
@@ -567,7 +565,7 @@
     return nullptr;
   }
 
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -643,7 +641,7 @@
         IDBDatabase::kNoKeyOrKeyRangeErrorMessage);
     return nullptr;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -658,7 +656,7 @@
     IDBRequest::AsyncTraceState metrics) {
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->DeleteRange(
+  db()->DeleteRange(
       transaction_->Id(), Id(), key_range,
       WTF::BindOnce(&IDBRequest::OnDelete, WrapPersistent(request)));
   return request;
@@ -670,7 +668,7 @@
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
 
-  BackendDB()->GetKeyGeneratorCurrentNumber(
+  db()->GetKeyGeneratorCurrentNumber(
       transaction_->Id(), Id(),
       WTF::BindOnce(&IDBRequest::OnGotKeyGeneratorCurrentNumber,
                     WrapWeakPersistent(request)));
@@ -700,7 +698,7 @@
         IDBDatabase::kTransactionReadOnlyErrorMessage);
     return nullptr;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -708,9 +706,8 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->Clear(
-      transaction_->Id(), Id(),
-      WTF::BindOnce(&IDBRequest::OnClear, WrapPersistent(request)));
+  db()->Clear(transaction_->Id(), Id(),
+              WTF::BindOnce(&IDBRequest::OnClear, WrapPersistent(request)));
   return request;
 }
 
@@ -759,8 +756,9 @@
     EventTarget* target = event->target();
     IDBRequest* request = static_cast<IDBRequest*>(target);
 
-    if (!database_->Backend())  // If database is stopped?
+    if (!database_) {  // If database is stopped?
       return;
+    }
 
     ScriptState::Scope scope(script_state_);
 
@@ -784,16 +782,15 @@
                                             ObjectStoreMetadata(),
                                             IndexMetadata(), value)});
 
-      database_->Backend()->SetIndexKeys(transaction_id_, object_store_id_,
-                                         IDBKey::Clone(primary_key),
-                                         std::move(index_keys));
+      database_->SetIndexKeys(transaction_id_, object_store_id_,
+                              IDBKey::Clone(primary_key),
+                              std::move(index_keys));
     } else {
       // Now that we are done indexing, tell the backend to go
       // back to processing tasks of type NormalTask.
       Vector<int64_t> index_ids;
       index_ids.push_back(IndexMetadata().id);
-      database_->Backend()->SetIndexesReady(transaction_id_, object_store_id_,
-                                            index_ids);
+      database_->SetIndexesReady(transaction_id_, object_store_id_, index_ids);
       database_.Clear();
     }
   }
@@ -852,7 +849,7 @@
         "The keyPath argument was an array and the multiEntry option is true.");
     return nullptr;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -860,8 +857,8 @@
 
   int64_t index_id = metadata_->max_index_id + 1;
   DCHECK_NE(index_id, IDBIndexMetadata::kInvalidId);
-  BackendDB()->CreateIndex(transaction_->Id(), Id(), index_id, name, key_path,
-                           options->unique(), options->multiEntry());
+  db()->CreateIndex(transaction_->Id(), Id(), index_id, name, key_path,
+                    options->unique(), options->multiEntry());
 
   ++metadata_->max_index_id;
 
@@ -957,13 +954,13 @@
                                       IDBDatabase::kNoSuchIndexErrorMessage);
     return;
   }
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return;
   }
 
-  BackendDB()->DeleteIndex(transaction_->Id(), Id(), index_id);
+  db()->DeleteIndex(transaction_->Id(), Id(), index_id);
 
   metadata_->indexes.erase(index_id);
   IDBIndexMap::iterator it = index_map_.find(name);
@@ -1002,7 +999,7 @@
   if (exception_state.HadException())
     return nullptr;
 
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -1021,8 +1018,8 @@
       script_state, this, transaction_.Get(), std::move(metrics));
   request->SetCursorDetails(indexed_db::kCursorKeyAndValue, direction);
 
-  BackendDB()->OpenCursor(Id(), IDBIndexMetadata::kInvalidId, range, direction,
-                          false, task_type, request);
+  db()->OpenCursor(Id(), IDBIndexMetadata::kInvalidId, range, direction, false,
+                   task_type, request);
   return request;
 }
 
@@ -1054,7 +1051,7 @@
   if (exception_state.HadException())
     return nullptr;
 
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -1064,8 +1061,8 @@
       script_state, this, transaction_.Get(), std::move(metrics));
   request->SetCursorDetails(indexed_db::kCursorKeyOnly, direction);
 
-  BackendDB()->OpenCursor(Id(), IDBIndexMetadata::kInvalidId, key_range,
-                          direction, true, mojom::IDBTaskType::Normal, request);
+  db()->OpenCursor(Id(), IDBIndexMetadata::kInvalidId, key_range, direction,
+                   true, mojom::IDBTaskType::Normal, request);
   return request;
 }
 
@@ -1094,7 +1091,7 @@
   if (exception_state.HadException())
     return nullptr;
 
-  if (!BackendDB()) {
+  if (!db()) {
     exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
                                       IDBDatabase::kDatabaseClosedErrorMessage);
     return nullptr;
@@ -1102,9 +1099,8 @@
 
   IDBRequest* request = IDBRequest::Create(
       script_state, this, transaction_.Get(), std::move(metrics));
-  BackendDB()->Count(
-      transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId, key_range,
-      WTF::BindOnce(&IDBRequest::OnCount, WrapWeakPersistent(request)));
+  db()->Count(transaction_->Id(), Id(), IDBIndexMetadata::kInvalidId, key_range,
+              WTF::BindOnce(&IDBRequest::OnCount, WrapWeakPersistent(request)));
   return request;
 }
 
@@ -1186,7 +1182,7 @@
   DCHECK(transaction_->IsVersionChange());
   DCHECK(transaction_->IsActive());
 
-  BackendDB()->RenameIndex(transaction_->Id(), Id(), index_id, new_name);
+  db()->RenameIndex(transaction_->Id(), Id(), index_id, new_name);
 
   auto metadata_iterator = metadata_->indexes.find(index_id);
   DCHECK_NE(metadata_iterator, metadata_->indexes.end()) << "Invalid index_id";
@@ -1210,8 +1206,8 @@
   return IDBIndexMetadata::kInvalidId;
 }
 
-WebIDBDatabase* IDBObjectStore::BackendDB() const {
-  return transaction_->BackendDB();
+IDBDatabase* IDBObjectStore::db() const {
+  return transaction_->db();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_object_store.h b/third_party/blink/renderer/modules/indexeddb/idb_object_store.h
index d37fa20..f159205 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_object_store.h
+++ b/third_party/blink/renderer/modules/indexeddb/idb_object_store.h
@@ -38,7 +38,6 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_request.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_transaction.h"
 #include "third_party/blink/renderer/modules/indexeddb/web_idb_cursor.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -191,7 +190,7 @@
   }
   void RenameIndex(int64_t index_id, const String& new_name);
 
-  WebIDBDatabase* BackendDB() const;
+  IDBDatabase* db() const;
 
  private:
   using IDBIndexMap = HeapHashMap<String, Member<IDBIndex>>;
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc b/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc
index 6da5506..ab00408 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.cc
@@ -106,11 +106,13 @@
       event_type_names::kBlocked, old_version, new_version_nullable));
 }
 
-void IDBOpenDBRequest::OnUpgradeNeeded(int64_t old_version,
-                                       std::unique_ptr<WebIDBDatabase> backend,
-                                       const IDBDatabaseMetadata& metadata,
-                                       mojom::blink::IDBDataLoss data_loss,
-                                       String data_loss_message) {
+void IDBOpenDBRequest::OnUpgradeNeeded(
+    int64_t old_version,
+    mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase> pending_database,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    const IDBDatabaseMetadata& metadata,
+    mojom::blink::IDBDataLoss data_loss,
+    String data_loss_message) {
   TRACE_EVENT0("IndexedDB", "IDBOpenDBRequest::onUpgradeNeeded()");
   probe::AsyncTask async_task(GetExecutionContext(), async_task_context(),
                               "upgradeNeeded");
@@ -122,8 +124,8 @@
   DCHECK(callbacks_receiver_);
 
   auto* idb_database = MakeGarbageCollected<IDBDatabase>(
-      GetExecutionContext(), std::move(backend), std::move(callbacks_receiver_),
-      std::move(connection_lifetime_));
+      GetExecutionContext(), std::move(callbacks_receiver_),
+      std::move(connection_lifetime_), std::move(pending_database));
   idb_database->SetMetadata(metadata);
 
   if (old_version == IDBDatabaseMetadata::kNoVersion) {
@@ -146,8 +148,10 @@
       data_loss_message));
 }
 
-void IDBOpenDBRequest::OnOpenDBSuccess(std::unique_ptr<WebIDBDatabase> backend,
-                                       const IDBDatabaseMetadata& metadata) {
+void IDBOpenDBRequest::OnOpenDBSuccess(
+    mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase> pending_database,
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    const IDBDatabaseMetadata& metadata) {
   TRACE_EVENT0("IndexedDB", "IDBOpenDBRequest::onSuccess(database)");
   probe::AsyncTask async_task(GetExecutionContext(), async_task_context(),
                               "success");
@@ -159,17 +163,17 @@
 
   IDBDatabase* idb_database = nullptr;
   if (ResultAsAny()) {
-    // Previous OnUpgradeNeeded call delivered the backend.
-    DCHECK(!backend.get());
+    DCHECK(!pending_database.is_valid());
     idb_database = ResultAsAny()->IdbDatabase();
     DCHECK(idb_database);
     DCHECK(!callbacks_receiver_);
   } else {
-    DCHECK(backend.get());
+    DCHECK(pending_database);
     DCHECK(callbacks_receiver_);
+
     idb_database = MakeGarbageCollected<IDBDatabase>(
-        GetExecutionContext(), std::move(backend),
-        std::move(callbacks_receiver_), std::move(connection_lifetime_));
+        GetExecutionContext(), std::move(callbacks_receiver_),
+        std::move(connection_lifetime_), std::move(pending_database));
     SetResult(MakeGarbageCollected<IDBAny>(idb_database));
   }
   idb_database->SetMetadata(metadata);
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.h b/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.h
index 2877cfa..5b75daaeb 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.h
+++ b/third_party/blink/renderer/modules/indexeddb/idb_open_db_request.h
@@ -35,7 +35,6 @@
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_request.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_transaction.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 
 namespace blink {
@@ -68,11 +67,13 @@
   // request cannot be issued after a request that needs processing.
   void OnBlocked(int64_t existing_version);
   void OnUpgradeNeeded(int64_t old_version,
-                       std::unique_ptr<WebIDBDatabase>,
+                       mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase>,
+                       scoped_refptr<base::SingleThreadTaskRunner>,
                        const IDBDatabaseMetadata&,
                        mojom::blink::IDBDataLoss,
                        String data_loss_message);
-  void OnOpenDBSuccess(std::unique_ptr<WebIDBDatabase>,
+  void OnOpenDBSuccess(mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase>,
+                       scoped_refptr<base::SingleThreadTaskRunner>,
                        const IDBDatabaseMetadata&);
   void OnDeleteDBSuccess(int64_t old_version);
   void OnDBFactoryError(DOMException*);
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_request_test.cc b/third_party/blink/renderer/modules/indexeddb/idb_request_test.cc
index eddf3aec..7ad9ad8 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_request_test.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_request_test.cc
@@ -61,7 +61,6 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.h"
 #include "third_party/blink/renderer/modules/indexeddb/mock_idb_database.h"
 #include "third_party/blink/renderer/modules/indexeddb/mock_idb_transaction.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/testing/task_environment.h"
@@ -188,14 +187,12 @@
   void BuildTransaction(V8TestingScope& scope,
                         MockIDBDatabase& mock_database,
                         MockIDBTransaction& mock_transaction_remote) {
-    auto database_backend = std::make_unique<WebIDBDatabase>(
-        mock_database.BindNewEndpointAndPassDedicatedRemote(),
-        blink::scheduler::GetSingleThreadTaskRunnerForTesting());
-    db_ = MakeGarbageCollected<IDBDatabase>(
-        scope.GetExecutionContext(), std::move(database_backend),
-        mojo::NullAssociatedReceiver(), mojo::NullRemote());
-
     auto* execution_context = scope.GetExecutionContext();
+
+    db_ = MakeGarbageCollected<IDBDatabase>(
+        execution_context, mojo::NullAssociatedReceiver(), mojo::NullRemote(),
+        mock_database.BindNewEndpointAndPassDedicatedRemote());
+
     IDBTransaction::TransactionMojoRemote transaction_remote(execution_context);
     mojo::PendingAssociatedReceiver<mojom::blink::IDBTransaction> receiver =
         transaction_remote.BindNewEndpointAndPassReceiver(
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_transaction.cc b/third_party/blink/renderer/modules/indexeddb/idb_transaction.cc
index c2a879c..822387f 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_transaction.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_transaction.cc
@@ -493,8 +493,8 @@
   // due to a constraint error), we're already asynchronous.
   AbortOutstandingRequests(/*queue_tasks=*/from_frontend);
 
-  if (from_frontend && BackendDB()) {
-    BackendDB()->Abort(id_);
+  if (from_frontend && database_) {
+    database_->Abort(id_);
   }
 }
 
@@ -585,10 +585,6 @@
   return mojom::blink::IDBTransactionMode::ReadOnly;
 }
 
-WebIDBDatabase* IDBTransaction::BackendDB() const {
-  return database_->Backend();
-}
-
 const String& IDBTransaction::mode() const {
   switch (mode_) {
     case mojom::blink::IDBTransactionMode::ReadOnly:
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_transaction.h b/third_party/blink/renderer/modules/indexeddb/idb_transaction.h
index b35a260d..5e10e466 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_transaction.h
+++ b/third_party/blink/renderer/modules/indexeddb/idb_transaction.h
@@ -30,6 +30,7 @@
 
 #include "base/dcheck_is_on.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink-forward.h"
+#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/core/dom/dom_string_list.h"
 #include "third_party/blink/renderer/core/dom/events/event_listener.h"
@@ -38,7 +39,6 @@
 #include "third_party/blink/renderer/modules/event_target_modules.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_metadata.h"
 #include "third_party/blink/renderer/modules/indexeddb/indexed_db.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_map.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_linked_hash_set.h"
@@ -112,9 +112,6 @@
 
   static mojom::blink::IDBTransactionMode StringToMode(const String&);
 
-  // When the connection is closed backend will be 0.
-  WebIDBDatabase* BackendDB() const;
-
   int64_t Id() const { return id_; }
   bool IsActive() const { return state_ == kActive; }
   bool IsFinished() const { return state_ == kFinished; }
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_transaction_test.cc b/third_party/blink/renderer/modules/indexeddb/idb_transaction_test.cc
index 999c8c52..1ac0789d 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_transaction_test.cc
+++ b/third_party/blink/renderer/modules/indexeddb/idb_transaction_test.cc
@@ -54,7 +54,6 @@
 #include "third_party/blink/renderer/modules/indexeddb/idb_value_wrapping.h"
 #include "third_party/blink/renderer/modules/indexeddb/mock_idb_database.h"
 #include "third_party/blink/renderer/modules/indexeddb/mock_idb_transaction.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
 #include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/testing/task_environment.h"
@@ -85,14 +84,12 @@
   void BuildTransaction(V8TestingScope& scope,
                         MockIDBDatabase& mock_database,
                         MockIDBTransaction& mock_transaction_remote) {
-    auto database_backend = std::make_unique<WebIDBDatabase>(
-        mock_database.BindNewEndpointAndPassDedicatedRemote(),
-        blink::scheduler::GetSingleThreadTaskRunnerForTesting());
-    db_ = MakeGarbageCollected<IDBDatabase>(
-        scope.GetExecutionContext(), std::move(database_backend),
-        mojo::NullAssociatedReceiver(), mojo::NullRemote());
-
     auto* execution_context = scope.GetExecutionContext();
+
+    db_ = MakeGarbageCollected<IDBDatabase>(
+        execution_context, mojo::NullAssociatedReceiver(), mojo::NullRemote(),
+        mock_database.BindNewEndpointAndPassDedicatedRemote());
+
     IDBTransaction::TransactionMojoRemote transaction_remote(execution_context);
     mojo::PendingAssociatedReceiver<mojom::blink::IDBTransaction> receiver =
         transaction_remote.BindNewEndpointAndPassReceiver(
diff --git a/third_party/blink/renderer/modules/indexeddb/mock_idb_database.cc b/third_party/blink/renderer/modules/indexeddb/mock_idb_database.cc
index f66311c..162f17a7 100644
--- a/third_party/blink/renderer/modules/indexeddb/mock_idb_database.cc
+++ b/third_party/blink/renderer/modules/indexeddb/mock_idb_database.cc
@@ -17,10 +17,8 @@
 mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase>
 MockIDBDatabase::BindNewEndpointAndPassDedicatedRemote() {
   auto remote = receiver_.BindNewEndpointAndPassDedicatedRemote();
-
   receiver_.set_disconnect_handler(
       WTF::BindOnce(&MockIDBDatabase::OnDisconnect, base::Unretained(this)));
-
   return remote;
 }
 
diff --git a/third_party/blink/renderer/modules/indexeddb/web_idb_database.cc b/third_party/blink/renderer/modules/indexeddb/web_idb_database.cc
deleted file mode 100644
index ec7a9c1..0000000
--- a/third_party/blink/renderer/modules/indexeddb/web_idb_database.cc
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright 2013 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_database.h"
-
-#include <utility>
-
-#include "base/format_macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/task/single_thread_task_runner.h"
-#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
-#include "mojo/public/cpp/bindings/self_owned_receiver.h"
-#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink.h"
-#include "third_party/blink/renderer/modules/indexeddb/idb_database_error.h"
-#include "third_party/blink/renderer/modules/indexeddb/idb_key_range.h"
-#include "third_party/blink/renderer/modules/indexeddb/idb_request.h"
-#include "third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits.h"
-#include "third_party/blink/renderer/modules/indexeddb/indexed_db_dispatcher.h"
-#include "third_party/blink/renderer/platform/heap/persistent.h"
-#include "third_party/blink/renderer/platform/wtf/functional.h"
-#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
-
-namespace blink {
-
-WebIDBDatabase::WebIDBDatabase(
-    mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase> pending_database,
-    scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : task_runner_(std::move(task_runner)) {
-  database_.Bind(std::move(pending_database), task_runner_);
-}
-
-WebIDBDatabase::~WebIDBDatabase() = default;
-
-void WebIDBDatabase::RenameObjectStore(int64_t transaction_id,
-                                       int64_t object_store_id,
-                                       const String& new_name) {
-  database_->RenameObjectStore(transaction_id, object_store_id, new_name);
-}
-
-void WebIDBDatabase::CreateTransaction(
-    mojo::PendingAssociatedReceiver<mojom::blink::IDBTransaction>
-        transaction_receiver,
-    int64_t transaction_id,
-    const Vector<int64_t>& object_store_ids,
-    mojom::blink::IDBTransactionMode mode,
-    mojom::blink::IDBTransactionDurability durability) {
-  database_->CreateTransaction(std::move(transaction_receiver), transaction_id,
-                               object_store_ids, mode, durability);
-}
-
-void WebIDBDatabase::VersionChangeIgnored() {
-  database_->VersionChangeIgnored();
-}
-
-void WebIDBDatabase::Get(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    int64_t index_id,
-    const IDBKeyRange* key_range,
-    bool key_only,
-    base::OnceCallback<void(mojom::blink::IDBDatabaseGetResultPtr)>
-        result_callback) {
-  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
-
-  mojom::blink::IDBKeyRangePtr key_range_ptr =
-      mojom::blink::IDBKeyRange::From(key_range);
-  database_->Get(transaction_id, object_store_id, index_id,
-                 std::move(key_range_ptr), key_only,
-                 std::move(result_callback));
-}
-
-void WebIDBDatabase::GetAll(int64_t transaction_id,
-                            int64_t object_store_id,
-                            int64_t index_id,
-                            const IDBKeyRange* key_range,
-                            int64_t max_count,
-                            bool key_only,
-                            IDBRequest* request) {
-  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
-
-  mojom::blink::IDBKeyRangePtr key_range_ptr =
-      mojom::blink::IDBKeyRange::From(key_range);
-  database_->GetAll(transaction_id, object_store_id, index_id,
-                    std::move(key_range_ptr), key_only, max_count,
-                    WTF::BindOnce(&IDBRequest::OnGetAll,
-                                  WrapWeakPersistent(request), key_only));
-}
-
-void WebIDBDatabase::SetIndexKeys(int64_t transaction_id,
-                                  int64_t object_store_id,
-                                  std::unique_ptr<IDBKey> primary_key,
-                                  Vector<IDBIndexKeys> index_keys) {
-  database_->SetIndexKeys(transaction_id, object_store_id,
-                          std::move(primary_key), std::move(index_keys));
-}
-
-void WebIDBDatabase::SetIndexesReady(int64_t transaction_id,
-                                     int64_t object_store_id,
-                                     const Vector<int64_t>& index_ids) {
-  database_->SetIndexesReady(transaction_id, object_store_id,
-                             std::move(index_ids));
-}
-
-void WebIDBDatabase::OpenCursor(int64_t object_store_id,
-                                int64_t index_id,
-                                const IDBKeyRange* key_range,
-                                mojom::blink::IDBCursorDirection direction,
-                                bool key_only,
-                                mojom::blink::IDBTaskType task_type,
-                                IDBRequest* request) {
-  IndexedDBDispatcher::ResetCursorPrefetchCaches(request->transaction()->Id(),
-                                                 nullptr);
-
-  mojom::blink::IDBKeyRangePtr key_range_ptr =
-      mojom::blink::IDBKeyRange::From(key_range);
-  database_->OpenCursor(
-      request->transaction()->Id(), object_store_id, index_id,
-      std::move(key_range_ptr), direction, key_only, task_type,
-      WTF::BindOnce(&IDBRequest::OnOpenCursor, WrapWeakPersistent(request)));
-}
-
-void WebIDBDatabase::Count(int64_t transaction_id,
-                           int64_t object_store_id,
-                           int64_t index_id,
-                           const IDBKeyRange* key_range,
-                           mojom::blink::IDBDatabase::CountCallback callback) {
-  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
-
-  database_->Count(transaction_id, object_store_id, index_id,
-                   mojom::blink::IDBKeyRange::From(key_range),
-                   std::move(callback));
-}
-
-void WebIDBDatabase::Delete(int64_t transaction_id,
-                            int64_t object_store_id,
-                            const IDBKey* primary_key,
-                            base::OnceCallback<void(bool)> success_callback) {
-  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
-
-  mojom::blink::IDBKeyRangePtr key_range_ptr =
-      mojom::blink::IDBKeyRange::From(IDBKeyRange::Create(primary_key));
-  database_->DeleteRange(transaction_id, object_store_id,
-                         std::move(key_range_ptr), std::move(success_callback));
-}
-
-void WebIDBDatabase::DeleteRange(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    const IDBKeyRange* key_range,
-    base::OnceCallback<void(bool)> success_callback) {
-  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
-
-  mojom::blink::IDBKeyRangePtr key_range_ptr =
-      mojom::blink::IDBKeyRange::From(key_range);
-  database_->DeleteRange(transaction_id, object_store_id,
-                         std::move(key_range_ptr), std::move(success_callback));
-}
-
-void WebIDBDatabase::GetKeyGeneratorCurrentNumber(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    mojom::blink::IDBDatabase::GetKeyGeneratorCurrentNumberCallback callback) {
-  database_->GetKeyGeneratorCurrentNumber(transaction_id, object_store_id,
-                                          std::move(callback));
-}
-
-void WebIDBDatabase::Clear(
-    int64_t transaction_id,
-    int64_t object_store_id,
-    mojom::blink::IDBDatabase::ClearCallback success_callback) {
-  IndexedDBDispatcher::ResetCursorPrefetchCaches(transaction_id, nullptr);
-  database_->Clear(transaction_id, object_store_id,
-                   std::move(success_callback));
-}
-
-void WebIDBDatabase::CreateIndex(int64_t transaction_id,
-                                 int64_t object_store_id,
-                                 int64_t index_id,
-                                 const String& name,
-                                 const IDBKeyPath& key_path,
-                                 bool unique,
-                                 bool multi_entry) {
-  database_->CreateIndex(transaction_id, object_store_id, index_id, name,
-                         key_path, unique, multi_entry);
-}
-
-void WebIDBDatabase::DeleteIndex(int64_t transaction_id,
-                                 int64_t object_store_id,
-                                 int64_t index_id) {
-  database_->DeleteIndex(transaction_id, object_store_id, index_id);
-}
-
-void WebIDBDatabase::RenameIndex(int64_t transaction_id,
-                                 int64_t object_store_id,
-                                 int64_t index_id,
-                                 const String& new_name) {
-  DCHECK(!new_name.IsNull());
-  database_->RenameIndex(transaction_id, object_store_id, index_id, new_name);
-}
-
-void WebIDBDatabase::Abort(int64_t transaction_id) {
-  database_->Abort(transaction_id);
-}
-
-void WebIDBDatabase::DidBecomeInactive() {
-  database_->DidBecomeInactive();
-}
-
-}  // namespace blink
diff --git a/third_party/blink/renderer/modules/indexeddb/web_idb_database.h b/third_party/blink/renderer/modules/indexeddb/web_idb_database.h
deleted file mode 100644
index 7ac0ace8..0000000
--- a/third_party/blink/renderer/modules/indexeddb/web_idb_database.h
+++ /dev/null
@@ -1,115 +0,0 @@
-// Copyright 2013 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_WEB_IDB_DATABASE_H_
-#define THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_WEB_IDB_DATABASE_H_
-
-#include <stdint.h>
-#include <memory>
-
-#include "base/task/single_thread_task_runner.h"
-#include "mojo/public/cpp/bindings/associated_remote.h"
-#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
-#include "mojo/public/cpp/bindings/pending_associated_remote.h"
-#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink.h"
-#include "third_party/blink/renderer/modules/indexeddb/web_idb_cursor.h"
-#include "third_party/blink/renderer/modules/modules_export.h"
-
-namespace blink {
-class IDBRequest;
-
-class MODULES_EXPORT WebIDBDatabase final {
- public:
-  WebIDBDatabase(
-      mojo::PendingAssociatedRemote<mojom::blink::IDBDatabase> pending_database,
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner);
-  ~WebIDBDatabase();
-
-  static const int64_t kMinimumIndexId = 30;
-
-  void RenameObjectStore(int64_t transaction_id,
-                         int64_t object_store_id,
-                         const String& new_name);
-  void CreateTransaction(mojo::PendingAssociatedReceiver<
-                             mojom::blink::IDBTransaction> transaction_receiver,
-                         int64_t transaction_id,
-                         const Vector<int64_t>& scope,
-                         mojom::blink::IDBTransactionMode mode,
-                         mojom::blink::IDBTransactionDurability durability);
-
-  void VersionChangeIgnored();
-
-  void Get(
-      int64_t transaction_id,
-      int64_t object_store_id,
-      int64_t index_id,
-      const IDBKeyRange*,
-      bool key_only,
-      base::OnceCallback<void(mojom::blink::IDBDatabaseGetResultPtr)> result);
-  void GetAll(int64_t transaction_id,
-              int64_t object_store_id,
-              int64_t index_id,
-              const IDBKeyRange*,
-              int64_t max_count,
-              bool key_only,
-              IDBRequest*);
-  void SetIndexKeys(int64_t transaction_id,
-                    int64_t object_store_id,
-                    std::unique_ptr<IDBKey> primary_key,
-                    Vector<IDBIndexKeys>);
-  void SetIndexesReady(int64_t transaction_id,
-                       int64_t object_store_id,
-                       const Vector<int64_t>& index_ids);
-  void OpenCursor(int64_t object_store_id,
-                  int64_t index_id,
-                  const IDBKeyRange*,
-                  mojom::blink::IDBCursorDirection direction,
-                  bool key_only,
-                  mojom::blink::IDBTaskType,
-                  IDBRequest*);
-  void Count(int64_t transaction_id,
-             int64_t object_store_id,
-             int64_t index_id,
-             const IDBKeyRange*,
-             mojom::blink::IDBDatabase::CountCallback callback);
-  void Delete(int64_t transaction_id,
-              int64_t object_store_id,
-              const IDBKey* primary_key,
-              mojom::blink::IDBDatabase::DeleteRangeCallback callback);
-  void DeleteRange(int64_t transaction_id,
-                   int64_t object_store_id,
-                   const IDBKeyRange*,
-                   mojom::blink::IDBDatabase::DeleteRangeCallback callback);
-  void GetKeyGeneratorCurrentNumber(
-      int64_t transaction_id,
-      int64_t object_store_id,
-      mojom::blink::IDBDatabase::GetKeyGeneratorCurrentNumberCallback callback);
-  void Clear(int64_t transaction_id,
-             int64_t object_store_id,
-             mojom::blink::IDBDatabase::ClearCallback callback);
-  void CreateIndex(int64_t transaction_id,
-                   int64_t object_store_id,
-                   int64_t index_id,
-                   const String& name,
-                   const IDBKeyPath&,
-                   bool unique,
-                   bool multi_entry);
-  void DeleteIndex(int64_t transaction_id,
-                   int64_t object_store_id,
-                   int64_t index_id);
-  void RenameIndex(int64_t transaction_id,
-                   int64_t object_store_id,
-                   int64_t index_id,
-                   const String& new_name);
-  void Abort(int64_t transaction_id);
-  void DidBecomeInactive();
-
- private:
-  mojo::AssociatedRemote<mojom::blink::IDBDatabase> database_;
-  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-};
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_INDEXEDDB_WEB_IDB_DATABASE_H_
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 1f8ce82a..492b4b9 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -667,7 +667,7 @@
     },
     {
       name: "ClipboardWellFormedHtmlSanitizationWrite",
-      status: "stable",
+      status: "experimental",
     },
     {
       // https://drafts.fxtf.org/css-masking/#typedef-geometry-box
@@ -2191,6 +2191,10 @@
       settable_from_internals: true,
     },
     {
+      name: "LayoutAlignForPositioned",
+      status: "stable",
+    },
+    {
       name: "LayoutFlexNewRowAlgorithmV3",
       status: "test",
     },
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-htb.html
new file mode 100644
index 0000000..786cec7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-htb.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-vlr.html
new file mode 100644
index 0000000..917e50d5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-vlr.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-vrl.html
new file mode 100644
index 0000000..b60d4615
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-ltr-vrl.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-htb.html
new file mode 100644
index 0000000..9bf919dd4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-htb.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-vlr.html
new file mode 100644
index 0000000..5c1e8c75
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-vlr.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-vrl.html
new file mode 100644
index 0000000..b7f00569
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-htb-rtl-vrl.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-htb.html
new file mode 100644
index 0000000..98e3c0b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-htb.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-vlr.html
new file mode 100644
index 0000000..73585d2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-vlr.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-vrl.html
new file mode 100644
index 0000000..39ecd38
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-ltr-vrl.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-htb.html
new file mode 100644
index 0000000..7a4167f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-htb.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-vlr.html
new file mode 100644
index 0000000..4ce7d46
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-vlr.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-vrl.html
new file mode 100644
index 0000000..0fe16044
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vlr-rtl-vrl.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-htb.html
new file mode 100644
index 0000000..98e6145a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-htb.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-vlr.html
new file mode 100644
index 0000000..d22b347
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-vlr.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-vrl.html
new file mode 100644
index 0000000..602b7afb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-ltr-vrl.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-htb.html
new file mode 100644
index 0000000..1dcfd87
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-htb.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-vlr.html
new file mode 100644
index 0000000..d22b347
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-vlr.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-vrl.html
new file mode 100644
index 0000000..602b7afb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/align-self-vrl-rtl-vrl.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="align-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="align-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-htb.html
new file mode 100644
index 0000000..cfef344
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-htb.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-vlr.html
new file mode 100644
index 0000000..55680f4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-vlr.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-vrl.html
new file mode 100644
index 0000000..57ee3af
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-ltr-vrl.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-htb.html
new file mode 100644
index 0000000..95e54c2b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-htb.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-vlr.html
new file mode 100644
index 0000000..e7224e7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-vlr.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-vrl.html
new file mode 100644
index 0000000..ba7e98a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-htb-rtl-vrl.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-width="20" data-offset-x="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-width="20" data-offset-x="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-width="20" data-offset-x="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-width="40" data-offset-x="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-htb.html
new file mode 100644
index 0000000..d47c50467
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-htb.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-vlr.html
new file mode 100644
index 0000000..71e3687
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-vlr.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-vrl.html
new file mode 100644
index 0000000..ae90d4d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-ltr-vrl.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-htb.html
new file mode 100644
index 0000000..1a192b56
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-htb.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-vlr.html
new file mode 100644
index 0000000..cb9986d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-vlr.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-vrl.html
new file mode 100644
index 0000000..fb717a0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vlr-rtl-vrl.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-htb.html
new file mode 100644
index 0000000..e2cbff3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-htb.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-vlr.html
new file mode 100644
index 0000000..5aa2f48
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-vlr.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-vrl.html
new file mode 100644
index 0000000..cf2db8d3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-ltr-vrl.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-htb.html
new file mode 100644
index 0000000..317e53e9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-htb.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-vlr.html
new file mode 100644
index 0000000..2d144d16
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-vlr.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-vrl.html
new file mode 100644
index 0000000..026c6e9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/justify-self-vrl-rtl-vrl.html
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: rtl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  position: absolute;
+  background: green;
+  inset: 0;
+}
+
+.item::before {
+  width: 20px;
+  height: 20px;
+  content: '';
+  display: block;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-start;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: self-end;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: center;" data-expected-height="20" data-offset-y="10"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: baseline;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: last baseline;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-start;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: self-end;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: left;" data-expected-height="20" data-offset-y="0"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: right;" data-expected-height="20" data-offset-y="20"></div>
+</div>
+
+<div class="container">
+  <div class="item rtl" style="justify-self: stretch;" data-expected-height="40" data-offset-y="0"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-htb.html
new file mode 100644
index 0000000..9e259c1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-htb.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  position: absolute;
+  background: green;
+  inset: 5px;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+}
+
+.safe {
+  align-self: safe end;
+}
+.unsafe {
+  align-self: unsafe end;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<!-- UNSAFE -->
+<br>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<!-- UNSAFE RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-vlr.html
new file mode 100644
index 0000000..d47b183
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-vlr.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  position: absolute;
+  background: green;
+  inset: 5px;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+}
+
+.safe {
+  align-self: safe end;
+}
+.unsafe {
+  align-self: unsafe end;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<!-- UNSAFE -->
+<br>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<!-- UNSAFE RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-vrl.html
new file mode 100644
index 0000000..34327620
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-align-self-vrl.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  position: absolute;
+  background: green;
+  inset: 5px;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+}
+
+.safe {
+  align-self: safe end;
+}
+.unsafe {
+  align-self: unsafe end;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<!-- UNSAFE -->
+<br>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<!-- UNSAFE RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="15"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-htb.html
new file mode 100644
index 0000000..0fa5cc3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-htb.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  position: absolute;
+  background: green;
+  inset: 5px;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+}
+
+.safe {
+  justify-self: safe end;
+}
+.unsafe {
+  justify-self: unsafe end;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<!-- UNSAFE -->
+<br>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="-5"></div>
+</div>
+
+<!-- UNSAFE RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-x="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-x="15"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-vlr.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-vlr.html
new file mode 100644
index 0000000..7554975f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-vlr.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-lr;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  position: absolute;
+  background: green;
+  inset: 5px;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+}
+
+.safe {
+  justify-self: safe end;
+}
+.unsafe {
+  justify-self: unsafe end;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<!-- UNSAFE -->
+<br>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<!-- UNSAFE RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="15"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-vrl.html
new file mode 100644
index 0000000..fe2405c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/safe-justify-self-vrl.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  direction: ltr;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  position: absolute;
+  background: green;
+  inset: 5px;
+  margin: 10px;
+  width: 30px;
+  height: 30px;
+}
+
+.safe {
+  justify-self: safe end;
+}
+.unsafe {
+  justify-self: unsafe end;
+}
+
+.rtl {
+  direction: rtl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<!-- UNSAFE -->
+<br>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<!-- RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="-5"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item safe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="-5"></div>
+</div>
+
+<!-- UNSAFE RTL -->
+<br>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: horizontal-tb; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: veritcal-rl; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-rl; direction: rtl;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: ltr;" data-offset-y="15"></div>
+</div>
+
+<div class="container rtl">
+  <div class="item unsafe" style="writing-mode: vertical-lr; direction: rtl;" data-offset-y="15"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-htb-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-htb-htb.html
new file mode 100644
index 0000000..dc7df332e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-htb-htb.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 60px;
+  height: 60px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  position: absolute;
+  background: green;
+  inset: 5px 10px 5px 10px;
+}
+
+.child::before {
+  aspect-ratio: 1/1;
+  min-width: 20px;
+  min-height: 20px;
+  width: 100%;
+  height: 100%;
+  content: '';
+  display: block;
+}
+
+.ar {
+  aspect-ratio: 1/1;
+  min-width: 20px;
+  min-height: 20px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<body>
+
+<div class="container">
+  <div class="item child" style="justify-self: start; align-self: start;" data-expected-width="20" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item child" style="justify-self: stretch; align-self: start;" data-expected-width="40" data-expected-height="40"></div>
+</div>
+
+<div class="container">
+  <div class="item child" style="justify-self: start; align-self: stretch;" data-expected-width="50" data-expected-height="50"></div>
+</div>
+
+<div class="container">
+  <div class="item child" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></div>
+</div>
+
+<br>
+
+<div class="container">
+  <div class="item ar" style="justify-self: start; align-self: start;" data-expected-width="20" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: stretch; align-self: start;" data-expected-width="40" data-expected-height="40"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: start; align-self: stretch;" data-expected-width="50" data-expected-height="50"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></div>
+</div>
+
+<br>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: start; align-self: start;" data-expected-width="10" data-expected-height="10"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: stretch; align-self: start;" data-expected-width="40" data-expected-height="40"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: start; align-self: stretch;" data-expected-width="50" data-expected-height="50"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></canvas>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-htb-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-htb-vrl.html
new file mode 100644
index 0000000..cd2c9b9a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-htb-vrl.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 60px;
+  height: 60px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  position: absolute;
+  background: green;
+  inset: 5px 10px 5px 10px;
+}
+
+.item::before {
+  aspect-ratio: 1/1;
+  min-width: 20px;
+  min-height: 20px;
+  width: 100%;
+  height: 100%;
+  content: '';
+  display: block;
+}
+
+.ar {
+  aspect-ratio: 1/1;
+  min-width: 20px;
+  min-height: 20px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<body>
+
+<div class="container">
+  <div class="item" style="justify-self: start; align-self: start;" data-expected-width="20" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch; align-self: start;" data-expected-width="40" data-expected-height="40"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: start; align-self: stretch;" data-expected-width="50" data-expected-height="50"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></div>
+</div>
+
+<br>
+
+<div class="container">
+  <div class="item ar" style="justify-self: start; align-self: start;" data-expected-width="20" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: stretch; align-self: start;" data-expected-width="40" data-expected-height="40"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: start; align-self: stretch;" data-expected-width="50" data-expected-height="50"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></div>
+</div>
+
+<br>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: start; align-self: start;" data-expected-width="10" data-expected-height="10"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: stretch; align-self: start;" data-expected-width="40" data-expected-height="40"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: start; align-self: stretch;" data-expected-width="50" data-expected-height="50"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></canvas>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-vrl-htb.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-vrl-htb.html
new file mode 100644
index 0000000..7b10021
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-vrl-htb.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 60px;
+  height: 60px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  position: absolute;
+  background: green;
+  inset: 5px 10px 5px 10px;
+}
+
+.item::before {
+  aspect-ratio: 1/1;
+  min-width: 20px;
+  min-height: 20px;
+  width: 100%;
+  height: 100%;
+  content: '';
+  display: block;
+}
+
+.ar {
+  aspect-ratio: 1/1;
+  min-width: 20px;
+  min-height: 20px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<body>
+
+<div class="container">
+  <div class="item" style="justify-self: start; align-self: start;" data-expected-width="20" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch; align-self: start;" data-expected-width="50" data-expected-height="50"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: start; align-self: stretch;" data-expected-width="40" data-expected-height="40"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></div>
+</div>
+
+<br>
+
+<div class="container">
+  <div class="item ar" style="justify-self: start; align-self: start;" data-expected-width="20" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: stretch; align-self: start;" data-expected-width="50" data-expected-height="50"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: start; align-self: stretch;" data-expected-width="40" data-expected-height="40"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></div>
+</div>
+
+<br>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: start; align-self: start;" data-expected-width="10" data-expected-height="10"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: stretch; align-self: start;" data-expected-width="50" data-expected-height="50"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: start; align-self: stretch;" data-expected-width="40" data-expected-height="40"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></canvas>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-vrl-vrl.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-vrl-vrl.html
new file mode 100644
index 0000000..10f11a9f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/stretch-intrinsic-size-vrl-vrl.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: vertical-rl;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 60px;
+  height: 60px;
+}
+
+.item {
+  writing-mode: vertical-rl;
+  position: absolute;
+  background: green;
+  inset: 5px 10px 5px 10px;
+}
+
+.item::before {
+  aspect-ratio: 1/1;
+  min-width: 20px;
+  min-height: 20px;
+  width: 100%;
+  height: 100%;
+  content: '';
+  display: block;
+}
+
+.ar {
+  aspect-ratio: 1/1;
+  min-width: 20px;
+  min-height: 20px;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<body>
+
+<div class="container">
+  <div class="item" style="justify-self: start; align-self: start;" data-expected-width="20" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch; align-self: start;" data-expected-width="50" data-expected-height="50"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: start; align-self: stretch;" data-expected-width="40" data-expected-height="40"></div>
+</div>
+
+<div class="container">
+  <div class="item" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></div>
+</div>
+
+<br>
+
+<div class="container">
+  <div class="item ar" style="justify-self: start; align-self: start;" data-expected-width="20" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: stretch; align-self: start;" data-expected-width="50" data-expected-height="50"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: start; align-self: stretch;" data-expected-width="40" data-expected-height="40"></div>
+</div>
+
+<div class="container">
+  <div class="item ar" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></div>
+</div>
+
+<br>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: start; align-self: start;" data-expected-width="10" data-expected-height="10"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: stretch; align-self: start;" data-expected-width="50" data-expected-height="50"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: start; align-self: stretch;" data-expected-width="40" data-expected-height="40"></canvas>
+</div>
+
+<div class="container">
+  <canvas width="10" height="10" class="item" style="justify-self: stretch; align-self: stretch;" data-expected-width="40" data-expected-height="50"></canvas>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/table-align-self-stretch.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/table-align-self-stretch.html
new file mode 100644
index 0000000..bedd0a5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/table-align-self-stretch.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#align-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  position: absolute;
+  background: green;
+  inset: 5px;
+  align-self: stretch;
+  display: table;
+}
+
+.item::before {
+  content: '';
+  display: block;
+  width: 10px;
+  height: 20px;
+}
+
+.big::before {
+  width: 50px;
+  height: 60px;
+}
+
+.vrl {
+  writing-mode: vertical-rl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<body>
+
+<div class="container">
+  <div class="item" data-expected-width="10" data-expected-height="30"></div>
+</div>
+
+<div class="container">
+  <div class="item big" data-expected-width="50" data-expected-height="60"></div>
+</div>
+
+<div class="container">
+  <div class="item vrl" data-expected-width="10" data-expected-height="30"></div>
+</div>
+
+<div class="container">
+  <div class="item big vrl" data-expected-width="50" data-expected-height="60"></div>
+</div>
+
+<br>
+
+<div class="container vrl">
+  <div class="item" data-expected-width="30" data-expected-height="20"></div>
+</div>
+
+<div class="container vrl">
+  <div class="item big" data-expected-width="50" data-expected-height="60"></div>
+</div>
+
+<div class="container vrl">
+  <div class="item vrl" data-expected-width="30" data-expected-height="20"></div>
+</div>
+
+<div class="container vrl">
+  <div class="item big vrl" data-expected-width="50" data-expected-height="60"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-align/abspos/table-justify-self-stretch.html b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/table-justify-self-stretch.html
new file mode 100644
index 0000000..c409b107
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-align/abspos/table-justify-self-stretch.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<link rel="help" href="https://drafts.csswg.org/css-align-3/#justify-abspos">
+<style>
+body {
+  margin: 0;
+}
+
+.container {
+  writing-mode: horizontal-tb;
+  display: inline-block;
+  position: relative;
+  margin: 20px;
+  border: solid 4px;
+  width: 40px;
+  height: 40px;
+}
+
+.item {
+  writing-mode: horizontal-tb;
+  position: absolute;
+  background: green;
+  inset: 5px;
+  justify-self: stretch;
+  display: table;
+}
+
+.item::before {
+  content: '';
+  display: block;
+  width: 10px;
+  height: 20px;
+}
+
+.big::before {
+  width: 50px;
+  height: 60px;
+}
+
+.vrl {
+  writing-mode: vertical-rl;
+}
+</style>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/check-layout-th.js"></script>
+
+<body onload="checkLayout('.item')">
+
+<body>
+
+<div class="container">
+  <div class="item" data-expected-width="30" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item big" data-expected-width="50" data-expected-height="60"></div>
+</div>
+
+<div class="container">
+  <div class="item vrl" data-expected-width="30" data-expected-height="20"></div>
+</div>
+
+<div class="container">
+  <div class="item big vrl" data-expected-width="50" data-expected-height="60"></div>
+</div>
+
+<br>
+
+<div class="container vrl">
+  <div class="item" data-expected-width="10" data-expected-height="30"></div>
+</div>
+
+<div class="container vrl">
+  <div class="item big" data-expected-width="50" data-expected-height="60"></div>
+</div>
+
+<div class="container vrl">
+  <div class="item vrl" data-expected-width="10" data-expected-height="30"></div>
+</div>
+
+<div class="container vrl">
+  <div class="item big vrl" data-expected-width="50" data-expected-height="60"></div>
+</div>
+
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/resources/register-service-worker-iframe.https.html b/third_party/blink/web_tests/external/wpt/shared-storage/resources/register-service-worker-iframe.https.html
new file mode 100644
index 0000000..547ab1d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/resources/register-service-worker-iframe.https.html
@@ -0,0 +1,66 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/common/utils.js></script>
+  <script src=/fenced-frame/resources/utils.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script src=/shared-storage/resources/util.sub.js></script>
+  <script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+  <script>
+    const INTERMEDIATE_FRAME_SUFFIX =
+      'able-fetch-request-fallback-to-network-iframe.https.html'
+    const ORIGIN = new URL("", location.href).origin;
+
+    window.addEventListener('message', async function handler(event) {
+      const data = event.data;
+      if (data.script && data.scope && data.port) {
+        var absoluteScope = (new URL(data.scope, window.location).href);
+        let oldReg =await navigator.serviceWorker.getRegistration(data.scope);
+        if (oldReg && oldReg.scope === absoluteScope) {
+          await oldReg.unregister();
+        }
+        let reg = await navigator.serviceWorker.register(data.script,
+                                                         { scope: data.scope });
+        let worker = reg.installing;
+        await new Promise(function(resolve) {
+          worker.addEventListener('statechange', function() {
+            if (worker.state == 'activated') {
+              resolve();
+            }
+          });
+        });
+        assert_not_equals(worker, null, 'worker is installing');
+
+        let result = await loadNestedSharedStorageFrameInNewFrame({
+          key: 'c', value: 'd',
+          hasSharedStorageWritableAttribute: true,
+          // Same-origin to this frame, cross-origin to top.
+          isSameOrigin: true,
+        });
+        const urls = [
+          {
+           "url": ORIGIN + data.scope + INTERMEDIATE_FRAME_SUFFIX,
+           "mode": "navigate",
+           "SSWHeader": "null"
+          },
+          {
+          "url": ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+          },
+          {
+            "url":  ORIGIN + result.nestedFrameUrl,
+            "mode": "navigate",
+            "SSWHeader": "null"
+          },
+        ];
+        await checkInterceptedUrls(worker, urls);
+        await verifyKeyValueForOrigin('c', 'd', ORIGIN);
+        await deleteKeyForOrigin('c', ORIGIN);
+        data.port.postMessage({msg: 'test completed'});
+        reg.unregister()
+        window.removeEventListener('message', handler);
+      }
+    });
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/resources/shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html b/third_party/blink/web_tests/external/wpt/shared-storage/resources/shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html
index 8229ce8..3451d914 100644
--- a/third_party/blink/web_tests/external/wpt/shared-storage/resources/shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/resources/shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html
@@ -18,5 +18,39 @@
         img.src = url;
       });
     }
+
+    function loadFrame(url, hasSharedStorageWritableAttribute) {
+      return new Promise(function(resolve, reject) {
+        var frame = document.createElement('iframe');
+        document.body.appendChild(frame);
+        frame.onload = function() {
+          window.parent.postMessage({msg: 'iframe loaded'}, "*");
+          resolve(frame);
+        };
+        frame.onerror = function() {
+          reject(new Error('Nested iframe load failed'));
+        };
+        if (hasSharedStorageWritableAttribute) {
+          frame.sharedStorageWritable = true;
+        }
+        frame.src = url;
+      });
+    }
+
+    function fetchUrl(url, hasSharedStorageWritableAttribute) {
+      return new Promise(function(resolve, reject) {
+        fetch(url, {sharedStorageWritable:
+                    hasSharedStorageWritableAttribute})
+          .then(response => {
+              if (!response.ok) {
+                throw new Error('Failed to fetch ' + url + '; '
+                  + String(response.status) + ' ' + response.statusText);
+              }
+              resolve(response);
+          }).catch(error => {
+              reject(error);
+          });
+      });
+    }
   </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/resources/util.js b/third_party/blink/web_tests/external/wpt/shared-storage/resources/util.js
index f827658..4a7fcc4 100644
--- a/third_party/blink/web_tests/external/wpt/shared-storage/resources/util.js
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/resources/util.js
@@ -196,3 +196,22 @@
   const result = await nextValueFromServer(outerKey);
   assert_equals(result, 'delete_key_loaded');
 }
+
+function getFetchedUrls(worker) {
+  return new Promise(function(resolve) {
+    var channel = new MessageChannel();
+    channel.port1.onmessage = function(msg) {
+      resolve(msg);
+    };
+    worker.postMessage({port: channel.port2}, [channel.port2]);
+  });
+}
+
+function checkInterceptedUrls(worker, expectedRequests) {
+  return getFetchedUrls(worker).then(function(msg) {
+    let actualRequests = msg.data.requests;
+    assert_equals(actualRequests.length, expectedRequests.length);
+    assert_equals(
+        JSON.stringify(actualRequests), JSON.stringify(expectedRequests));
+  });
+}
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/resources/util.sub.js b/third_party/blink/web_tests/external/wpt/shared-storage/resources/util.sub.js
index 970c33b7..f147209d 100644
--- a/third_party/blink/web_tests/external/wpt/shared-storage/resources/util.sub.js
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/resources/util.sub.js
@@ -69,3 +69,49 @@
   document.body.appendChild(frame);
   return promise;
 }
+
+async function loadNestedSharedStorageFrameInNewFrame(data) {
+  const SCOPE = '/shared-storage/resources/shared-storage-writ';
+  const INTERMEDIATE_FRAME_SUFFIX =
+      'able-fetch-request-fallback-to-network-iframe.https.html'
+  const CROSS_ORIGIN = 'https://{{domains[www]}}:{{ports[https][0]}}';
+
+  let {key, value, hasSharedStorageWritableAttribute, isSameOrigin} = data;
+
+  const windowPromise = new Promise((resolve, reject) => {
+    window.addEventListener('message', async function handler(evt) {
+      if (evt.data.msg && evt.data.msg === 'iframe loaded') {
+        window.removeEventListener('message', handler);
+        resolve();
+      }
+    });
+    window.addEventListener('error', () => {
+      reject(new Error('Navigation error'));
+    });
+  });
+
+  const framePromise = new Promise((resolve, reject) => {
+    let frame = document.createElement('iframe');
+    frame.src = SCOPE + INTERMEDIATE_FRAME_SUFFIX;
+    frame.onload = function() {
+      resolve(frame);
+    };
+    frame.onerror = function() {
+      reject(new Error('Iframe load failed'));
+    };
+    document.body.appendChild(frame);
+  });
+  let frame = await framePromise;
+
+  let rawWriteHeader = `set;key=${key};value=${value}`;
+  let writeHeader = encodeURIComponent(rawWriteHeader);
+  const sameOriginNestedSrc = `/shared-storage/resources/` +
+      `shared-storage-write.py?write=${writeHeader}`;
+  const nestedSrc =
+      isSameOrigin ? sameOriginNestedSrc : CROSS_ORIGIN + sameOriginNestedSrc;
+
+  let nestedFrame = frame.contentWindow.loadFrame(
+      nestedSrc, hasSharedStorageWritableAttribute);
+  await windowPromise;
+  return {frame: frame, nestedFrame: nestedFrame, nestedFrameUrl: nestedSrc};
+}
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-fetch.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-fetch.tentative.https.sub.html
new file mode 100644
index 0000000..ea7af527
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-fetch.tentative.https.sub.html
@@ -0,0 +1,109 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/common/utils.js></script>
+  <script src=/fenced-frame/resources/utils.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+  <script>
+    const SCOPE = '/shared-storage/resources/'
+          + 'shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html';
+    const SCRIPT = '/shared-storage/resources/'
+      + 'shared-storage-writable-fetch-request-fallback-to-network-worker.js';
+    const SAME_ORIGIN = new URL("", location.href).origin;
+    const CROSS_ORIGIN = 'https://{{domains[www]}}:{{ports[https][0]}}';
+
+    async function fetchSharedStorageUrlInNewFrame(data) {
+      let {test, key, value, hasSharedStorageWritableAttribute, isSameOrigin}
+        = data;
+
+      const framePromise = new Promise((resolve, reject) => {
+        let frame = document.createElement('iframe');
+        frame.src = SCOPE;
+        frame.onload = function() {
+          resolve(frame);
+        };
+        frame.onerror = function() {
+          reject(new Error('Iframe load failed'));
+        };
+        test.add_cleanup(function() {
+          frame.remove();
+        });
+        document.body.appendChild(frame);
+      });
+      let frame = await framePromise;
+
+      let rawWriteHeader = `set;key=${key};value=${value}`;
+      let writeHeader = encodeURIComponent(rawWriteHeader);
+      const sameOriginSrc = `/shared-storage/resources/` +
+        `shared-storage-write.py?write=${writeHeader}`;
+      const src = isSameOrigin ?
+        sameOriginSrc :
+        CROSS_ORIGIN + sameOriginSrc;
+      return {
+        response: frame.contentWindow.fetchUrl(src,
+          hasSharedStorageWritableAttribute),
+        url: src,
+      };
+    }
+
+    promise_test(async t => {
+      await service_worker_unregister(t, SCOPE);
+      let reg = await navigator.serviceWorker.register(SCRIPT,
+                                                       { scope: SCOPE });
+      t.add_cleanup(_ => reg.unregister());
+      let worker = reg.installing;
+      await wait_for_state(t, worker, 'activated');
+      assert_not_equals(worker, null, 'worker is installing');
+
+      let {response, url} = await fetchSharedStorageUrlInNewFrame({
+        test: t,
+        key: 'a', value: 'b',
+        hasSharedStorageWritableAttribute: true,
+        isSameOrigin: true,
+      });
+      checkInterceptedUrls(worker, [
+        {"url": SAME_ORIGIN + SCOPE, "mode": "navigate", "SSWHeader": "null"},
+        {
+          "url": SAME_ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+        },
+        {"url": SAME_ORIGIN + url, "mode": "cors", "SSWHeader": "null"},
+      ]);
+      await verifyKeyValueForOrigin('a', 'b', SAME_ORIGIN);
+      await deleteKeyForOrigin('a', SAME_ORIGIN);
+    }, 'test fetch(url, {sharedStorageWritable: true}) via JS from service '
+       + 'worker for same origin fetch');
+
+    promise_test(async t => {
+      await service_worker_unregister(t, SCOPE);
+      let reg = await navigator.serviceWorker.register(SCRIPT,
+                                                       { scope: SCOPE });
+      t.add_cleanup(_ => reg.unregister());
+      let worker = reg.installing;
+      await wait_for_state(t, worker, 'activated');
+      assert_not_equals(worker, null, 'worker is installing');
+
+      let {response, url} = await fetchSharedStorageUrlInNewFrame({
+        test: t,
+        key: 'c', value: 'd',
+        hasSharedStorageWritableAttribute: true,
+        isSameOrigin: false,
+      });
+      checkInterceptedUrls(worker, [
+        {"url": SAME_ORIGIN + SCOPE, "mode": "navigate", "SSWHeader": "null"},
+        {
+          "url": SAME_ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+        },
+        {"url": url, "mode": "cors", "SSWHeader": "null"},
+      ]);
+      await verifyKeyValueForOrigin('c', 'd', CROSS_ORIGIN);
+      await deleteKeyForOrigin('c', CROSS_ORIGIN);
+    }, 'test fetch(url, {sharedStorageWritable: true}) via JS from service '
+       + 'worker for cross origin fetch');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-iframe.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-iframe.tentative.https.sub.html
new file mode 100644
index 0000000..9eb2820
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-iframe.tentative.https.sub.html
@@ -0,0 +1,98 @@
+<!doctype html>
+<body>
+  <script src=/resources/testharness.js></script>
+  <script src=/resources/testharnessreport.js></script>
+  <script src=/common/utils.js></script>
+  <script src=/fenced-frame/resources/utils.js></script>
+  <script src=/shared-storage/resources/util.js></script>
+  <script src=/shared-storage/resources/util.sub.js></script>
+  <script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
+  <script>
+    const SCOPE = '/shared-storage/resources/shared-storage-writ';
+    const INTERMEDIATE_FRAME_SUFFIX =
+      'able-fetch-request-fallback-to-network-iframe.https.html'
+    const SCRIPT = '/shared-storage/resources/'
+      + 'shared-storage-writable-fetch-request-fallback-to-network-worker.js';
+    const WORKER_FRAME = '/shared-storage/resources/'
+      + 'register-service-worker-iframe.https.html';
+    const SAME_ORIGIN = new URL("", location.href).origin;
+    const CROSS_ORIGIN = 'https://{{domains[www]}}:{{ports[https][0]}}';
+
+    promise_test(async t => {
+      await service_worker_unregister(t, SCOPE);
+      let reg = await navigator.serviceWorker.register(SCRIPT,
+                                                       { scope: SCOPE });
+      t.add_cleanup(_ => reg.unregister());
+      let worker = reg.installing;
+      await wait_for_state(t, worker, 'activated');
+      assert_not_equals(worker, null, 'worker is installing');
+
+      let {frame, nestedFrame, nestedFrameUrl} =
+        await loadNestedSharedStorageFrameInNewFrame({
+          key: 'a', value: 'b',
+          hasSharedStorageWritableAttribute: true,
+          isSameOrigin: true,
+        });
+      t.add_cleanup(function() {
+        frame.remove();
+      });
+      checkInterceptedUrls(worker, [
+        {
+           "url": SAME_ORIGIN + SCOPE + INTERMEDIATE_FRAME_SUFFIX,
+           "mode": "navigate",
+           "SSWHeader": "null"
+        },
+        {
+          "url": SAME_ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+        },
+        {
+          "url": SAME_ORIGIN + nestedFrameUrl,
+          "mode": "navigate",
+          "SSWHeader": "null"
+        },
+      ]);
+      await verifyKeyValueForOrigin('a', 'b', SAME_ORIGIN);
+      await deleteKeyForOrigin('a', SAME_ORIGIN);
+    }, 'test <iframe sharedstoragewritable src=[url]> via JS from service '
+       + 'worker for same origin iframe');
+
+    promise_test(async t => {
+      const workerFramePromise = new Promise((resolve, reject) => {
+        let workerFrame = document.createElement('iframe');
+        workerFrame.src = CROSS_ORIGIN + WORKER_FRAME;
+        workerFrame.id = 'worker_frame';
+        workerFrame.onload = function() {
+          resolve(workerFrame);
+        };
+        workerFrame.onerror = function() {
+          reject(new Error('Worker frame load failed'));
+        };
+        t.add_cleanup(function() {
+          workerFrame.remove();
+        });
+        document.body.appendChild(workerFrame);
+      });
+      let workerFrame = await workerFramePromise;
+
+      const messagePromise = new Promise((resolve, reject) => {
+        let channel = new MessageChannel();
+        channel.port1.onmessage = function(e) {
+          resolve(e.data);
+        };
+        let message = {
+            script: SCRIPT,
+            scope: SCOPE,
+            port: channel.port2,
+        };
+        document.getElementById('worker_frame').contentWindow
+          .postMessage(message, "*",
+                       [channel.port2]);
+      });
+      let {msg} = await messagePromise;
+      assert_equals(msg, 'test completed');
+    }, 'test <iframe sharedstoragewritable src=[url]> via JS from service '
+       + 'worker for cross origin iframe');
+  </script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-img.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-img.tentative.https.sub.html
index 9e7326d..6d48155 100644
--- a/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-img.tentative.https.sub.html
+++ b/third_party/blink/web_tests/external/wpt/shared-storage/shared-storage-writable-service-worker-img.tentative.https.sub.html
@@ -7,23 +7,6 @@
   <script src=/shared-storage/resources/util.js></script>
   <script src=/service-workers/service-worker/resources/test-helpers.sub.js></script>
   <script>
-    function getFetchedUrls(worker) {
-      return new Promise(function(resolve) {
-          var channel = new MessageChannel();
-          channel.port1.onmessage = function(msg) { resolve(msg); };
-          worker.postMessage({port: channel.port2}, [channel.port2]);
-        });
-    }
-
-    function checkInterceptedUrls(worker, expectedRequests) {
-      return getFetchedUrls(worker)
-        .then(function(msg) {
-          let actualRequests = msg.data.requests;
-          assert_equals(actualRequests.length, expectedRequests.length);
-          assert_equals(JSON.stringify(actualRequests), JSON.stringify(expectedRequests));
-        });
-    }
-
     const SCOPE = '/shared-storage/resources/'
           + 'shared-storage-writable-fetch-request-fallback-to-network-iframe.https.html';
     const SCRIPT = '/shared-storage/resources/'
@@ -31,10 +14,11 @@
     const SAME_ORIGIN = new URL("", location.href).origin;
     const CROSS_ORIGIN = 'https://{{domains[www]}}:{{ports[https][0]}}';
 
-    async function loadSharedStorageImageFromNewFrame(data) {
-      let {test, key, value, hasSharedStorageWritableAttribute, isSameOrigin} = data;
+    async function loadSharedStorageImageInNewFrame(data) {
+      let {test, key, value, hasSharedStorageWritableAttribute, isSameOrigin}
+        = data;
 
-      const frame_promise = new Promise((resolve, reject) => {
+      const framePromise = new Promise((resolve, reject) => {
         let frame = document.createElement('iframe');
         frame.src = SCOPE;
         frame.onload = function() {
@@ -48,27 +32,30 @@
         });
         document.body.appendChild(frame);
       });
-      let frame = await frame_promise;
+      let frame = await framePromise;
 
       const sameOriginImgSrc = `/shared-storage/resources/` +
         `shared-storage-writable-pixel.png?key=${key}&value=${value}`;
-      const imgSrc = isSameOrigin ? sameOriginImgSrc : CROSS_ORIGIN + sameOriginImgSrc;
+      const imgSrc = isSameOrigin ?
+        sameOriginImgSrc :
+        CROSS_ORIGIN + sameOriginImgSrc;
       return {
         loadedImage: frame.contentWindow.loadImage(imgSrc,
-                                                   hasSharedStorageWritableAttribute),
+          hasSharedStorageWritableAttribute),
         imageUrl: imgSrc,
       };
     }
 
     promise_test(async t => {
       await service_worker_unregister(t, SCOPE);
-      let reg = await navigator.serviceWorker.register(SCRIPT, { scope: SCOPE });
+      let reg = await navigator.serviceWorker.register(SCRIPT,
+                                                       { scope: SCOPE });
       t.add_cleanup(_ => reg.unregister());
       let worker = reg.installing;
       await wait_for_state(t, worker, 'activated');
       assert_not_equals(worker, null, 'worker is installing');
 
-      let {loadedImage, imageUrl} =  await loadSharedStorageImageFromNewFrame({
+      let {loadedImage, imageUrl} = await loadSharedStorageImageInNewFrame({
         test: t,
         key: 'a', value: 'b',
         hasSharedStorageWritableAttribute: true,
@@ -76,7 +63,11 @@
       });
       checkInterceptedUrls(worker, [
         {"url": SAME_ORIGIN + SCOPE, "mode": "navigate", "SSWHeader": "null"},
-        {"url": SAME_ORIGIN + "/resources/testharness.js", "mode": "no-cors", "SSWHeader": "null"},
+        {
+          "url": SAME_ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+        },
         {"url": SAME_ORIGIN + imageUrl, "mode": "no-cors", "SSWHeader": "null"},
       ]);
       await verifyKeyValueForOrigin('a', 'b', SAME_ORIGIN);
@@ -86,13 +77,14 @@
 
     promise_test(async t => {
       await service_worker_unregister(t, SCOPE);
-      let reg = await navigator.serviceWorker.register(SCRIPT, { scope: SCOPE });
+      let reg = await navigator.serviceWorker.register(SCRIPT,
+                                                       { scope: SCOPE });
       t.add_cleanup(_ => reg.unregister());
       let worker = reg.installing;
       await wait_for_state(t, worker, 'activated');
       assert_not_equals(worker, null, 'worker is installing');
 
-      let {loadedImage, imageUrl} =  await loadSharedStorageImageFromNewFrame({
+      let {loadedImage, imageUrl} = await loadSharedStorageImageInNewFrame({
         test: t,
         key: 'c', value: 'd',
         hasSharedStorageWritableAttribute: true,
@@ -100,7 +92,11 @@
       });
       checkInterceptedUrls(worker, [
         {"url": SAME_ORIGIN + SCOPE, "mode": "navigate", "SSWHeader": "null"},
-        {"url": SAME_ORIGIN + "/resources/testharness.js", "mode": "no-cors", "SSWHeader": "null"},
+        {
+          "url": SAME_ORIGIN + "/resources/testharness.js",
+          "mode": "no-cors",
+          "SSWHeader": "null"
+        },
         {"url": imageUrl, "mode": "no-cors", "SSWHeader": "null"},
       ]);
       await verifyKeyValueForOrigin('c', 'd', CROSS_ORIGIN);
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https-expected.txt
index 2265f789..88e9473 100644
--- a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https-expected.txt
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment-nosw.https-expected.txt
@@ -1,3 +1,4 @@
 This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = TypeError: test_driver.set_rph_registration_mode is not a function
+Harness Error. harness_status.status = 1 , harness_status.message = error: Action set_rph_registration_mode not implemented
+[NOTRUN] registerProtocolHandler() and a handler with %s in the fragment (does not use a service worker)
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https-expected.txt
index 2265f789..52929258 100644
--- a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https-expected.txt
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-fragment.https-expected.txt
@@ -1,3 +1,4 @@
 This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = TypeError: test_driver.set_rph_registration_mode is not a function
+Harness Error. harness_status.status = 1 , harness_status.message = error: Action set_rph_registration_mode not implemented
+[NOTRUN] registerProtocolHandler() and a handler with %s in the fragment
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https-expected.txt
index 2265f789..b73c2bd 100644
--- a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https-expected.txt
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-path.https-expected.txt
@@ -1,3 +1,4 @@
 This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = TypeError: test_driver.set_rph_registration_mode is not a function
+Harness Error. harness_status.status = 1 , harness_status.message = error: Action set_rph_registration_mode not implemented
+[NOTRUN] registerProtocolHandler() and a handler with %s in the path
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https-expected.txt
index 2265f789..71ed126 100644
--- a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https-expected.txt
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query-nosw.https-expected.txt
@@ -1,3 +1,4 @@
 This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = TypeError: test_driver.set_rph_registration_mode is not a function
+Harness Error. harness_status.status = 1 , harness_status.message = error: Action set_rph_registration_mode not implemented
+[NOTRUN] registerProtocolHandler() and a handler with %s in the query (does not use a service worker)
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https-expected.txt
index 2265f789..813f466c 100644
--- a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https-expected.txt
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol-handler-query.https-expected.txt
@@ -1,3 +1,4 @@
 This is a testharness.js-based test.
-Harness Error. harness_status.status = 1 , harness_status.message = TypeError: test_driver.set_rph_registration_mode is not a function
+Harness Error. harness_status.status = 1 , harness_status.message = error: Action set_rph_registration_mode not implemented
+[NOTRUN] registerProtocolHandler() and a handler with %s in the query
 Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/resources/testdriver.js b/third_party/blink/web_tests/resources/testdriver.js
index 93419c6..70c7991 100644
--- a/third_party/blink/web_tests/resources/testdriver.js
+++ b/third_party/blink/web_tests/resources/testdriver.js
@@ -647,7 +647,7 @@
          *
          * This function places `Secure Payment
          * Confirmation <https://w3c.github.io/secure-payment-confirmation>`_ into
-         * an automated 'autoaccept' or 'autoreject' mode, to allow testing
+         * an automated 'autoAccept' or 'autoReject' mode, to allow testing
          * without user interaction with the transaction UX prompt.
          *
          * Matches the `Set SPC Transaction Mode
@@ -667,8 +667,8 @@
          * @param {String} mode - The `transaction mode
          *                        <https://w3c.github.io/secure-payment-confirmation/#enumdef-transactionautomationmode>`_
          *                        to set. Must be one of "``none``",
-         *                        "``autoaccept``", or
-         *                        "``autoreject``".
+         *                        "``autoAccept``", or
+         *                        "``autoReject``".
          * @param {WindowProxy} context - Browsing context in which
          *                                to run the call, or null for the current
          *                                browsing context.
@@ -681,6 +681,42 @@
         },
 
         /**
+         * Sets the current registration automation mode for Register Protocol Handlers.
+         *
+         * This function places `Register Protocol Handlers
+         * <https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers>`_ into
+         * an automated 'autoAccept' or 'autoReject' mode, to allow testing
+         * without user interaction with the transaction UX prompt.
+         *
+         * Matches the `Set Register Protocol Handler Mode
+         * <https://html.spec.whatwg.org/multipage/system-state.html#set-rph-registration-mode>`_
+         * WebDriver command.
+         *
+         * @example
+         * await test_driver.set_rph_registration_mode("autoAccept");
+         * test.add_cleanup(() => {
+         *   return test_driver.set_rph_registration_mode("none");
+         * });
+         *
+         * navigator.registerProtocolHandler('web+soup', 'soup?url=%s');
+         *
+         * @param {String} mode - The `registration mode
+         *                        <https://html.spec.whatwg.org/multipage/system-state.html#registerprotocolhandler()-automation-mode>`_
+         *                        to set. Must be one of "``none``",
+         *                        "``autoAccept``", or
+         *                        "``autoReject``".
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} Fulfilled after the transaction mode has been set,
+         *                    or rejected if setting the mode fails.
+         */
+        set_rph_registration_mode: function(mode, context=null) {
+          return window.test_driver_internal.set_rph_registration_mode(mode, context);
+        },
+
+        /**
          * Cancels the Federated Credential Management dialog
          *
          * Matches the `Cancel dialog
@@ -699,6 +735,25 @@
         },
 
         /**
+         * Clicks a button on the Federated Credential Management dialog
+         *
+         * Matches the `Click dialog button
+         * <https://fedidcg.github.io/FedCM/#webdriver-clickdialogbutton>`_
+         * WebDriver command.
+         *
+         * @param {String} dialog_button - String enum representing the dialog button to click.
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} Fulfilled after the button is clicked,
+         *                    or rejected in case the WebDriver command errors
+         */
+        click_fedcm_dialog_button: function(dialog_button, context=null) {
+          return window.test_driver_internal.click_fedcm_dialog_button(dialog_button, context);
+        },
+
+        /**
          * Selects an account from the Federated Credential Management dialog
          *
          * Matches the `Select account
@@ -806,6 +861,149 @@
          */
         reset_fedcm_cooldown: function(context=null) {
           return window.test_driver_internal.reset_fedcm_cooldown(context);
+        },
+
+        /**
+         * Creates a virtual sensor for use with the Generic Sensors APIs.
+         *
+         * Matches the `Create Virtual Sensor
+         * <https://w3c.github.io/sensors/#create-virtual-sensor-command>`_
+         * WebDriver command.
+         *
+         * Once created, a virtual sensor is available to all navigables under
+         * the same top-level traversable (i.e. all frames in the same page,
+         * regardless of origin).
+         *
+         * @param {String} sensor_type - A `virtual sensor type
+         *                               <https://w3c.github.io/sensors/#virtual-sensor-metadata-virtual-sensor-type>`_
+         *                               such as "accelerometer".
+         * @param {Object} [sensor_params={}] - Optional parameters described
+         *                                     in `Create Virtual Sensor
+         *                                     <https://w3c.github.io/sensors/#create-virtual-sensor-command>`_.
+         * @param {WindowProxy} [context=null] - Browsing context in which to
+         *                                       run the call, or null for the
+         *                                       current browsing context.
+         *
+         * @returns {Promise} Fulfilled when virtual sensor is created.
+         *                    Rejected in case the WebDriver command errors out
+         *                    (including if a virtual sensor of the same type
+         *                    already exists).
+         */
+        create_virtual_sensor: function(sensor_type, sensor_params={}, context=null) {
+          return window.test_driver_internal.create_virtual_sensor(sensor_type, sensor_params, context);
+        },
+
+        /**
+         * Causes a virtual sensor to report a new reading to any connected
+         * platform sensor.
+         *
+         * Matches the `Update Virtual Sensor Reading
+         * <https://w3c.github.io/sensors/#update-virtual-sensor-reading-command>`_
+         * WebDriver command.
+         *
+         * Note: The ``Promise`` it returns may fulfill before or after a
+         * "reading" event is fired. When using
+         * :js:func:`EventWatcher.wait_for`, it is necessary to take this into
+         * account:
+         *
+         * Note: New values may also be discarded due to the checks in `update
+         * latest reading
+         * <https://w3c.github.io/sensors/#update-latest-reading>`_.
+         *
+         * @example
+         * // Avoid races between EventWatcher and update_virtual_sensor().
+         * // This assumes you are sure this reading will be processed (see
+         * // the example below otherwise).
+         * const reading = { x: 1, y: 2, z: 3 };
+         * await Promise.all([
+         *   test_driver.update_virtual_sensor('gyroscope', reading),
+         *   watcher.wait_for('reading')
+         * ]);
+         *
+         * @example
+         * // Do not wait forever if you are not sure the reading will be
+         * // processed.
+         * const readingPromise = watcher.wait_for('reading');
+         * const timeoutPromise = new Promise(resolve => {
+         *     t.step_timeout(() => resolve('TIMEOUT', 3000))
+         * });
+         *
+         * const reading = { x: 1, y: 2, z: 3 };
+         * await test_driver.update_virtual_sensor('gyroscope', 'reading');
+         *
+         * const value =
+         *     await Promise.race([timeoutPromise, readingPromise]);
+         * if (value !== 'TIMEOUT') {
+         *   // Do something. The "reading" event was fired.
+         * }
+         *
+         * @param {String} sensor_type - A `virtual sensor type
+         *                               <https://w3c.github.io/sensors/#virtual-sensor-metadata-virtual-sensor-type>`_
+         *                               such as "accelerometer".
+         * @param {Object} reading - An Object describing a reading in a format
+         *                           dependent on ``sensor_type`` (e.g. ``{x:
+         *                           1, y: 2, z: 3}`` or ``{ illuminance: 42
+         *                           }``).
+         * @param {WindowProxy} [context=null] - Browsing context in which to
+         *                                       run the call, or null for the
+         *                                       current browsing context.
+         *
+         * @returns {Promise} Fulfilled after the reading update reaches the
+         *                    virtual sensor. Rejected in case the WebDriver
+         *                    command errors out (including if a virtual sensor
+         *                    of the given type does not exist).
+         */
+        update_virtual_sensor: function(sensor_type, reading, context=null) {
+          return window.test_driver_internal.update_virtual_sensor(sensor_type, reading, context);
+        },
+
+        /**
+         * Triggers the removal of a virtual sensor if it exists.
+         *
+         * Matches the `Delete Virtual Sensor
+         * <https://w3c.github.io/sensors/#delete-virtual-sensor-command>`_
+         * WebDriver command.
+         *
+         * @param {String} sensor_type - A `virtual sensor type
+         *                               <https://w3c.github.io/sensors/#virtual-sensor-metadata-virtual-sensor-type>`_
+         *                               such as "accelerometer".
+         * @param {WindowProxy} [context=null] - Browsing context in which to
+         *                                       run the call, or null for the
+         *                                       current browsing context.
+         *
+         * @returns {Promise} Fulfilled after the virtual sensor has been
+         *                    removed or if a sensor of the given type does not
+         *                    exist. Rejected in case the WebDriver command
+         *                    errors out.
+
+         */
+        remove_virtual_sensor: function(sensor_type, context=null) {
+          return window.test_driver_internal.remove_virtual_sensor(sensor_type, context);
+        },
+
+        /**
+         * Returns information about a virtual sensor.
+         *
+         * Matches the `Get Virtual Sensor Information
+         * <https://w3c.github.io/sensors/#get-virtual-sensor-information-command>`_
+         * WebDriver command.
+         *
+         * @param {String} sensor_type - A `virtual sensor type
+         *                               <https://w3c.github.io/sensors/#virtual-sensor-metadata-virtual-sensor-type>`_
+         *                               such as "accelerometer".
+         * @param {WindowProxy} [context=null] - Browsing context in which to
+         *                                       run the call, or null for the
+         *                                       current browsing context.
+         *
+         * @returns {Promise} Fulfilled with an Object with the properties
+         *                    described in `Get Virtual Sensor Information
+         *                    <https://w3c.github.io/sensors/#get-virtual-sensor-information-command>`_.
+         *                    Rejected in case the WebDriver command errors out
+         *                    (including if a virtual sensor of the given type
+         *                    does not exist).
+         */
+        get_virtual_sensor_information: function(sensor_type, context=null) {
+            return window.test_driver_internal.get_virtual_sensor_information(sensor_type, context);
         }
     };
 
@@ -932,10 +1130,18 @@
             throw new Error("set_spc_transaction_mode() is not implemented by testdriver-vendor.js");
         },
 
+        set_rph_registration_mode: function(mode, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
         async cancel_fedcm_dialog(context=null) {
             throw new Error("cancel_fedcm_dialog() is not implemented by testdriver-vendor.js");
         },
 
+        async click_fedcm_dialog_button(dialog_button, context=null) {
+            throw new Error("click_fedcm_dialog_button() is not implemented by testdriver-vendor.js");
+        },
+
         async select_fedcm_account(account_index, context=null) {
             throw new Error("select_fedcm_account() is not implemented by testdriver-vendor.js");
         },
@@ -958,6 +1164,22 @@
 
         async reset_fedcm_cooldown(context=null) {
             throw new Error("reset_fedcm_cooldown() is not implemented by testdriver-vendor.js");
+        },
+
+        async create_virtual_sensor(sensor_type, sensor_params, context=null) {
+            throw new Error("create_virtual_sensor() is not implemented by testdriver-vendor.js");
+        },
+
+        async update_virtual_sensor(sensor_type, reading, context=null) {
+            throw new Error("update_virtual_sensor() is not implemented by testdriver-vendor.js");
+        },
+
+        async remove_virtual_sensor(sensor_type, context=null) {
+            throw new Error("remove_virtual_sensor() is not implemented by testdriver-vendor.js");
+        },
+
+        async get_virtual_sensor_information(sensor_type, context=null) {
+            throw new Error("get_virtual_sensor_information() is not implemented by testdriver-vendor.js");
         }
     };
 })();
diff --git a/third_party/perfetto b/third_party/perfetto
index ff3b631..0c15215 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit ff3b6318208109a97dc91f5f653edfca466d3cca
+Subproject commit 0c152157409b6d7ba49a83eeeab1a358a1098d20
diff --git a/third_party/skia b/third_party/skia
index 053490e..f82251f 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 053490edfa703f3433d97a299ee80f2acc93de71
+Subproject commit f82251f0091c52eee7125c06786d3c7e72c15327
diff --git a/third_party/wpt_tools/README.chromium b/third_party/wpt_tools/README.chromium
index 3828fac6..f6cc35a 100644
--- a/third_party/wpt_tools/README.chromium
+++ b/third_party/wpt_tools/README.chromium
@@ -1,7 +1,7 @@
 Name: web-platform-tests - Test Suites for Web Platform specifications
 Short Name: wpt
 URL: https://github.com/web-platform-tests/wpt/
-Version: c359284745e2a2f6c44af320d1d6c43ce51f25bc
+Version: a50aec6c90d6ce28629fa4eb5cbeba0d1d65c53b
 License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
 Security Critical: no
 Shipped: no
diff --git a/third_party/wpt_tools/WPTIncludeList b/third_party/wpt_tools/WPTIncludeList
index 33415ff..cd318ba 100644
--- a/third_party/wpt_tools/WPTIncludeList
+++ b/third_party/wpt_tools/WPTIncludeList
@@ -283,6 +283,7 @@
 ./tools/webdriver/webdriver/bidi/modules/script.py
 ./tools/webdriver/webdriver/bidi/modules/session.py
 ./tools/webdriver/webdriver/bidi/transport.py
+./tools/webdriver/webdriver/bidi/undefined.py
 ./tools/webdriver/webdriver/client.py
 ./tools/webdriver/webdriver/error.py
 ./tools/webdriver/webdriver/protocol.py
diff --git a/third_party/wpt_tools/wpt/resources/testdriver.js b/third_party/wpt_tools/wpt/resources/testdriver.js
index 0327e17..70c7991 100644
--- a/third_party/wpt_tools/wpt/resources/testdriver.js
+++ b/third_party/wpt_tools/wpt/resources/testdriver.js
@@ -647,7 +647,7 @@
          *
          * This function places `Secure Payment
          * Confirmation <https://w3c.github.io/secure-payment-confirmation>`_ into
-         * an automated 'autoaccept' or 'autoreject' mode, to allow testing
+         * an automated 'autoAccept' or 'autoReject' mode, to allow testing
          * without user interaction with the transaction UX prompt.
          *
          * Matches the `Set SPC Transaction Mode
@@ -667,8 +667,8 @@
          * @param {String} mode - The `transaction mode
          *                        <https://w3c.github.io/secure-payment-confirmation/#enumdef-transactionautomationmode>`_
          *                        to set. Must be one of "``none``",
-         *                        "``autoaccept``", or
-         *                        "``autoreject``".
+         *                        "``autoAccept``", or
+         *                        "``autoReject``".
          * @param {WindowProxy} context - Browsing context in which
          *                                to run the call, or null for the current
          *                                browsing context.
@@ -681,6 +681,42 @@
         },
 
         /**
+         * Sets the current registration automation mode for Register Protocol Handlers.
+         *
+         * This function places `Register Protocol Handlers
+         * <https://html.spec.whatwg.org/multipage/system-state.html#custom-handlers>`_ into
+         * an automated 'autoAccept' or 'autoReject' mode, to allow testing
+         * without user interaction with the transaction UX prompt.
+         *
+         * Matches the `Set Register Protocol Handler Mode
+         * <https://html.spec.whatwg.org/multipage/system-state.html#set-rph-registration-mode>`_
+         * WebDriver command.
+         *
+         * @example
+         * await test_driver.set_rph_registration_mode("autoAccept");
+         * test.add_cleanup(() => {
+         *   return test_driver.set_rph_registration_mode("none");
+         * });
+         *
+         * navigator.registerProtocolHandler('web+soup', 'soup?url=%s');
+         *
+         * @param {String} mode - The `registration mode
+         *                        <https://html.spec.whatwg.org/multipage/system-state.html#registerprotocolhandler()-automation-mode>`_
+         *                        to set. Must be one of "``none``",
+         *                        "``autoAccept``", or
+         *                        "``autoReject``".
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} Fulfilled after the transaction mode has been set,
+         *                    or rejected if setting the mode fails.
+         */
+        set_rph_registration_mode: function(mode, context=null) {
+          return window.test_driver_internal.set_rph_registration_mode(mode, context);
+        },
+
+        /**
          * Cancels the Federated Credential Management dialog
          *
          * Matches the `Cancel dialog
@@ -699,21 +735,22 @@
         },
 
         /**
-         * Accepts a FedCM "Confirm IDP login" dialog.
+         * Clicks a button on the Federated Credential Management dialog
          *
-         * Matches the `Confirm IDP Login
-         * <https://fedidcg.github.io/FedCM/#webdriver-confirmidplogin>`_
+         * Matches the `Click dialog button
+         * <https://fedidcg.github.io/FedCM/#webdriver-clickdialogbutton>`_
          * WebDriver command.
          *
+         * @param {String} dialog_button - String enum representing the dialog button to click.
          * @param {WindowProxy} context - Browsing context in which
          *                                to run the call, or null for the current
          *                                browsing context.
          *
-         * @returns {Promise} Fulfilled after the IDP login has started,
+         * @returns {Promise} Fulfilled after the button is clicked,
          *                    or rejected in case the WebDriver command errors
          */
-        confirm_idp_login: function(context=null) {
-          return window.test_driver_internal.confirm_idp_login(context);
+        click_fedcm_dialog_button: function(dialog_button, context=null) {
+          return window.test_driver_internal.click_fedcm_dialog_button(dialog_button, context);
         },
 
         /**
@@ -1093,12 +1130,16 @@
             throw new Error("set_spc_transaction_mode() is not implemented by testdriver-vendor.js");
         },
 
+        set_rph_registration_mode: function(mode, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
         async cancel_fedcm_dialog(context=null) {
             throw new Error("cancel_fedcm_dialog() is not implemented by testdriver-vendor.js");
         },
 
-        async confirm_idp_login(context=null) {
-            throw new Error("confirm_idp_login() is not implemented by testdriver-vendor.js");
+        async click_fedcm_dialog_button(dialog_button, context=null) {
+            throw new Error("click_fedcm_dialog_button() is not implemented by testdriver-vendor.js");
         },
 
         async select_fedcm_account(account_index, context=null) {
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
index 7b688dd1..75505ea 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/error.py
@@ -63,6 +63,10 @@
     error_code = "no such handle"
 
 
+class NoSuchHistoryEntryException(BidiException):
+    error_code = "no such history entry"
+
+
 class NoSuchNodeException(BidiException):
     error_code = "no such node"
 
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/_module.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/_module.py
index 060010b..45e2f1c 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/_module.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/_module.py
@@ -9,6 +9,8 @@
     TYPE_CHECKING,
 )
 
+from ..undefined import UNDEFINED
+
 if TYPE_CHECKING:
     from ..client import BidiSession
 
@@ -49,7 +51,8 @@
         self.params_fn = fn
         self.result_fn: Optional[Callable[..., Any]] = None
 
-    def result(self, fn: Callable[[Any, MutableMapping[str, Any]], Any]) -> None:
+    def result(self, fn: Callable[[Any, MutableMapping[str, Any]],
+                                  Any]) -> None:
         self.result_fn = fn
 
     def __set_name__(self, owner: Any, name: str) -> None:
@@ -61,7 +64,7 @@
         @functools.wraps(params_fn)
         async def inner(self: Any, **kwargs: Any) -> Any:
             raw_result = kwargs.pop("raw_result", False)
-            params = params_fn(self, **kwargs)
+            params = remove_undefined(params_fn(self, **kwargs))
 
             # Convert the classname and the method name to a bidi command name
             mod_name = owner.__name__[0].lower() + owner.__name__[1:]
@@ -85,6 +88,7 @@
 
 
 class BidiModule:
+
     def __init__(self, session: "BidiSession"):
         self.session = session
 
@@ -96,3 +100,7 @@
     for i in range(1, len(parts)):
         parts[i] = parts[i].title()
     return "".join(parts)
+
+
+def remove_undefined(obj: Mapping[str, Any]) -> Mapping[str, Any]:
+    return {key: value for key, value in obj.items() if value != UNDEFINED}
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/browsing_context.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/browsing_context.py
index 2938be2e..e0371a7 100644
--- a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/browsing_context.py
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/modules/browsing_context.py
@@ -1,19 +1,16 @@
 import base64
+from enum import Enum
 from typing import Any, Dict, List, Mapping, MutableMapping, Optional, Union
 
 from ._module import BidiModule, command
+from ..undefined import UNDEFINED, Undefined
 
 
 class ElementOptions(Dict[str, Any]):
-    def __init__(
-        self, element: Mapping[str, Any], scroll_into_view: Optional[bool] = None
-    ):
+    def __init__(self, element: Mapping[str, Any]):
         self["type"] = "element"
         self["element"] = element
 
-        if scroll_into_view is not None:
-            self["scrollIntoView"] = scroll_into_view
-
 
 class BoxOptions(Dict[str, Any]):
     def __init__(self, x: float, y: float, width: float, height: float):
@@ -27,6 +24,19 @@
 ClipOptions = Union[ElementOptions, BoxOptions]
 
 
+class OriginOptions(Enum):
+    DOCUMENT = "document"
+    VIEWPORT = "viewport"
+
+
+class FormatOptions(Dict[str, Any]):
+    def __init__(self, type: str, quality: Optional[float] = None):
+        dict.__init__(self, type=type)
+
+        if quality is not None:
+            self["quality"] = quality
+
+
 class BrowsingContext(BidiModule):
     @command
     def activate(self, context: str) -> Mapping[str, Any]:
@@ -34,12 +44,20 @@
 
     @command
     def capture_screenshot(
-        self, context: str, clip: Optional[ClipOptions] = None
+        self,
+        context: str,
+        clip: Optional[ClipOptions] = None,
+        origin: Optional[OriginOptions] = None,
+        format: Optional[FormatOptions] = None,
     ) -> Mapping[str, Any]:
         params: MutableMapping[str, Any] = {"context": context}
 
+        if format is not None:
+            params["format"] = format
         if clip is not None:
             params["clip"] = clip
+        if origin is not None:
+            params["origin"] = origin
 
         return params
 
@@ -180,16 +198,20 @@
     @command
     def set_viewport(self,
                      context: str,
-                     viewport: Optional[Mapping[str, Any]] = None,
-                     device_pixel_ratio: Optional[float] = None) -> Mapping[str, Any]:
+                     viewport: Union[Optional[Mapping[str, Any]], Undefined] = UNDEFINED,
+                     device_pixel_ratio: Union[Optional[float], Undefined] = UNDEFINED) -> Mapping[str, Any]:
         params: MutableMapping[str, Any] = {
             "context": context,
         }
 
-        if viewport is not None:
+        if viewport is not UNDEFINED:
             params["viewport"] = viewport
 
-        if device_pixel_ratio is not None:
+        if device_pixel_ratio is not UNDEFINED:
             params["devicePixelRatio"] = device_pixel_ratio
 
         return params
+
+    @command
+    def traverse_history(self, context: str, delta: int) -> Mapping[str, Any]:
+        return {"context": context, "delta": delta}
diff --git a/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/undefined.py b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/undefined.py
new file mode 100644
index 0000000..8dad6e1
--- /dev/null
+++ b/third_party/wpt_tools/wpt/tools/webdriver/webdriver/bidi/undefined.py
@@ -0,0 +1,6 @@
+class Undefined:
+    def __init__(self) -> None:
+        raise RuntimeError('Import UNDEFINED instead.')
+
+
+UNDEFINED = Undefined.__new__(Undefined)
diff --git a/third_party/wpt_tools/wpt/tools/wpt/android.py b/third_party/wpt_tools/wpt/tools/wpt/android.py
index c99acf3..1061181 100644
--- a/third_party/wpt_tools/wpt/tools/wpt/android.py
+++ b/third_party/wpt_tools/wpt/tools/wpt/android.py
@@ -15,7 +15,7 @@
 wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir))
 
 
-NDK_VERSION = "r23c"
+NDK_VERSION = "r25c"
 CMDLINE_TOOLS_VERSION_STRING = "11.0"
 CMDLINE_TOOLS_VERSION = "9644228"
 
diff --git a/third_party/wpt_tools/wpt/tools/wpt/run.py b/third_party/wpt_tools/wpt/tools/wpt/run.py
index c5012af1..4297c392 100644
--- a/third_party/wpt_tools/wpt/tools/wpt/run.py
+++ b/third_party/wpt_tools/wpt/tools/wpt/run.py
@@ -395,22 +395,23 @@
                               device_serial=device_serial,
                               prompt=kwargs["prompt"])
 
-        if "ADB_PATH" not in os.environ:
-            adb_path = os.path.join(android.get_paths(None)["sdk"],
-                                    "platform-tools",
-                                    "adb")
-            os.environ["ADB_PATH"] = adb_path
-        adb_path = os.environ["ADB_PATH"]
+        if kwargs["adb_binary"] is None:
+            if "ADB_PATH" not in os.environ:
+                adb_path = os.path.join(android.get_paths(None)["sdk"],
+                                        "platform-tools",
+                                        "adb")
+                os.environ["ADB_PATH"] = adb_path
+            kwargs["adb_binary"] = os.environ["ADB_PATH"]
 
-        self._logcat = AndroidLogcat(adb_path, base_path=kwargs["logcat_dir"])
+        self._logcat = AndroidLogcat(kwargs["adb_binary"], base_path=kwargs["logcat_dir"])
 
         for device_serial in kwargs["device_serial"]:
-            device = mozdevice.ADBDeviceFactory(adb=adb_path,
+            device = mozdevice.ADBDeviceFactory(adb=kwargs["adb_binary"],
                                                 device=device_serial)
             self._logcat.start(device_serial)
             if self.browser.apk_path:
                 device.uninstall_app(app)
-                device.install_app(self.browser.apk_path)
+                device.install_app(self.browser.apk_path, timeout=600)
             elif not device.is_app_installed(app):
                 raise WptrunError("app %s not installed on device %s" %
                                   (app, device_serial))
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/edge.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/edge.py
index c6936e7..5b49545 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/edge.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/edge.py
@@ -95,7 +95,7 @@
         return [self.webdriver_binary, f"--port={self.port}"] + self.webdriver_args
 
 
-def run_info_extras(**kwargs):
+def run_info_extras(logger, **kwargs):
     osReleaseCommand = r"(Get-ItemProperty 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion').ReleaseId"
     osBuildCommand = r"(Get-ItemProperty 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion').BuildLabEx"
     try:
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/epiphany.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/epiphany.py
index 912173a..562b2dc 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/epiphany.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/epiphany.py
@@ -71,5 +71,5 @@
     return {}
 
 
-def run_info_extras(**kwargs):
+def run_info_extras(logger, **kwargs):
     return {"webkit_port": "gtk"}
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/firefox.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/firefox.py
index 72f6c85..0cb5ff5d 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -193,7 +193,7 @@
             "supports_debugger": True}
 
 
-def run_info_extras(**kwargs):
+def run_info_extras(logger, **kwargs):
 
     def get_bool_pref_if_exists(pref):
         for key, value in kwargs.get('extra_prefs', []):
@@ -213,7 +213,8 @@
           "fission": not kwargs.get("disable_fission"),
           "sessionHistoryInParent": (not kwargs.get("disable_fission") or
                                      not get_bool_pref("fission.disableSessionHistoryInParent")),
-          "swgl": get_bool_pref("gfx.webrender.software")}
+          "swgl": get_bool_pref("gfx.webrender.software"),
+          "privateBrowsing": (kwargs["tags"] is not None and ("privatebrowsing" in kwargs["tags"]))}
 
     rv.update(run_info_browser_version(**kwargs))
 
@@ -567,6 +568,7 @@
         if self.lsan_handler:
             self.lsan_handler.process()
         if self.leak_report_file is not None:
+            processed_files = None
             if not clean_shutdown:
                 # If we didn't get a clean shutdown there probably isn't a leak report file
                 self.logger.warning("Firefox didn't exit cleanly, not processing leak logs")
@@ -575,7 +577,7 @@
                 # content process crashed and in that case we don't want the test to fail.
                 # Ideally we would record which content process crashed and just skip those.
                 self.logger.info("PROCESS LEAKS %s" % self.leak_report_file)
-                mozleak.process_leak_log(
+                processed_files = mozleak.process_leak_log(
                     self.leak_report_file,
                     leak_thresholds=self.mozleak_thresholds,
                     ignore_missing_leaks=["tab", "gmplugin"],
@@ -583,6 +585,11 @@
                     stack_fixer=self.stack_fixer,
                     scope=self.group_metadata.get("scope"),
                     allowed=self.mozleak_allowed)
+            if processed_files:
+                for path in processed_files:
+                    if os.path.exists(path):
+                        os.unlink(path)
+            # Fallback for older versions of mozleak, or if we didn't shutdown cleanly
             if os.path.exists(self.leak_report_file):
                 os.unlink(self.leak_report_file)
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/firefox_android.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/firefox_android.py
index 1937d97..6ebe4a6 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/firefox_android.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/firefox_android.py
@@ -1,6 +1,8 @@
 # mypy: allow-untyped-defs
 
 import os
+import subprocess
+import re
 
 from mozrunner import FennecEmulatorRunner, get_app_context
 
@@ -14,6 +16,7 @@
 from .base import (Browser,
                    ExecutorBrowser)
 from .firefox import (get_timeout_multiplier,  # noqa: F401
+                      run_info_browser_version,
                       run_info_extras as fx_run_info_extras,
                       update_properties,  # noqa: F401
                       executor_kwargs as fx_executor_kwargs,  # noqa: F401
@@ -87,14 +90,45 @@
     return []
 
 
-def run_info_extras(**kwargs):
-    rv = fx_run_info_extras(**kwargs)
+def run_info_extras(logger, **kwargs):
+    rv = fx_run_info_extras(logger, **kwargs)
     package = kwargs["package_name"]
     rv.update({"e10s": True if package is not None and "geckoview" in package else False,
                "headless": False})
+
+    if kwargs["browser_version"] is None:
+        rv.update(run_info_browser_version(**kwargs))
+
+        if rv.get("browser_version") is None:
+            # If we didn't get the browser version from the apk, try to get it from adb dumpsys
+            rv["browser_version"] = get_package_browser_version(logger,
+                                                                kwargs["adb_binary"],
+                                                                kwargs["package_name"])
+
     return rv
 
 
+def get_package_browser_version(logger, adb_binary, package_name):
+    if adb_binary is None:
+        logger.warning("Couldn't run adb to get Firefox Android version number")
+        return None
+    try:
+        completed = subprocess.run([adb_binary, "shell", "dumpsys", "package", package_name],
+                                   check=True,
+                                   capture_output=True,
+                                   encoding="utf8")
+    except subprocess.CalledProcessError as e:
+        logger.warning(f"adb failed with return code {e.returncode}\nCaptured stderr:\n{e.stderr}")
+        return None
+
+    version_name_re = re.compile(r"^\s+versionName=(.*)")
+    for line in completed.stdout.splitlines():
+        m = version_name_re.match(line)
+        if m is not None:
+            return m.group(1)
+    logger.warning("Failed to find versionName property in dumpsys output")
+
+
 def env_options():
     return {"server_host": "127.0.0.1",
             "supports_debugger": True}
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
index 40f5f548..44d289c7 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
@@ -69,7 +69,7 @@
     return {}
 
 
-def run_info_extras(**kwargs):
+def run_info_extras(logger, **kwargs):
     webdriver_binary = kwargs["webdriver_binary"]
     rv = {}
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/webkit.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/webkit.py
index cecfbe4e..a3e8d13 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/webkit.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/webkit.py
@@ -72,7 +72,7 @@
     return {}
 
 
-def run_info_extras(**kwargs):
+def run_info_extras(logger, **kwargs):
     return {"webkit_port": kwargs["webkit_port"]}
 
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/webkitgtk_minibrowser.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/webkitgtk_minibrowser.py
index 355ffe35..29a9563 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/webkitgtk_minibrowser.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/webkitgtk_minibrowser.py
@@ -76,7 +76,7 @@
     return {}
 
 
-def run_info_extras(**kwargs):
+def run_info_extras(logger, **kwargs):
     return {"webkit_port": "gtk"}
 
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/actions.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/actions.py
index d987553..f698b92d 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/actions.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/actions.py
@@ -277,6 +277,18 @@
         self.logger.debug("Setting SPC transaction mode to %s" % mode)
         return self.protocol.spc_transactions.set_spc_transaction_mode(mode)
 
+class SetRPHRegistrationModeAction:
+    name = "set_rph_registration_mode"
+
+    def __init__(self, logger, protocol):
+        self.logger = logger
+        self.protocol = protocol
+
+    def __call__(self, payload):
+        mode = payload["mode"]
+        self.logger.debug("Setting RPH registration mode to %s" % mode)
+        return self.protocol.rph_registrations.set_rph_registration_mode(mode)
+
 class CancelFedCMDialogAction:
     name = "cancel_fedcm_dialog"
 
@@ -442,6 +454,7 @@
            RemoveAllCredentialsAction,
            SetUserVerifiedAction,
            SetSPCTransactionModeAction,
+           SetRPHRegistrationModeAction,
            CancelFedCMDialogAction,
            ConfirmIDPLoginAction,
            SelectFedCMAccountAction,
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
index e89b57b57..7ef9c9b 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
@@ -760,7 +760,10 @@
                 except AttributeError as e:
                     # If we fail to get an attribute from the protocol presumably that's a
                     # ProtocolPart we don't implement
-                    if getattr(e, "obj") == self.protocol:
+                    # AttributeError got an obj property in Python 3.10, for older versions we
+                    # fall back to looking at the error message.
+                    if ((hasattr(e, "obj") and getattr(e, "obj") == self.protocol) or
+                        "'{self.protocol.__class__.__name__}' has no attribute" in str(e)):
                         raise NotImplementedError from e
         except self.unimplemented_exc:
             self.logger.warning("Action %s not implemented" % action)
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
index 1f183f1..8d60f1e 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -1037,8 +1037,14 @@
         self.implementation_kwargs = {}
         if reftest_internal:
             self.implementation_kwargs["screenshot"] = reftest_screenshot
-            self.implementation_kwargs["chrome_scope"] = (browser_version is not None and
-                                                          int(browser_version.split(".")[0]) < 82)
+            self.implementation_kwargs["chrome_scope"] = False
+            # Older versions of Gecko require switching to chrome scope to run refests
+            if browser_version is not None:
+                try:
+                    major_version = int(browser_version.split(".")[0])
+                    self.implementation_kwargs["chrome_scope"] = major_version < 82
+                except ValueError:
+                    pass
         self.close_after_done = close_after_done
         self.has_window = False
         self.original_pref_values = {}
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
index e3760b8..8b61f16c 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
@@ -32,6 +32,7 @@
                        WindowProtocolPart,
                        DebugProtocolPart,
                        SPCTransactionsProtocolPart,
+                       RPHRegistrationsProtocolPart,
                        FedCMProtocolPart,
                        VirtualSensorProtocolPart,
                        merge_dicts)
@@ -363,6 +364,13 @@
         body = {"mode": mode}
         return self.webdriver.send_session_command("POST", "secure-payment-confirmation/set-mode", body)
 
+class WebDriverRPHRegistrationsProtocolPart(RPHRegistrationsProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def set_rph_registration_mode(self, mode):
+        body = {"mode": mode}
+        return self.webdriver.send_session_command("POST", "custom-handlers/set-mode", body)
 
 class WebDriverFedCMProtocolPart(FedCMProtocolPart):
     def setup(self):
@@ -432,6 +440,7 @@
                   WebDriverSetPermissionProtocolPart,
                   WebDriverVirtualAuthenticatorProtocolPart,
                   WebDriverSPCTransactionsProtocolPart,
+                  WebDriverRPHRegistrationsProtocolPart,
                   WebDriverFedCMProtocolPart,
                   WebDriverDebugProtocolPart,
                   WebDriverVirtualSensorPart]
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
index cac25e4..2bae86c 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
@@ -616,6 +616,18 @@
         :param str mode: The automation mode to set"""
         pass
 
+class RPHRegistrationsProtocolPart(ProtocolPart):
+    """Protocol part for Custom Handlers registrations"""
+    __metaclass__ = ABCMeta
+
+    name = "rph_registrations"
+
+    @abstractmethod
+    def set_rph_registration_mode(self, mode):
+        """Set the RPH registration automation mode
+
+        :param str mode: The automation mode to set"""
+        pass
 
 class FedCMProtocolPart(ProtocolPart):
     """Protocol part for Federated Credential Management"""
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/products.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/products.py
index 5b26557..c81396f 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/products.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/products.py
@@ -31,7 +31,7 @@
         self.env_options = getattr(module, data["env_options"])()
         self.get_env_extras = getattr(module, data["env_extras"])
         self.run_info_extras = (getattr(module, data["run_info_extras"])
-                                if "run_info_extras" in data else lambda **kwargs:{})
+                                if "run_info_extras" in data else lambda product, **kwargs:{})
         self.get_timeout_multiplier = getattr(module, data["timeout_multiplier"])
 
         self.executor_classes = {}
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testdriver-extra.js b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testdriver-extra.js
index 29c6615..4cb54626 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testdriver-extra.js
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testdriver-extra.js
@@ -269,6 +269,10 @@
         return create_action("set_spc_transaction_mode", {mode, context});
     };
 
+    window.test_driver_internal.set_rph_registration_mode = function(mode, context = null) {
+        return create_action("set_rph_registration_mode", {mode, context});
+    };
+
     window.test_driver_internal.cancel_fedcm_dialog = function(context = null) {
         return create_action("cancel_fedcm_dialog", {context});
     };
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
index f29e902..2d81cab 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
@@ -640,7 +640,7 @@
         self.recording.set(["testrunner", "test"] + self.state.test.id.split("/")[1:])
         self.logger.test_start(self.state.test.id, subsuite=self.state.subsuite)
         if self.rerun > 1:
-            self.logger.info("Run %d/%d" % (self.run_count, self.rerun))
+            self.logger.info(f"Run {self.run_count + 1}/{self.rerun}")
             self.send_message("reset")
         self.run_count += 1
         if self.debug_info is None:
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
index b9e51901..d65369b 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
@@ -52,7 +52,7 @@
 def get_loader(test_paths: wptcommandline.TestPaths,
                product: products.Product,
                **kwargs: Any) -> Tuple[testloader.TestQueueBuilder, testloader.TestLoader]:
-    run_info_extras = product.run_info_extras(**kwargs)
+    run_info_extras = product.run_info_extras(logger, **kwargs)
     base_run_info = wpttest.get_run_info(kwargs["run_info"],
                                          product.name,
                                          browser_version=kwargs.get("browser_version"),
diff --git a/third_party/wpt_tools/wpt/websockets/handlers/sleep_10_v13_wsh.py b/third_party/wpt_tools/wpt/websockets/handlers/sleep_10_v13_wsh.py
index b0f1dde..bdef2f2 100755
--- a/third_party/wpt_tools/wpt/websockets/handlers/sleep_10_v13_wsh.py
+++ b/third_party/wpt_tools/wpt/websockets/handlers/sleep_10_v13_wsh.py
@@ -4,16 +4,7 @@
 from mod_pywebsocket import msgutil
 
 def web_socket_do_extra_handshake(request):
-    request.connection.write(b'x')
-    time.sleep(2)
-    request.connection.write(b'x')
-    time.sleep(2)
-    request.connection.write(b'x')
-    time.sleep(2)
-    request.connection.write(b'x')
-    time.sleep(2)
-    request.connection.write(b'x')
-    time.sleep(2)
+    time.sleep(10)
     return
 
 def web_socket_transfer_data(request):
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4dfd6f2..ea24b61 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -9127,7 +9127,9 @@
   <int value="4" label="Invalid reporting_origin field"/>
   <int value="5" label="Invalid report_type field"/>
   <int value="6" label="Mismatched reporting origin between source and report"/>
-  <int value="7" label="Metadata could not be read from database"/>
+  <int value="7"
+      label="(Obsolete) Metadata could not be read from database. Removed in
+             2023/12."/>
   <int value="8" label="Source data missing - event level report"/>
   <int value="9" label="Source data missing - aggregatable report"/>
   <int value="10" label="Source data found - null aggregatable report"/>
@@ -31491,6 +31493,8 @@
   <int value="-783999903" label="FilesTrashDrive:disabled"/>
   <int value="-783890018" label="LacrosProfileMigrationForAnyUser:disabled"/>
   <int value="-783093620" label="WebViewHitTestInBlinkOnTouchStart:enabled"/>
+  <int value="-782689039"
+      label="FencedFramesCrossOriginAutomaticBeacons:enabled"/>
   <int value="-782193212"
       label="AutofillEnablePaymentsMandatoryReauth:enabled"/>
   <int value="-781625651"
@@ -34574,6 +34578,8 @@
   <int value="684112629" label="BluetoothCoredump:disabled"/>
   <int value="684806628" label="TranslateLanguageByULP:disabled"/>
   <int value="685406951" label="UseNotificationCompatBuilder:disabled"/>
+  <int value="685571935"
+      label="FencedFramesCrossOriginAutomaticBeacons:disabled"/>
   <int value="685916283" label="enable-zip-archiver-on-file-manager"/>
   <int value="686929809" label="BorealisWebUIInstaller:enabled"/>
   <int value="687838135" label="ThirdPartyDoodles:disabled"/>
diff --git a/tools/metrics/histograms/histograms_index.txt b/tools/metrics/histograms/histograms_index.txt
index 1bb6d21..7d00183f 100644
--- a/tools/metrics/histograms/histograms_index.txt
+++ b/tools/metrics/histograms/histograms_index.txt
@@ -166,6 +166,7 @@
 tools/metrics/histograms/metadata/quickstart/enums.xml
 tools/metrics/histograms/metadata/quickstart/histograms.xml
 tools/metrics/histograms/metadata/quota/histograms.xml
+tools/metrics/histograms/metadata/readaloud/histograms.xml
 tools/metrics/histograms/metadata/renderer/histograms.xml
 tools/metrics/histograms/metadata/renderer4/histograms.xml
 tools/metrics/histograms/metadata/safe_browsing/enums.xml
diff --git a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
index 9b1c176..051ae25 100644
--- a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
@@ -347,6 +347,10 @@
 xiaohuic@chromium.org
 # quota
 ayui@chromium.org
+# readaloud
+andreaxg@google.com
+basiaz@google.com
+iwells@google.com
 # renderer4
 schenney@chromium.org
 # renderer
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 2dd014a..96530ad 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -124,11 +124,7 @@
 
 <histogram base="true" name="Blink.Accessibility.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>ikilpatrick@chromium.org</owner>
   <owner>layout-dev@chromium.org</owner>
@@ -149,11 +145,7 @@
 <histogram base="true"
     name="Blink.AnchorElementMetricsIntersectionObserver.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -176,11 +168,7 @@
 
 <histogram base="true" name="Blink.Animate.UpdateTime" units="microseconds"
     expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -715,11 +703,7 @@
 
 <histogram base="true" name="Blink.CompositingCommit.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -740,11 +724,7 @@
 
 <histogram base="true" name="Blink.CompositingInputs.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -798,11 +778,7 @@
 
 <histogram base="true" name="Blink.ContentDocumentUpdate.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -1096,11 +1072,7 @@
 
 <histogram base="true" name="Blink.DisplayLockIntersectionObserver.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -1342,6 +1314,16 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.FedCm.DomainHint.NumMatchingAccounts"
+    enum="FedCmNumAccounts" expires_after="2024-06-12">
+  <owner>npm@chromium.org</owner>
+  <owner>web-identity-eng@google.com</owner>
+  <summary>
+    Records the number of accounts the match a domain hint. Records at the time
+    the accounts received by the FedCM API are filtered.
+  </summary>
+</histogram>
+
 <histogram name="Blink.FedCm.Error.ErrorDialogResult"
     enum="FedCmErrorDialogResult" expires_after="M130">
   <owner>tanzachary@chromium.org</owner>
@@ -1481,8 +1463,8 @@
   </summary>
 </histogram>
 
-<histogram name="Blink.FedCm.LoginHint" enum="FedCmNumAccounts"
-    expires_after="2024-05-18">
+<histogram name="Blink.FedCm.LoginHint.NumMatchingAccounts"
+    enum="FedCmNumAccounts" expires_after="2024-05-18">
   <owner>npm@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -2010,11 +1992,7 @@
 
 <histogram base="true" name="Blink.ForcedStyleAndLayout.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2048,11 +2026,7 @@
 
 <histogram base="true" name="Blink.HandleInputEvents.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2072,11 +2046,7 @@
 
 <histogram base="true" name="Blink.HitTestDocumentUpdate.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2445,11 +2415,7 @@
 
 <histogram base="true" name="Blink.ImplCompositorCommit.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2471,11 +2437,7 @@
 
 <histogram base="true" name="Blink.IntersectionObservation.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2497,11 +2459,7 @@
 <histogram base="true"
     name="Blink.IntersectionObservationInternalCount.UpdateTime" units="count"
     expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>szager@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2514,11 +2472,7 @@
 <histogram base="true"
     name="Blink.IntersectionObservationJavascriptCount.UpdateTime"
     units="count" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>szager@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2530,11 +2484,7 @@
 
 <histogram base="true" name="Blink.JavascriptDocumentUpdate.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2559,11 +2509,7 @@
 
 <histogram base="true" name="Blink.JavascriptIntersectionObserver.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2616,11 +2562,7 @@
 
 <histogram base="true" name="Blink.Layout.UpdateTime" units="microseconds"
     expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>layout-dev@chromium.org</owner>
@@ -2663,11 +2605,7 @@
 
 <histogram base="true" name="Blink.LazyLoadIntersectionObserver.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2905,11 +2843,7 @@
 
 <histogram base="true" name="Blink.MainFrame.UpdateTime" units="microseconds"
     expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -2929,11 +2863,7 @@
 
 <histogram base="true" name="Blink.MediaIntersectionObserver.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -3183,11 +3113,7 @@
 
 <histogram base="true" name="Blink.Paint.UpdateTime" units="microseconds"
     expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -3207,11 +3133,7 @@
 
 <histogram base="true" name="Blink.ParseStyleSheet.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>cduvall@chromium.org</owner>
   <owner>jam@chromium.org</owner>
@@ -3230,6 +3152,18 @@
   </summary>
 </histogram>
 
+<histogram base="true" name="Blink.PossibleSynchronizedScrollCount.UpdateTime"
+    units="count" expires_after="2024-06-01">
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
+
+  <owner>vollick@chromium.org</owner>
+  <owner>flackr@chromium.org</owner>
+  <summary>
+    The total number times a page is estimated to have attempted a sync-scroll
+    update (i.e., synchronizing an effect with scroll).
+  </summary>
+</histogram>
+
 <histogram name="Blink.PreloadRequestWaitTime" units="ms"
     expires_after="2023-10-22">
   <owner>cduvall@chromium.org</owner>
@@ -3242,11 +3176,7 @@
 
 <histogram base="true" name="Blink.PrePaint.UpdateTime" units="microseconds"
     expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -3534,11 +3464,7 @@
 
 <histogram base="true" name="Blink.ServiceDocumentUpdate.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -3721,11 +3647,7 @@
 
 <histogram base="true" name="Blink.Style.UpdateTime" units="microseconds"
     expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>layout-dev@chromium.org</owner>
@@ -3757,11 +3679,7 @@
 
 <histogram name="Blink.UpdateViewportIntersection.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -4170,11 +4088,7 @@
 
 <histogram base="true" name="Blink.UserDrivenDocumentUpdate.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -4262,11 +4176,7 @@
 
 <histogram base="true" name="Blink.VisualUpdateDelay.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>szager@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
@@ -4281,11 +4191,7 @@
 
 <histogram base="true" name="Blink.WaitForCommit.UpdateTime"
     units="microseconds" expires_after="2024-06-01">
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" -->
-
-<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" -->
+<!-- Name completed by histogram_suffixes name="BlinkUpdateTimeSuffixes" -->
 
   <owner>pdr@chromium.org</owner>
   <owner>paint-dev@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/families/enums.xml b/tools/metrics/histograms/metadata/families/enums.xml
index a910b8c..7683362 100644
--- a/tools/metrics/histograms/metadata/families/enums.xml
+++ b/tools/metrics/histograms/metadata/families/enums.xml
@@ -180,22 +180,6 @@
   <int value="4" label="Regular User"/>
 </enum>
 
-<enum name="KidsChromeManagementClientParsingError">
-  <int value="0" label="Success"/>
-  <int value="1" label="Response dictionary failure"/>
-  <int value="2" label="Display classification failure"/>
-  <int value="3" label="Invalid display classification"/>
-</enum>
-
-<enum name="KidsManagementURLCheckerResponseStatus">
-  <int value="0" label="Success"/>
-  <int value="1" label="Network Error"/>
-  <int value="2" label="Unexpected Classification Value"/>
-  <int value="3" label="Parsing Error"/>
-  <int value="4" label="Token Error"/>
-  <int value="5" label="Http Error"/>
-</enum>
-
 <enum name="LegacySupervisedUserStatus">
   <int value="0" label="Displayed on login screen"/>
   <int value="1" label="Hidden on login screen"/>
diff --git a/tools/metrics/histograms/metadata/families/histograms.xml b/tools/metrics/histograms/metadata/families/histograms.xml
index 934be12e..f112db1b 100644
--- a/tools/metrics/histograms/metadata/families/histograms.xml
+++ b/tools/metrics/histograms/metadata/families/histograms.xml
@@ -697,44 +697,6 @@
   </summary>
 </histogram>
 
-<histogram name="ManagedUsers.KidsManagementClassifyUrlFailureDelay" units="ms"
-    expires_after="2024-12-12">
-  <owner>escordeiro@chromium.org</owner>
-  <owner>unichrome-eng@google.com</owner>
-  <owner>cros-families-eng@google.com</owner>
-  <summary>
-    The extra page load delays introduced by network requests due to the
-    supervised user url filtering feature, measured once per page load, for
-    failed requests. This is the delay to fetch the token and call the
-    ClassifyUrl rpc, when there is a cache miss.
-  </summary>
-</histogram>
-
-<histogram name="ManagedUsers.KidsManagementClassifyUrlSuccessDelay" units="ms"
-    expires_after="2024-12-12">
-  <owner>escordeiro@chromium.org</owner>
-  <owner>unichrome-eng@google.com</owner>
-  <owner>cros-families-eng@google.com</owner>
-  <summary>
-    The extra page load delays introduced by network requests due to the
-    supervised user url filtering feature, measured once per page load. This is
-    the delay to fetch the token and call the ClassifyUrl rpc, when there is a
-    cache miss. Only recorded for successful requests.
-  </summary>
-</histogram>
-
-<histogram name="ManagedUsers.KidsManagementUrlCheckerResponseStatus"
-    enum="KidsManagementURLCheckerResponseStatus" expires_after="2024-12-12">
-  <owner>escordeiro@chromium.org</owner>
-  <owner>unichrome-eng@google.com</owner>
-  <owner>cros-families-eng@google.com</owner>
-  <summary>
-    The counts of response status from supervised user
-    KidsManagementURLCheckerCLient. Each entry includes the outcome of a request
-    (i.e. success, net error, parsing error).
-  </summary>
-</histogram>
-
 <histogram name="ManagedUsers.RequestPermissionSource"
     enum="ManagedUserURLRequestPermissionSource" expires_after="2024-04-28">
   <owner>agawronska@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
index 77154afb..af15fb7 100644
--- a/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
+++ b/tools/metrics/histograms/metadata/histogram_suffixes_list.xml
@@ -548,80 +548,10 @@
   <affected-histogram name="WebRTC.Stun.BatchSuccessPercent.UnknownNAT"/>
 </histogram_suffixes>
 
-<histogram_suffixes name="BlinkUpdateTimeAggregatedSuffixes" separator=".">
+<histogram_suffixes name="BlinkUpdateTimeSuffixes" separator=".">
   <suffix name="AggregatedPreFCP"
       label="All the time spent Pre First Contentful Paint in this component"/>
-  <affected-histogram name="Blink.Accessibility.UpdateTime"/>
-  <affected-histogram
-      name="Blink.AnchorElementMetricsIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.Animate.UpdateTime"/>
-  <affected-histogram name="Blink.CompositingCommit.UpdateTime"/>
-  <affected-histogram name="Blink.CompositingInputs.UpdateTime"/>
-  <affected-histogram name="Blink.ContentDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.DisplayLockIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.ForcedStyleAndLayout.UpdateTime"/>
-  <affected-histogram name="Blink.HandleInputEvents.UpdateTime"/>
-  <affected-histogram name="Blink.HitTestDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.ImplCompositorCommit.UpdateTime"/>
-  <affected-histogram name="Blink.IntersectionObservation.UpdateTime"/>
-  <affected-histogram
-      name="Blink.IntersectionObservationInternalCount.UpdateTime"/>
-  <affected-histogram
-      name="Blink.IntersectionObservationJavascriptCount.UpdateTime"/>
-  <affected-histogram name="Blink.JavascriptDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.JavascriptIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.Layout.UpdateTime"/>
-  <affected-histogram name="Blink.LazyLoadIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.MainFrame.UpdateTime"/>
-  <affected-histogram name="Blink.MediaIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.Paint.UpdateTime"/>
-  <affected-histogram name="Blink.ParseStyleSheet.UpdateTime"/>
-  <affected-histogram name="Blink.PrePaint.UpdateTime"/>
-  <affected-histogram name="Blink.ServiceDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.Style.UpdateTime"/>
-  <affected-histogram name="Blink.UpdateViewportIntersection.UpdateTime"/>
-  <affected-histogram name="Blink.UserDrivenDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.VisualUpdateDelay.UpdateTime"/>
-  <affected-histogram name="Blink.WaitForCommit.UpdateTime"/>
-</histogram_suffixes>
-
-<histogram_suffixes name="BlinkUpdateTimePostFCPSuffixes" separator=".">
   <suffix name="PostFCP" label="Update occurred after First Contentful Paint."/>
-  <affected-histogram name="Blink.Accessibility.UpdateTime"/>
-  <affected-histogram
-      name="Blink.AnchorElementMetricsIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.Animate.UpdateTime"/>
-  <affected-histogram name="Blink.CompositingCommit.UpdateTime"/>
-  <affected-histogram name="Blink.CompositingInputs.UpdateTime"/>
-  <affected-histogram name="Blink.ContentDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.DisplayLockIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.ForcedStyleAndLayout.UpdateTime"/>
-  <affected-histogram name="Blink.HandleInputEvents.UpdateTime"/>
-  <affected-histogram name="Blink.HitTestDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.ImplCompositorCommit.UpdateTime"/>
-  <affected-histogram name="Blink.IntersectionObservation.UpdateTime"/>
-  <affected-histogram
-      name="Blink.IntersectionObservationInternalCount.UpdateTime"/>
-  <affected-histogram
-      name="Blink.IntersectionObservationJavascriptCount.UpdateTime"/>
-  <affected-histogram name="Blink.JavascriptDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.JavascriptIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.Layout.UpdateTime"/>
-  <affected-histogram name="Blink.LazyLoadIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.MainFrame.UpdateTime"/>
-  <affected-histogram name="Blink.MediaIntersectionObserver.UpdateTime"/>
-  <affected-histogram name="Blink.Paint.UpdateTime"/>
-  <affected-histogram name="Blink.ParseStyleSheet.UpdateTime"/>
-  <affected-histogram name="Blink.PrePaint.UpdateTime"/>
-  <affected-histogram name="Blink.ServiceDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.Style.UpdateTime"/>
-  <affected-histogram name="Blink.UpdateViewportIntersection.UpdateTime"/>
-  <affected-histogram name="Blink.UserDrivenDocumentUpdate.UpdateTime"/>
-  <affected-histogram name="Blink.VisualUpdateDelay.UpdateTime"/>
-  <affected-histogram name="Blink.WaitForCommit.UpdateTime"/>
-</histogram_suffixes>
-
-<histogram_suffixes name="BlinkUpdateTimePreFCPSuffixes" separator=".">
   <suffix name="PreFCP" label="Update occurred before First Contentful Paint."/>
   <affected-histogram name="Blink.Accessibility.UpdateTime"/>
   <affected-histogram
@@ -648,6 +578,7 @@
   <affected-histogram name="Blink.MediaIntersectionObserver.UpdateTime"/>
   <affected-histogram name="Blink.Paint.UpdateTime"/>
   <affected-histogram name="Blink.ParseStyleSheet.UpdateTime"/>
+  <affected-histogram name="Blink.PossibleSynchronizedScrollCount.UpdateTime"/>
   <affected-histogram name="Blink.PrePaint.UpdateTime"/>
   <affected-histogram name="Blink.ServiceDocumentUpdate.UpdateTime"/>
   <affected-histogram name="Blink.Style.UpdateTime"/>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 1a2aa85..6a04445a 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1834,18 +1834,6 @@
   </summary>
 </histogram>
 
-<histogram name="History.FirstUpdateTime" units="ms" expires_after="2020-02-02">
-  <owner>yiyaoliu@chromium.org</owner>
-  <component>UI&gt;Browser&gt;History</component>
-  <summary>
-    The amount of time for function
-    history::TopSitesBackend::UpdateTopSitesOnDBThread to execute when this
-    function is called during startup. Excludes the case where local
-    TopSitesDatabase db_ is unavailable, i.e. where the update doesn't really
-    happen.
-  </summary>
-</histogram>
-
 <histogram name="History.ForeignVisitsLegacy" units="visits"
     expires_after="2024-04-28">
   <owner>tommycli@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/readaloud/OWNERS b/tools/metrics/histograms/metadata/readaloud/OWNERS
new file mode 100644
index 0000000..70798d9
--- /dev/null
+++ b/tools/metrics/histograms/metadata/readaloud/OWNERS
@@ -0,0 +1,7 @@
+per-file OWNERS=file://tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+andreaxg@google.com
+basiaz@google.com
+iwells@google.com
\ No newline at end of file
diff --git a/tools/metrics/histograms/metadata/readaloud/histograms.xml b/tools/metrics/histograms/metadata/readaloud/histograms.xml
new file mode 100644
index 0000000..3a9dd43
--- /dev/null
+++ b/tools/metrics/histograms/metadata/readaloud/histograms.xml
@@ -0,0 +1,39 @@
+<!--
+Copyright 2023 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<!--
+This file is used to generate a comprehensive list of Compose histograms
+along with a detailed description for each histogram.
+
+For best practices on writing histogram descriptions, see
+https://chromium.googlesource.com/chromium/src.git/+/HEAD/tools/metrics/histograms/README.md
+
+Please follow the instructions in the OWNERS file in this directory to find a
+reviewer. If no OWNERS file exists, please consider signing up at
+go/reviewing-metrics (Googlers only), as all subdirectories are expected to
+have an OWNERS file. As a last resort you can send the CL to
+chromium-metrics-reviews@google.com.
+-->
+
+<histogram-configuration>
+
+<histograms>
+
+<histogram name="ReadAloud.IsPageReadable" enum="BooleanSuccess"
+    expires_after="2024-06-02">
+  <owner>andreaxg@google.com</owner>
+  <owner>basiaz@google.com</owner>
+  <owner>iwells@google.com</owner>
+  <summary>
+    Histogram for recording if a page readability check comes back successful or
+    not. The readability check is run on each page load for Android clients who
+    are eligible for ReadAloud.
+  </summary>
+</histogram>
+
+</histograms>
+
+</histogram-configuration>
diff --git a/tools/metrics/histograms/metadata/renderer/histograms.xml b/tools/metrics/histograms/metadata/renderer/histograms.xml
index 9ed385da..548669f 100644
--- a/tools/metrics/histograms/metadata/renderer/histograms.xml
+++ b/tools/metrics/histograms/metadata/renderer/histograms.xml
@@ -511,6 +511,20 @@
   </summary>
 </histogram>
 
+<histogram name="Renderer.PossibleSynchronizedScroll" enum="BooleanSuccess"
+    expires_after="2024-04-28">
+  <owner>vollick@chromium.org</owner>
+  <owner>flackr@chromium.org</owner>
+  <summary>
+    Counts the number of times we estimate that a page (whose main frame is the
+    outermost main frame) is attempting to synchronously make updates in
+    response to scroll (eg, to implement a JavaScript-based parallax effect). We
+    make this estimation based on a heuristic that checks that we a) have a
+    scroll handler, b) have accessed scroll position, and c) have made an update
+    (and this update can be realized by scheduling rAF).
+  </summary>
+</histogram>
+
 <histogram name="Renderer.Preload.UnusedResource" enum="BooleanSuccess"
     expires_after="2024-06-09">
   <owner>yoavweiss@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 564caf7..a923733 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -5004,6 +5004,14 @@
       The time spent parsing style sheets.
     </summary>
   </metric>
+  <metric name="PossibleSynchronizedScrollCount">
+    <summary>
+      The total number times a page (whose main frame is both the outermost main
+      frame and is local) is estimated to have attempted a sync- scroll update
+      (i.e., synchronizing an effect with scroll) between navigation and First
+      Contentful Paint.
+    </summary>
+  </metric>
   <metric name="PrePaint">
     <summary>
       The time spent in pre-paint document lifecycle work, between navigation
@@ -6065,6 +6073,27 @@
       The total main frame time spent parsing style sheets.
     </summary>
   </metric>
+  <metric name="PossibleSynchronizedScrollCount">
+    <summary>
+      The total number times a page (whose main frame is both the outermost main
+      frame and is local) is estimated to have attempted a sync- scroll update
+      (i.e., synchronizing an effect with scroll).
+    </summary>
+    <aggregation>
+      <history>
+        <statistics>
+          <quantiles type="std-percentiles"/>
+        </statistics>
+      </history>
+    </aggregation>
+  </metric>
+  <metric name="PossibleSynchronizedScrollCountBeginMainFrame">
+    <summary>
+      The total number times a page (whose main frame is both the outermost main
+      frame and is local) is estimated to have attempted a sync- scroll update
+      (i.e., synchronizing an effect with scroll).
+    </summary>
+  </metric>
   <metric name="PrePaint">
     <summary>
       The time taken for pre-paint for the main frame in microseconds during the
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index cf192c1..d86b663 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -14,7 +14,7 @@
         },
         "mac": {
             "hash": "5d8dbe78fcd33af1ed7c69c5e49a5b44f9159848",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/8c0a74dad3bea98a5284009a273f5a4b573ecfe9/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/ff3b6318208109a97dc91f5f653edfca466d3cca/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "28d38c5eef93cf965ad3b3c656d4419f8e55f864",
@@ -22,7 +22,7 @@
         },
         "linux": {
             "hash": "a72dcf8e23bc86107f2d7929810907e2b5082e31",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/12b9622ad9250c5411740ffcfbd8958f47bd7cf5/trace_processor_shell"
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/ff3b6318208109a97dc91f5f653edfca466d3cca/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/rust/build_rust.py b/tools/rust/build_rust.py
index 682e500..cbaa4c6b 100755
--- a/tools/rust/build_rust.py
+++ b/tools/rust/build_rust.py
@@ -753,6 +753,11 @@
         GitCherryPick(RUST_SRC_DIR, 'https://github.com/rust-lang/rust.git',
                       'a0c5079889b1f86dd9e246d8863a5c8b44fbdb78')
 
+        # TODO: Remove once
+        # https://github.com/rust-lang/rust/pull/118866 has been merged.
+        GitCherryPick(RUST_SRC_DIR, 'https://github.com/rust-lang/rust.git',
+                      '46a801559127441675f2341bd1d684809a47def1')
+
         path = FetchBetaPackage('cargo', checkout_revision)
         if sys.platform == 'win32':
             cargo_bin = os.path.join(path, 'cargo', 'bin', 'cargo.exe')
diff --git a/ui/accessibility/BUILD.gn b/ui/accessibility/BUILD.gn
index 6099edb..c3155c5 100644
--- a/ui/accessibility/BUILD.gn
+++ b/ui/accessibility/BUILD.gn
@@ -526,6 +526,8 @@
     sources = [ "android/javatests/src/org/chromium/ui/accessibility/AccessibilityStateTest.java" ]
     deps = [
       ":ax_base_java",
+      ":ui_accessibility_features_java",
+      "//base:base_java_test_support",
       "//base:base_junit_test_support",
       "//third_party/androidx:androidx_test_runner_java",
       "//third_party/junit:junit",
diff --git a/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java b/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java
index 3b43edf..1846f84 100644
--- a/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java
+++ b/ui/accessibility/android/java/src/org/chromium/ui/accessibility/AccessibilityState.java
@@ -565,7 +565,7 @@
                         == AccessibilityServiceInfo.FEEDBACK_GENERIC);
     }
 
-    static void updateAccessibilityServices() {
+    protected static void updateAccessibilityServices() {
         long now = SystemClock.elapsedRealtimeNanos() / 1000;
         if (!sInitialized) {
             sState = new State(false, false, false, false, false, false, false, false);
@@ -1178,4 +1178,12 @@
         sInitialized = true;
         sIsInTestingMode = true;
     }
+
+    protected static void uninitializeForTesting() {
+        sState = null;
+        sAccessibilityManager = null;
+        sInitialized = false;
+        sIsInTestingMode = false;
+        sPreInitCachedValuePerformGesturesEnabled = null;
+    }
 }
diff --git a/ui/accessibility/android/javatests/src/org/chromium/ui/accessibility/AccessibilityStateTest.java b/ui/accessibility/android/javatests/src/org/chromium/ui/accessibility/AccessibilityStateTest.java
index 0b68311..6a696c4 100644
--- a/ui/accessibility/android/javatests/src/org/chromium/ui/accessibility/AccessibilityStateTest.java
+++ b/ui/accessibility/android/javatests/src/org/chromium/ui/accessibility/AccessibilityStateTest.java
@@ -21,13 +21,18 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.robolectric.RuntimeEnvironment;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Features;
+import org.chromium.base.test.util.Features.EnableFeatures;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
@@ -42,6 +47,24 @@
     private static final String EVENT_TYPE_MASK_ERROR =
             "Conversion of event masks to event types not correct.";
 
+    private static final int MOCK_EVENT_TYPE_MASK =
+            AccessibilityEvent.TYPE_VIEW_CLICKED
+                    | AccessibilityEvent.TYPE_VIEW_FOCUSED
+                    | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+                    | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
+    private static final int MOCK_FLAG_TYPE_MASK =
+            AccessibilityServiceInfo.DEFAULT
+                    | AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+                    | AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+                    | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
+                    | AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+
+    private static final int MOCK_CAPABILITY_TYPE_MASK =
+            AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT;
+
+    @Rule public TestRule mProcessor = new Features.JUnitProcessor();
+
     private Context mContext;
 
     @Before
@@ -59,6 +82,11 @@
         AccessibilityState.setStateMaskForTesting(CAPABILITIES_MASK_HEURISTIC, 0);
     }
 
+    @After
+    public void tearDown() {
+        AccessibilityState.uninitializeForTesting();
+    }
+
     @Test
     @SmallTest
     public void testSimpleString() {
@@ -287,6 +315,7 @@
 
     @Test
     @SmallTest
+    @EnableFeatures(UiAccessibilityFeatures.START_SURFACE_ACCESSIBILITY_CHECK)
     public void testCalculateHeuristicState_Autofill_passwordManager() {
         AccessibilityServiceInfo myService =
                 new BuilderForTests(mContext)
@@ -297,14 +326,19 @@
                         .setFlags(AccessibilityState.PASSWORD_MANAGER_FLAG_TYPE_MASK)
                         .setCapabilities(AccessibilityState.PASSWORD_MANAGER_CAPABILITY_TYPE_MASK)
                         .build();
+        startTestWithService(
+                myService,
+                "android/com.android.server.autofill.AutofillCompatAccessibilityService");
 
-        Assert.assertNotNull(myService);
-        AccessibilityState.calculateHeuristicState(myService);
+        AccessibilityState.updateAccessibilityServices();
+
+        Assert.assertTrue(AccessibilityState.isAnyAccessibilityServiceEnabled());
         Assert.assertFalse(AccessibilityState.areOnlyPasswordManagerMasksRequested());
     }
 
     @Test
     @SmallTest
+    @EnableFeatures(UiAccessibilityFeatures.START_SURFACE_ACCESSIBILITY_CHECK)
     public void testCalculateHeuristicState_notAutofill_notPasswordManager() {
         AccessibilityServiceInfo myService =
                 new BuilderForTests(mContext)
@@ -312,14 +346,17 @@
                         .setFlags(~0)
                         .setCapabilities(~0)
                         .build();
+        startTestWithService(myService);
 
-        Assert.assertNotNull(myService);
-        AccessibilityState.calculateHeuristicState(myService);
+        AccessibilityState.updateAccessibilityServices();
+
+        Assert.assertTrue(AccessibilityState.isAnyAccessibilityServiceEnabled());
         Assert.assertFalse(AccessibilityState.areOnlyPasswordManagerMasksRequested());
     }
 
     @Test
     @SmallTest
+    @EnableFeatures(UiAccessibilityFeatures.START_SURFACE_ACCESSIBILITY_CHECK)
     public void testCalculateHeuristicState_notAutofill_passwordManager() {
         AccessibilityServiceInfo myService =
                 new BuilderForTests(mContext)
@@ -327,12 +364,67 @@
                         .setFlags(AccessibilityState.PASSWORD_MANAGER_FLAG_TYPE_MASK)
                         .setCapabilities(AccessibilityState.PASSWORD_MANAGER_CAPABILITY_TYPE_MASK)
                         .build();
+        startTestWithService(myService);
 
-        Assert.assertNotNull(myService);
-        AccessibilityState.calculateHeuristicState(myService);
+        AccessibilityState.updateAccessibilityServices();
+
+        Assert.assertTrue(AccessibilityState.isAnyAccessibilityServiceEnabled());
         Assert.assertTrue(AccessibilityState.areOnlyPasswordManagerMasksRequested());
     }
 
+    @Test
+    @SmallTest
+    @EnableFeatures(UiAccessibilityFeatures.START_SURFACE_ACCESSIBILITY_CHECK)
+    public void testTogglingMisconfiguredAccessibilityServices() {
+        // This service has the same config as Microsoft Authenticator during recent P0.
+        AccessibilityServiceInfo errorProneService =
+                new BuilderForTests(mContext)
+                        .setEventTypes(MOCK_EVENT_TYPE_MASK)
+                        .setFlags(MOCK_FLAG_TYPE_MASK)
+                        .setCapabilities(MOCK_CAPABILITY_TYPE_MASK)
+                        .build();
+
+        // This service has the correct config for a password manager.
+        AccessibilityServiceInfo properConfigService =
+                new BuilderForTests(mContext)
+                        .setEventTypes(MOCK_EVENT_TYPE_MASK)
+                        .setFlags(MOCK_FLAG_TYPE_MASK)
+                        .setCapabilities(
+                                MOCK_CAPABILITY_TYPE_MASK
+                                        | AccessibilityServiceInfo
+                                                .CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION)
+                        .build();
+
+        startTestWithService(errorProneService);
+
+        AccessibilityState.updateAccessibilityServices();
+
+        Assert.assertTrue(AccessibilityState.isAnyAccessibilityServiceEnabled());
+        // Before P0 fix, this call would have (incorrectly) returned true.
+        Assert.assertFalse(AccessibilityState.isTouchExplorationEnabled());
+
+        // Now enable the proper config, and ensure we do not enter an infinite loop and that
+        // we now show touch exploration as being enabled.
+        AccessibilityState.setEnabledServiceInfoListForTesting(List.of(properConfigService));
+
+        AccessibilityState.updateAccessibilityServices();
+
+        Assert.assertTrue(AccessibilityState.isAnyAccessibilityServiceEnabled());
+        Assert.assertTrue(AccessibilityState.isTouchExplorationEnabled());
+    }
+
+    private void startTestWithService(AccessibilityServiceInfo newService) {
+        startTestWithService(
+                newService, "com.example.google/app.accessibility.AccessibilityService");
+    }
+
+    private void startTestWithService(AccessibilityServiceInfo newService, String serviceName) {
+        Assert.assertNotNull(newService);
+        Assert.assertFalse(AccessibilityState.isAnyAccessibilityServiceEnabled());
+        AccessibilityState.setEnabledServiceInfoListForTesting(List.of(newService));
+        AccessibilityState.setEnabledServiceStringForTesting(serviceName);
+    }
+
     public static class BuilderForTests {
 
         private Context mContext;
diff --git a/ui/gfx/x/connection.cc b/ui/gfx/x/connection.cc
index e9034ec7de..b11df74 100644
--- a/ui/gfx/x/connection.cc
+++ b/ui/gfx/x/connection.cc
@@ -183,6 +183,7 @@
 Connection::~Connection() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  window_event_manager_.Reset();
   platform_event_source.reset();
 }
 
diff --git a/ui/gfx/x/window_event_manager.cc b/ui/gfx/x/window_event_manager.cc
index 7aee20e..9b5b32040 100644
--- a/ui/gfx/x/window_event_manager.cc
+++ b/ui/gfx/x/window_event_manager.cc
@@ -113,10 +113,18 @@
     : connection_(connection) {}
 
 WindowEventManager::~WindowEventManager() {
+  Reset();
+}
+
+void WindowEventManager::Reset() {
+  if (!connection_) {
+    return;
+  }
   // Clear events still requested by not-yet-deleted ScopedEventSelectors.
   for (const auto& mask_pair : mask_map_) {
     SetEventMask(connection_, mask_pair.first, EventMask::NoEvent);
   }
+  connection_ = nullptr;
 }
 
 void WindowEventManager::SelectEvents(Window window, EventMask event_mask) {
@@ -143,7 +151,9 @@
     return;
   }
 
-  SetEventMask(connection_, window, new_mask);
+  if (connection_) {
+    SetEventMask(connection_, window, new_mask);
+  }
 
   if (new_mask == EventMask::NoEvent) {
     mask_map_.erase(window);
diff --git a/ui/gfx/x/window_event_manager.h b/ui/gfx/x/window_event_manager.h
index 4b85f49..c0158edd 100644
--- a/ui/gfx/x/window_event_manager.h
+++ b/ui/gfx/x/window_event_manager.h
@@ -53,6 +53,8 @@
 
   ~WindowEventManager();
 
+  void Reset();
+
  private:
   friend class ScopedEventSelector;
 
@@ -70,7 +72,7 @@
   // necessary.
   void AfterMaskChanged(Window window, EventMask old_mask);
 
-  const raw_ptr<Connection> connection_;
+  raw_ptr<Connection> connection_;
 
   std::map<Window, std::unique_ptr<MultiMask>> mask_map_;
 };
diff --git a/v8 b/v8
index ba99d7f..b057218 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit ba99d7f61f605e0c4c5131f01ff9d4fc36ab269b
+Subproject commit b057218023961f8b94b0313dd6ddbcc274c06e74