diff --git a/.gitmodules b/.gitmodules
index e29d456d..577f35cc 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -341,6 +341,9 @@
 [submodule "third_party/securemessage/src"]
 	path = third_party/securemessage/src
 	url = https://chromium.googlesource.com/external/github.com/google/securemessage.git
+[submodule "third_party/speedometer/v3.0"]
+	path = third_party/speedometer/v3.0
+	url = https://chromium.googlesource.com/external/github.com/WebKit/Speedometer.git
 [submodule "third_party/ukey2/src"]
 	path = third_party/ukey2/src
 	url = https://chromium.googlesource.com/external/github.com/google/ukey2.git
diff --git a/DEPS b/DEPS
index 42e9a37..e41c35a 100644
--- a/DEPS
+++ b/DEPS
@@ -246,7 +246,7 @@
   #
   # CQ_INCLUDE_TRYBOTS=luci.chrome.try:lacros-amd64-generic-chrome-skylab
   # CQ_INCLUDE_TRYBOTS=luci.chrome.try:lacros-arm-generic-chrome-skylab
-  'lacros_sdk_version': '15580.0.0',
+  'lacros_sdk_version': '15594.0.0',
 
   # Generate location tag metadata to include in tests result data uploaded
   # to ResultDB. This isn't needed on some configs and the tool that generates
@@ -314,11 +314,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '97569bf6d194d12b46efbfc76b3f6b3e0d152252',
+  'src_internal_revision': '41d516d494867e38abcdef24e629510b56ef7d49',
   # 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': '335f748463db07bd9b238a2d3271528e89569316',
+  'skia_revision': '66e367b12e96cce50722a541f73048a83fdc6a7e',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -369,7 +369,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
-  'freetype_revision': '4a0c5639f7d6ab8b36e0f3989c76d09b2604e5b6',
+  'freetype_revision': '2d9fce53d4ce89f36075168282fcdd7289e082f9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -389,7 +389,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '48e812dfb95279ba02d1213898a3b60dab1cdd01',
+  'catapult_revision': '599ca89cf441075ca3373a6fb46c2ec7da62bbff',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
@@ -449,7 +449,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '9dffe91f6a4f715007a2e949dfca94eb02077479',
+  'dawn_revision': '6a90b51aab40b6703a7cec3108f9008322fdf31c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -849,7 +849,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '4375123d4e1ac4d9e10d3ddf8878ca5972bca19d',
+    '7aa5067e7b4b11457c64ba3fa44255d05ae6c638',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1055,7 +1055,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'y-6G_oNg8Q8knCjhqWX-urW46DUoWqVOimixrV7A1u8C',
+          'version': '8t7AhzW4MtCWgADVSJwwMnTq0eckG2d6c3586tGxmdQC',
       },
     ],
     'condition': 'checkout_android',
@@ -1299,13 +1299,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '7688e784503525b598a18991e190038381c333cf',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e6f40ea0341552c3e78fb107fe9f37b87d95f321',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '1a1e2f467cae76c971250f0d30d86783e622c447',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '9f07965c141f3b35e84a10efcef6095e5c14380d',
     'condition': 'checkout_src_internal',
   },
 
@@ -1777,7 +1777,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '08377bc86ca266241b49296fcfc4086b9b4038de',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '9102e6d39287a1e0c912ac0b5e088e3c886f2688',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1962,7 +1962,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '2ee990a4cb91b41491f83b52c9520476b18a9fd8',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'a041a97f630555469a9eac49eb2b51eece8ddf4f',
+    Var('webrtc_git') + '/src.git' + '@' + 'e17ac9fb05426dd2a8c7306e9a6bbb414682b43d',
 
   # Wuffs' canonical repository is at github.com/google/wuffs, but we use
   # Skia's mirror of Wuffs, the same as in upstream Skia's DEPS file.
@@ -4251,7 +4251,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '1b35a5961cccad1d65cd2be93e68f23cc9bab20a',
+        '425fdef2091ba80f95c72ab9f998633f7045d516',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/OWNERS b/OWNERS
index ffe1b68e..04558b0 100644
--- a/OWNERS
+++ b/OWNERS
@@ -256,6 +256,7 @@
 per-file third_party/gles2_conform=*
 per-file third_party/googlemac=*
 per-file third_party/khronos_glcts=*
+per-file third_party/speedometer/v3.0=*
 per-file third_party/widevine/cdm/chromeos=*
 per-file third_party/widevine/cdm/linux=*
 per-file third_party/widevine/cdm/mac=*
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index e5cf3d48..36596998 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -36,6 +36,8 @@
     "accelerators/accelerator_history_impl.cc",
     "accelerators/accelerator_history_impl.h",
     "accelerators/accelerator_ids.h",
+    "accelerators/accelerator_launcher_state_machine.cc",
+    "accelerators/accelerator_launcher_state_machine.h",
     "accelerators/accelerator_notifications.cc",
     "accelerators/accelerator_notifications.h",
     "accelerators/accelerator_prefs.cc",
@@ -3036,6 +3038,7 @@
     "accelerators/accelerator_controller_unittest.cc",
     "accelerators/accelerator_filter_unittest.cc",
     "accelerators/accelerator_history_unittest.cc",
+    "accelerators/accelerator_launcher_state_machine_unittest.cc",
     "accelerators/accelerator_table_unittest.cc",
     "accelerators/accelerator_tracker_unittest.cc",
     "accelerators/accelerator_unittest.cc",
@@ -3816,6 +3819,8 @@
     "//chromeos/ash/services/assistant/public/cpp",
     "//chromeos/ash/services/assistant/public/mojom",
     "//chromeos/ash/services/bluetooth_config:test_support",
+    "//chromeos/ash/services/bluetooth_config/public/cpp",
+    "//chromeos/ash/services/bluetooth_config/public/mojom",
     "//chromeos/ash/services/federated/public/cpp",
     "//chromeos/ash/services/federated/public/cpp:test_support",
     "//chromeos/ash/services/hotspot_config:hotspot_config",
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index b908b2a..bce396d 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -8,6 +8,7 @@
 #include <utility>
 
 #include "ash/accelerators/accelerator_commands.h"
+#include "ash/accelerators/accelerator_launcher_state_machine.h"
 #include "ash/accelerators/accelerator_notifications.h"
 #include "ash/accelerators/debug_commands.h"
 #include "ash/accessibility/accessibility_controller_impl.h"
@@ -27,6 +28,7 @@
 #include "ash/wm/window_state.h"
 #include "base/check.h"
 #include "base/containers/contains.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
@@ -39,6 +41,8 @@
 #include "ui/base/accelerators/accelerator_manager.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/events/ash/keyboard_layout_util.h"
+#include "ui/events/event_constants.h"
+#include "ui/ozone/public/ozone_platform.h"
 
 namespace ash {
 
@@ -214,7 +218,8 @@
 bool CanHandleToggleAppList(
     const ui::Accelerator& accelerator,
     const ui::Accelerator& previous_accelerator,
-    const std::set<ui::KeyboardCode>& currently_pressed_keys) {
+    const std::set<ui::KeyboardCode>& currently_pressed_keys,
+    const AcceleratorLauncherStateMachine& launcher_state_machine) {
   // Check if the accelerator pressed is a RWIN/LWIN, if so perform a
   // secondary check.
   if (accelerator.key_code() != ui::VKEY_LWIN &&
@@ -222,6 +227,10 @@
     return true;
   }
 
+  if (base::FeatureList::IsEnabled(features::kShortcutStateMachines)) {
+    return launcher_state_machine.CanHandleLauncher();
+  }
+
   for (auto key : currently_pressed_keys) {
     // The AppList accelerator is triggered on search(VKEY_LWIN) key release.
     // Sometimes users will press and release the search key while holding other
@@ -248,9 +257,11 @@
     // When spoken feedback is enabled, we should neither toggle the list nor
     // consume the key since Search+Shift is one of the shortcuts the a11y
     // feature uses. crbug.com/132296
-    if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled())
+    if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled()) {
       return false;
+    }
   }
+
   return true;
 }
 
@@ -384,6 +395,8 @@
     AshAcceleratorConfiguration* config)
     : accelerator_manager_(std::make_unique<ui::AcceleratorManager>()),
       accelerator_history_(std::make_unique<AcceleratorHistoryImpl>()),
+      launcher_state_machine_(std::make_unique<AcceleratorLauncherStateMachine>(
+          ui::OzonePlatform::GetInstance()->GetInputController())),
       accelerator_configuration_(config),
       output_volume_metric_delay_timer_(
           FROM_HERE,
@@ -409,6 +422,8 @@
   // interferes with Accelerator processing. See https://crbug.com/1174603.
   aura::Env::GetInstance()->AddPreTargetHandler(
       accelerator_history_.get(), ui::EventTarget::Priority::kAccessibility);
+  aura::Env::GetInstance()->AddPreTargetHandler(
+      launcher_state_machine_.get(), ui::EventTarget::Priority::kAccessibility);
 }
 
 AcceleratorControllerImpl::~AcceleratorControllerImpl() {
@@ -421,6 +436,8 @@
     accelerator_configuration_->RemoveObserver(this);
   }
   aura::Env::GetInstance()->RemovePreTargetHandler(accelerator_history_.get());
+  aura::Env::GetInstance()->RemovePreTargetHandler(
+      launcher_state_machine_.get());
 }
 
 void AcceleratorControllerImpl::InputMethodChanged(InputMethodManager* manager,
@@ -740,7 +757,8 @@
     case AcceleratorAction::kToggleAppList:
       return CanHandleToggleAppList(
           accelerator, previous_accelerator,
-          accelerator_history_->currently_pressed_keys());
+          accelerator_history_->currently_pressed_keys(),
+          *launcher_state_machine_);
     case AcceleratorAction::kToggleCalendar:
       return true;
     case AcceleratorAction::kToggleCapsLock:
diff --git a/ash/accelerators/accelerator_controller_impl.h b/ash/accelerators/accelerator_controller_impl.h
index dfe2213..443067f 100644
--- a/ash/accelerators/accelerator_controller_impl.h
+++ b/ash/accelerators/accelerator_controller_impl.h
@@ -13,6 +13,7 @@
 #include <vector>
 
 #include "ash/accelerators/accelerator_history_impl.h"
+#include "ash/accelerators/accelerator_launcher_state_machine.h"
 #include "ash/accelerators/accelerator_table.h"
 #include "ash/accelerators/ash_accelerator_configuration.h"
 #include "ash/accelerators/exit_warning_handler.h"
@@ -235,6 +236,7 @@
 
   // A tracker for the current and previous accelerators.
   std::unique_ptr<AcceleratorHistoryImpl> accelerator_history_;
+  std::unique_ptr<AcceleratorLauncherStateMachine> launcher_state_machine_;
 
   // Manages all accelerator mappings.
   raw_ptr<AshAcceleratorConfiguration, ExperimentalAsh>
diff --git a/ash/accelerators/accelerator_launcher_state_machine.cc b/ash/accelerators/accelerator_launcher_state_machine.cc
new file mode 100644
index 0000000..4f2aebd
--- /dev/null
+++ b/ash/accelerators/accelerator_launcher_state_machine.cc
@@ -0,0 +1,118 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/accelerators/accelerator_launcher_state_machine.h"
+
+#include "base/containers/fixed_flat_set.h"
+#include "ui/base/accelerators/accelerator.h"
+#include "ui/events/event.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+#include "ui/events/types/event_type.h"
+#include "ui/ozone/public/input_controller.h"
+#include "ui/ozone/public/ozone_platform.h"
+
+namespace ash {
+
+constexpr auto kMetaKeys =
+    base::MakeFixedFlatSet<ui::KeyboardCode>({ui::VKEY_LWIN, ui::VKEY_RWIN});
+
+constexpr auto kShiftKeys = base::MakeFixedFlatSet<ui::KeyboardCode>(
+    {ui::VKEY_SHIFT, ui::VKEY_LSHIFT, ui::VKEY_RSHIFT});
+
+AcceleratorLauncherStateMachine::AcceleratorLauncherStateMachine(
+    ui::InputController* input_controller)
+    : input_controller_(input_controller) {}
+
+AcceleratorLauncherStateMachine::~AcceleratorLauncherStateMachine() = default;
+
+void AcceleratorLauncherStateMachine::OnKeyEvent(ui::KeyEvent* event) {
+  if (event->type() != ui::ET_KEY_RELEASED &&
+      event->type() != ui::ET_KEY_PRESSED) {
+    return;
+  }
+
+  switch (current_state_) {
+    // When in kStart, if anything but Meta or Shift is pressed, we move to
+    // kSuppress.
+    // If Shift is pressed, its a no-op.
+    // If Meta is pressed, we move to kPrimed.
+    //
+    // kTrigger and kStart share the same logic as they are the same state
+    // except kTrigger will allow the launcher to be opened while kStart will
+    // not.
+    case LauncherState::kTrigger:
+    case LauncherState::kStart:
+      if (event->type() != ui::ET_KEY_PRESSED) {
+        current_state_ = LauncherState::kStart;
+        break;
+      }
+
+      if (kShiftKeys.contains(event->key_code())) {
+        current_state_ = LauncherState::kStart;
+        break;
+      }
+
+      if (!kMetaKeys.contains(event->key_code())) {
+        current_state_ = LauncherState::kSuppress;
+        break;
+      }
+
+      current_state_ = LauncherState::kPrimed;
+      break;
+
+    // In kPrimed, if anything besides Meta is pressed or released, we move to
+    // kSuppress.
+    // If Meta is released, we move to kTrigger.
+    case LauncherState::kPrimed:
+      if (!kMetaKeys.contains(event->key_code())) {
+        current_state_ = LauncherState::kSuppress;
+        break;
+      }
+
+      if (event->type() == ui::ET_KEY_PRESSED) {
+        break;
+      }
+
+      current_state_ = LauncherState::kTrigger;
+      break;
+
+    // While in kSuppress, if there is ever a point where no keys are being
+    // pressed, we move to kStart.
+    case LauncherState::kSuppress:
+      if (!input_controller_->AreAnyKeysPressed()) {
+        current_state_ = LauncherState::kStart;
+      }
+      break;
+  }
+}
+
+void AcceleratorLauncherStateMachine::OnMouseEvent(ui::MouseEvent* event) {
+  if (event->type() != ui::ET_MOUSE_PRESSED &&
+      event->type() != ui::ET_MOUSE_RELEASED) {
+    return;
+  }
+
+  switch (current_state_) {
+    // If the Mouse is pressed during any non-kSuppress state, move to
+    // kSuppress.
+    case LauncherState::kStart:
+    case LauncherState::kTrigger:
+    case LauncherState::kPrimed:
+      if (event->type() == ui::ET_MOUSE_PRESSED) {
+        current_state_ = LauncherState::kSuppress;
+      }
+      break;
+
+    // If the mouse is released during kSuppress and there are no keys pressed,
+    // move back to kStart.
+    case LauncherState::kSuppress:
+      if (event->type() == ui::ET_MOUSE_RELEASED &&
+          !input_controller_->AreAnyKeysPressed()) {
+        current_state_ = LauncherState::kStart;
+      }
+      break;
+  }
+}
+
+}  // namespace ash
diff --git a/ash/accelerators/accelerator_launcher_state_machine.h b/ash/accelerators/accelerator_launcher_state_machine.h
new file mode 100644
index 0000000..b1a5b0d
--- /dev/null
+++ b/ash/accelerators/accelerator_launcher_state_machine.h
@@ -0,0 +1,62 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_ACCELERATORS_ACCELERATOR_LAUNCHER_STATE_MACHINE_H_
+#define ASH_ACCELERATORS_ACCELERATOR_LAUNCHER_STATE_MACHINE_H_
+
+#include "ash/ash_export.h"
+#include "base/allocator/partition_allocator/pointers/raw_ptr.h"
+#include "ui/events/event_handler.h"
+#include "ui/ozone/public/input_controller.h"
+
+namespace ash {
+
+// Keeps track when the launcher is able to be opened via Search release or
+// Shift + Search release shortcuts.
+class ASH_EXPORT AcceleratorLauncherStateMachine : public ui::EventHandler {
+ public:
+  enum class LauncherState {
+    // Initial state, when we are waiting for our first bit of input for the
+    // state machine.
+    kStart,
+
+    // When the launcher can be triggered. In any other state, the launcher will
+    // be suppressed.
+    kTrigger,
+
+    // When we are waiting solely for the release of Meta so we can move to
+    // kTrigger.
+    kPrimed,
+
+    // When the launcher accelerator is being suppressed until there is a state
+    // where all keys are released.
+    kSuppress,
+  };
+
+  explicit AcceleratorLauncherStateMachine(
+      ui::InputController* input_controller);
+  AcceleratorLauncherStateMachine(const AcceleratorLauncherStateMachine&) =
+      delete;
+  AcceleratorLauncherStateMachine& operator=(
+      const AcceleratorLauncherStateMachine&) = delete;
+  ~AcceleratorLauncherStateMachine() override;
+
+  // ui::EventHandler:
+  void OnKeyEvent(ui::KeyEvent* event) override;
+  void OnMouseEvent(ui::MouseEvent* event) override;
+
+  bool CanHandleLauncher() const {
+    return current_state_ == LauncherState::kTrigger;
+  }
+
+  LauncherState current_state() const { return current_state_; }
+
+ private:
+  LauncherState current_state_ = LauncherState::kStart;
+  raw_ptr<ui::InputController> input_controller_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_ACCELERATORS_ACCELERATOR_LAUNCHER_STATE_MACHINE_H_
diff --git a/ash/accelerators/accelerator_launcher_state_machine_unittest.cc b/ash/accelerators/accelerator_launcher_state_machine_unittest.cc
new file mode 100644
index 0000000..e56b8b7
--- /dev/null
+++ b/ash/accelerators/accelerator_launcher_state_machine_unittest.cc
@@ -0,0 +1,244 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/accelerators/accelerator_launcher_state_machine.h"
+
+#include "ash/test/ash_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/devices/stylus_state.h"
+#include "ui/events/event.h"
+#include "ui/events/event_constants.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+#include "ui/events/types/event_type.h"
+#include "ui/ozone/public/input_controller.h"
+
+namespace ash::accelerators {
+
+namespace {
+
+using EventTypeVariant = absl::variant<ui::MouseEvent, ui::KeyEvent, bool>;
+using LauncherState = AcceleratorLauncherStateMachine::LauncherState;
+
+const bool kKeysPressed = true;
+const bool kNoKeysPressed = false;
+
+class MockInputController : public ui::InputController {
+ public:
+  MOCK_METHOD(bool, AreAnyKeysPressed, (), (override));
+
+ private:
+  bool HasMouse() override { return false; }
+  bool HasPointingStick() override { return false; }
+  bool HasTouchpad() override { return false; }
+  bool HasHapticTouchpad() override { return false; }
+  bool IsCapsLockEnabled() override { return false; }
+  void SetCapsLockEnabled(bool enabled) override {}
+  void SetNumLockEnabled(bool enabled) override {}
+  bool IsAutoRepeatEnabled() override { return true; }
+  void SetAutoRepeatEnabled(bool enabled) override {}
+  void SetAutoRepeatRate(const base::TimeDelta& delay,
+                         const base::TimeDelta& interval) override {}
+  void GetAutoRepeatRate(base::TimeDelta* delay,
+                         base::TimeDelta* interval) override {}
+  void SetCurrentLayoutByName(const std::string& layout_name) override {}
+  void SetKeyboardKeyBitsMapping(
+      base::flat_map<int, std::vector<uint64_t>> key_bits_mapping) override {}
+  std::vector<uint64_t> GetKeyboardKeyBits(int id) override {
+    return std::vector<uint64_t>();
+  }
+  void SetTouchEventLoggingEnabled(bool enabled) override {
+    NOTIMPLEMENTED_LOG_ONCE();
+  }
+  void SuspendMouseAcceleration() override {}
+  void EndMouseAccelerationSuspension() override {}
+  void SetThreeFingerClick(bool enabled) override {}
+  void SetGamepadKeyBitsMapping(
+      base::flat_map<int, std::vector<uint64_t>> key_bits_mapping) override {}
+  std::vector<uint64_t> GetGamepadKeyBits(int id) override {
+    return std::vector<uint64_t>();
+  }
+  void SetTapToClickPaused(bool state) override {}
+  void GetTouchDeviceStatus(GetTouchDeviceStatusReply reply) override {
+    std::move(reply).Run(std::string());
+  }
+  void GetTouchEventLog(const base::FilePath& out_dir,
+                        GetTouchEventLogReply reply) override {
+    std::move(reply).Run(std::vector<base::FilePath>());
+  }
+  void DescribeForLog(DescribeForLogReply reply) const override {
+    std::move(reply).Run(std::string());
+  }
+  void SetInternalTouchpadEnabled(bool enabled) override {}
+  bool IsInternalTouchpadEnabled() const override { return false; }
+  void SetTouchscreensEnabled(bool enabled) override {}
+  void GetStylusSwitchState(GetStylusSwitchStateReply reply) override {
+    std::move(reply).Run(ui::StylusState::REMOVED);
+  }
+  void SetInternalKeyboardFilter(
+      bool enable_filter,
+      std::vector<ui::DomCode> allowed_keys) override {}
+  void GetGesturePropertiesService(
+      mojo::PendingReceiver<ui::ozone::mojom::GesturePropertiesService>
+          receiver) override {}
+  void PlayVibrationEffect(int id,
+                           uint8_t amplitude,
+                           uint16_t duration_millis) override {}
+  void StopVibration(int id) override {}
+  void PlayHapticTouchpadEffect(
+      ui::HapticTouchpadEffect effect_type,
+      ui::HapticTouchpadEffectStrength strength) override {}
+  void SetHapticTouchpadEffectForNextButtonRelease(
+      ui::HapticTouchpadEffect effect_type,
+      ui::HapticTouchpadEffectStrength strength) override {}
+  void SetTouchpadSensitivity(absl::optional<int> device_id,
+                              int value) override {}
+  void SetTouchpadScrollSensitivity(absl::optional<int> device_id,
+                                    int value) override {}
+  void SetTouchpadHapticFeedback(absl::optional<int> device_id,
+                                 bool enabled) override {}
+  void SetTouchpadHapticClickSensitivity(absl::optional<int> device_id,
+                                         int value) override {}
+  void SetTapToClick(absl::optional<int> device_id, bool enabled) override {}
+  void SetTapDragging(absl::optional<int> device_id, bool enabled) override {}
+  void SetNaturalScroll(absl::optional<int> device_id, bool enabled) override {}
+  void SetMouseSensitivity(absl::optional<int> device_id, int value) override {}
+  void SetMouseScrollSensitivity(absl::optional<int> device_id,
+                                 int value) override {}
+  void SetMouseReverseScroll(absl::optional<int> device_id,
+                             bool enabled) override {}
+  void SetMouseAcceleration(absl::optional<int> device_id,
+                            bool enabled) override {}
+  void SetMouseScrollAcceleration(absl::optional<int> device_id,
+                                  bool enabled) override {}
+  void SetPointingStickSensitivity(absl::optional<int> device_id,
+                                   int value) override {}
+  void SetPointingStickAcceleration(absl::optional<int> device_id,
+                                    bool enabled) override {}
+  void SetTouchpadAcceleration(absl::optional<int> device_id,
+                               bool enabled) override {}
+  void SetTouchpadScrollAcceleration(absl::optional<int> device_id,
+                                     bool enabled) override {}
+  void SetPrimaryButtonRight(absl::optional<int> device_id,
+                             bool right) override {}
+  void SetPointingStickPrimaryButtonRight(absl::optional<int> device_id,
+                                          bool right) override {}
+};
+
+ui::KeyEvent KeyPress(ui::KeyboardCode key_code) {
+  return ui::KeyEvent(ui::ET_KEY_PRESSED, key_code, ui::EF_NONE);
+}
+
+ui::KeyEvent KeyRelease(ui::KeyboardCode key_code) {
+  return ui::KeyEvent(ui::ET_KEY_RELEASED, key_code, ui::EF_NONE);
+}
+
+ui::Event& GetEventFromVariant(EventTypeVariant& event) {
+  if (absl::holds_alternative<ui::MouseEvent>(event)) {
+    return absl::get<ui::MouseEvent>(event);
+  } else {
+    return absl::get<ui::KeyEvent>(event);
+  }
+}
+
+}  // namespace
+
+class AcceleratorLauncherStateMachineTest
+    : public AshTestBase,
+      public testing::WithParamInterface<
+          std::tuple<std::vector<EventTypeVariant>, LauncherState>> {
+ public:
+  void SetUp() override {
+    AshTestBase::SetUp();
+
+    std::tie(events_, expected_state_) = GetParam();
+
+    input_controller_ = std::make_unique<MockInputController>();
+    launcher_state_machine_ = std::make_unique<AcceleratorLauncherStateMachine>(
+        input_controller_.get());
+  }
+
+  void TearDown() override {
+    launcher_state_machine_.reset();
+    AshTestBase::TearDown();
+  }
+
+ protected:
+  std::unique_ptr<AcceleratorLauncherStateMachine> launcher_state_machine_;
+  std::unique_ptr<MockInputController> input_controller_;
+  std::vector<EventTypeVariant> events_;
+  LauncherState expected_state_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    Works,
+    AcceleratorLauncherStateMachineTest,
+    testing::ValuesIn(std::vector<
+                      std::tuple<std::vector<EventTypeVariant>, LauncherState>>{
+        // Pressing and releasing Meta key allows launcher to trigger.
+        {{kKeysPressed, KeyPress(ui::VKEY_LWIN), kNoKeysPressed,
+          KeyRelease(ui::VKEY_LWIN)},
+         LauncherState::kTrigger},
+
+        // Pressing Shift -> Meta allows launcher to open.
+        {{kKeysPressed, KeyPress(ui::VKEY_SHIFT), KeyPress(ui::VKEY_LWIN),
+          KeyRelease(ui::VKEY_LWIN)},
+         LauncherState::kTrigger},
+
+        // Getting into a suppressed state and then releasing all keys resets
+        // and allows you to trigger the launcher via Meta press and release.
+        {{kKeysPressed, KeyPress(ui::VKEY_LWIN), KeyPress(ui::VKEY_A),
+          KeyRelease(ui::VKEY_A), kNoKeysPressed, KeyRelease(ui::VKEY_LWIN),
+          kKeysPressed, KeyPress(ui::VKEY_LWIN), kNoKeysPressed,
+          KeyRelease(ui::VKEY_LWIN)},
+         LauncherState::kTrigger},
+
+        // Meta -> Shift -> Shift release -> Meta release does not trigger and
+        // instead puts us back at the start.
+        {{kKeysPressed, KeyPress(ui::VKEY_LWIN), KeyPress(ui::VKEY_SHIFT),
+          KeyRelease(ui::VKEY_SHIFT), kNoKeysPressed,
+          KeyRelease(ui::VKEY_LWIN)},
+         LauncherState::kStart},
+
+        // Shift -> Meta leaves us in kPrimed.
+        {{kKeysPressed, KeyPress(ui::VKEY_SHIFT), KeyPress(ui::VKEY_LWIN)},
+         LauncherState::kPrimed},
+
+        // Pressing any key besides shift or meta leaves us in kSuppress.
+        {{kKeysPressed, KeyPress(ui::VKEY_A)}, LauncherState::kSuppress},
+
+        // Pressing any key besides shift or meta and releasing resets us back
+        // to kStart.
+        {{kKeysPressed, KeyPress(ui::VKEY_A), kNoKeysPressed,
+          KeyRelease(ui::VKEY_A)},
+         LauncherState::kStart},
+
+        // Pressing A -> Press and release Meta leaves us still in kSuppress and
+        // does not allow the launcher to open.
+        {{kKeysPressed, KeyPress(ui::VKEY_A), KeyPress(ui::VKEY_LWIN),
+          KeyRelease(ui::VKEY_LWIN)},
+         LauncherState::kSuppress},
+
+        // Press A -> Press B -> Release A leaves us still in kSuppress as B is
+        // still being held down.
+        {{kKeysPressed, KeyPress(ui::VKEY_A), KeyPress(ui::VKEY_B),
+          KeyRelease(ui::VKEY_A)},
+         LauncherState::kSuppress},
+    }));
+
+TEST_P(AcceleratorLauncherStateMachineTest, StateTest) {
+  for (auto& event : events_) {
+    if (absl::holds_alternative<bool>(event)) {
+      ON_CALL(*input_controller_, AreAnyKeysPressed())
+          .WillByDefault(testing::Return(absl::get<bool>(event)));
+      continue;
+    }
+
+    launcher_state_machine_->OnEvent(&GetEventFromVariant(event));
+  }
+
+  EXPECT_EQ(expected_state_, launcher_state_machine_->current_state());
+}
+
+}  // namespace ash::accelerators
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index cb8e012..3009a7173 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -2354,6 +2354,12 @@
              "ShortcutCustomizationJelly",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// If enabled, system shortcuts will utilize state machiens instead of
+// keeping track of entire history of keys pressed.
+BASE_FEATURE(kShortcutStateMachines,
+             "ShortcutStateMachines",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enables or disables a toggle to enable Bluetooth debug logs.
 BASE_FEATURE(kShowBluetoothDebugLogToggle,
              "ShowBluetoothDebugLogToggle",
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 7aaaa27..d460544 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -700,6 +700,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kShortcutCustomizationJelly);
 COMPONENT_EXPORT(ASH_CONSTANTS)
+BASE_DECLARE_FEATURE(kShortcutStateMachines);
+COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kShowBluetoothDebugLogToggle);
 COMPONENT_EXPORT(ASH_CONSTANTS)
 BASE_DECLARE_FEATURE(kShowLiveCaptionInVideoConferenceTray);
diff --git a/ash/host/ash_window_tree_host_platform_unittest.cc b/ash/host/ash_window_tree_host_platform_unittest.cc
index 729408e..2f5a0b7 100644
--- a/ash/host/ash_window_tree_host_platform_unittest.cc
+++ b/ash/host/ash_window_tree_host_platform_unittest.cc
@@ -127,6 +127,7 @@
   void GetGesturePropertiesService(
       mojo::PendingReceiver<ui::ozone::mojom::GesturePropertiesService>
           receiver) override {}
+  bool AreAnyKeysPressed() override { return false; }
 
   bool GetAccelerationSuspended() { return acceleration_suspended_; }
 
diff --git a/ash/system/bluetooth/bluetooth_detailed_view.h b/ash/system/bluetooth/bluetooth_detailed_view.h
index a00f10e..062fead 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view.h
+++ b/ash/system/bluetooth/bluetooth_detailed_view.h
@@ -68,8 +68,9 @@
   // when testing, where the implementation might not inherit from views::View.
   virtual views::View* GetAsView() = 0;
 
-  // Updates the detailed view to reflect a Bluetooth state of |enabled|.
-  virtual void UpdateBluetoothEnabledState(bool enabled) = 0;
+  // Updates the detailed view to reflect a Bluetooth state of |system_state|.
+  virtual void UpdateBluetoothEnabledState(
+      const bluetooth_config::mojom::BluetoothSystemState system_state) = 0;
 
   // Creates a targetable row for a single device within the device list. The
   // client is expected to configure the returned view themselves, and to use
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_controller.cc b/ash/system/bluetooth/bluetooth_detailed_view_controller.cc
index 9eff3e97..a2c61b7 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_controller.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_controller.cc
@@ -151,13 +151,11 @@
 }
 
 void BluetoothDetailedViewController::BluetoothEnabledStateChanged() {
-  const bool bluetooth_enabled_state =
-      IsBluetoothEnabledOrEnabling(system_state_);
   if (view_)
-    view_->UpdateBluetoothEnabledState(bluetooth_enabled_state);
+    view_->UpdateBluetoothEnabledState(system_state_);
   if (device_list_controller_) {
     device_list_controller_->UpdateBluetoothEnabledState(
-        bluetooth_enabled_state);
+        IsBluetoothEnabledOrEnabling(system_state_));
   }
 }
 
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl.cc b/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
index f1a7f19..df4e6fc0 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
@@ -23,6 +23,8 @@
 #include "ash/system/tray/tri_view.h"
 #include "base/check.h"
 #include "base/functional/bind.h"
+#include "chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h"
+#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "device/bluetooth/chromeos/bluetooth_utils.h"
 #include "ui/accessibility/ax_enums.mojom-shared.h"
@@ -45,6 +47,10 @@
 namespace ash {
 namespace {
 
+using bluetooth_config::IsBluetoothEnabled;
+using bluetooth_config::IsBluetoothEnabledOrEnabling;
+using bluetooth_config::mojom::BluetoothSystemState;
+
 constexpr auto kToggleRowTriViewInsets = gfx::Insets::VH(8, 24);
 constexpr auto kMainContainerMargins = gfx::Insets::TLBR(2, 0, 0, 0);
 constexpr auto kPairNewDeviceIconMargins = gfx::Insets::TLBR(0, 2, 0, 0);
@@ -61,7 +67,7 @@
   CreateScrollableList();
   CreateTopContainer();
   CreateMainContainer();
-  UpdateBluetoothEnabledState(/*enabled=*/false);
+  UpdateBluetoothEnabledState(BluetoothSystemState::kDisabled);
   device::RecordUiSurfaceDisplayed(
       device::BluetoothUiSurface::kBluetoothQuickSettings);
 }
@@ -72,29 +78,34 @@
   return this;
 }
 
-void BluetoothDetailedViewImpl::UpdateBluetoothEnabledState(bool enabled) {
+void BluetoothDetailedViewImpl::UpdateBluetoothEnabledState(
+    const BluetoothSystemState system_state) {
+  bool is_enabled_or_enabling = IsBluetoothEnabledOrEnabling(system_state);
+
   // Use square corners on the bottom edge when Bluetooth is enabled.
-  top_container_->SetBehavior(enabled
+  top_container_->SetBehavior(is_enabled_or_enabling
                                   ? RoundedContainer::Behavior::kTopRounded
                                   : RoundedContainer::Behavior::kAllRounded);
-  main_container_->SetVisible(enabled);
+  main_container_->SetVisible(is_enabled_or_enabling);
 
   // Update the top container Bluetooth icon.
   toggle_icon_->SetImage(ui::ImageModel::FromVectorIcon(
-      enabled ? kSystemMenuBluetoothIcon : kSystemMenuBluetoothDisabledIcon,
+      is_enabled_or_enabling ? kSystemMenuBluetoothIcon
+                             : kSystemMenuBluetoothDisabledIcon,
       cros_tokens::kCrosSysOnSurface));
 
   // Update the top container on/off label.
   toggle_row_->text_label()->SetText(l10n_util::GetStringUTF16(
-      enabled ? IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED_SHORT
-              : IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED_SHORT));
+      is_enabled_or_enabling ? IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED_SHORT
+                             : IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED_SHORT));
 
   // Update the toggle row and button tooltips. The entire row is clickable.
   std::u16string tooltip_template =
-      enabled ? l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED_TOOLTIP)
-              : l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED_TOOLTIP);
+      is_enabled_or_enabling
+          ? l10n_util::GetStringUTF16(
+                IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED_TOOLTIP)
+          : l10n_util::GetStringUTF16(
+                IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED_TOOLTIP);
   std::u16string tooltip_text = l10n_util::GetStringFUTF16(
       IDS_ASH_STATUS_TRAY_BLUETOOTH_TOGGLE_TOOLTIP, tooltip_template);
   toggle_row_->SetTooltipText(tooltip_text);
@@ -103,10 +114,14 @@
       l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_BLUETOOTH));
 
   // Ensure the toggle button is in sync with the current Bluetooth state.
-  if (toggle_button_->GetIsOn() != enabled) {
-    toggle_button_->SetIsOn(enabled);
+  if (toggle_button_->GetIsOn() != is_enabled_or_enabling) {
+    toggle_button_->SetIsOn(is_enabled_or_enabling);
   }
 
+  // Only show "Pair new device" button if bluetooth is enabled.
+  // see (b/297044914)
+  pair_new_device_view_->SetVisible(IsBluetoothEnabled(system_state));
+
   InvalidateLayout();
 }
 
@@ -256,10 +271,14 @@
 void BluetoothDetailedViewImpl::ToggleBluetoothState(bool new_state) {
   delegate()->OnToggleClicked(new_state);
 
+  const BluetoothSystemState new_system_state =
+      new_state ? BluetoothSystemState::kEnabling
+                : BluetoothSystemState::kDisabling;
+
   // Avoid the situation where there is a delay between the toggle becoming
   // enabled/disabled and Bluetooth becoming enabled/disabled by forcing the
   // view state to match the toggle state.
-  UpdateBluetoothEnabledState(new_state);
+  UpdateBluetoothEnabledState(new_system_state);
 }
 
 BEGIN_METADATA(BluetoothDetailedViewImpl, TrayDetailedView)
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl.h b/ash/system/bluetooth/bluetooth_detailed_view_impl.h
index 402a6ff8..6a8affe 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_impl.h
+++ b/ash/system/bluetooth/bluetooth_detailed_view_impl.h
@@ -11,6 +11,7 @@
 #include "ash/system/tray/tray_detailed_view.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
 namespace views {
@@ -39,7 +40,9 @@
 
   // BluetoothDetailedView:
   views::View* GetAsView() override;
-  void UpdateBluetoothEnabledState(bool enabled) override;
+  void UpdateBluetoothEnabledState(
+      const bluetooth_config::mojom::BluetoothSystemState system_state)
+      override;
   BluetoothDeviceListItemView* AddDeviceListItem() override;
   views::View* AddDeviceListSubHeader(const gfx::VectorIcon& icon,
                                       int text_id) override;
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl_unittest.cc b/ash/system/bluetooth/bluetooth_detailed_view_impl_unittest.cc
index 8f77515..cf740563 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_impl_unittest.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_impl_unittest.cc
@@ -24,6 +24,7 @@
 namespace {
 
 using bluetooth_config::mojom::BluetoothDeviceProperties;
+using bluetooth_config::mojom::BluetoothSystemState;
 using bluetooth_config::mojom::PairedBluetoothDeviceProperties;
 using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr;
 
@@ -124,8 +125,10 @@
   HoverHighlightView* toggle_row = GetToggleRow();
   Switch* toggle_button = GetToggleButton();
   RoundedContainer* main_container = GetMainContainer();
+  views::Button* pair_new_device_view = GetPairNewDeviceView();
 
-  bluetooth_detailed_view_->UpdateBluetoothEnabledState(true);
+  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabled);
 
   EXPECT_EQ(u"On", toggle_row->text_label()->GetText());
   EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is on.",
@@ -134,8 +137,10 @@
   EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is on.",
             toggle_button->GetTooltipText());
   EXPECT_TRUE(main_container->GetVisible());
+  EXPECT_TRUE(pair_new_device_view->GetVisible());
 
-  bluetooth_detailed_view_->UpdateBluetoothEnabledState(false);
+  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kDisabled);
 
   EXPECT_EQ(u"Off", toggle_row->text_label()->GetText());
   EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is off.",
@@ -144,6 +149,17 @@
   EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is off.",
             toggle_button->GetTooltipText());
   EXPECT_FALSE(main_container->GetVisible());
+
+  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabling);
+  EXPECT_EQ(u"On", toggle_row->text_label()->GetText());
+  EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is on.",
+            toggle_row->GetTooltipText());
+  EXPECT_TRUE(toggle_button->GetIsOn());
+  EXPECT_EQ(u"Toggle Bluetooth. Bluetooth is on.",
+            toggle_button->GetTooltipText());
+  EXPECT_TRUE(main_container->GetVisible());
+  EXPECT_FALSE(pair_new_device_view->GetVisible());
 }
 
 TEST_F(BluetoothDetailedViewImplTest, PressingToggleRowNotifiesDelegate) {
@@ -157,17 +173,22 @@
 
 TEST_F(BluetoothDetailedViewImplTest, PressingToggleButtonNotifiesDelegate) {
   Switch* toggle_button = GetToggleButton();
+  views::Button* pair_new_device_view = GetPairNewDeviceView();
+
   EXPECT_FALSE(toggle_button->GetIsOn());
   EXPECT_FALSE(bluetooth_detailed_view_delegate_.last_toggle_state_);
+  EXPECT_FALSE(pair_new_device_view->GetVisible());
 
   LeftClickOn(toggle_button);
 
   EXPECT_TRUE(toggle_button->GetIsOn());
   EXPECT_TRUE(bluetooth_detailed_view_delegate_.last_toggle_state_);
+  EXPECT_FALSE(pair_new_device_view->GetVisible());
 }
 
 TEST_F(BluetoothDetailedViewImplTest, PressingPairNewDeviceNotifiesDelegate) {
-  bluetooth_detailed_view_->UpdateBluetoothEnabledState(true);
+  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabled);
   views::test::RunScheduledLayout(bluetooth_detailed_view_);
 
   // Clicking the "pair new device" row notifies the delegate.
@@ -178,7 +199,8 @@
 }
 
 TEST_F(BluetoothDetailedViewImplTest, SelectingDeviceListItemNotifiesDelegate) {
-  bluetooth_detailed_view_->UpdateBluetoothEnabledState(true);
+  bluetooth_detailed_view_->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabled);
 
   // Create a simulated device and add it to the list.
   PairedBluetoothDevicePropertiesPtr paired_properties =
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_legacy.cc b/ash/system/bluetooth/bluetooth_detailed_view_legacy.cc
index eb78545e2..6b4b3458 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_legacy.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_legacy.cc
@@ -24,6 +24,8 @@
 #include "ash/system/tray/tri_view.h"
 #include "base/check.h"
 #include "base/memory/ptr_util.h"
+#include "chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h"
+#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
 #include "device/bluetooth/chromeos/bluetooth_utils.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/geometry/insets.h"
@@ -37,6 +39,10 @@
 
 namespace ash {
 
+using bluetooth_config::IsBluetoothEnabled;
+using bluetooth_config::IsBluetoothEnabledOrEnabling;
+using bluetooth_config::mojom::BluetoothSystemState;
+
 BluetoothDetailedViewLegacy::BluetoothDetailedViewLegacy(
     DetailedViewDelegate* detailed_view_delegate,
     BluetoothDetailedView::Delegate* delegate)
@@ -48,7 +54,7 @@
   CreateScrollableList();
   CreateDisabledView();
   CreatePairNewDeviceView();
-  UpdateBluetoothEnabledState(/*enabled=*/false);
+  UpdateBluetoothEnabledState(BluetoothSystemState::kDisabled);
   device::RecordUiSurfaceDisplayed(
       device::BluetoothUiSurface::kBluetoothQuickSettings);
 }
@@ -59,23 +65,31 @@
   return this;
 }
 
-void BluetoothDetailedViewLegacy::UpdateBluetoothEnabledState(bool enabled) {
-  disabled_view_->SetVisible(!enabled);
-  pair_new_device_view_->SetVisible(enabled);
-  scroller()->SetVisible(enabled);
+void BluetoothDetailedViewLegacy::UpdateBluetoothEnabledState(
+    const BluetoothSystemState system_state) {
+  bool is_enabled_or_enabling = IsBluetoothEnabledOrEnabling(system_state);
+  disabled_view_->SetVisible(!is_enabled_or_enabling);
+  scroller()->SetVisible(is_enabled_or_enabling);
+
+  // Only show "Pair new device" button if bluetooth is enabled.
+  // see (b/297044914)
+  pair_new_device_view_->SetVisible(IsBluetoothEnabled(system_state));
 
   const std::u16string toggle_tooltip =
-      enabled ? l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED_TOOLTIP)
-              : l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED_TOOLTIP);
+      is_enabled_or_enabling
+          ? l10n_util::GetStringUTF16(
+                IDS_ASH_STATUS_TRAY_BLUETOOTH_ENABLED_TOOLTIP)
+          : l10n_util::GetStringUTF16(
+                IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED_TOOLTIP);
   toggle_button_->SetTooltipText(l10n_util::GetStringFUTF16(
       IDS_ASH_STATUS_TRAY_BLUETOOTH_TOGGLE_TOOLTIP, toggle_tooltip));
 
-  // The toggle should already have animated to |enabled| unless it has become
-  // out of sync with the Bluetooth state, so just set it to the correct state.
-  if (toggle_button_->GetIsOn() != enabled)
-    toggle_button_->SetIsOn(enabled);
+  // The toggle should already have animated to |is_enabled_or_enabling| unless
+  // it has become out of sync with the Bluetooth state, so just set it to the
+  // correct state.
+  if (toggle_button_->GetIsOn() != is_enabled_or_enabling) {
+    toggle_button_->SetIsOn(is_enabled_or_enabling);
+  }
 
   Layout();
 }
@@ -219,10 +233,14 @@
   const bool toggle_state = toggle_button_->GetIsOn();
   delegate()->OnToggleClicked(toggle_state);
 
+  const BluetoothSystemState new_system_state =
+      toggle_state ? BluetoothSystemState::kEnabling
+                   : BluetoothSystemState::kDisabling;
+
   // Avoid the situation where there is a delay between the toggle becoming
   // enabled/disabled and Bluetooth becoming enabled/disabled by forcing the
   // view state to match the toggle state.
-  UpdateBluetoothEnabledState(toggle_state);
+  UpdateBluetoothEnabledState(new_system_state);
 }
 
 }  // namespace ash
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_legacy.h b/ash/system/bluetooth/bluetooth_detailed_view_legacy.h
index 483aa81..38007170 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_legacy.h
+++ b/ash/system/bluetooth/bluetooth_detailed_view_legacy.h
@@ -49,7 +49,9 @@
 
   // BluetoothDetailedView:
   views::View* GetAsView() override;
-  void UpdateBluetoothEnabledState(bool enabled) override;
+  void UpdateBluetoothEnabledState(
+      const bluetooth_config::mojom::BluetoothSystemState system_state)
+      override;
   BluetoothDeviceListItemView* AddDeviceListItem() override;
   views::View* AddDeviceListSubHeader(const gfx::VectorIcon& icon,
                                       int text_id) override;
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_legacy_unittest.cc b/ash/system/bluetooth/bluetooth_detailed_view_legacy_unittest.cc
index 9a240ae..4c2bf26 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_legacy_unittest.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_legacy_unittest.cc
@@ -38,6 +38,7 @@
 namespace {
 
 using bluetooth_config::mojom::BluetoothDeviceProperties;
+using bluetooth_config::mojom::BluetoothSystemState;
 using bluetooth_config::mojom::PairedBluetoothDeviceProperties;
 using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr;
 
@@ -194,30 +195,43 @@
   EXPECT_FALSE(pair_new_device_view->GetVisible());
   EXPECT_TRUE(disabled_view->GetVisible());
 
-  bluetooth_detailed_view()->UpdateBluetoothEnabledState(true);
+  bluetooth_detailed_view()->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabled);
 
   EXPECT_TRUE(toggle_button->GetIsOn());
   EXPECT_TRUE(pair_new_device_view->GetVisible());
   EXPECT_FALSE(disabled_view->GetVisible());
 
-  bluetooth_detailed_view()->UpdateBluetoothEnabledState(false);
+  bluetooth_detailed_view()->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kDisabled);
 
   EXPECT_FALSE(toggle_button->GetIsOn());
   EXPECT_FALSE(pair_new_device_view->GetVisible());
   EXPECT_TRUE(disabled_view->GetVisible());
+
+  bluetooth_detailed_view()->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabling);
+
+  EXPECT_TRUE(toggle_button->GetIsOn());
+  EXPECT_FALSE(pair_new_device_view->GetVisible());
+  EXPECT_FALSE(disabled_view->GetVisible());
 }
 
 TEST_F(BluetoothDetailedViewLegacyTest, PressingToggleNotifiesDelegate) {
   views::ToggleButton* toggle_button = FindBluetoothToggleButton();
+  views::View* pair_new_device_view = FindPairNewDeviceView();
+
   EXPECT_FALSE(toggle_button->GetIsOn());
   EXPECT_FALSE(
       bluetooth_detailed_view_delegate()->last_bluetooth_toggle_state());
+  EXPECT_FALSE(pair_new_device_view->GetVisible());
 
   LeftClickOn(toggle_button);
 
   EXPECT_TRUE(toggle_button->GetIsOn());
   EXPECT_TRUE(
       bluetooth_detailed_view_delegate()->last_bluetooth_toggle_state());
+  EXPECT_FALSE(pair_new_device_view->GetVisible());
 }
 
 TEST_F(BluetoothDetailedViewLegacyTest, BluetoothToggleHasCorrectTooltipText) {
@@ -229,7 +243,8 @@
                     IDS_ASH_STATUS_TRAY_BLUETOOTH_DISABLED_TOOLTIP)),
             toggle_button->GetTooltipText());
 
-  bluetooth_detailed_view()->UpdateBluetoothEnabledState(true);
+  bluetooth_detailed_view()->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabled);
   EXPECT_EQ(l10n_util::GetStringFUTF16(
                 IDS_ASH_STATUS_TRAY_BLUETOOTH_TOGGLE_TOOLTIP,
                 l10n_util::GetStringUTF16(
@@ -245,7 +260,8 @@
   EXPECT_EQ(0u, bluetooth_detailed_view_delegate()
                     ->on_pair_new_device_requested_call_count());
 
-  bluetooth_detailed_view()->UpdateBluetoothEnabledState(true);
+  bluetooth_detailed_view()->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabled);
   LeftClickOn(pair_new_device_button);
   EXPECT_EQ(1u, bluetooth_detailed_view_delegate()
                     ->on_pair_new_device_requested_call_count());
@@ -255,7 +271,8 @@
   IconButton* pair_new_device_button = FindPairNewDeviceClickableView();
   views::View* pair_new_device_view = FindPairNewDeviceView();
 
-  bluetooth_detailed_view()->UpdateBluetoothEnabledState(true);
+  bluetooth_detailed_view()->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabled);
 
   EXPECT_EQ(2u, pair_new_device_view->children().size());
   EXPECT_STREQ("Separator",
@@ -278,7 +295,8 @@
 
 TEST_F(BluetoothDetailedViewLegacyTest,
        SelectingDeviceListItemNotifiesDelegate) {
-  bluetooth_detailed_view()->UpdateBluetoothEnabledState(true);
+  bluetooth_detailed_view()->UpdateBluetoothEnabledState(
+      BluetoothSystemState::kEnabled);
 
   PairedBluetoothDevicePropertiesPtr paired_properties =
       PairedBluetoothDeviceProperties::New();
diff --git a/ash/system/bluetooth/fake_bluetooth_detailed_view.cc b/ash/system/bluetooth/fake_bluetooth_detailed_view.cc
index 86d3009..92c7b3b 100644
--- a/ash/system/bluetooth/fake_bluetooth_detailed_view.cc
+++ b/ash/system/bluetooth/fake_bluetooth_detailed_view.cc
@@ -6,11 +6,16 @@
 
 #include "ash/system/bluetooth/bluetooth_device_list_item_view.h"
 #include "ash/system/tray/tri_view.h"
+#include "chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h"
+#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/views/controls/label.h"
 
 namespace ash {
 
+using bluetooth_config::IsBluetoothEnabledOrEnabling;
+using bluetooth_config::mojom::BluetoothSystemState;
+
 FakeBluetoothDetailedView::FakeBluetoothDetailedView(Delegate* delegate)
     : BluetoothDetailedView(delegate),
       device_list_(std::make_unique<views::View>()) {}
@@ -21,8 +26,9 @@
   return this;
 }
 
-void FakeBluetoothDetailedView::UpdateBluetoothEnabledState(bool enabled) {
-  last_bluetooth_enabled_state_ = enabled;
+void FakeBluetoothDetailedView::UpdateBluetoothEnabledState(
+    const BluetoothSystemState system_state) {
+  last_bluetooth_enabled_state_ = IsBluetoothEnabledOrEnabling(system_state);
 }
 
 BluetoothDeviceListItemView* FakeBluetoothDetailedView::AddDeviceListItem() {
diff --git a/ash/system/bluetooth/fake_bluetooth_detailed_view.h b/ash/system/bluetooth/fake_bluetooth_detailed_view.h
index e5ab8bd..5a5343f 100644
--- a/ash/system/bluetooth/fake_bluetooth_detailed_view.h
+++ b/ash/system/bluetooth/fake_bluetooth_detailed_view.h
@@ -49,7 +49,9 @@
  private:
   // BluetoothDetailedView:
   views::View* GetAsView() override;
-  void UpdateBluetoothEnabledState(bool enabled) override;
+  void UpdateBluetoothEnabledState(
+      const bluetooth_config::mojom::BluetoothSystemState system_state)
+      override;
   BluetoothDeviceListItemView* AddDeviceListItem() override;
   views::View* AddDeviceListSubHeader(const gfx::VectorIcon& /*icon*/,
                                       int text_id) override;
diff --git a/ash/system/input_device_settings/input_device_settings_dispatcher_unittest.cc b/ash/system/input_device_settings/input_device_settings_dispatcher_unittest.cc
index c7ebbf0..3444ddd 100644
--- a/ash/system/input_device_settings/input_device_settings_dispatcher_unittest.cc
+++ b/ash/system/input_device_settings/input_device_settings_dispatcher_unittest.cc
@@ -158,6 +158,7 @@
   void SetHapticTouchpadEffectForNextButtonRelease(
       ui::HapticTouchpadEffect effect_type,
       ui::HapticTouchpadEffectStrength strength) override {}
+  bool AreAnyKeysPressed() override { return false; }
 };
 }  // namespace
 
diff --git a/ash/system/media/media_color_theme.cc b/ash/system/media/media_color_theme.cc
index 42dfa2d..52d6f1c 100644
--- a/ash/system/media/media_color_theme.cc
+++ b/ash/system/media/media_color_theme.cc
@@ -12,9 +12,24 @@
   media_message_center::MediaColorTheme theme;
   theme.primary_foreground_color_id = cros_tokens::kCrosSysOnSurface;
   theme.secondary_foreground_color_id = cros_tokens::kCrosSysSecondary;
-  theme.primary_container_color_id = cros_tokens::kCrosSysPrimary;
-  theme.secondary_container_color_id = cros_tokens::kCrosSysInversePrimary;
-  theme.system_container_color_id = cros_tokens::kCrosSysSystemPrimaryContainer;
+
+  // Colors for the play/pause button.
+  theme.play_button_foreground_color_id = cros_tokens::kCrosSysSecondary;
+  theme.play_button_container_color_id =
+      cros_tokens::kCrosSysRippleNeutralOnSubtle;
+  theme.pause_button_foreground_color_id =
+      cros_tokens::kCrosSysSystemOnPrimaryContainer;
+  theme.pause_button_container_color_id =
+      cros_tokens::kCrosSysSystemPrimaryContainer;
+
+  // Colors for the progress view.
+  theme.playing_progress_foreground_color_id = cros_tokens::kCrosSysPrimary;
+  theme.playing_progress_background_color_id =
+      cros_tokens::kCrosSysHighlightShape;
+  theme.paused_progress_foreground_color_id = cros_tokens::kCrosSysSecondary;
+  theme.paused_progress_background_color_id =
+      cros_tokens::kCrosSysHoverOnSubtle;
+
   theme.background_color_id = cros_tokens::kCrosSysSystemOnBase;
   theme.separator_color_id = cros_tokens::kCrosSysSeparator;
   theme.error_foreground_color_id = cros_tokens::kCrosSysError;
diff --git a/ash/system/phonehub/app_stream_connection_error_dialog.cc b/ash/system/phonehub/app_stream_connection_error_dialog.cc
index 7c89fbf..7a3a7a79 100644
--- a/ash/system/phonehub/app_stream_connection_error_dialog.cc
+++ b/ash/system/phonehub/app_stream_connection_error_dialog.cc
@@ -78,10 +78,15 @@
     SetPaintToLayer();
     layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
     layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
+    layer()->SetRoundedCornerRadius(
+        gfx::RoundedCornersF(kDialogRoundedCornerRadius));
 
     SetBackground(views::CreateThemedRoundedRectBackground(
-        GetColorProvider()->GetColor(kColorAshShieldAndBase80),
+        static_cast<ui::ColorId>(cros_tokens::kCrosSysBaseElevated),
         kDialogRoundedCornerRadius));
+    SetBorder(std::make_unique<views::HighlightBorder>(
+        kDialogRoundedCornerRadius,
+        views::HighlightBorder::Type::kHighlightBorder1));
 
     view_shadow_ = std::make_unique<ViewShadow>(this, kDialogShadowElevation);
     view_shadow_->SetRoundedCornerRadius(kDialogRoundedCornerRadius);
@@ -116,6 +121,9 @@
     if (chromeos::features::IsJellyrollEnabled()) {
       TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosTitle1,
                                             *title_);
+    } else {
+      title_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
+          AshColorProvider::ContentLayerType::kTextColorPrimary));
     }
 
     title_->SetPaintToLayer();
@@ -234,16 +242,6 @@
     return gfx::Size(kDialogWidth, GetHeightForWidth(kDialogWidth));
   }
 
-  void OnThemeChanged() override {
-    views::WidgetDelegateView::OnThemeChanged();
-
-    SetBorder(std::make_unique<views::HighlightBorder>(
-        kDialogRoundedCornerRadius,
-        views::HighlightBorder::Type::kHighlightBorder1));
-    title_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
-        AshColorProvider::ContentLayerType::kTextColorPrimary));
-  }
-
   void OnStartTetheringClicked(const ui::Event& event) {
     if (start_tethering_callback_) {
       std::move(start_tethering_callback_).Run(event);
diff --git a/ash/system/unified/classroom_bubble_base_view.cc b/ash/system/unified/classroom_bubble_base_view.cc
index 3faf9d9..50aaf68 100644
--- a/ash/system/unified/classroom_bubble_base_view.cc
+++ b/ash/system/unified/classroom_bubble_base_view.cc
@@ -138,6 +138,7 @@
 
 void ClassroomBubbleBaseView::OnGetAssignments(
     const std::u16string& list_name,
+    bool initial_update,
     bool success,
     std::vector<std::unique_ptr<GlanceablesClassroomAssignment>> assignments) {
   const gfx::Size old_preferred_size = GetPreferredSize();
@@ -177,6 +178,11 @@
 
   if (old_preferred_size != GetPreferredSize()) {
     PreferredSizeChanged();
+
+    if (!initial_update) {
+      GetWidget()->LayoutRootViewIfNecessary();
+      ScrollViewToVisible();
+    }
   }
 }
 
diff --git a/ash/system/unified/classroom_bubble_base_view.h b/ash/system/unified/classroom_bubble_base_view.h
index 8b27af32..58ec3f13 100644
--- a/ash/system/unified/classroom_bubble_base_view.h
+++ b/ash/system/unified/classroom_bubble_base_view.h
@@ -60,6 +60,7 @@
   // Handles received assignments by rendering them in `list_container_view_`.
   void OnGetAssignments(
       const std::u16string& list_name,
+      bool initial_update,
       bool success,
       std::vector<std::unique_ptr<GlanceablesClassroomAssignment>> assignments);
 
diff --git a/ash/system/unified/classroom_bubble_student_view.cc b/ash/system/unified/classroom_bubble_student_view.cc
index d00598f..07a1f9cd 100644
--- a/ash/system/unified/classroom_bubble_student_view.cc
+++ b/ash/system/unified/classroom_bubble_student_view.cc
@@ -98,8 +98,9 @@
           std::make_unique<ClassroomStudentComboboxModel>()) {
   combo_box_view_->SetCallback(base::BindRepeating(
       &ClassroomBubbleStudentView::SelectedAssignmentListChanged,
-      base::Unretained(this)));
-  SelectedAssignmentListChanged();
+      base::Unretained(this),
+      /*initial_update=*/false));
+  SelectedAssignmentListChanged(/*initial_update=*/true);
 }
 
 ClassroomBubbleStudentView::~ClassroomBubbleStudentView() = default;
@@ -121,7 +122,8 @@
   }
 }
 
-void ClassroomBubbleStudentView::SelectedAssignmentListChanged() {
+void ClassroomBubbleStudentView::SelectedAssignmentListChanged(
+    bool initial_update) {
   auto* const client =
       Shell::Get()->glanceables_v2_controller()->GetClassroomClient();
   if (!client) {
@@ -140,9 +142,10 @@
 
   AboutToRequestAssignments();
 
-  auto callback = base::BindOnce(&ClassroomBubbleStudentView::OnGetAssignments,
-                                 weak_ptr_factory_.GetWeakPtr(),
-                                 GetAssignmentListName(selected_index));
+  auto callback =
+      base::BindOnce(&ClassroomBubbleStudentView::OnGetAssignments,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     GetAssignmentListName(selected_index), initial_update);
   switch (kStudentAssignmentsListTypeOrdered[selected_index]) {
     case StudentAssignmentsListType::kAssigned:
       empty_list_label_->SetText(l10n_util::GetStringUTF16(
diff --git a/ash/system/unified/classroom_bubble_student_view.h b/ash/system/unified/classroom_bubble_student_view.h
index 9332c94..0c3b1c94 100644
--- a/ash/system/unified/classroom_bubble_student_view.h
+++ b/ash/system/unified/classroom_bubble_student_view.h
@@ -25,7 +25,7 @@
   void OnSeeAllPressed() override;
 
   // Handle switching between assignment lists.
-  void SelectedAssignmentListChanged();
+  void SelectedAssignmentListChanged(bool initial_update);
 
   base::WeakPtrFactory<ClassroomBubbleStudentView> weak_ptr_factory_{this};
 };
diff --git a/ash/system/unified/classroom_bubble_teacher_view.cc b/ash/system/unified/classroom_bubble_teacher_view.cc
index c99b54fe..2d759a0 100644
--- a/ash/system/unified/classroom_bubble_teacher_view.cc
+++ b/ash/system/unified/classroom_bubble_teacher_view.cc
@@ -95,8 +95,9 @@
   CHECK(features::IsGlanceablesV2ClassroomTeacherViewEnabled());
   combo_box_view_->SetCallback(base::BindRepeating(
       &ClassroomBubbleTeacherView::SelectedAssignmentListChanged,
-      base::Unretained(this)));
-  SelectedAssignmentListChanged();
+      base::Unretained(this),
+      /*initial_update=*/false));
+  SelectedAssignmentListChanged(/*initial_update=*/true);
 }
 
 ClassroomBubbleTeacherView::~ClassroomBubbleTeacherView() = default;
@@ -117,7 +118,8 @@
   }
 }
 
-void ClassroomBubbleTeacherView::SelectedAssignmentListChanged() {
+void ClassroomBubbleTeacherView::SelectedAssignmentListChanged(
+    bool initial_update) {
   auto* const client =
       Shell::Get()->glanceables_v2_controller()->GetClassroomClient();
   if (!client) {
@@ -136,9 +138,10 @@
 
   AboutToRequestAssignments();
 
-  auto callback = base::BindOnce(&ClassroomBubbleTeacherView::OnGetAssignments,
-                                 weak_ptr_factory_.GetWeakPtr(),
-                                 GetAssignmentListName(selected_index));
+  auto callback =
+      base::BindOnce(&ClassroomBubbleTeacherView::OnGetAssignments,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     GetAssignmentListName(selected_index), initial_update);
   switch (kTeacherAssignmentsListTypeOrdered[selected_index]) {
     case TeacherAssignmentsListType::kDueSoon:
       return client->GetTeacherAssignmentsWithApproachingDueDate(
diff --git a/ash/system/unified/classroom_bubble_teacher_view.h b/ash/system/unified/classroom_bubble_teacher_view.h
index 9198912a..06ed2e3 100644
--- a/ash/system/unified/classroom_bubble_teacher_view.h
+++ b/ash/system/unified/classroom_bubble_teacher_view.h
@@ -24,7 +24,7 @@
   void OnSeeAllPressed() override;
 
   // Handle switching between assignment lists.
-  void SelectedAssignmentListChanged();
+  void SelectedAssignmentListChanged(bool initial_update);
 
   base::WeakPtrFactory<ClassroomBubbleTeacherView> weak_ptr_factory_{this};
 };
diff --git a/ash/system/unified/date_tray.cc b/ash/system/unified/date_tray.cc
index da25b51..9933ab30 100644
--- a/ash/system/unified/date_tray.cc
+++ b/ash/system/unified/date_tray.cc
@@ -86,7 +86,7 @@
       ash::Shell::Get()->glanceables_v2_controller();
   if (glanceables_controller &&
       glanceables_controller->AreGlanceablesAvailable()) {
-    ShowGlanceableBubble();
+    ShowGlanceableBubble(/*from_keyboard=*/false);
   }
 }
 
@@ -104,6 +104,12 @@
   }
 }
 
+void DateTray::HideBubbleWithView(const TrayBubbleView* bubble_view) {
+  if (bubble_ && bubble_->GetBubbleView() == bubble_view) {
+    CloseBubble();
+  }
+}
+
 void DateTray::HideBubble(const TrayBubbleView* bubble_view) {
   CloseBubble();
 }
@@ -141,7 +147,7 @@
     // Hide the unified_system_tray_ bubble.
     unified_system_tray_->CloseBubble();
     // Open the glanceables bubble.
-    ShowGlanceableBubble();
+    ShowGlanceableBubble(event.IsKeyEvent());
   } else {
     // Need to set the date tray as active before notifying the system tray of
     // an action because we need the system tray to know that the date tray is
@@ -151,8 +157,8 @@
   }
 }
 
-void DateTray::ShowGlanceableBubble() {
-  bubble_ = std::make_unique<GlanceableTrayBubble>(this);
+void DateTray::ShowGlanceableBubble(bool from_keyboard) {
+  bubble_ = std::make_unique<GlanceableTrayBubble>(this, from_keyboard);
   SetIsActive(true);
 }
 
diff --git a/ash/system/unified/date_tray.h b/ash/system/unified/date_tray.h
index 49457d9c..24749153 100644
--- a/ash/system/unified/date_tray.h
+++ b/ash/system/unified/date_tray.h
@@ -48,7 +48,7 @@
   void UpdateAfterLoginStatusChange() override;
   void ShowBubble() override;
   void CloseBubble() override;
-  void HideBubbleWithView(const TrayBubbleView* bubble_view) override {}
+  void HideBubbleWithView(const TrayBubbleView* bubble_view) override;
   void HideBubble(const TrayBubbleView* bubble_view) override;
   void ClickedOutsideBubble() override;
   void UpdateTrayItemColor(bool is_active) override;
@@ -60,7 +60,9 @@
   // Callback called when this tray is pressed.
   void OnButtonPressed(const ui::Event& event);
 
-  void ShowGlanceableBubble();
+  // `from_keyboard` - whether `ShowGlanceableBubble()` is being shown in
+  // response to a keyboard event.
+  void ShowGlanceableBubble(bool from_keyboard);
   void HideGlanceableBubble();
 
  private:
diff --git a/ash/system/unified/date_tray_unittest.cc b/ash/system/unified/date_tray_unittest.cc
index 64221b8..56af483 100644
--- a/ash/system/unified/date_tray_unittest.cc
+++ b/ash/system/unified/date_tray_unittest.cc
@@ -281,15 +281,15 @@
     return GetUnifiedSystemTray()->IsShowingCalendarView();
   }
 
-  void LeftClickOnOpenBubble() {
+  views::View* GetBubbleView() {
     if (AreGlanceablesV2Enabled()) {
-      LeftClickOn(GetGlanceableTrayBubble()->GetBubbleView());
-
-    } else {
-      LeftClickOn(GetUnifiedSystemTray()->bubble()->GetBubbleView());
+      return GetGlanceableTrayBubble()->GetBubbleView();
     }
+    return GetUnifiedSystemTray()->bubble()->GetBubbleView();
   }
 
+  void LeftClickOnOpenBubble() { LeftClickOn(GetBubbleView()); }
+
   std::u16string GetTimeViewText() {
     return date_tray_->time_view_->time_view()
         ->horizontal_label_date_for_test()
@@ -424,6 +424,78 @@
   }
 }
 
+TEST_P(DateTrayTest, DontActivateBubbleIfShownByTap) {
+  // Clicking on the `DateTray` -> show the calendar bubble.
+  LeftClickOn(GetDateTray());
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(IsBubbleShown());
+  EXPECT_TRUE(AreContentsViewShown());
+  EXPECT_TRUE(GetDateTray()->is_active());
+
+  views::Widget* const bubble_widget = GetBubbleView()->GetWidget();
+  // The bubble should not be activated if the calendar/glanceables bubble gets
+  // shown by tapping the date tray.
+  EXPECT_FALSE(bubble_widget->IsActive());
+
+  if (AreGlanceablesV2Enabled()) {
+    glanceables_classroom_client()
+        ->RespondToPendingIsStudentRoleEnabledCallbacks(
+            /*is_active=*/true);
+    ASSERT_TRUE(glanceables_classroom_client()
+                    ->RespondToNextPendingStudentAssignmentsCallback(
+                        CreateAssignmentsForStudents(/*count=*/1)));
+  }
+  EXPECT_FALSE(bubble_widget->IsActive());
+
+  // The user should be able to activate the bubble
+  PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB);
+
+  EXPECT_TRUE(bubble_widget->IsActive());
+
+  views::View* const focused_view =
+      bubble_widget->GetFocusManager()->GetFocusedView();
+  ASSERT_TRUE(focused_view);
+  // Verify that the calendar view gets the focus.
+  if (AreGlanceablesV2Enabled()) {
+    EXPECT_TRUE(
+        GetGlanceableTrayBubble()->GetCalendarView()->Contains(focused_view));
+  }
+  EXPECT_STREQ("CalendarDateCellView", focused_view->GetClassName());
+}
+
+TEST_P(DateTrayTest, ActivateBubbleIfShownByKeyboard) {
+  GetDateTray()->RequestFocus();
+  PressAndReleaseKey(ui::KeyboardCode::VKEY_RETURN);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(IsBubbleShown());
+  EXPECT_TRUE(AreContentsViewShown());
+  EXPECT_TRUE(GetDateTray()->is_active());
+
+  views::Widget* const bubble_widget = GetBubbleView()->GetWidget();
+  // Verify that the bubble gets activated if opened via keyboard.
+  EXPECT_TRUE(bubble_widget->IsActive());
+
+  if (AreGlanceablesV2Enabled()) {
+    glanceables_classroom_client()
+        ->RespondToPendingIsStudentRoleEnabledCallbacks(
+            /*is_active=*/true);
+    ASSERT_TRUE(glanceables_classroom_client()
+                    ->RespondToNextPendingStudentAssignmentsCallback(
+                        CreateAssignmentsForStudents(/*count=*/1)));
+  }
+  EXPECT_TRUE(bubble_widget->IsActive());
+
+  views::View* const focused_view =
+      bubble_widget->GetFocusManager()->GetFocusedView();
+  ASSERT_TRUE(focused_view);
+  // Verify that the calendar view gets the focus.
+  if (AreGlanceablesV2Enabled()) {
+    EXPECT_TRUE(
+        GetGlanceableTrayBubble()->GetCalendarView()->Contains(focused_view));
+  }
+  EXPECT_STREQ("CalendarDateCellView", focused_view->GetClassName());
+}
+
 // Tests the behavior when clicking on different areas.
 TEST_P(DateTrayTest, ClickingArea) {
   // Clicking on the `DateTray` -> show the calendar bubble.
@@ -746,7 +818,7 @@
   assignment_selector->MenuSelectionAt(2);
   ASSERT_TRUE(glanceables_classroom_client()
                   ->RespondToNextPendingStudentAssignmentsCallback(
-                      CreateAssignmentsForTeachers(/*count=*/1)));
+                      CreateAssignmentsForStudents(/*count=*/1)));
 
   scroll_view->GetWidget()->LayoutRootViewIfNecessary();
   EXPECT_TRUE(scroll_view->GetBoundsInScreen().Contains(
@@ -802,22 +874,23 @@
   EXPECT_TRUE(scroll_view->GetBoundsInScreen().Contains(
       calendar_view->GetBoundsInScreen()));
 
-  const int calendar_view_bottom = calendar_view->GetBoundsInScreen().bottom();
-
   views::Combobox* assignment_selector =
       views::AsViewClass<views::Combobox>(teacher_view->GetViewByID(
           base::to_underlying(GlanceablesViewId::kClassroomBubbleComboBox)));
   ASSERT_TRUE(assignment_selector);
 
+  teacher_view->ScrollViewToVisible();
+
+  const int calendar_view_bottom = calendar_view->GetBoundsInScreen().bottom();
   assignment_selector->MenuSelectionAt(2);
   ASSERT_TRUE(glanceables_classroom_client()
                   ->RespondToNextPendingTeacherAssignmentsCallback(
-                      CreateAssignmentsForTeachers(/*count=*/1)));
+                      CreateAssignmentsForTeachers(/*count=*/2)));
 
   scroll_view->GetWidget()->LayoutRootViewIfNecessary();
-  EXPECT_FALSE(scroll_view->GetBoundsInScreen().Contains(
-      teacher_view->GetBoundsInScreen()));
   EXPECT_TRUE(scroll_view->GetBoundsInScreen().Contains(
+      teacher_view->GetBoundsInScreen()));
+  EXPECT_FALSE(scroll_view->GetBoundsInScreen().Contains(
       calendar_view->GetBoundsInScreen()));
 
   EXPECT_EQ(calendar_view_bottom, calendar_view->GetBoundsInScreen().bottom());
@@ -987,7 +1060,7 @@
   GetGlanceableTrayBubble()->GetBubbleWidget()->Close();
   ASSERT_TRUE(glanceables_classroom_client()
                   ->RespondToNextPendingStudentAssignmentsCallback(
-                      CreateAssignmentsForTeachers(/*count=*/1)));
+                      CreateAssignmentsForStudents(/*count=*/3)));
 
   base::RunLoop().RunUntilIdle();
   EXPECT_FALSE(IsBubbleShown());
@@ -1031,6 +1104,7 @@
 
   // Focus the student assignment selector, and verify the whole student
   // glanceable becomes visible.
+  PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB);
   views::Combobox* assignment_selector =
       views::AsViewClass<views::Combobox>(student_view->GetViewByID(
           base::to_underlying(GlanceablesViewId::kClassroomBubbleComboBox)));
diff --git a/ash/system/unified/glanceable_tray_bubble.cc b/ash/system/unified/glanceable_tray_bubble.cc
index 83c3cbd..7776c36 100644
--- a/ash/system/unified/glanceable_tray_bubble.cc
+++ b/ash/system/unified/glanceable_tray_bubble.cc
@@ -10,10 +10,12 @@
 #include "ash/system/tray/tray_utils.h"
 #include "ash/system/unified/glanceable_tray_bubble_view.h"
 #include "ash/system/unified/tasks_bubble_view.h"
+#include "ui/views/view_utils.h"
 
 namespace ash {
 
-GlanceableTrayBubble::GlanceableTrayBubble(DateTray* tray) : tray_(tray) {
+GlanceableTrayBubble::GlanceableTrayBubble(DateTray* tray, bool from_keyboard)
+    : tray_(tray) {
   TrayBubbleView::InitParams init_params =
       CreateInitParamsForTrayBubble(tray, /*anchor_to_shelf_corner=*/true);
   // TODO(b:277268122): Update with glanceable spec.
@@ -27,34 +29,17 @@
     *init_params.insets -= gfx::Insets::VH(8, 0);
   }
 
-  bubble_view_ = new GlanceableTrayBubbleView(init_params, tray_->shelf());
-
-  // bubble_widget_ takes ownership of the bubble_view_.
-  bubble_widget_ = views::BubbleDialogDelegateView::CreateBubble(bubble_view_);
-  bubble_widget_->AddObserver(this);
-  TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
-  bubble_view_->InitializeContents();
-  bubble_view_->InitializeAndShowBubble();
-
-  tray->tray_event_filter()->AddBubble(this);
+  auto bubble_view =
+      std::make_unique<GlanceableTrayBubbleView>(init_params, tray_->shelf());
+  if (from_keyboard) {
+    bubble_view->SetCanActivate(true);
+  }
+  bubble_wrapper_ = std::make_unique<TrayBubbleWrapper>(tray);
+  bubble_wrapper_->ShowBubble(std::move(bubble_view));
 }
 
 GlanceableTrayBubble::~GlanceableTrayBubble() {
-  tray_->tray_event_filter()->RemoveBubble(this);
-
-  if (bubble_widget_) {
-    bubble_widget_->RemoveObserver(this);
-    bubble_widget_->Close();
-  }
-}
-
-void GlanceableTrayBubble::OnWidgetDestroying(views::Widget* widget) {
-  CHECK_EQ(bubble_widget_, widget);
-  bubble_widget_->RemoveObserver(this);
-  bubble_widget_ = nullptr;
-
-  // `tray_->CloseBubble()` will delete `this`.
-  tray_->CloseBubble();
+  bubble_wrapper_->bubble_view()->ResetDelegate();
 }
 
 TrayBackgroundView* GlanceableTrayBubble::GetTray() const {
@@ -62,31 +47,36 @@
 }
 
 TrayBubbleView* GlanceableTrayBubble::GetBubbleView() const {
-  return bubble_view_;
+  return bubble_wrapper_->bubble_view();
 }
 
 views::Widget* GlanceableTrayBubble::GetBubbleWidget() const {
-  return bubble_widget_;
+  return bubble_wrapper_->GetBubbleWidget();
+  ;
 }
 
 TasksBubbleView* GlanceableTrayBubble::GetTasksView() {
-  return bubble_view_->GetTasksView();
+  return GetGlanceableTrayBubbleView()->GetTasksView();
 }
 
 ClassroomBubbleTeacherView* GlanceableTrayBubble::GetClassroomTeacherView() {
-  return bubble_view_->GetClassroomTeacherView();
+  return GetGlanceableTrayBubbleView()->GetClassroomTeacherView();
 }
 
 ClassroomBubbleStudentView* GlanceableTrayBubble::GetClassroomStudentView() {
-  return bubble_view_->GetClassroomStudentView();
+  return GetGlanceableTrayBubbleView()->GetClassroomStudentView();
 }
 
 CalendarView* GlanceableTrayBubble::GetCalendarView() {
-  return bubble_view_->GetCalendarView();
+  return GetGlanceableTrayBubbleView()->GetCalendarView();
 }
 
 bool GlanceableTrayBubble::IsBubbleActive() const {
-  return bubble_widget_->IsActive();
+  return GetBubbleWidget()->IsActive();
+}
+
+GlanceableTrayBubbleView* GlanceableTrayBubble::GetGlanceableTrayBubbleView() {
+  return views::AsViewClass<GlanceableTrayBubbleView>(GetBubbleView());
 }
 
 }  // namespace ash
diff --git a/ash/system/unified/glanceable_tray_bubble.h b/ash/system/unified/glanceable_tray_bubble.h
index 4d95d9a..22d981b7 100644
--- a/ash/system/unified/glanceable_tray_bubble.h
+++ b/ash/system/unified/glanceable_tray_bubble.h
@@ -6,6 +6,7 @@
 #define ASH_SYSTEM_UNIFIED_GLANCEABLE_TRAY_BUBBLE_H_
 
 #include "ash/system/tray/tray_bubble_base.h"
+#include "ash/system/tray/tray_bubble_wrapper.h"
 #include "ash/system/unified/date_tray.h"
 #include "base/memory/raw_ptr.h"
 
@@ -21,16 +22,15 @@
 // Shows the bubble on the constructor, and closes the bubble on the destructor.
 class ASH_EXPORT GlanceableTrayBubble : public TrayBubbleBase {
  public:
-  explicit GlanceableTrayBubble(DateTray* tray);
+  // `from_keyboard` - whether the bubble is shown in response to a keyboard
+  // event, in which case the bubble should be activated when shown.
+  GlanceableTrayBubble(DateTray* tray, bool from_keyboard);
 
   GlanceableTrayBubble(const GlanceableTrayBubble&) = delete;
   GlanceableTrayBubble& operator=(const GlanceableTrayBubble&) = delete;
 
   ~GlanceableTrayBubble() override;
 
-  // views::WidgetObserver:
-  void OnWidgetDestroying(views::Widget* widget) override;
-
   // TrayBubbleBase:
   TrayBackgroundView* GetTray() const override;
   TrayBubbleView* GetBubbleView() const override;
@@ -43,20 +43,17 @@
   CalendarView* GetCalendarView();
 
  private:
+  // Wrapper around `GetBubbleView()` that returns the bubble view as
+  // `GlanceableTrayBubbleView`.
+  GlanceableTrayBubbleView* GetGlanceableTrayBubbleView();
+
   void UpdateBubble();
 
   // Owner of this class.
   raw_ptr<DateTray, ExperimentalAsh> tray_;
 
-  // Widget that contains GlanceableTrayView. Unowned.
-  // When the widget is closed by deactivation, |bubble_widget_| pointer is
-  // invalidated and we have to delete GlanceableTrayBubble by calling
-  // DateTray::CloseBubble().
-  // In order to do this, we observe OnWidgetDestroying().
-  raw_ptr<views::Widget, ExperimentalAsh> bubble_widget_ = nullptr;
-
-  // Main bubble view anchored to `tray_`.
-  raw_ptr<GlanceableTrayBubbleView, ExperimentalAsh> bubble_view_ = nullptr;
+  // Bubble wrapper that manages the bubble widget when shown.
+  std::unique_ptr<TrayBubbleWrapper> bubble_wrapper_;
 };
 
 }  // namespace ash
diff --git a/ash/system/unified/glanceable_tray_bubble_view.cc b/ash/system/unified/glanceable_tray_bubble_view.cc
index fafaf4f..47336f9 100644
--- a/ash/system/unified/glanceable_tray_bubble_view.cc
+++ b/ash/system/unified/glanceable_tray_bubble_view.cc
@@ -185,6 +185,14 @@
 
   scroll_view_->SetContents(std::move(child_glanceable_container));
 
+  if (!calendar_view_) {
+    calendar_view_ =
+        scroll_view_->contents()->AddChildView(std::make_unique<CalendarView>(
+            detailed_view_delegate_.get(), /*for_glanceables_container=*/true));
+    // TODO(b:277268122): Update with glanceable spec.
+    calendar_view_->SetPreferredSize(gfx::Size(kRevampedTrayMenuWidth, 400));
+  }
+
   auto* const tasks_client =
       Shell::Get()->glanceables_v2_controller()->GetTasksClient();
   if (should_show_non_calendar_glanceables && tasks_client) {
@@ -194,13 +202,6 @@
                        weak_ptr_factory_.GetWeakPtr()));
   }
 
-  if (!calendar_view_) {
-    calendar_view_ =
-        scroll_view_->contents()->AddChildView(std::make_unique<CalendarView>(
-            detailed_view_delegate_.get(), /*for_glanceables_container=*/true));
-    // TODO(b:277268122): Update with glanceable spec.
-    calendar_view_->SetPreferredSize(gfx::Size(kRevampedTrayMenuWidth, 400));
-  }
   int max_height = CalculateMaxTrayBubbleHeight(shelf_->GetWindow());
   SetMaxHeight(max_height);
   ChangeAnchorAlignment(shelf_->alignment());
@@ -225,10 +226,15 @@
           base::Unretained(&classroom_bubble_teacher_view_)));
     }
   }
+
+  calendar_view_->ScrollViewToVisible();
 }
 
-bool GlanceableTrayBubbleView::CanActivate() const {
-  return true;
+void GlanceableTrayBubbleView::AddedToWidget() {
+  if (!scroll_view_) {
+    InitializeContents();
+  }
+  TrayBubbleView::AddedToWidget();
 }
 
 void GlanceableTrayBubbleView::OnWidgetClosing(views::Widget* widget) {
@@ -267,6 +273,12 @@
       scroll_contents->children().begin();
   *view = scroll_contents->AddChildViewAt(
       std::make_unique<T>(detailed_view_delegate_.get()), calendar_view_index);
+
+  views::View* const default_focused_child =
+      scroll_contents->GetChildrenFocusList().front();
+  if (default_focused_child != calendar_view_) {
+    calendar_view_->InsertBeforeInFocusList(default_focused_child);
+  }
 }
 
 void GlanceableTrayBubbleView::AddTaskBubbleViewIfNeeded(
@@ -280,6 +292,12 @@
       std::make_unique<TasksBubbleView>(detailed_view_delegate_.get(),
                                         task_lists),
       0);
+
+  views::View* const default_focused_child =
+      scroll_contents->GetChildrenFocusList().front();
+  if (default_focused_child != calendar_view_) {
+    calendar_view_->InsertBeforeInFocusList(default_focused_child);
+  }
 }
 
 void GlanceableTrayBubbleView::OnGlanceablesContainerPreferredSizeChanged() {
diff --git a/ash/system/unified/glanceable_tray_bubble_view.h b/ash/system/unified/glanceable_tray_bubble_view.h
index 41c097a..3a47111b 100644
--- a/ash/system/unified/glanceable_tray_bubble_view.h
+++ b/ash/system/unified/glanceable_tray_bubble_view.h
@@ -45,7 +45,7 @@
   CalendarView* GetCalendarView() { return calendar_view_; }
 
   // TrayBubbleView:
-  bool CanActivate() const override;
+  void AddedToWidget() override;
   void OnWidgetClosing(views::Widget* widget) override;
 
   // ScreenLayoutObserver:
diff --git a/ash/system/unified/quick_settings_view.cc b/ash/system/unified/quick_settings_view.cc
index 7a3ae45..ff9acf4 100644
--- a/ash/system/unified/quick_settings_view.cc
+++ b/ash/system/unified/quick_settings_view.cc
@@ -44,32 +44,6 @@
 constexpr auto kPageIndicatorMargin = gfx::Insets::TLBR(0, 0, 8, 0);
 constexpr auto kSlidersContainerMargin = gfx::Insets::TLBR(4, 0, 0, 0);
 
-class DetailedViewContainer : public views::View {
- public:
-  METADATA_HEADER(DetailedViewContainer);
-
-  DetailedViewContainer() {
-    SetLayoutManager(std::make_unique<views::BoxLayout>(
-        views::BoxLayout::Orientation::kVertical));
-  }
-
-  DetailedViewContainer(const DetailedViewContainer&) = delete;
-  DetailedViewContainer& operator=(const DetailedViewContainer&) = delete;
-
-  ~DetailedViewContainer() override = default;
-
-  // views::View:
-  void Layout() override {
-    for (auto* child : children()) {
-      child->SetBoundsRect(GetContentsBounds());
-    }
-    views::View::Layout();
-  }
-};
-
-BEGIN_METADATA(DetailedViewContainer, views::View)
-END_METADATA
-
 class AccessibilityFocusHelperView : public views::View {
  public:
   explicit AccessibilityFocusHelperView(UnifiedSystemTrayController* controller)
@@ -174,8 +148,8 @@
   footer_ = system_tray_container_->AddChildView(
       std::make_unique<QuickSettingsFooter>(controller_));
 
-  detailed_view_container_ =
-      AddChildView(std::make_unique<DetailedViewContainer>());
+  detailed_view_container_ = AddChildView(std::make_unique<views::View>());
+  detailed_view_container_->SetUseDefaultFillLayout(true);
   detailed_view_container_->SetVisible(false);
 
   system_tray_container_->AddChildView(
diff --git a/ash/system/unified/tasks_bubble_view.cc b/ash/system/unified/tasks_bubble_view.cc
index 5a169b5..46cf29c 100644
--- a/ash/system/unified/tasks_bubble_view.cc
+++ b/ash/system/unified/tasks_bubble_view.cc
@@ -160,7 +160,7 @@
   list_footer_view_->SetID(
       base::to_underlying(GlanceablesViewId::kTasksBubbleListFooter));
 
-  SelectedTasksListChanged();
+  ScheduleUpdateTasksList(/*initial_update=*/true);
 }
 
 TasksBubbleView::~TasksBubbleView() = default;
@@ -185,10 +185,10 @@
 void TasksBubbleView::SelectedTasksListChanged() {
   weak_ptr_factory_.InvalidateWeakPtrs();
 
-  ScheduleUpdateTasksList();
+  ScheduleUpdateTasksList(/*initial_update=*/false);
 }
 
-void TasksBubbleView::ScheduleUpdateTasksList() {
+void TasksBubbleView::ScheduleUpdateTasksList(bool initial_update) {
   if (!task_list_combo_box_view_->GetSelectedIndex().has_value()) {
     return;
   }
@@ -202,11 +202,12 @@
       active_task_list->id,
       base::BindOnce(&TasksBubbleView::UpdateTasksList,
                      weak_ptr_factory_.GetWeakPtr(), active_task_list->id,
-                     active_task_list->title));
+                     active_task_list->title, initial_update));
 }
 
 void TasksBubbleView::UpdateTasksList(const std::string& task_list_id,
                                       const std::string& task_list_title,
+                                      bool initial_update,
                                       ui::ListModel<GlanceablesTask>* tasks) {
   const gfx::Size old_preferred_size = GetPreferredSize();
   progress_bar_->UpdateProgressBarVisibility(/*visible=*/false);
@@ -248,6 +249,10 @@
 
   if (old_preferred_size != GetPreferredSize()) {
     PreferredSizeChanged();
+    if (!initial_update) {
+      GetWidget()->LayoutRootViewIfNecessary();
+      ScrollViewToVisible();
+    }
   }
 }
 
diff --git a/ash/system/unified/tasks_bubble_view.h b/ash/system/unified/tasks_bubble_view.h
index 800ee065..8714a3a3 100644
--- a/ash/system/unified/tasks_bubble_view.h
+++ b/ash/system/unified/tasks_bubble_view.h
@@ -93,9 +93,10 @@
 
   // Handles switching between tasks lists.
   void SelectedTasksListChanged();
-  void ScheduleUpdateTasksList();
+  void ScheduleUpdateTasksList(bool initial_update);
   void UpdateTasksList(const std::string& task_list_id,
                        const std::string& task_list_title,
+                       bool initial_update,
                        ui::ListModel<GlanceablesTask>* tasks);
 
   // Announces text describing the task list state through a screen
diff --git a/ash/utility/haptics_tracking_test_input_controller.cc b/ash/utility/haptics_tracking_test_input_controller.cc
index f8cb608..2b4edf05 100644
--- a/ash/utility/haptics_tracking_test_input_controller.cc
+++ b/ash/utility/haptics_tracking_test_input_controller.cc
@@ -219,6 +219,10 @@
     mojo::PendingReceiver<ui::ozone::mojom::GesturePropertiesService>
         receiver) {}
 
+bool HapticsTrackingTestInputController::AreAnyKeysPressed() {
+  return false;
+}
+
 int HapticsTrackingTestInputController::GetSentHapticCount(
     ui::HapticTouchpadEffect effect,
     ui::HapticTouchpadEffectStrength strength) const {
diff --git a/ash/utility/haptics_tracking_test_input_controller.h b/ash/utility/haptics_tracking_test_input_controller.h
index a023e71c..46a73f1a 100644
--- a/ash/utility/haptics_tracking_test_input_controller.h
+++ b/ash/utility/haptics_tracking_test_input_controller.h
@@ -106,6 +106,7 @@
   void GetGesturePropertiesService(
       mojo::PendingReceiver<ui::ozone::mojom::GesturePropertiesService>
           receiver) override;
+  bool AreAnyKeysPressed() override;
 
   // Returns haptic count for effect/strength combination for testing.
   int GetSentHapticCount(ui::HapticTouchpadEffect effect,
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index c70b54c..f12b8d1 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -556,46 +556,20 @@
   }
 
   // Fade in/out the minimized windows and position non-minimized windows.
-  float scroll_ratio = y_offset / WmGestureHandler::kVerticalThresholdDp;
+  const float scroll_ratio = y_offset / WmGestureHandler::kVerticalThresholdDp;
   for (size_t i = 0; i < window_list_.size(); ++i) {
-    auto* window_item = window_list_[i].get();
-    // TODO(b/297427797) : We should avoid going deep into the implementation
-    // `OverviewItem` in `OverviewGrid`.
-    auto* window = window_item->GetWindow();
-    auto* window_item_leaf = window_item->GetLeafItemForWindow(window);
-    gfx::RectF rect = cached_rects_[i];
-    // If this is the first scroll update, position minimized windows
-    // and hide the headers of non-minimized windows.
-    if (first_scroll) {
-      // TODO(sammiequon): This should use
-      // `ScopedOverviewTransformWindow::IsMinimizedOrTucked()` since tucked
-      // windows behave like minimized windows in overview, even if continuous
-      // scroll and tucked windows will not be supported together.
-      if (WindowState::Get(window)->IsMinimized()) {
-        window_item->SetBounds(rect, OVERVIEW_ANIMATION_NONE);
-      } else {
-        window_item_leaf->overview_item_view()->layer()->SetOpacity(0.0f);
-      }
-    }
-    // For all scroll updates, set the opacity of minimized windows and
-    // reposition non-minimized windows.
-    if (WindowState::Get(window_item->GetWindow())->IsMinimized()) {
-      float opacity = std::clamp(0.01f, scroll_ratio, 1.f);
-      window_item_leaf->overview_item_view()->layer()->SetOpacity(opacity);
-    } else {
-      rect = gfx::Tween::RectFValueBetween(
-          scroll_ratio, gfx::RectF(window_item->GetWindow()->bounds()), rect);
-      window_item->SetBounds(rect, OVERVIEW_ANIMATION_NONE);
-    }
+    // TODO(b/297923747): The assumption here is: For item at index `i` in
+    // `window_list_`, there is a corresponding correct target rect also at
+    // index `i` in `cached_rects_`. We need to make sure that this assumption
+    // is still standing when integrating with snap groups.
+    window_list_[i]->OnOverviewItemContinuousScroll(cached_rects_[i],
+                                                    first_scroll, scroll_ratio);
   }
 
-  // Move the desk bar up/down.
+  // Move the desks bar up/down.
   if (auto* desks_bar = desks_bar_view()) {
-    gfx::Transform transform;
-    transform.Translate(
-        0, -desks_bar->GetBoundsInScreen().height() +
-               (desks_bar->GetBoundsInScreen().height() * scroll_ratio));
-    desks_bar->layer()->SetTransform(transform);
+    desks_bar->layer()->SetTransform(gfx::Transform::MakeTranslation(
+        0, desks_bar->height() * (scroll_ratio - 1)));
   }
 }
 
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index 65b400c1..091d0cbfc 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -903,6 +903,39 @@
   is_being_dragged_ = false;
 }
 
+void OverviewItem::OnOverviewItemContinuousScroll(
+    const gfx::RectF& target_bounds,
+    bool first_scroll,
+    float scroll_ratio) {
+  const auto* window = GetWindow();
+  const bool is_window_minimized = WindowState::Get(window)->IsMinimized();
+  auto* item_layer = overview_item_view_->layer();
+
+  // If this is the first scroll update, position minimized windows
+  // and hide the headers of non-minimized windows.
+  if (first_scroll) {
+    // TODO(sammiequon): This should use
+    // `ScopedOverviewTransformWindow::IsMinimizedOrTucked()` since tucked
+    // windows behave like minimized windows in overview, even if continuous
+    // scroll and tucked windows will not be supported together.
+    if (is_window_minimized) {
+      SetBounds(target_bounds, OVERVIEW_ANIMATION_NONE);
+    } else {
+      item_layer->SetOpacity(0.0f);
+    }
+  }
+
+  // For all scroll updates, set the opacity of minimized windows and
+  // reposition non-minimized windows.
+  if (is_window_minimized) {
+    item_layer->SetOpacity(std::clamp(0.01f, scroll_ratio, 1.f));
+  } else {
+    SetBounds(gfx::Tween::RectFValueBetween(
+                  scroll_ratio, gfx::RectF(window->bounds()), target_bounds),
+              OVERVIEW_ANIMATION_NONE);
+  }
+}
+
 void OverviewItem::SetVisibleDuringItemDragging(bool visible, bool animate) {
   aura::Window::Windows windows = GetWindowsForHomeGesture();
   float new_opacity = visible ? 1.f : 0.f;
diff --git a/ash/wm/overview/overview_item.h b/ash/wm/overview/overview_item.h
index 9507793c..02cb0f1 100644
--- a/ash/wm/overview/overview_item.h
+++ b/ash/wm/overview/overview_item.h
@@ -97,6 +97,9 @@
   bool IsDragItem() const override;
   void OnOverviewItemDragStarted(OverviewItemBase* item) override;
   void OnOverviewItemDragEnded(bool snap) override;
+  void OnOverviewItemContinuousScroll(const gfx::RectF& target_bounds,
+                                      bool first_scroll,
+                                      float scroll_ratio) override;
   void SetVisibleDuringItemDragging(bool visible, bool animate) override;
   void UpdateShadowTypeForDrag(bool is_dragging) override;
   void UpdateCannotSnapWarningVisibility(bool animate) override;
diff --git a/ash/wm/overview/overview_item_base.h b/ash/wm/overview/overview_item_base.h
index 85838c1..10be64c6 100644
--- a/ash/wm/overview/overview_item_base.h
+++ b/ash/wm/overview/overview_item_base.h
@@ -123,7 +123,10 @@
   // Returns true if `target` is contained in this OverviewItem.
   virtual bool Contains(const aura::Window* target) const = 0;
 
-  // Returns the direct `OverviewItem` that represents the given `window`.
+  // Returns the direct `OverviewItem` that represents the given `window`. This
+  // is temporarily added for the current overview tests, we should avoid using
+  // this API moving forward.
+  // TODO(b/297580539): Completely get rid of this API.
   virtual OverviewItem* GetLeafItemForWindow(aura::Window* window) = 0;
 
   // Restores and animates the managed window(s) to its non overview mode state.
@@ -228,6 +231,13 @@
   virtual void OnOverviewItemDragStarted(OverviewItemBase* item) = 0;
   virtual void OnOverviewItemDragEnded(bool snap) = 0;
 
+  // Called when performing the continuous scroll on overview item to set bounds
+  // and opacity with pre-calculated `target_bounds`. `first_scroll` is used to
+  // decide if any special handlings are needed for first scroll update.
+  virtual void OnOverviewItemContinuousScroll(const gfx::RectF& target_bouns,
+                                              bool first_scroll,
+                                              float scroll_ratio) = 0;
+
   // Shows/Hides window item during window dragging. Used when swiping up a
   // window from shelf.
   virtual void SetVisibleDuringItemDragging(bool visible, bool animate) = 0;
diff --git a/base/allocator/partition_allocator/BUILD.gn b/base/allocator/partition_allocator/BUILD.gn
index 279a717..e5cfa75 100644
--- a/base/allocator/partition_allocator/BUILD.gn
+++ b/base/allocator/partition_allocator/BUILD.gn
@@ -349,6 +349,8 @@
     "partition_alloc_base/cxx20_is_constant_evaluated.h",
     "partition_alloc_base/debug/alias.cc",
     "partition_alloc_base/debug/alias.h",
+    "partition_alloc_base/debug/stack_trace.cc",
+    "partition_alloc_base/debug/stack_trace.h",
     "partition_alloc_base/export_template.h",
     "partition_alloc_base/gtest_prod_util.h",
     "partition_alloc_base/immediate_crash.h",
@@ -375,6 +377,7 @@
     "partition_alloc_base/numerics/safe_math_clang_gcc_impl.h",
     "partition_alloc_base/numerics/safe_math_shared_impl.h",
     "partition_alloc_base/posix/eintr_wrapper.h",
+    "partition_alloc_base/process/process_handle.h",
     "partition_alloc_base/rand_util.cc",
     "partition_alloc_base/rand_util.h",
     "partition_alloc_base/scoped_clear_last_error.h",
@@ -403,7 +406,9 @@
 
   if (is_win) {
     sources += [
+      "partition_alloc_base/debug/stack_trace_win.cc",
       "partition_alloc_base/memory/page_size_win.cc",
+      "partition_alloc_base/process/process_handle_win.cc",
       "partition_alloc_base/rand_util_win.cc",
       "partition_alloc_base/scoped_clear_last_error_win.cc",
       "partition_alloc_base/threading/platform_thread_win.cc",
@@ -411,22 +416,31 @@
     ]
   } else if (is_posix) {
     sources += [
+      "partition_alloc_base/debug/stack_trace_posix.cc",
       "partition_alloc_base/files/file_util.h",
       "partition_alloc_base/files/file_util_posix.cc",
       "partition_alloc_base/memory/page_size_posix.cc",
       "partition_alloc_base/posix/safe_strerror.cc",
       "partition_alloc_base/posix/safe_strerror.h",
+      "partition_alloc_base/process/process_handle_posix.cc",
       "partition_alloc_base/rand_util_posix.cc",
       "partition_alloc_base/threading/platform_thread_internal_posix.h",
       "partition_alloc_base/threading/platform_thread_posix.cc",
       "partition_alloc_base/time/time_conversion_posix.cc",
     ]
 
+    if (is_linux || is_chromeos) {
+      sources += [ "partition_alloc_base/debug/stack_trace_linux.cc" ]
+    }
+
     if (is_android || is_chromeos_ash) {
       sources += [ "partition_alloc_base/time/time_android.cc" ]
     }
     if (is_apple) {
-      sources += [ "partition_alloc_base/time/time_apple.mm" ]
+      sources += [
+        "partition_alloc_base/debug/stack_trace_mac.cc",
+        "partition_alloc_base/time/time_apple.mm",
+      ]
     } else {
       sources += [ "partition_alloc_base/time/time_now_posix.cc" ]
     }
@@ -448,6 +462,7 @@
     # Only android build requires native_library, and native_library depends
     # on file_path. So file_path is added if is_android = true.
     sources += [
+      "partition_alloc_base/debug/stack_trace_android.cc",
       "partition_alloc_base/files/file_path.cc",
       "partition_alloc_base/files/file_path.h",
       "partition_alloc_base/native_library.cc",
@@ -750,6 +765,7 @@
     "PA_DCHECK_IS_ON=$_dcheck_is_on",
     "PA_EXPENSIVE_DCHECKS_ARE_ON=$enable_expensive_dchecks",
     "PA_DCHECK_IS_CONFIGURABLE=$dcheck_is_configurable",
+    "PA_CAN_UNWIND_WITH_FRAME_POINTERS=$can_unwind_with_frame_pointers",
   ]
 }
 
diff --git a/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.cc b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.cc
new file mode 100644
index 0000000..58e6a87
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.cc
@@ -0,0 +1,252 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h"
+
+#include "base/allocator/partition_allocator/partition_alloc_base/check.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/compiler_specific.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/process/process_handle.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/threading/platform_thread.h"
+#include "build/build_config.h"
+
+#include <stdint.h>
+
+#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && defined(__GLIBC__)
+extern "C" void* __libc_stack_end;
+#endif
+
+namespace partition_alloc::internal::base::debug {
+namespace {
+
+#if BUILDFLAG(PA_CAN_UNWIND_WITH_FRAME_POINTERS)
+
+#if defined(__arm__) && defined(__GNUC__) && !defined(__clang__)
+// GCC and LLVM generate slightly different frames on ARM, see
+// https://llvm.org/bugs/show_bug.cgi?id=18505 - LLVM generates
+// x86-compatible frame, while GCC needs adjustment.
+constexpr size_t kStackFrameAdjustment = sizeof(uintptr_t);
+#else
+constexpr size_t kStackFrameAdjustment = 0;
+#endif
+
+// On Arm-v8.3+ systems with pointer authentication codes (PAC), signature bits
+// are set in the top bits of the pointer, which confuses test assertions.
+// Because the signature size can vary based on the system configuration, use
+// the xpaclri instruction to remove the signature.
+static uintptr_t StripPointerAuthenticationBits(uintptr_t ptr) {
+#if defined(ARCH_CPU_ARM64)
+  // A single Chromium binary currently spans all Arm systems (including those
+  // with and without pointer authentication). xpaclri is used here because it's
+  // in the HINT space and treated as a no-op on older Arm cores (unlike the
+  // more generic xpaci which has a new encoding). The downside is that ptr has
+  // to be moved to x30 to use this instruction. TODO(richard.townsend@arm.com):
+  // replace with an intrinsic once that is available.
+  register uintptr_t x30 __asm("x30") = ptr;
+  asm("xpaclri" : "+r"(x30));
+  return x30;
+#else
+  // No-op on other platforms.
+  return ptr;
+#endif
+}
+
+uintptr_t GetNextStackFrame(uintptr_t fp) {
+  const uintptr_t* fp_addr = reinterpret_cast<const uintptr_t*>(fp);
+  PA_MSAN_UNPOISON(fp_addr, sizeof(uintptr_t));
+  return fp_addr[0] - kStackFrameAdjustment;
+}
+
+uintptr_t GetStackFramePC(uintptr_t fp) {
+  const uintptr_t* fp_addr = reinterpret_cast<const uintptr_t*>(fp);
+  PA_MSAN_UNPOISON(&fp_addr[1], sizeof(uintptr_t));
+  return StripPointerAuthenticationBits(fp_addr[1]);
+}
+
+bool IsStackFrameValid(uintptr_t fp, uintptr_t prev_fp, uintptr_t stack_end) {
+  // With the stack growing downwards, older stack frame must be
+  // at a greater address that the current one.
+  if (fp <= prev_fp) {
+    return false;
+  }
+
+  // Assume huge stack frames are bogus.
+  if (fp - prev_fp > 100000) {
+    return false;
+  }
+
+  // Check alignment.
+  if (fp & (sizeof(uintptr_t) - 1)) {
+    return false;
+  }
+
+  if (stack_end) {
+    // Both fp[0] and fp[1] must be within the stack.
+    if (fp > stack_end - 2 * sizeof(uintptr_t)) {
+      return false;
+    }
+
+    // Additional check to filter out false positives.
+    if (GetStackFramePC(fp) < 32768) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// ScanStackForNextFrame() scans the stack for a valid frame to allow unwinding
+// past system libraries. Only supported on Linux where system libraries are
+// usually in the middle of the trace:
+//
+//   TraceStackFramePointers
+//   <more frames from Chrome>
+//   base::WorkSourceDispatch   <-- unwinding stops (next frame is invalid),
+//   g_main_context_dispatch        ScanStackForNextFrame() is called
+//   <more frames from glib>
+//   g_main_context_iteration
+//   base::MessagePumpGlib::Run <-- ScanStackForNextFrame() finds valid frame,
+//   base::RunLoop::Run             unwinding resumes
+//   <more frames from Chrome>
+//   __libc_start_main
+//
+// ScanStackForNextFrame() returns 0 if it couldn't find a valid frame
+// (or if stack scanning is not supported on the current platform).
+uintptr_t ScanStackForNextFrame(uintptr_t fp, uintptr_t stack_end) {
+  // Enough to resume almost all prematurely terminated traces.
+  constexpr size_t kMaxStackScanArea = 8192;
+
+  if (!stack_end) {
+    // Too dangerous to scan without knowing where the stack ends.
+    return 0;
+  }
+
+  fp += sizeof(uintptr_t);  // current frame is known to be invalid
+  uintptr_t last_fp_to_scan =
+      std::min(fp + kMaxStackScanArea, stack_end) - sizeof(uintptr_t);
+  for (; fp <= last_fp_to_scan; fp += sizeof(uintptr_t)) {
+    uintptr_t next_fp = GetNextStackFrame(fp);
+    if (IsStackFrameValid(next_fp, fp, stack_end)) {
+      // Check two frames deep. Since stack frame is just a pointer to
+      // a higher address on the stack, it's relatively easy to find
+      // something that looks like one. However two linked frames are
+      // far less likely to be bogus.
+      uintptr_t next2_fp = GetNextStackFrame(next_fp);
+      if (IsStackFrameValid(next2_fp, next_fp, stack_end)) {
+        return fp;
+      }
+    }
+  }
+
+  return 0;
+}
+
+#endif  // BUILDFLAG(PA_CAN_UNWIND_WITH_FRAME_POINTERS)
+
+}  // namespace
+
+#if BUILDFLAG(PA_CAN_UNWIND_WITH_FRAME_POINTERS)
+
+// We force this function to be inlined into its callers (e.g.
+// TraceStackFramePointers()) in all build modes so we don't have to worry about
+// conditionally skipping a frame based on potential inlining or tail calls.
+__attribute__((always_inline)) size_t TraceStackFramePointersInternal(
+    uintptr_t fp,
+    uintptr_t stack_end,
+    size_t max_depth,
+    size_t skip_initial,
+    bool enable_scanning,
+    const void** out_trace) {
+  size_t depth = 0;
+  while (depth < max_depth) {
+    uintptr_t pc = GetStackFramePC(fp);
+    if (skip_initial != 0) {
+      skip_initial--;
+    } else {
+      out_trace[depth++] = reinterpret_cast<const void*>(pc);
+    }
+
+    uintptr_t next_fp = GetNextStackFrame(fp);
+    if (IsStackFrameValid(next_fp, fp, stack_end)) {
+      fp = next_fp;
+      continue;
+    }
+
+    if (!enable_scanning) {
+      break;
+    }
+
+    next_fp = ScanStackForNextFrame(fp, stack_end);
+    if (next_fp) {
+      fp = next_fp;
+    } else {
+      break;
+    }
+  }
+
+  return depth;
+}
+
+PA_NOINLINE size_t TraceStackFramePointers(const void** out_trace,
+                                           size_t max_depth,
+                                           size_t skip_initial,
+                                           bool enable_scanning) {
+  return TraceStackFramePointersInternal(
+      reinterpret_cast<uintptr_t>(__builtin_frame_address(0)) -
+          kStackFrameAdjustment,
+      GetStackEnd(), max_depth, skip_initial, enable_scanning, out_trace);
+}
+
+#endif  // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
+
+#if BUILDFLAG(PA_CAN_UNWIND_WITH_FRAME_POINTERS)
+uintptr_t GetStackEnd() {
+#if BUILDFLAG(IS_ANDROID)
+  // Bionic reads proc/maps on every call to pthread_getattr_np() when called
+  // from the main thread. So we need to cache end of stack in that case to get
+  // acceptable performance.
+  // For all other threads pthread_getattr_np() is fast enough as it just reads
+  // values from its pthread_t argument.
+  static uintptr_t main_stack_end = 0;
+
+  bool is_main_thread = GetCurrentProcId() == PlatformThread::CurrentId();
+  if (is_main_thread && main_stack_end) {
+    return main_stack_end;
+  }
+
+  uintptr_t stack_begin = 0;
+  size_t stack_size = 0;
+  pthread_attr_t attributes;
+  int error = pthread_getattr_np(pthread_self(), &attributes);
+  if (!error) {
+    error = pthread_attr_getstack(
+        &attributes, reinterpret_cast<void**>(&stack_begin), &stack_size);
+    pthread_attr_destroy(&attributes);
+  }
+  PA_BASE_DCHECK(!error);
+
+  uintptr_t stack_end = stack_begin + stack_size;
+  if (is_main_thread) {
+    main_stack_end = stack_end;
+  }
+  return stack_end;  // 0 in case of error
+#elif BUILDFLAG(IS_APPLE)
+  // No easy way to get end of the stack for non-main threads,
+  // see crbug.com/617730.
+  return reinterpret_cast<uintptr_t>(pthread_get_stackaddr_np(pthread_self()));
+#else
+
+#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && defined(__GLIBC__)
+  if (GetCurrentProcId() == PlatformThread::CurrentId()) {
+    // For the main thread we have a shortcut.
+    return reinterpret_cast<uintptr_t>(__libc_stack_end);
+  }
+#endif
+
+  // Don't know how to get end of the stack.
+  return 0;
+#endif
+}
+#endif  // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
+
+}  // namespace partition_alloc::internal::base::debug
diff --git a/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h
new file mode 100644
index 0000000..e6fc592
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h
@@ -0,0 +1,74 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_DEBUG_STACK_TRACE_H_
+#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_DEBUG_STACK_TRACE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/allocator/partition_allocator/partition_alloc_base/component_export.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h"
+#include "build/build_config.h"
+
+namespace partition_alloc::internal::base::debug {
+
+// Returns end of the stack, or 0 if we couldn't get it.
+#if BUILDFLAG(PA_CAN_UNWIND_WITH_FRAME_POINTERS)
+PA_COMPONENT_EXPORT(PARTITION_ALLOC)
+uintptr_t GetStackEnd();
+#endif
+
+// Record a stack trace with up to |count| frames into |trace|. Returns the
+// number of frames read.
+PA_COMPONENT_EXPORT(PARTITION_ALLOC)
+size_t CollectStackTrace(void** trace, size_t count);
+
+PA_COMPONENT_EXPORT(PARTITION_ALLOC)
+void PrintStackTrace(void** trace, size_t count);
+
+#if BUILDFLAG(IS_POSIX)
+PA_COMPONENT_EXPORT(PARTITION_ALLOC)
+void OutputStackTrace(unsigned index,
+                      uintptr_t address,
+                      uintptr_t base_address,
+                      const char* module_name,
+                      uintptr_t offset);
+#endif
+
+#if BUILDFLAG(PA_CAN_UNWIND_WITH_FRAME_POINTERS)
+
+// For stack scanning to be efficient it's very important for the thread to
+// be started by Chrome. In that case we naturally terminate unwinding once
+// we reach the origin of the stack (i.e. GetStackEnd()). If the thread is
+// not started by Chrome (e.g. Android's main thread), then we end up always
+// scanning area at the origin of the stack, wasting time and not finding any
+// frames (since Android libraries don't have frame pointers). Scanning is not
+// enabled on other posix platforms due to legacy reasons.
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+constexpr bool kEnableScanningByDefault = true;
+#else
+constexpr bool kEnableScanningByDefault = false;
+#endif
+
+// Traces the stack by using frame pointers. This function is faster but less
+// reliable than StackTrace. It should work for debug and profiling builds,
+// but not for release builds (although there are some exceptions).
+//
+// Writes at most |max_depth| frames (instruction pointers) into |out_trace|
+// after skipping |skip_initial| frames. Note that the function itself is not
+// added to the trace so |skip_initial| should be 0 in most cases.
+// Returns number of frames written. |enable_scanning| enables scanning on
+// platforms that do not enable scanning by default.
+PA_COMPONENT_EXPORT(PARTITION_ALLOC)
+size_t TraceStackFramePointers(const void** out_trace,
+                               size_t max_depth,
+                               size_t skip_initial,
+                               bool enable_scanning = kEnableScanningByDefault);
+
+#endif  // BUILDFLAG(PA_CAN_UNWIND_WITH_FRAME_POINTERS)
+
+}  // namespace partition_alloc::internal::base::debug
+
+#endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_DEBUG_STACK_TRACE_H_
diff --git a/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_android.cc b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_android.cc
new file mode 100644
index 0000000..7ae9fc8
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_android.cc
@@ -0,0 +1,75 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h"
+
+#include "base/allocator/partition_allocator/partition_alloc_base/logging.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/strings/safe_sprintf.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <unwind.h>
+
+namespace partition_alloc::internal::base::debug {
+
+namespace {
+
+struct StackCrawlState {
+  StackCrawlState(uintptr_t* frames, size_t max_depth)
+      : frames(frames),
+        frame_count(0),
+        max_depth(max_depth),
+        have_skipped_self(false) {}
+
+  uintptr_t* frames;
+  size_t frame_count;
+  size_t max_depth;
+  bool have_skipped_self;
+};
+
+_Unwind_Reason_Code TraceStackFrame(_Unwind_Context* context, void* arg) {
+  StackCrawlState* state = static_cast<StackCrawlState*>(arg);
+  uintptr_t ip = _Unwind_GetIP(context);
+
+  // The first stack frame is this function itself.  Skip it.
+  if (ip != 0 && !state->have_skipped_self) {
+    state->have_skipped_self = true;
+    return _URC_NO_REASON;
+  }
+
+  state->frames[state->frame_count++] = ip;
+  if (state->frame_count >= state->max_depth) {
+    return _URC_END_OF_STACK;
+  }
+  return _URC_NO_REASON;
+}
+
+}  // namespace
+
+size_t CollectStackTrace(void** trace, size_t count) {
+  StackCrawlState state(reinterpret_cast<uintptr_t*>(trace), count);
+  _Unwind_Backtrace(&TraceStackFrame, &state);
+  return state.frame_count;
+}
+
+void OutputStackTrace(unsigned index,
+                      uintptr_t address,
+                      uintptr_t base_address,
+                      const char* module_name,
+                      uintptr_t offset) {
+  size_t module_name_len = strlen(module_name);
+
+  char buffer[256];
+  if (module_name_len > 4 &&
+      !strcmp(module_name + module_name_len - 4, ".apk")) {
+    strings::SafeSPrintf(buffer, "#%02d pc 0x%0x %s (offset 0x%0x)\n", index,
+                         address - base_address, module_name, offset);
+  } else {
+    strings::SafeSPrintf(buffer, "#%02d pc 0x%0x %s\n", index,
+                         address - base_address, module_name);
+  }
+  PA_RAW_LOG(INFO, buffer);
+}
+
+}  // namespace partition_alloc::internal::base::debug
diff --git a/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_linux.cc b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_linux.cc
new file mode 100644
index 0000000..2283c4b9
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_linux.cc
@@ -0,0 +1,28 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h"
+
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h"
+#include "build/build_config.h"
+
+namespace partition_alloc::internal::base::debug {
+
+size_t CollectStackTrace(void** trace, size_t count) {
+  // NOTE: This code MUST be async-signal safe (it's used by in-process
+  // stack dumping signal handler). NO malloc or stdio is allowed here.
+
+#if BUILDFLAG(PA_CAN_UNWIND_WITH_FRAME_POINTERS)
+  // Regarding Linux and Android, backtrace API internally invokes malloc().
+  // So the API is not available inside memory allocation. Instead try tracing
+  // using frame pointers.
+  return base::debug::TraceStackFramePointers(const_cast<const void**>(trace),
+                                              count, 0);
+#else
+  // Not able to obtain stack traces.
+  return 0;
+#endif
+}
+
+}  // namespace partition_alloc::internal::base::debug
diff --git a/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_mac.cc b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_mac.cc
new file mode 100644
index 0000000..a50c6bce
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_mac.cc
@@ -0,0 +1,38 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h"
+
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/debugging_buildflags.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/numerics/safe_conversions.h"
+#include "build/build_config.h"
+
+// Surprisingly, uClibc defines __GLIBC__ in some build configs, but
+// execinfo.h and backtrace(3) are really only present in glibc and in macOS
+// libc.
+#if BUILDFLAG(IS_APPLE) || \
+    (defined(__GLIBC__) && !defined(__UCLIBC__) && !defined(__AIX))
+#define HAVE_BACKTRACE
+#include <execinfo.h>
+#endif
+
+namespace partition_alloc::internal::base::debug {
+
+size_t CollectStackTrace(void** trace, size_t count) {
+  // NOTE: This code MUST be async-signal safe (it's used by in-process
+  // stack dumping signal handler). NO malloc or stdio is allowed here.
+
+#if BUILDFLAG(IS_APPLE) && defined(HAVE_BACKTRACE)
+  // Regarding Apple, no /proc is available. Try backtrace API.
+  // Though the backtrace API man page does not list any possible negative
+  // return values, we take no chance.
+  return base::saturated_cast<size_t>(
+      backtrace(trace, base::saturated_cast<int>(count)));
+#else
+  // Not able to obtain stack traces.
+  return 0;
+#endif
+}
+
+}  // namespace partition_alloc::internal::base::debug
diff --git a/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_posix.cc b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_posix.cc
new file mode 100644
index 0000000..9f89fa2
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_posix.cc
@@ -0,0 +1,413 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h"
+
+#include "base/allocator/partition_allocator/partition_alloc_base/logging.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/posix/eintr_wrapper.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/strings/safe_sprintf.h"
+
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_APPLE)
+#include <link.h>  // For ElfW() macro.
+#endif
+
+#if BUILDFLAG(IS_APPLE)
+#define HAVE_DLADDR
+#include <dlfcn.h>
+#endif
+
+namespace partition_alloc::internal::base::debug {
+
+namespace {
+
+#if !BUILDFLAG(IS_APPLE)
+
+constexpr size_t kBufferSize = 4096u;
+
+enum {
+  kMapReadable = 1u,
+  kMapWritable = 2u,
+  kMapExecutable = 4u,
+  kMapPrivate = 8u,
+};
+
+bool ParseAddress(const char** ptr,
+                  const char* end,
+                  uintptr_t* address_return) {
+  const char* start = *ptr;
+
+  // 0xNN = 2 characters
+  const char* max_address = start + sizeof(void*) * 2;
+  uintptr_t value = 0;
+
+  const char* p = start;
+  for (; p < end && p < max_address; ++p) {
+    if ('0' <= *p && *p <= '9') {
+      value = (value << 4) | (unsigned char)(*p - '0');
+    } else if ('a' <= *p && *p <= 'f') {
+      value = (value << 4) | (unsigned char)(*p - 'a' + 10);
+    } else {
+      break;
+    }
+  }
+  if (p == start) {
+    return false;
+  }
+  *ptr = p;
+  if (address_return) {
+    *address_return = value;
+  }
+  return true;
+}
+
+bool ParseInteger(const char** ptr, const char* end) {
+  const char* start = *ptr;
+
+  const char* p = start;
+  for (; p < end && '0' <= *p && *p <= '9'; ++p)
+    ;
+  *ptr = p;
+  return p > start;
+}
+
+bool ParsePermissions(const char** ptr,
+                      const char* end,
+                      unsigned* permission_return) {
+  unsigned permission = 0u;
+  const char* p = *ptr;
+  if (p < end && (*p == 'r' || *p == '-')) {
+    permission |= (*p == 'r') ? kMapReadable : 0u;
+    ++p;
+  } else {
+    return false;
+  }
+  if (p < end && (*p == 'w' || *p == '-')) {
+    permission |= (*p == 'w') ? kMapWritable : 0u;
+    ++p;
+  } else {
+    return false;
+  }
+  if (p < end && (*p == 'x' || *p == '-')) {
+    permission |= (*p == 'w') ? kMapExecutable : 0u;
+    ++p;
+  } else {
+    return false;
+  }
+  if (p < end && (*p == 'p' || *p == '-' || *p == 's')) {
+    permission |= (*p == 'w') ? kMapPrivate : 0u;
+    ++p;
+  } else {
+    return false;
+  }
+  *ptr = p;
+  if (permission_return) {
+    *permission_return = permission;
+  }
+  return true;
+}
+
+bool ParseMapsLine(const char* line_start,
+                   const char* line_end,
+                   uintptr_t* start_address_return,
+                   uintptr_t* end_address_return,
+                   unsigned* permission_return,
+                   uintptr_t* offset_return,
+                   const char** module_name) {
+  const char* ptr = line_start;
+  if (!ParseAddress(&ptr, line_end, start_address_return)) {
+    return false;
+  }
+  // Delimiter
+  if (ptr >= line_end || *ptr != '-') {
+    return false;
+  }
+  ++ptr;
+  if (!ParseAddress(&ptr, line_end, end_address_return)) {
+    return false;
+  }
+
+  // Delimiter
+  if (ptr >= line_end || *ptr != ' ') {
+    return false;
+  }
+  ++ptr;
+
+  // skip permissions.
+  if (!ParsePermissions(&ptr, line_end, permission_return)) {
+    return false;
+  }
+
+  // Delimiter
+  if (ptr >= line_end || *ptr != ' ') {
+    return false;
+  }
+  ++ptr;
+
+  // skip offset
+  if (ParseAddress(&ptr, line_end, offset_return)) {
+    if (ptr >= line_end || *ptr != ' ') {
+      return false;
+    }
+    ++ptr;
+
+    // skip dev
+    if (!ParseAddress(&ptr, line_end, nullptr)) {
+      return false;
+    }
+    if (ptr >= line_end || *ptr != ':') {
+      return false;
+    }
+    ++ptr;
+    if (!ParseAddress(&ptr, line_end, nullptr)) {
+      return false;
+    }
+
+    // Delimiter
+    if (ptr >= line_end || *ptr != ' ') {
+      return false;
+    }
+    ++ptr;
+
+    // skip inode
+    if (!ParseInteger(&ptr, line_end)) {
+      return false;
+    }
+  } else {
+    if (offset_return) {
+      *offset_return = 0u;
+    }
+  }
+  if (ptr >= line_end || *ptr != ' ') {
+    return false;
+  }
+  for (; ptr < line_end && *ptr == ' '; ++ptr)
+    ;
+  if (ptr <= line_end && module_name) {
+    *module_name = ptr;
+  }
+  return true;
+}
+
+#if !BUILDFLAG(IS_ANDROID)
+
+ssize_t ReadFromOffset(const int fd,
+                       void* buf,
+                       const size_t count,
+                       const size_t offset) {
+  char* buf0 = reinterpret_cast<char*>(buf);
+  size_t num_bytes = 0;
+  while (num_bytes < count) {
+    ssize_t len;
+    len = PA_HANDLE_EINTR(pread(fd, buf0 + num_bytes, count - num_bytes,
+                                static_cast<off_t>(offset + num_bytes)));
+    if (len < 0) {  // There was an error other than EINTR.
+      return -1;
+    }
+    if (len == 0) {  // Reached EOF.
+      break;
+    }
+    num_bytes += static_cast<size_t>(len);
+  }
+  return static_cast<ssize_t>(num_bytes);
+}
+
+void UpdateBaseAddress(unsigned permissions,
+                       uintptr_t start_address,
+                       uintptr_t* base_address) {
+  // Determine the base address by reading ELF headers in process memory.
+  // Skip non-readable maps.
+  if (!(permissions & kMapReadable)) {
+    return;
+  }
+
+  int mem_fd = PA_HANDLE_EINTR(open("/proc/self/mem", O_RDONLY));
+  if (mem_fd == -1) {
+    PA_RAW_LOG(ERROR, "Failed to open /proc/self/mem\n");
+    return;
+  }
+
+  ElfW(Ehdr) ehdr;
+  ssize_t len =
+      ReadFromOffset(mem_fd, &ehdr, sizeof(ElfW(Ehdr)), start_address);
+  if (len == sizeof(ElfW(Ehdr))) {
+    if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0) {
+      switch (ehdr.e_type) {
+        case ET_EXEC:
+          *base_address = 0;
+          break;
+        case ET_DYN:
+          // Find the segment containing file offset 0. This will correspond
+          // to the ELF header that we just read. Normally this will have
+          // virtual address 0, but this is not guaranteed. We must subtract
+          // the virtual address from the address where the ELF header was
+          // mapped to get the base address.
+          //
+          // If we fail to find a segment for file offset 0, use the address
+          // of the ELF header as the base address.
+          *base_address = start_address;
+          for (unsigned i = 0; i != ehdr.e_phnum; ++i) {
+            ElfW(Phdr) phdr;
+            len =
+                ReadFromOffset(mem_fd, &phdr, sizeof(ElfW(Phdr)),
+                               start_address + ehdr.e_phoff + i * sizeof(phdr));
+            if (len == sizeof(ElfW(Phdr)) && phdr.p_type == PT_LOAD &&
+                phdr.p_offset == 0) {
+              *base_address = start_address - phdr.p_vaddr;
+              break;
+            }
+          }
+          break;
+        default:
+          // ET_REL or ET_CORE. These aren't directly executable, so they don't
+          // affect the base address.
+          break;
+      }
+    }
+  }
+  close(mem_fd);
+}
+
+#endif  // !BUILDFLAG(IS_ANDROID)
+
+void PrintStackTraceInternal(void* const* trace, size_t count) {
+  int fd = PA_HANDLE_EINTR(open("/proc/self/maps", O_RDONLY));
+  if (fd == -1) {
+    PA_RAW_LOG(ERROR, "Failed to open /proc/self/maps\n");
+    return;
+  }
+
+  char buffer[kBufferSize];
+  char* dest = buffer;
+  char* buffer_end = buffer + kBufferSize;
+#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_APPLE)
+  uintptr_t base_address = 0u;
+#endif
+
+  while (dest < buffer_end) {
+    ssize_t bytes_read = PA_HANDLE_EINTR(read(fd, dest, buffer_end - dest));
+    if (bytes_read == 0) {
+      break;
+    }
+    if (bytes_read < 0) {
+      PA_RAW_LOG(ERROR, "Failed to read /proc/self/maps\n");
+      break;
+    }
+
+    char* read_end = dest + bytes_read;
+    char* parsed = buffer;
+    char* line_start = buffer;
+    // It is difficult to remember entire memory regions and to use them
+    // to process stack traces. Instead, try to parse each line of
+    // /proc/self/maps and to process matched stack traces. This will
+    // make the order of the output stack traces different from the input.
+    for (char* line_end = buffer; line_end < read_end; ++line_end) {
+      if (*line_end == '\n') {
+        parsed = line_end + 1;
+        *line_end = '\0';
+        uintptr_t start_address = 0u;
+        uintptr_t end_address = 0u;
+        uintptr_t offset = 0u;
+        unsigned permissions = 0u;
+        const char* module_name = nullptr;
+        bool ok =
+            ParseMapsLine(line_start, line_end, &start_address, &end_address,
+                          &permissions, &offset, &module_name);
+        if (ok) {
+#if !BUILDFLAG(IS_ANDROID)
+          UpdateBaseAddress(permissions, start_address, &base_address);
+#endif
+          if (module_name && *module_name != '\0') {
+            for (size_t i = 0; i < count; i++) {
+#if BUILDFLAG(IS_ANDROID)
+              // Subtract one as return address of function may be in the next
+              // function when a function is annotated as noreturn.
+              uintptr_t address = reinterpret_cast<uintptr_t>(trace[i]) - 1;
+              uintptr_t base_address = start_address;
+#else
+              uintptr_t address = reinterpret_cast<uintptr_t>(trace[i]);
+#endif
+              if (start_address <= address && address < end_address) {
+                OutputStackTrace(i, address, base_address, module_name, offset);
+              }
+            }
+          }
+        } else {
+          PA_RAW_LOG(ERROR, "Parse failed.\n");
+        }
+        line_start = parsed;
+      }
+    }
+    if (parsed == buffer) {
+      // /proc/self/maps contains too long line (> kBufferSize).
+      PA_RAW_LOG(ERROR, "/proc/self/maps has too long line.\n");
+      break;
+    }
+    if (parsed < read_end) {
+      size_t left_chars = read_end - parsed;
+      memmove(buffer, parsed, left_chars);
+      dest = buffer + left_chars;
+    } else {
+      dest = buffer;
+    }
+  }
+  close(fd);
+}
+#endif  // !BUILDFLAG(IS_APPLE)
+
+#if BUILDFLAG(IS_APPLE)
+// Since /proc/self/maps is not available, use dladdr() to obtain module
+// names and offsets inside the modules from the given addresses.
+void PrintStackTraceInternal(void* const* trace, size_t size) {
+  // NOTE: This code MUST be async-signal safe (it's used by in-process
+  // stack dumping signal handler). NO malloc or stdio is allowed here.
+
+  Dl_info dl_info;
+  for (size_t i = 0; i < size; ++i) {
+    const bool dl_info_found = dladdr(trace[i], &dl_info) != 0;
+    if (dl_info_found) {
+      const char* last_sep = strrchr(dl_info.dli_fname, '/');
+      const char* basename = last_sep ? last_sep + 1 : dl_info.dli_fname;
+
+      // Use atos with --offset to obtain symbols from the printed addresses,
+      // e.g.
+      //  #01 0x0000000106225d6c  (base_unittests+0x0000000001999d6c)
+      //  bash-3.2$ atos -o out/default/base_unittests --offset
+      //   0x0000000001999d6c
+      //  partition_alloc::internal::PartitionAllocTest_Basic_Test::TestBody()
+      //  (in base_unittests) + 156
+      OutputStackTrace(i, reinterpret_cast<uintptr_t>(trace[i]),
+                       reinterpret_cast<uintptr_t>(dl_info.dli_fbase), basename,
+                       0u);
+    } else {
+      OutputStackTrace(i, reinterpret_cast<uintptr_t>(trace[i]), 0u, "???", 0u);
+    }
+  }
+}
+#endif  // BUILDFLAG(IS_APPLE)
+
+}  // namespace
+
+void PrintStackTrace(void** trace, size_t count) {
+  PrintStackTraceInternal(trace, count);
+}
+
+// stack_trace_android.cc defines its own OutputStackTrace.
+#if !BUILDFLAG(IS_ANDROID)
+void OutputStackTrace(unsigned index,
+                      uintptr_t address,
+                      uintptr_t base_address,
+                      const char* module_name,
+                      uintptr_t) {
+  char buffer[256];
+  strings::SafeSPrintf(buffer, "#%02d 0x%0x  (%s+0x%0x)\n", index, address,
+                       module_name, address - base_address);
+  PA_RAW_LOG(INFO, buffer);
+}
+#endif  // !BUILDFLAG(IS_ANDROID)
+
+}  // namespace partition_alloc::internal::base::debug
diff --git a/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_win.cc b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_win.cc
new file mode 100644
index 0000000..931f8e7
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace_win.cc
@@ -0,0 +1,104 @@
+// Copyright 2012 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/allocator/partition_allocator/partition_alloc_base/debug/stack_trace.h"
+
+#include "base/allocator/partition_allocator/partition_alloc_base/logging.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/process/process_handle.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/strings/safe_sprintf.h"
+
+#include <windows.h>
+
+#include <psapi.h>
+
+namespace partition_alloc::internal::base::debug {
+
+namespace {
+
+void PrintStackTraceInternal(void** trace, size_t count) {
+  HANDLE process_handle = OpenProcess(
+      PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, GetCurrentProcId());
+  if (!process_handle) {
+    return;
+  }
+
+  bool is_output_trace[count];
+  for (size_t i = 0; i < count; ++i) {
+    is_output_trace[i] = false;
+  }
+  DWORD bytes_required = 0;
+  if (EnumProcessModules(process_handle, nullptr, 0, &bytes_required)) {
+    HMODULE* module_array = nullptr;
+    LPBYTE module_array_bytes = nullptr;
+
+    if (bytes_required) {
+      module_array_bytes = (LPBYTE)LocalAlloc(LPTR, bytes_required);
+      if (module_array_bytes) {
+        unsigned int module_count = bytes_required / sizeof(HMODULE);
+        module_array = reinterpret_cast<HMODULE*>(module_array_bytes);
+
+        if (EnumProcessModules(process_handle, module_array, bytes_required,
+                               &bytes_required)) {
+          for (unsigned i = 0; i < module_count; ++i) {
+            MODULEINFO info;
+            if (GetModuleInformation(process_handle, module_array[i], &info,
+                                     sizeof(info))) {
+              char module_name[MAX_PATH + 1];
+              bool module_name_checked = false;
+              for (unsigned j = 0; j < count; j++) {
+                uintptr_t base_of_dll =
+                    reinterpret_cast<uintptr_t>(info.lpBaseOfDll);
+                uintptr_t address = reinterpret_cast<uintptr_t>(trace[j]);
+                if (base_of_dll <= address &&
+                    address < base_of_dll + info.SizeOfImage) {
+                  if (!module_name_checked) {
+                    size_t module_name_length = GetModuleFileNameExA(
+                        process_handle, module_array[i], module_name,
+                        sizeof(module_name) - 1);
+                    module_name[module_name_length] = '\0';
+                    module_name_checked = true;
+                  }
+                  // llvm-symbolizer needs --relative-address to symbolize the
+                  // "address - base_of_dll".
+                  char buffer[256];
+                  strings::SafeSPrintf(buffer, "#%d 0x%x (%s+0x%x)\n", j,
+                                       address, module_name,
+                                       address - base_of_dll);
+                  PA_RAW_LOG(INFO, buffer);
+                  is_output_trace[j] = true;
+                }
+              }
+            }
+          }
+        }
+        LocalFree(module_array_bytes);
+      }
+    }
+  }
+
+  for (size_t i = 0; i < count; ++i) {
+    if (is_output_trace[i]) {
+      continue;
+    }
+    char buffer[256];
+    strings::SafeSPrintf(buffer, "#%d 0x%x <unknown>\n", i,
+                         reinterpret_cast<uintptr_t>(trace[i]));
+    PA_RAW_LOG(INFO, buffer);
+  }
+
+  CloseHandle(process_handle);
+}
+
+}  // namespace
+
+PA_NOINLINE size_t CollectStackTrace(void** trace, size_t count) {
+  // When walking our own stack, use CaptureStackBackTrace().
+  return CaptureStackBackTrace(0, count, trace, NULL);
+}
+
+void PrintStackTrace(void** trace, size_t count) {
+  PrintStackTraceInternal(trace, count);
+}
+
+}  // namespace partition_alloc::internal::base::debug
diff --git a/base/allocator/partition_allocator/partition_alloc_base/process/process_handle.h b/base/allocator/partition_allocator/partition_alloc_base/process/process_handle.h
new file mode 100644
index 0000000..64b266d
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/process/process_handle.h
@@ -0,0 +1,46 @@
+// 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 BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_PROCESS_PROCESS_HANDLE_H_
+#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_PROCESS_PROCESS_HANDLE_H_
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include "base/allocator/partition_allocator/partition_alloc_base/component_export.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/allocator/partition_allocator/partition_alloc_base/win/windows_types.h"
+#endif
+
+#if BUILDFLAG(IS_FUCHSIA)
+#include <zircon/types.h>
+#endif
+
+namespace partition_alloc::internal::base {
+
+// ProcessHandle is a platform specific type which represents the underlying OS
+// handle to a process.
+// ProcessId is a number which identifies the process in the OS.
+#if BUILDFLAG(IS_WIN)
+typedef DWORD ProcessId;
+const ProcessId kNullProcessId = 0;
+#elif BUILDFLAG(IS_FUCHSIA)
+typedef zx_koid_t ProcessId;
+const ProcessId kNullProcessId = ZX_KOID_INVALID;
+#elif BUILDFLAG(IS_POSIX)
+// On POSIX, our ProcessHandle will just be the PID.
+typedef pid_t ProcessId;
+const ProcessId kNullProcessId = 0;
+#endif  // BUILDFLAG(IS_WIN)
+
+// Returns the id of the current process.
+// Note that on some platforms, this is not guaranteed to be unique across
+// processes (use GetUniqueIdForProcess if uniqueness is required).
+PA_COMPONENT_EXPORT(PARTITION_ALLOC) ProcessId GetCurrentProcId();
+
+}  // namespace partition_alloc::internal::base
+
+#endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_PROCESS_PROCESS_HANDLE_H_
diff --git a/base/allocator/partition_allocator/partition_alloc_base/process/process_handle_posix.cc b/base/allocator/partition_allocator/partition_alloc_base/process/process_handle_posix.cc
new file mode 100644
index 0000000..1f8e1c6
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/process/process_handle_posix.cc
@@ -0,0 +1,15 @@
+// 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 "base/allocator/partition_allocator/partition_alloc_base/process/process_handle.h"
+
+#include <unistd.h>
+
+namespace partition_alloc::internal::base {
+
+ProcessId GetCurrentProcId() {
+  return getpid();
+}
+
+}  // namespace partition_alloc::internal::base
diff --git a/base/allocator/partition_allocator/partition_alloc_base/process/process_handle_win.cc b/base/allocator/partition_allocator/partition_alloc_base/process/process_handle_win.cc
new file mode 100644
index 0000000..a316ba2
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/process/process_handle_win.cc
@@ -0,0 +1,15 @@
+// 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 "base/allocator/partition_allocator/partition_alloc_base/process/process_handle.h"
+
+#include <windows.h>
+
+namespace partition_alloc::internal::base {
+
+ProcessId GetCurrentProcId() {
+  return ::GetCurrentProcessId();
+}
+
+}  // namespace partition_alloc::internal::base
diff --git a/base/mac/launch_application.mm b/base/mac/launch_application.mm
index 4b67e4a..8d06953 100644
--- a/base/mac/launch_application.mm
+++ b/base/mac/launch_application.mm
@@ -123,8 +123,8 @@
   // find a running application matching the app we were trying to launch.
   // Only do this if `options.create_new_instance` is false though, as
   // otherwise we wouldn't know which instance to return.
-  if (IsAtLeastOS11() && IsAtMostOS12() && !create_new_instance && !app &&
-      ShouldScanRunningAppsForError(error)) {
+  if ((MacOSMajorVersion() == 11 || MacOSMajorVersion() == 12) &&
+      !create_new_instance && !app && ShouldScanRunningAppsForError(error)) {
     NSArray<NSRunningApplication*>* all_apps =
         NSWorkspace.sharedWorkspace.runningApplications;
     for (NSRunningApplication* running_app in all_apps) {
diff --git a/base/mac/launch_application_unittest.mm b/base/mac/launch_application_unittest.mm
index 5e4d39c8a..6608ad4 100644
--- a/base/mac/launch_application_unittest.mm
+++ b/base/mac/launch_application_unittest.mm
@@ -378,7 +378,7 @@
   EXPECT_NSEQ(LaunchEventName(1), @"applicationDidFinishLaunching");
   EXPECT_NSEQ(LaunchEventName(2), @"openURLs");
 
-  if (IsOS11()) {
+  if (MacOSMajorVersion() == 11) {
     // macOS 11 (and only macOS 11) appears to sometimes trigger the openURLs
     // calls in reverse order.
     std::vector<std::string> received_urls;
diff --git a/base/mac/mac_util.h b/base/mac/mac_util.h
index 759f5c2b..986dba8 100644
--- a/base/mac/mac_util.h
+++ b/base/mac/mac_util.h
@@ -10,6 +10,7 @@
 #include <stdint.h>
 
 #include <string>
+#include <string_view>
 
 #include "base/base_export.h"
 
@@ -61,90 +62,17 @@
 // an error, or true otherwise.
 BASE_EXPORT bool RemoveQuarantineAttribute(const FilePath& file_path);
 
-namespace internal {
-
-// Returns the system's macOS major and minor version numbers combined into an
-// integer value. For example, for macOS Sierra this returns 1012, and for macOS
-// Big Sur it returns 1100. Note that the accuracy returned by this function is
-// as granular as the major version number of Darwin.
-BASE_EXPORT int MacOSVersion();
-
-}  // namespace internal
-
-// Run-time OS version checks. Prefer @available in Objective-C files. If that
-// is not possible, use these functions instead of
-// base::SysInfo::OperatingSystemVersionNumbers. Prefer the "AtLeast" and
-// "AtMost" variants to those that check for a specific version, unless you know
-// for sure that you need to check for a specific version.
-
-#define DEFINE_OLD_IS_OS_FUNCS_CR_MIN_REQUIRED(V, DEPLOYMENT_TARGET_TEST) \
-  inline bool IsOS10_##V() {                                              \
-    DEPLOYMENT_TARGET_TEST(>, V, false)                                   \
-    return internal::MacOSVersion() == 1000 + V;                          \
-  }
-
-#define DEFINE_IS_OS_FUNCS_CR_MIN_REQUIRED(V, DEPLOYMENT_TARGET_TEST) \
-  inline bool IsOS##V() {                                             \
-    DEPLOYMENT_TARGET_TEST(>, V, false)                               \
-    return internal::MacOSVersion() == V * 100;                       \
-  }
-
-#define DEFINE_IS_OS_FUNCS(V, DEPLOYMENT_TARGET_TEST)           \
-  DEFINE_IS_OS_FUNCS_CR_MIN_REQUIRED(V, DEPLOYMENT_TARGET_TEST) \
-  inline bool IsAtLeastOS##V() {                                \
-    DEPLOYMENT_TARGET_TEST(>=, V, true)                         \
-    return internal::MacOSVersion() >= V * 100;                 \
-  }                                                             \
-  inline bool IsAtMostOS##V() {                                 \
-    DEPLOYMENT_TARGET_TEST(>, V, false)                         \
-    return internal::MacOSVersion() <= V * 100;                 \
-  }
-
-#define OLD_TEST_DEPLOYMENT_TARGET(OP, V, RET)                  \
-  if (MAC_OS_X_VERSION_MIN_REQUIRED OP MAC_OS_X_VERSION_10_##V) \
-    return RET;
-#define TEST_DEPLOYMENT_TARGET(OP, V, RET)                     \
-  if (MAC_OS_X_VERSION_MIN_REQUIRED OP MAC_OS_VERSION_##V##_0) \
-    return RET;
-#define IGNORE_DEPLOYMENT_TARGET(OP, V, RET)
-
-// Notes:
-// - When bumping the minimum version of the macOS required by Chromium, remove
-//   lines from below corresponding to versions of the macOS no longer
-//   supported. Ensure that the minimum supported version uses the
-//   DEFINE_OLD_IS_OS_FUNCS_CR_MIN_REQUIRED macro. When macOS 11.0 is the
-//   minimum required version, remove all the OLD versions of the macros.
-// - When bumping the minimum version of the macOS SDK required to build
-//   Chromium, remove the #ifdef that switches between
-//   TEST_DEPLOYMENT_TARGET and IGNORE_DEPLOYMENT_TARGET.
-
-// Versions of macOS supported at runtime but whose SDK is not supported for
-// building.
-DEFINE_OLD_IS_OS_FUNCS_CR_MIN_REQUIRED(15, OLD_TEST_DEPLOYMENT_TARGET)
-DEFINE_IS_OS_FUNCS(11, TEST_DEPLOYMENT_TARGET)
-DEFINE_IS_OS_FUNCS(12, TEST_DEPLOYMENT_TARGET)
-
-// Versions of macOS supported at runtime and whose SDK is supported for
-// building.
-#ifdef MAC_OS_VERSION_13_0
-DEFINE_IS_OS_FUNCS(13, TEST_DEPLOYMENT_TARGET)
-#else
-DEFINE_IS_OS_FUNCS(13, IGNORE_DEPLOYMENT_TARGET)
-#endif
-
-#ifdef MAC_OS_VERSION_14_0
-DEFINE_IS_OS_FUNCS(14, TEST_DEPLOYMENT_TARGET)
-#else
-DEFINE_IS_OS_FUNCS(14, IGNORE_DEPLOYMENT_TARGET)
-#endif
-
-#undef DEFINE_OLD_IS_OS_FUNCS_CR_MIN_REQUIRED
-#undef DEFINE_OLD_IS_OS_FUNCS
-#undef DEFINE_IS_OS_FUNCS_CR_MIN_REQUIRED
-#undef DEFINE_IS_OS_FUNCS
-#undef OLD_TEST_DEPLOYMENT_TARGET
-#undef TEST_DEPLOYMENT_TARGET
-#undef IGNORE_DEPLOYMENT_TARGET
+// The following two functions return the version of the macOS currently
+// running. MacOSVersion() returns the full trio of version numbers, packed into
+// one int (e.g. macOS 12.6.5 returns 12'06'05), and MacOSMajorVersion() returns
+// only the major version number (e.g. macOS 12.6.5 returns 12). Use for runtime
+// OS version checking. Prefer to use @available in Objective-C files. Note that
+// this does not include any Rapid Security Response (RSR) suffixes (the "(a)"
+// at the end of version numbers.)
+BASE_EXPORT __attribute__((const)) int MacOSVersion();
+inline __attribute__((const)) int MacOSMajorVersion() {
+  return MacOSVersion() / 1'00'00;
+}
 
 enum class CPUType {
   kIntel,
@@ -225,6 +153,12 @@
 // instead.
 BASE_EXPORT void OpenSystemSettingsPane(SystemSettingsPane pane);
 
+// ------- For testing --------
+
+// An implementation detail of `MacOSVersion()` above, exposed for testing.
+BASE_EXPORT int ParseOSProductVersionForTesting(
+    const std::string_view& version);
+
 }  // namespace base::mac
 
 #endif  // BASE_MAC_MAC_UTIL_H_
diff --git a/base/mac/mac_util.mm b/base/mac/mac_util.mm
index 1da035e..efde819 100644
--- a/base/mac/mac_util.mm
+++ b/base/mac/mac_util.mm
@@ -15,17 +15,23 @@
 #include <sys/utsname.h>
 #include <sys/xattr.h>
 
+#include <string>
+#include <string_view>
+#include <vector>
+
 #include "base/apple/bridging.h"
 #include "base/apple/bundle_locations.h"
 #include "base/apple/foundation_util.h"
 #include "base/apple/osstatus_logging.h"
 #include "base/apple/scoped_cftyperef.h"
+#include "base/check.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/mac/scoped_aedesc.h"
 #include "base/mac/scoped_ioobject.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/threading/scoped_blocking_call.h"
@@ -296,86 +302,82 @@
 
 namespace {
 
-// Returns the running system's Darwin major version. Don't call this, it's an
-// implementation detail and its result is meant to be cached by
-// MacOSVersionInternal().
-int DarwinMajorVersionInternal() {
-  // base::OperatingSystemVersionNumbers() at one time called Gestalt(), which
-  // was observed to be able to spawn threads (see https://crbug.com/53200).
-  // Nowadays that function calls -[NSProcessInfo operatingSystemVersion], whose
-  // current implementation does things like hit the file system, which is
-  // possibly a blocking operation. Either way, it's overkill for what needs to
-  // be done here.
-  //
-  // uname, on the other hand, is implemented as a simple series of sysctl
-  // system calls to obtain the relevant data from the kernel. The data is
-  // compiled right into the kernel, so no threads or blocking or other
-  // funny business is necessary.
-  //
-  // TODO: Switch to the kern.osproductversion sysctl? It's compiled in and
-  // should require less Darwin offset guessing and parsing.
+std::string StringSysctlByName(const char* name) {
+  size_t buf_len;
+  int result = sysctlbyname(name, nullptr, &buf_len, nullptr, 0);
+  PCHECK(result == 0);
+  CHECK_GE(buf_len, 1u);
 
-  struct utsname uname_info;
-  if (uname(&uname_info) != 0) {
-    DPLOG(ERROR) << "uname";
-    return 0;
-  }
+  std::string value(buf_len - 1, '\0');
+  result = sysctlbyname(name, &value[0], &buf_len, nullptr, 0);
+  PCHECK(result == 0);
+  CHECK_EQ(value[buf_len - 1], '\0');
 
-  if (strcmp(uname_info.sysname, "Darwin") != 0) {
-    DLOG(ERROR) << "unexpected uname sysname " << uname_info.sysname;
-    return 0;
-  }
+  return value;
+}
 
-  int darwin_major_version = 0;
-  char* dot = strchr(uname_info.release, '.');
-  if (dot) {
-    if (!base::StringToInt(
-            base::StringPiece(uname_info.release,
-                              static_cast<size_t>(dot - uname_info.release)),
-            &darwin_major_version)) {
-      dot = nullptr;
+int ParseOSProductVersion(const std::string_view& version) {
+  int macos_version = 0;
+
+  // The number of parts that need to be a part of the return value
+  // (major/minor/bugfix).
+  int parts = 3;
+
+  // When a Rapid Security Response is applied to a system, the UI will display
+  // an additional letter (e.g. "13.4.1 (a)"). That extra letter should not be
+  // present in `version_string`; in fact, the version string should not contain
+  // any spaces. However, take the first string-delimited "word" for parsing.
+  std::vector<std::string_view> words = base::SplitStringPiece(
+      version, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  CHECK_GE(words.size(), 1u);
+
+  // There are expected to be either two or three numbers separated by a dot.
+  // Walk through them, and add them to the version string.
+  for (const auto& value_str : base::SplitStringPiece(
+           words[0], ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
+    int value;
+    bool success = base::StringToInt(value_str, &value);
+    CHECK(success);
+    macos_version *= 100;
+    macos_version += value;
+    if (--parts == 0) {
+      break;
     }
   }
 
-  if (!dot) {
-    DLOG(ERROR) << "could not parse uname release " << uname_info.release;
-    return 0;
+  // While historically the string has comprised exactly two or three numbers
+  // separated by a dot, it's not inconceivable that it might one day be only
+  // one number. Therefore, only check to see that at least one number was found
+  // and processed.
+  CHECK_LE(parts, 2);
+
+  // Tack on as many '00 digits as needed to be sure that exactly three version
+  // numbers are returned.
+  for (int i = 0; i < parts; ++i) {
+    macos_version *= 100;
   }
 
-  return darwin_major_version;
-}
+  // Checks that the value is within expected bounds corresponding to released
+  // OS version numbers. The most important bit is making sure that the "10.16"
+  // compatibility mode isn't engaged.
+  CHECK(macos_version >= 10'00'00);
+  CHECK(macos_version < 10'16'00 || macos_version >= 11'00'00);
 
-// The implementation of MacOSVersion() as defined in the header. Don't call
-// this, it's an implementation detail and the result is meant to be cached by
-// MacOSVersion().
-int MacOSVersionInternal() {
-  int darwin_major_version = DarwinMajorVersionInternal();
-
-  // Darwin major versions 6 through 19 corresponded to macOS versions 10.2
-  // through 10.15.
-  CHECK(darwin_major_version >= 6);
-  if (darwin_major_version <= 19) {
-    return 1000 + darwin_major_version - 4;
-  }
-
-  // Darwin major version 20 corresponds to macOS version 11.0. Assume a
-  // correspondence between Darwin's major version numbers and macOS major
-  // version numbers.
-  int macos_major_version = darwin_major_version - 9;
-
-  return macos_major_version * 100;
+  return macos_version;
 }
 
 }  // namespace
 
-namespace internal {
-
-int MacOSVersion() {
-  static int macos_version = MacOSVersionInternal();
-  return macos_version;
+int ParseOSProductVersionForTesting(const std::string_view& version) {
+  return ParseOSProductVersion(version);
 }
 
-}  // namespace internal
+int MacOSVersion() {
+  static int macos_version =
+      ParseOSProductVersion(StringSysctlByName("kern.osproductversion"));
+
+  return macos_version;
+}
 
 namespace {
 
@@ -493,7 +495,7 @@
   // guessing. Clarity was requested from Apple in FB11753405.
   switch (pane) {
     case SystemSettingsPane::kAccessibility_Captions:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.Accessibility-Settings."
               @"extension?Captioning";
       } else {
@@ -502,7 +504,7 @@
       }
       break;
     case SystemSettingsPane::kDateTime:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url =
             @"x-apple.systempreferences:com.apple.Date-Time-Settings.extension";
       } else {
@@ -510,7 +512,7 @@
       }
       break;
     case SystemSettingsPane::kNetwork_Proxies:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.Network-Settings.extension?"
               @"Proxies";
       } else {
@@ -519,7 +521,7 @@
       }
       break;
     case SystemSettingsPane::kPrintersScanners:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.Print-Scan-Settings."
               @"extension";
       } else {
@@ -527,7 +529,7 @@
       }
       break;
     case SystemSettingsPane::kPrivacySecurity_Accessibility:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.settings.PrivacySecurity."
               @"extension?Privacy_Accessibility";
       } else {
@@ -536,7 +538,7 @@
       }
       break;
     case SystemSettingsPane::kPrivacySecurity_Bluetooth:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.settings.PrivacySecurity."
               @"extension?Privacy_Bluetooth";
       } else {
@@ -545,7 +547,7 @@
       }
       break;
     case SystemSettingsPane::kPrivacySecurity_Camera:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.settings.PrivacySecurity."
               @"extension?Privacy_Camera";
       } else {
@@ -554,7 +556,7 @@
       }
       break;
     case SystemSettingsPane::kPrivacySecurity_Extensions_Sharing:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         // See ShareKit, -[SHKSharingServicePicker openAppExtensionsPrefpane].
         url = @"x-apple.systempreferences:com.apple.ExtensionsPreferences?"
               @"Sharing";
@@ -574,7 +576,7 @@
       }
       break;
     case SystemSettingsPane::kPrivacySecurity_LocationServices:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.settings.PrivacySecurity."
               @"extension?Privacy_LocationServices";
       } else {
@@ -583,7 +585,7 @@
       }
       break;
     case SystemSettingsPane::kPrivacySecurity_Microphone:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.settings.PrivacySecurity."
               @"extension?Privacy_Microphone";
       } else {
@@ -592,7 +594,7 @@
       }
       break;
     case SystemSettingsPane::kPrivacySecurity_ScreenRecording:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.settings.PrivacySecurity."
               @"extension?Privacy_ScreenCapture";
       } else {
@@ -601,7 +603,7 @@
       }
       break;
     case SystemSettingsPane::kTrackpad:
-      if (IsAtLeastOS13()) {
+      if (MacOSMajorVersion() >= 13) {
         url = @"x-apple.systempreferences:com.apple.Trackpad-Settings."
               @"extension";
       } else {
diff --git a/base/mac/mac_util_unittest.mm b/base/mac/mac_util_unittest.mm
index bdff8a60..96a8546 100644
--- a/base/mac/mac_util_unittest.mm
+++ b/base/mac/mac_util_unittest.mm
@@ -145,85 +145,41 @@
   }
 }
 
-TEST_F(MacUtilTest, IsOSEllipsis) {
+TEST_F(MacUtilTest, MacOSVersion) {
   int32_t major, minor, bugfix;
   base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
 
-  // The patterns here are:
-  // - FALSE/FALSE/TRUE (it is not the earlier version, it is not "at most" the
-  //   earlier version, it is "at least" the earlier version)
-  // - TRUE/TRUE/TRUE (it is the same version, it is "at most" the same version,
-  //   it is "at least" the same version)
-  // - FALSE/TRUE/FALSE (it is not the later version, it is "at most" the later
-  //   version, it is not "at least" the later version)
-
-#define TEST_FOR_PAST_OS(V)      \
-  EXPECT_FALSE(IsOS##V());       \
-  EXPECT_FALSE(IsAtMostOS##V()); \
-  EXPECT_TRUE(IsAtLeastOS##V());
-
-#define TEST_FOR_SAME_OS(V)     \
-  EXPECT_TRUE(IsOS##V());       \
-  EXPECT_TRUE(IsAtMostOS##V()); \
-  EXPECT_TRUE(IsAtLeastOS##V());
-
-#define TEST_FOR_FUTURE_OS(V)   \
-  EXPECT_FALSE(IsOS##V());      \
-  EXPECT_TRUE(IsAtMostOS##V()); \
-  EXPECT_FALSE(IsAtLeastOS##V());
-
-  if (major == 10) {
-    if (minor == 15) {
-      EXPECT_TRUE(IsOS10_15());
-
-      TEST_FOR_FUTURE_OS(11);
-      TEST_FOR_FUTURE_OS(12);
-      TEST_FOR_FUTURE_OS(13);
-      TEST_FOR_FUTURE_OS(14);
-    } else {
-      // macOS 10.15 was the end of the line.
-      FAIL() << "Unexpected 10.x macOS.";
-    }
-  } else if (major == 11) {
-    EXPECT_FALSE(IsOS10_15());
-
-    TEST_FOR_SAME_OS(11);
-    TEST_FOR_FUTURE_OS(12);
-    TEST_FOR_FUTURE_OS(13);
-    TEST_FOR_FUTURE_OS(14);
-  } else if (major == 12) {
-    EXPECT_FALSE(IsOS10_15());
-
-    TEST_FOR_PAST_OS(11);
-    TEST_FOR_SAME_OS(12);
-    TEST_FOR_FUTURE_OS(13);
-    TEST_FOR_FUTURE_OS(14);
-  } else if (major == 13) {
-    EXPECT_FALSE(IsOS10_15());
-
-    TEST_FOR_PAST_OS(11);
-    TEST_FOR_PAST_OS(12);
-    TEST_FOR_SAME_OS(13);
-    TEST_FOR_FUTURE_OS(14);
-  } else if (major == 14) {
-    EXPECT_FALSE(IsOS10_15());
-
-    TEST_FOR_PAST_OS(11);
-    TEST_FOR_PAST_OS(12);
-    TEST_FOR_PAST_OS(13);
-    TEST_FOR_SAME_OS(14);
-  } else {
-    // The spooky future.
-    FAIL() << "Time to update the OS macros!";
-  }
+  EXPECT_EQ(major * 1'00'00 + minor * 1'00 + bugfix, MacOSVersion());
+  EXPECT_EQ(major, MacOSMajorVersion());
 }
 
-#undef TEST_FOR_PAST_10_OS
-#undef TEST_FOR_PAST_OS
-#undef TEST_FOR_SAME_10_OS
-#undef TEST_FOR_SAME_OS
-#undef TEST_FOR_FUTURE_10_OS
-#undef TEST_FOR_FUTURE_OS
+TEST_F(MacUtilTest, ParseOSProductVersion) {
+  // Various strings in shapes that would be expected to be returned from the
+  // API that would need to be parsed.
+  EXPECT_EQ(10'06'02, ParseOSProductVersionForTesting("10.6.2"));
+  EXPECT_EQ(10'15'00, ParseOSProductVersionForTesting("10.15"));
+  EXPECT_EQ(13'05'01, ParseOSProductVersionForTesting("13.5.1"));
+  EXPECT_EQ(14'00'00, ParseOSProductVersionForTesting("14.0"));
+
+  // Various strings in shapes that would not be expected, but that should parse
+  // without CHECKing.
+  EXPECT_EQ(13'04'01, ParseOSProductVersionForTesting("13.4.1 (c)"));
+  EXPECT_EQ(14'00'00, ParseOSProductVersionForTesting("14.0.0"));
+  EXPECT_EQ(18'00'00, ParseOSProductVersionForTesting("18"));
+  EXPECT_EQ(18'03'04, ParseOSProductVersionForTesting("18.3.4.3.2.5"));
+
+  // Various strings in shapes that are so unexpected that they should not
+  // parse.
+  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("Mac OS X 10.0"),
+                            "");
+  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting(""), "");
+  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("  "), "");
+  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("."), "");
+  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("10.a.5"), "");
+  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("१०.१५.७"), "");
+  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("7.6.1"), "");
+  EXPECT_DEATH_IF_SUPPORTED(ParseOSProductVersionForTesting("10.16"), "");
+}
 
 TEST_F(MacUtilTest, ParseModelIdentifier) {
   std::string model;
diff --git a/base/system/sys_info.h b/base/system/sys_info.h
index b8295c7..54e7276a 100644
--- a/base/system/sys_info.h
+++ b/base/system/sys_info.h
@@ -135,9 +135,10 @@
 
   // Retrieves detailed numeric values for the OS version.
   // DON'T USE THIS ON THE MAC OR WINDOWS to determine the current OS release
-  // for OS version-specific feature checks and workarounds. If you must use
-  // an OS version check instead of a feature check, use the base::mac::IsOS*
-  // family from base/mac/mac_util.h, or base::win::GetVersion from
+  // for OS version-specific feature checks and workarounds. If you must use an
+  // OS version check instead of a feature check, use
+  // base::mac::MacOSVersion()/MacOSMajorVersion() family from
+  // base/mac/mac_util.h, or base::win::GetVersion() from
   // base/win/windows_version.h.
   static void OperatingSystemVersionNumbers(int32_t* major_version,
                                             int32_t* minor_version,
diff --git a/build/config/chromeos/rules.gni b/build/config/chromeos/rules.gni
index 732afc35..09d13df 100644
--- a/build/config/chromeos/rules.gni
+++ b/build/config/chromeos/rules.gni
@@ -303,7 +303,7 @@
                            "test_exe",
                          ])
 
-  if (!defined(skip_generating_board_args)) {
+  if (!defined(skip_generating_board_args) && !is_skylab) {
     skip_generating_board_args = false
   }
 
@@ -441,7 +441,7 @@
       executable_args += [ "--strip-chrome" ]
     }
 
-    if (!skip_generating_board_args || is_skylab) {
+    if (!skip_generating_board_args) {
       executable_args += [
         "--board",
         cros_board,
diff --git a/cc/test/layer_tree_test.h b/cc/test/layer_tree_test.h
index e1fbf9c..907789b 100644
--- a/cc/test/layer_tree_test.h
+++ b/cc/test/layer_tree_test.h
@@ -222,6 +222,9 @@
   bool use_skia_vulkan() const {
     return renderer_type_ == viz::RendererType::kSkiaVk;
   }
+  bool use_skia_graphite() const {
+    return renderer_type_ == viz::RendererType::kSkiaGraphite;
+  }
 
   const viz::RendererType renderer_type_;
 
diff --git a/cc/trees/layer_tree_host_pixeltest_filters.cc b/cc/trees/layer_tree_host_pixeltest_filters.cc
index a368ef10..1d09c78 100644
--- a/cc/trees/layer_tree_host_pixeltest_filters.cc
+++ b/cc/trees/layer_tree_host_pixeltest_filters.cc
@@ -1265,6 +1265,9 @@
     if (use_software_renderer()) {
       expected_result = expected_result.InsertBeforeExtensionASCII("_sw");
     }
+    if (use_skia_graphite()) {
+      expected_result = expected_result.InsertBeforeExtensionASCII("_graphite");
+    }
     RunPixelTest(std::move(root), expected_result);
   }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
index 4ec0396..5d30b3b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediator.java
@@ -840,7 +840,7 @@
             String searchText = getCurrentSearchText();
             if (!preserveFolderBookmarksOnEmptySearch || !TextUtils.isEmpty(searchText)) {
                 setBookmarks(mBookmarkQueryHandler.buildBookmarkListForSearch(
-                        searchText, getCurrentSearchPowerFilter()));
+                        searchText.trim(), getCurrentSearchPowerFilter()));
             }
         }
 
@@ -1375,7 +1375,7 @@
     }
 
     private void onSearchTextChangeCallback(String searchText) {
-        searchText = searchText == null ? "" : searchText.trim();
+        searchText = searchText == null ? "" : searchText;
         if (BookmarkFeatures.isAndroidImprovedBookmarksEnabled()) {
             getSearchBoxPropertyModel().set(BookmarkSearchBoxRowProperties.SEARCH_TEXT, searchText);
             getSearchBoxPropertyModel().set(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/AreaMotionEventFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/AreaMotionEventFilter.java
index 92ccd709..fa35569 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/AreaMotionEventFilter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/AreaMotionEventFilter.java
@@ -123,6 +123,15 @@
                     || e.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) {
                 mHasHoverEnterOrMoveEventInArea = true;
                 return super.onInterceptHoverEventInternal(e);
+            } else if (e.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
+                // A hover exit event is recorded inside the filter area when another screen
+                // interaction through a gesture event occurs. In this case, we will give a chance
+                // to the event filter to handle such an exit event if a previous hover entry/move
+                // event was intercepted.
+                if (mHasHoverEnterOrMoveEventInArea) {
+                    mHasHoverEnterOrMoveEventInArea = false;
+                    return super.onInterceptHoverEventInternal(e);
+                }
             }
         } else {
             // If there was a previous hover into/within the rect, potentially handle hovering out
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageBridge.java
index 0be14ee1..992f3d8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadMessageBridge.java
@@ -49,6 +49,7 @@
                 Snackbar.make(context.getString(R.string.download_file_type_not_supported), null,
                         Snackbar.TYPE_NOTIFICATION, Snackbar.UMA_AUTO_LOGIN);
         snackbar.setAction(context.getString(R.string.ok), null);
+        snackbar.setSingleLine(false);
         snackbarManager.showSnackbar(snackbar);
     }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
index e1b5e4a..cf0e8f9 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkManagerMediatorTest.java
@@ -1517,4 +1517,24 @@
         PropertyModel model = mModelList.get(0).model;
         assertFalse(model.get(BookmarkSearchBoxRowProperties.SHOPPING_CHIP_VISIBILITY));
     }
+
+    @Test
+    @EnableFeatures(ChromeFeatureList.ANDROID_IMPROVED_BOOKMARKS)
+    public void testSearchWithWhitespace() {
+        finishLoading();
+        mMediator.openFolder(mFolderId1);
+        assertEquals(ViewType.SEARCH_BOX, mModelList.get(0).type);
+
+        PropertyModel propertyModel = mModelList.get(0).model;
+        Callback<String> searchTextCallback =
+                propertyModel.get(BookmarkSearchBoxRowProperties.SEARCH_TEXT_CHANGE_CALLBACK);
+        assertNotNull(searchTextCallback);
+
+        String queryWithWhitespace = " foo ";
+        searchTextCallback.onResult(queryWithWhitespace);
+        // Model queries should be trimmed, but the View property should still have whitespace.
+        assertEquals(
+                queryWithWhitespace, propertyModel.get(BookmarkSearchBoxRowProperties.SEARCH_TEXT));
+        verify(mBookmarkModel).searchBookmarks(eq("foo"), anyInt());
+    }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/AreaMotionEventFilterUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/AreaMotionEventFilterUnitTest.java
index 0f1a882..f224967 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/AreaMotionEventFilterUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/compositor/layouts/eventfilter/AreaMotionEventFilterUnitTest.java
@@ -72,6 +72,21 @@
     }
 
     @Test
+    public void testHoverExitInterceptionWithinFilterArea() {
+        // Intercept an ACTION_HOVER_ENTER into the filter area.
+        boolean intercepted = mEventFilter.onInterceptHoverEvent(mHoverEnterEvent);
+
+        // Intercept an ACTION_HOVER_EXIT inside the filter area potentially triggered by another
+        // gesture motion event. In this case the hover exit action will be recorded from within the
+        // rect.
+        var hoverExitEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_EXIT, 50.f, 50.f, 0);
+        intercepted = mEventFilter.onInterceptHoverEvent(hoverExitEvent);
+        Assert.assertTrue("Hover exit event in area should be intercepted.", intercepted);
+        Assert.assertFalse("|mHasHoverEnterOrMoveEventInArea| should be set to false.",
+                mEventFilter.getHasHoverEnterOrMoveEventInAreaForTesting());
+    }
+
+    @Test
     public void testHoverEnterInterceptionOutsideFilterArea() {
         // Do not intercept an ACTION_HOVER_ENTER outside the filter area.
         mHoverEnterEvent.setLocation(101.f, 101.f);
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index dd073b7..c0d0c96a 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -14642,6 +14642,11 @@
       <message name="IDS_WEBAUTHN_TRANSPORT_USB" desc="Menu item text. The user selects this to use a security key (an external physical device for user authentication) plugged in to the USB port of the computer.">
         USB security key
       </message>
+      <if expr="is_macosx">
+        <message name="IDS_WEBAUTHN_YOUR_CHROME_PROFILE" desc="Menu item text. The user selects this to use a hardware-based authentication mechanism that is built in to the computer, such as a fingerprint reader. The credential itself is stored in the Chrome profile, however, and this text emphasizes that.">
+          Your Chrome profile
+        </message>
+      </if>
       <message name="IDS_WEBAUTHN_TRANSPORT_INTERNAL" desc="Menu item text. The user selects this to use a hardware-based authentication mechanism that is built in to the computer, such as a fingerprint reader.">
         This device
       </message>
diff --git a/chrome/app/generated_resources_grd/IDS_WEBAUTHN_YOUR_CHROME_PROFILE.png.sha1 b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_YOUR_CHROME_PROFILE.png.sha1
new file mode 100644
index 0000000..df2d60c
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_WEBAUTHN_YOUR_CHROME_PROFILE.png.sha1
@@ -0,0 +1 @@
+f5a397a6e1a358021490f34a42faaa615862be15
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 131b825e..e88957e4 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -1011,6 +1011,15 @@
   <message name="IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_TURN_OFF_BODY_TEXT" desc="Body text for the confirmation dialog that spawns when attempting to turn off bulk pinning in Settings: Files: Google Drive.">
     New files in My Drive will stop syncing automatically to this Chromebook
   </message>
+  <message name="IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_TITLE_TEXT" desc="Title text for the dialog that spawns  when a user tries to turn on bulk pinning whilst we're still listing files in Settings: Files: Google Drive.">
+    Checking storage space…
+  </message>
+  <message name="IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_BODY_TEXT" desc="Body text for the dialog that spawns when a user tries to turn on bulk pinning whilst we're still listing files in Settings: Files: Google Drive.">
+    We are in the process of checking storage space…
+  </message>
+  <message name="IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_ITEMS_FOUND_BODY_TEXT" desc="Body text for the dialog that spawns when a user tries to turn on bulk pinning whilst we're still listing files and we have a known count of listed files in Settings: Files: Google Drive.">
+    We are in the process of checking storage space. <ph name="ITEMS_FOUND">$1<ex>1,072</ex></ph> items found.
+  </message>
   <message name="IDS_SETTINGS_GOOGLE_DRIVE_REMOVE_ACCESS_BUTTON_LABEL" desc="Button label indicates that, when pressed, will disconnect the users Google Drive access in Settings: Files: Google Drive">
     Remove Drive access
   </message>
@@ -2739,6 +2748,18 @@
   <message name="IDS_SETTINGS_NO_REMAPPING_OPTION_LABEL" translateable="false" desc="Label of no remapping option in the button remapping action dropdown menu.">
     None
   </message>
+  <message name="IDS_SETTINGS_CUSTOMIZE_BUTTONS_RENAMING_DIALOG_TITLE" translateable="false" desc="Title of the button renaming dialog in customize device buttons page.">
+    Change button name
+  </message>
+  <message name="IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CANCEL" translateable="false" desc="Label of cancel button on dialogs in customize device buttons pages.">
+    Cancel
+  </message>
+  <message name="IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_SAVE" translateable="false" desc="Label of save button on dialogs in customize device buttons pages.">
+    Save
+  </message>
+  <message name="IDS_SETTINGS_CUSTOMIZE_BUTTONS_RENAMING_DIALOG_INPUT_LABEL" translateable="false" desc="Label above renaming button input field on the dialog in customize device buttons pages.">
+    New button name
+  </message>
 
   <!-- Bluetooth page (OS settings) -->
   <message name="IDS_SETTINGS_BLUETOOTH_CONNECTED" desc="In Bluetooth device list, this label is shown below a device which is already connected.">
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_BODY_TEXT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_BODY_TEXT.png.sha1
new file mode 100644
index 0000000..1cd65c2
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_BODY_TEXT.png.sha1
@@ -0,0 +1 @@
+6d824641cb44b2a5df2f3b2a94cc3ad43a36385b
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_ITEMS_FOUND_BODY_TEXT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_ITEMS_FOUND_BODY_TEXT.png.sha1
new file mode 100644
index 0000000..46e83d4
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_ITEMS_FOUND_BODY_TEXT.png.sha1
@@ -0,0 +1 @@
+5e1e72b54a21d22cd03a8183054668425b3e69ce
\ No newline at end of file
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_TITLE_TEXT.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_TITLE_TEXT.png.sha1
new file mode 100644
index 0000000..1cd65c2
--- /dev/null
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_TITLE_TEXT.png.sha1
@@ -0,0 +1 @@
+6d824641cb44b2a5df2f3b2a94cc3ad43a36385b
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index 9e424ad2d..d49b1ab 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -1629,6 +1629,9 @@
   <message name="IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_RADIO_GROUP_ARIA_LABEL" desc="Accessibility label which will be read by screen readers when focus enters the energy saver mode radio group. When energy saver mode is enabled, this radio group will be expanded and allow the user to choose between different triggering options.">
     Energy saver options
   </message>
+  <message name="IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_LINK_OS_SETTING_DESCRIPTION" desc="Description on the OS settings link to turn on energy saver mode">
+    Turn on Energy Saver to extend battery life
+  </message>
 
   <!-- Languages Page -->
   <message name="IDS_SETTINGS_LANGUAGES_PAGE_TITLE" desc="Name of the settings page which displays language preferences.">
diff --git a/chrome/app/settings_strings_grdp/IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_LINK_OS_SETTING_DESCRIPTION.png.sha1 b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_LINK_OS_SETTING_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..ebf3ab0
--- /dev/null
+++ b/chrome/app/settings_strings_grdp/IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_LINK_OS_SETTING_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+6735e49b18a874cc20bb2acea4860f093f3692ba
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 1cbef63..ebc59b9 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -10895,12 +10895,6 @@
      FEATURE_VALUE_TYPE(ash::features::kAdvancedDocumentScanAPI)},
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if BUILDFLAG(IS_ANDROID)
-    {"enable-builtin-hls", flag_descriptions::kEnableBuiltinHlsName,
-     flag_descriptions::kEnableBuiltinHlsDescription, kOsAndroid,
-     FEATURE_VALUE_TYPE(media::kBuiltInHlsPlayer)},
-#endif
-
     // NOTE: Adding a new flag requires adding a corresponding entry to enum
     // "LoginCustomFlags" in tools/metrics/histograms/enums.xml. See "Flag
     // Histograms" in tools/metrics/histograms/README.md (run the
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.cc b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
index f27ab00..3816fa8 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.cc
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.cc
@@ -381,9 +381,10 @@
 
 jboolean AutocompleteControllerAndroid::OnSuggestionTouchDown(
     JNIEnv* env,
-    jint match_index,
+    uintptr_t match_ptr,
+    int match_index,
     const base::android::JavaParamRef<jobject>& j_web_contents) {
-  const auto& match = autocomplete_controller_->result().match_at(match_index);
+  const auto& match = *reinterpret_cast<AutocompleteMatch*>(match_ptr);
 
   if (SearchPrefetchService* search_prefetch_service =
           SearchPrefetchServiceFactory::GetForProfile(profile_)) {
@@ -446,10 +447,10 @@
 }
 
 ScopedJavaLocalRef<jobject>
-AutocompleteControllerAndroid::GetMatchingTabForSuggestion(JNIEnv* env,
-                                                           jint match_index) {
-  const AutocompleteMatch& match =
-      autocomplete_controller_->result().match_at(match_index);
+AutocompleteControllerAndroid::GetMatchingTabForSuggestion(
+    JNIEnv* env,
+    uintptr_t match_ptr) {
+  const auto& match = *reinterpret_cast<AutocompleteMatch*>(match_ptr);
   return match.GetMatchingJavaTab().get(env);
 }
 
diff --git a/chrome/browser/android/omnibox/autocomplete_controller_android.h b/chrome/browser/android/omnibox/autocomplete_controller_android.h
index b904db1..e48174a 100644
--- a/chrome/browser/android/omnibox/autocomplete_controller_android.h
+++ b/chrome/browser/android/omnibox/autocomplete_controller_android.h
@@ -69,7 +69,8 @@
       const base::android::JavaParamRef<jobject>& j_web_contents);
   jboolean OnSuggestionTouchDown(
       JNIEnv* env,
-      jint match_index,
+      uintptr_t match_ptr,
+      int match_index,
       const base::android::JavaParamRef<jobject>& j_web_contents);
   void DeleteMatch(JNIEnv* env, uintptr_t match_ptr);
   void DeleteMatchElement(JNIEnv* env, uintptr_t match_ptr, jint element_index);
@@ -82,7 +83,7 @@
       const base::android::JavaParamRef<jobjectArray>& jnew_query_params);
   base::android::ScopedJavaLocalRef<jobject> GetMatchingTabForSuggestion(
       JNIEnv* env,
-      jint match_index);
+      uintptr_t match_ptr);
 
   // Pass detected voice matches down to VoiceSuggestionsProvider.
   void SetVoiceMatches(
diff --git a/chrome/browser/apps/app_service/BUILD.gn b/chrome/browser/apps/app_service/BUILD.gn
index fc465e7..4a4cf59b 100644
--- a/chrome/browser/apps/app_service/BUILD.gn
+++ b/chrome/browser/apps/app_service/BUILD.gn
@@ -435,7 +435,10 @@
   }
 
   if (is_chromeos_lacros) {
-    sources += [ "app_service_proxy_lacros_unittest.cc" ]
+    sources += [
+      "app_service_proxy_lacros_unittest.cc",
+      "metrics/website_metrics_service_lacros_unittest.cc",
+    ]
   } else {
     sources += [ "publishers/app_publisher_unittest.cc" ]
   }
diff --git a/chrome/browser/apps/app_service/app_service_proxy_lacros.cc b/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
index 9da2638..26f9332 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_lacros.cc
@@ -107,6 +107,11 @@
   return browser_app_instance_tracker_.get();
 }
 
+apps::WebsiteMetricsServiceLacros*
+AppServiceProxyLacros::WebsiteMetricsService() {
+  return metrics_service_.get();
+}
+
 absl::optional<IconKey> AppServiceProxyLacros::GetIconKey(
     const std::string& app_id) {
   return outer_icon_loader_.GetIconKey(app_id);
diff --git a/chrome/browser/apps/app_service/app_service_proxy_lacros.h b/chrome/browser/apps/app_service/app_service_proxy_lacros.h
index 5f679ba..00706ae2e 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_lacros.h
+++ b/chrome/browser/apps/app_service/app_service_proxy_lacros.h
@@ -17,6 +17,7 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/browser_app_launcher.h"
 #include "chrome/browser/apps/app_service/launch_result_type.h"
+#include "chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.h"
 #include "chromeos/crosapi/mojom/app_service.mojom.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/services/app_service/public/cpp/app_capability_access_cache.h"
@@ -101,6 +102,8 @@
 
   apps::BrowserAppInstanceTracker* BrowserAppInstanceTracker();
 
+  apps::WebsiteMetricsServiceLacros* WebsiteMetricsService();
+
   // apps::IconLoader overrides.
   absl::optional<IconKey> GetIconKey(const std::string& app_id) override;
   std::unique_ptr<Releaser> LoadIconFromIconKey(
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.cc b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.cc
index 28eee431d..06c8d33 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.cc
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.cc
@@ -98,6 +98,7 @@
   // Also notify observers.
   for (auto& observer : observers_) {
     observer.OnAppPlatformMetricsInit(app_platform_app_metrics_.get());
+    observer.OnWebsiteMetricsInit(website_metrics_.get());
   }
 }
 
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h
index d8e8e74..1ca0ddf 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service.h
@@ -30,8 +30,8 @@
 // Chrome OS.
 class AppPlatformMetricsService {
  public:
-  // Observer that can be used to monitor certain app platform metrics component
-  // lifecycle.
+  // Observer that can be used to monitor the lifecycle of certain components
+  // owned by `AppPlatformMetricsService`.
   class Observer : public base::CheckedObserver {
    public:
     Observer() = default;
@@ -45,6 +45,11 @@
     virtual void OnAppPlatformMetricsInit(
         AppPlatformMetrics* app_platform_metrics) {}
 
+    // Triggered once the `WebsiteMetrics` component is initialized. This
+    // enables external components to delay interactions with the component
+    // until it is ready.
+    virtual void OnWebsiteMetricsInit(WebsiteMetrics* website_metrics) {}
+
     // Triggered when the `AppPlatformMetricsService` will be destroyed. This
     // can be used by observer to unregister itself as an observer as well as
     // prevent use-after-free errors.
@@ -70,6 +75,8 @@
     return app_platform_app_metrics_.get();
   }
 
+  apps::WebsiteMetrics* WebsiteMetrics() { return website_metrics_.get(); }
+
   // Add observer to the observer list.
   void AddObserver(Observer* observer);
 
@@ -81,7 +88,6 @@
 
  private:
   friend class AppPlatformInputMetricsTest;
-  friend class WebsiteMetricsBrowserTest;
 
   // Helper function to check if a new day has arrived.
   void CheckForNewDay();
@@ -112,8 +118,8 @@
   std::unique_ptr<apps::WebsiteMetrics> website_metrics_;
   std::unique_ptr<apps::AppDiscoveryMetrics> app_discovery_metrics_;
 
-  // List of observers that will be notified of certain app platform metrics
-  // component lifecycle changes.
+  // List of observers that will be notified of certain component lifecycle
+  // changes.
   base::ObserverList<Observer> observers_;
 };
 
diff --git a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
index a0d7985..bca4729 100644
--- a/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
+++ b/chrome/browser/apps/app_service/metrics/app_platform_metrics_service_unittest.cc
@@ -155,6 +155,11 @@
               (AppPlatformMetrics * app_platform_metrics),
               (override));
 
+  MOCK_METHOD(void,
+              OnWebsiteMetricsInit,
+              (WebsiteMetrics * website_metrics),
+              (override));
+
   MOCK_METHOD(void, OnAppPlatformMetricsServiceWillBeDestroyed, (), (override));
 };
 
@@ -3369,6 +3374,22 @@
   app_platform_metrics_service.reset();
 }
 
+TEST_P(AppPlatformMetricsServiceObserverTest,
+       NotifyObserversOnWebsiteMetricsInit) {
+  MockObserver* const observer_ptr = observer();
+  AppPlatformMetricsService app_platform_metrics_service(profile());
+  app_platform_metrics_service.AddObserver(observer_ptr);
+
+  EXPECT_CALL(*observer_ptr, OnWebsiteMetricsInit)
+      .WillOnce([&](WebsiteMetrics* website_metrics) {
+        EXPECT_THAT(website_metrics,
+                    Eq(app_platform_metrics_service.WebsiteMetrics()));
+      });
+  app_platform_metrics_service.Start(
+      AppServiceProxyFactory::GetForProfile(profile())->AppRegistryCache(),
+      AppServiceProxyFactory::GetForProfile(profile())->InstanceRegistry());
+}
+
 INSTANTIATE_TEST_SUITE_P(All,
                          AppPlatformMetricsServiceObserverTest,
                          ::testing::Bool() /* IsLacrosEnabled */);
diff --git a/chrome/browser/apps/app_service/metrics/website_metrics_browsertest.cc b/chrome/browser/apps/app_service/metrics/website_metrics_browsertest.cc
index 0707b49..2a39818 100644
--- a/chrome/browser/apps/app_service/metrics/website_metrics_browsertest.cc
+++ b/chrome/browser/apps/app_service/metrics/website_metrics_browsertest.cc
@@ -306,10 +306,10 @@
   WebsiteMetrics* website_metrics() {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
     DCHECK(website_metrics_service_);
-    return website_metrics_service_->website_metrics_.get();
+    return website_metrics_service_->WebsiteMetrics();
 #else
     DCHECK(app_platform_metrics_service_);
-    return app_platform_metrics_service_->website_metrics_.get();
+    return app_platform_metrics_service_->WebsiteMetrics();
 #endif
   }
 
diff --git a/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.cc b/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.cc
index feaf45b8..f934269 100644
--- a/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.cc
+++ b/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.h"
 
+#include "base/check.h"
 #include "base/time/time.h"
 #include "chromeos/crosapi/mojom/device_attributes.mojom.h"
 #include "chromeos/lacros/lacros_service.h"
@@ -25,7 +26,12 @@
 WebsiteMetricsServiceLacros::WebsiteMetricsServiceLacros(Profile* profile)
     : profile_(profile) {}
 
-WebsiteMetricsServiceLacros::~WebsiteMetricsServiceLacros() = default;
+WebsiteMetricsServiceLacros::~WebsiteMetricsServiceLacros() {
+  // Notify observers.
+  for (auto& observer : observers_) {
+    observer.OnWebsiteMetricsServiceLacrosWillBeDestroyed();
+  }
+}
 
 // static
 void WebsiteMetricsServiceLacros::RegisterProfilePrefs(
@@ -54,6 +60,7 @@
 }
 
 void WebsiteMetricsServiceLacros::Start() {
+  CHECK(website_metrics_);
   // Check every `kFiveMinutes` to record websites usage time.
   five_minutes_timer_.Start(FROM_HERE, kFiveMinutes, this,
                             &WebsiteMetricsServiceLacros::CheckForFiveMinutes);
@@ -62,6 +69,21 @@
   noisy_appkm_reporting_interval_timer_.Start(
       FROM_HERE, kNoisyAppKMReportInterval, this,
       &WebsiteMetricsServiceLacros::CheckForNoisyAppKMReportingInterval);
+
+  // Also notify observers.
+  for (auto& observer : observers_) {
+    observer.OnWebsiteMetricsInit(website_metrics_.get());
+  }
+}
+
+void WebsiteMetricsServiceLacros::AddObserver(
+    WebsiteMetricsServiceLacros::Observer* observer) {
+  observers_.AddObserver(observer);
+}
+
+void WebsiteMetricsServiceLacros::RemoveObserver(
+    WebsiteMetricsServiceLacros::Observer* observer) {
+  observers_.RemoveObserver(observer);
 }
 
 void WebsiteMetricsServiceLacros::SetWebsiteMetricsForTesting(
diff --git a/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.h b/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.h
index cfa6cb2..e3f931a 100644
--- a/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.h
+++ b/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros.h
@@ -9,6 +9,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/observer_list_types.h"
 #include "base/timer/timer.h"
 #include "chrome/browser/apps/app_service/metrics/website_metrics.h"
 
@@ -21,6 +22,26 @@
 // Lacros side.
 class WebsiteMetricsServiceLacros {
  public:
+  // Observer that can be used to monitor the lifecycle of certain components
+  // owned by `WebsiteMetricsServiceLacros`.
+  class Observer : public base::CheckedObserver {
+   public:
+    Observer() = default;
+    Observer(const Observer&) = delete;
+    Observer& operator=(const Observer&) = delete;
+    ~Observer() override = default;
+
+    // Triggered once the `WebsiteMetrics` component is initialized. This
+    // enables external components to delay interactions with the component
+    // until it is ready.
+    virtual void OnWebsiteMetricsInit(WebsiteMetrics* website_metrics) {}
+
+    // Triggered when `WebsiteMetricsServiceLacros` will be destroyed. This can
+    // be used by observer to unregister itself as an observer as well as
+    // prevent use-after-free errors.
+    virtual void OnWebsiteMetricsServiceLacrosWillBeDestroyed() = 0;
+  };
+
   explicit WebsiteMetricsServiceLacros(Profile* profile);
   WebsiteMetricsServiceLacros(const WebsiteMetricsServiceLacros&) = delete;
   WebsiteMetricsServiceLacros& operator=(const WebsiteMetricsServiceLacros&) =
@@ -35,6 +56,11 @@
   // Start the timer for website metrics.
   void Start();
 
+  void AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  apps::WebsiteMetrics* WebsiteMetrics() { return website_metrics_.get(); }
+
   void SetWebsiteMetricsForTesting(
       std::unique_ptr<apps::WebsiteMetrics> website_metrics);
 
@@ -62,6 +88,9 @@
 
   std::unique_ptr<apps::WebsiteMetrics> website_metrics_;
 
+  // List of observers used to monitor component lifecycle changes.
+  base::ObserverList<Observer> observers_;
+
   base::WeakPtrFactory<WebsiteMetricsServiceLacros> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros_unittest.cc b/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros_unittest.cc
new file mode 100644
index 0000000..5c695c2
--- /dev/null
+++ b/chrome/browser/apps/app_service/metrics/website_metrics_service_lacros_unittest.cc
@@ -0,0 +1,99 @@
+// 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/apps/app_service/metrics/website_metrics_service_lacros.h"
+
+#include <memory>
+
+#include "base/allocator/partition_allocator/pointers/raw_ptr.h"
+#include "chrome/browser/apps/app_service/metrics/website_metrics.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_profile.h"
+#include "chrome/test/base/testing_profile_manager.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace apps {
+namespace {
+
+// Test user id.
+constexpr char kTestUserId[] = "123";
+
+// Mock observer used to observe `WebsiteMetricsServiceLacros` for testing
+// purposes.
+class MockObserver : public WebsiteMetricsServiceLacros::Observer {
+ public:
+  MockObserver() = default;
+  MockObserver(const MockObserver&) = delete;
+  MockObserver& operator=(const MockObserver&) = delete;
+  ~MockObserver() override = default;
+
+  MOCK_METHOD(void,
+              OnWebsiteMetricsInit,
+              (WebsiteMetrics * website_metrics),
+              (override));
+
+  MOCK_METHOD(void,
+              OnWebsiteMetricsServiceLacrosWillBeDestroyed,
+              (),
+              (override));
+};
+
+class WebsiteMetricsServiceLacrosTest : public ::testing::Test {
+ protected:
+  WebsiteMetricsServiceLacrosTest()
+      : profile_manager_(TestingBrowserProcess::GetGlobal()) {}
+
+  void SetUp() override {
+    ASSERT_TRUE(profile_manager_.SetUp());
+    profile_ = profile_manager_.CreateTestingProfile(kTestUserId);
+    metrics_service_ = std::make_unique<WebsiteMetricsServiceLacros>(profile_);
+    metrics_service_->AddObserver(&observer_);
+  }
+
+  void TearDown() override {
+    if (metrics_service_) {
+      // Unregister observer to eliminate noise during teardown.
+      metrics_service_->RemoveObserver(&observer_);
+    }
+  }
+
+  ::content::BrowserTaskEnvironment task_environment_;
+  TestingProfileManager profile_manager_;
+  raw_ptr<TestingProfile> profile_;
+  std::unique_ptr<WebsiteMetricsServiceLacros> metrics_service_;
+  MockObserver observer_;
+};
+
+TEST_F(WebsiteMetricsServiceLacrosTest, NotifyObserverOnWebsiteMetricsInit) {
+  auto website_metrics = std::make_unique<WebsiteMetrics>(
+      profile_, /*user_type_by_device_type=*/0);
+  auto* const website_metrics_ptr = website_metrics.get();
+  metrics_service_->SetWebsiteMetricsForTesting(std::move(website_metrics));
+
+  EXPECT_CALL(observer_, OnWebsiteMetricsInit(website_metrics_ptr)).Times(1);
+  metrics_service_->Start();
+}
+
+TEST_F(WebsiteMetricsServiceLacrosTest,
+       DoNotNotifyObserversOnWebsiteMetricsInitIfUnregistered) {
+  // Unregister observer.
+  metrics_service_->RemoveObserver(&observer_);
+
+  auto website_metrics = std::make_unique<WebsiteMetrics>(
+      profile_, /*user_type_by_device_type=*/0);
+  metrics_service_->SetWebsiteMetricsForTesting(std::move(website_metrics));
+
+  EXPECT_CALL(observer_, OnWebsiteMetricsInit).Times(0);
+  metrics_service_->Start();
+}
+
+TEST_F(WebsiteMetricsServiceLacrosTest, NotifyObserverOnDestruction) {
+  EXPECT_CALL(observer_, OnWebsiteMetricsServiceLacrosWillBeDestroyed).Times(1);
+  metrics_service_.reset();
+}
+
+}  // namespace
+}  // namespace apps
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app.cc b/chrome/browser/apps/app_service/promise_apps/promise_app.cc
index 7862ab8..981015b 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app.cc
@@ -21,16 +21,12 @@
 PromiseAppIcon::~PromiseAppIcon() = default;
 
 bool PromiseApp::operator==(const PromiseApp& rhs) const {
-  return this->package_id == rhs.package_id && this->name == rhs.name &&
-         this->progress == rhs.progress && this->status == rhs.status &&
-         this->should_show == rhs.should_show;
+  return this->package_id == rhs.package_id && this->progress == rhs.progress &&
+         this->status == rhs.status && this->should_show == rhs.should_show;
 }
 
 PromiseAppPtr PromiseApp::Clone() const {
   auto promise_app = std::make_unique<PromiseApp>(package_id);
-  if (name.has_value()) {
-    promise_app->name = name;
-  }
   if (progress.has_value()) {
     promise_app->progress = progress;
   }
@@ -43,7 +39,6 @@
 
 std::ostream& operator<<(std::ostream& out, const PromiseApp& promise_app) {
   out << "Package_id: " << promise_app.package_id.ToString() << std::endl;
-  out << "- Name: " << promise_app.name.value_or("N/A") << std::endl;
   out << "- Progress: "
       << (promise_app.progress.has_value()
               ? base::NumberToString(promise_app.progress.value())
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app.h b/chrome/browser/apps/app_service/promise_apps/promise_app.h
index 35c3719..3ac724e 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app.h
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app.h
@@ -36,7 +36,6 @@
 
   PackageId package_id;
 
-  absl::optional<std::string> name;
   absl::optional<float> progress;
   PromiseStatus status = PromiseStatus::kUnknown;
 
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_unittest.cc
index 20815ba..035a427f 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_registry_cache_unittest.cc
@@ -169,7 +169,6 @@
 
 TEST_F(PromiseAppRegistryCacheObserverTest, OnPromiseAppUpdate_NewPromiseApp) {
   auto promise_app = std::make_unique<PromiseApp>(kTestPackageId);
-  promise_app->name = "Test";
   promise_app->progress = 0;
   promise_app->status = PromiseStatus::kPending;
   promise_app->should_show = false;
@@ -197,7 +196,6 @@
   // Check that we get the appropriate update when going from pending to
   // installing.
   auto promise_app_installing = std::make_unique<PromiseApp>(kTestPackageId);
-  promise_app_installing->name = "Test";
   promise_app_installing->progress = 0.4;
   promise_app_installing->status = PromiseStatus::kInstalling;
   promise_app_installing->should_show = true;
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_service.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_service.cc
index 1093fd8..3d40fd0 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_service.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_service.cc
@@ -35,6 +35,7 @@
 #include "url/gurl.h"
 
 namespace {
+
 const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
     net::DefineNetworkTrafficAnnotation("promise_app_service_download_icon",
                                         R"(
@@ -104,7 +105,7 @@
 
   const PackageId package_id = delta->package_id;
   bool is_existing_registration =
-      promise_app_registry_cache_->HasPromiseApp(delta->package_id);
+      promise_app_registry_cache_->HasPromiseApp(package_id);
 
   // If the app is in the AppRegistryCache, then it already has an item in the
   // Launcher/ Shelf and we don't need to create a new promise app item to
@@ -234,7 +235,6 @@
     return;
   }
   if (!promise_app_info->GetPackageId().has_value() ||
-      !promise_app_info->GetName().has_value() ||
       promise_app_info->GetIcons().size() == 0) {
     LOG(ERROR) << "Cannot update promise app " << package_id.ToString()
                << " due to incomplete Almanac Promise App API response.";
@@ -258,11 +258,6 @@
     return;
   }
 
-  PromiseAppPtr promise_app =
-      std::make_unique<PromiseApp>(promise_app_info->GetPackageId().value());
-  promise_app->name = promise_app_info->GetName().value();
-  OnPromiseApp(std::move(promise_app));
-
   pending_download_count_[package_id] = promise_app_info->GetIcons().size();
 
   for (auto icon : promise_app_info->GetIcons()) {
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_service_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_service_unittest.cc
index e0d86f9..76156eb 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_service_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_service_unittest.cc
@@ -154,43 +154,14 @@
   int current_num_updates_;
 };
 
-TEST_F(PromiseAppServiceTest, OnPromiseApp_AlmanacResponseUpdatesPromiseApp) {
-  proto::PromiseAppResponse response;
-  response.set_package_id(kTestPackageId.ToString());
-  response.set_name("Name");
-  response.add_icons();
-  response.mutable_icons(0)->set_url("www.image");
-
-  url_loader_factory()->AddResponse(
-      PromiseAppAlmanacConnector::GetServerUrl().spec(),
-      response.SerializeAsString());
-
-  // We expect 2 Promise App Registry Cache updates in this test:
-  // The first update is when we initially register the promise app in the
-  // cache. The second update is when we take the app name provided by the
-  // Almanac API response and save it to the promise app object.
-  ExpectNumUpdates(/*num_updates=*/2);
-
-  // Add promise app to the cache, which will trigger an Almanac API call.
-  service()->OnPromiseApp(std::make_unique<PromiseApp>(kTestPackageId));
-
-  // Wait for all the updates to trigger.
-  WaitForPromiseAppUpdates();
-
-  const PromiseApp* promise_app_result = cache()->GetPromiseApp(kTestPackageId);
-  EXPECT_TRUE(promise_app_result->name.has_value());
-  EXPECT_EQ(promise_app_result->name.value(), "Name");
-}
-
 // Tests that icons can be successfully downloaded from the URLs provided by an
 // Almanac response.
-TEST_F(PromiseAppServiceTest, OnPromiseApp_IconsDownloaded) {
+TEST_F(PromiseAppServiceTest, OnPromiseApp_AlmanacIconsDownloaded) {
   std::string url = "http://image.test/test.png";
   std::string url_other = "http://image.test/second-test.png";
 
   proto::PromiseAppResponse response;
   response.set_package_id(kTestPackageId.ToString());
-  response.set_name("Name");
   response.add_icons();
   response.mutable_icons(0)->set_url(url);
   response.add_icons();
@@ -208,13 +179,11 @@
   // Confirm there aren't any icons for the package yet.
   EXPECT_FALSE(icon_cache()->DoesPackageIdHaveIcons(kTestPackageId));
 
-  // We expect 3 Promise App Registry Cache updates in this test:
+  // We expect 2 Promise App Registry Cache updates in this test:
   // The first update is when we initially register the promise app in the
-  // cache. The second update is when we take the app name provided by the
-  // Almanac API response and save it to the promise app object. The third is
-  // after we finish downloading all the icons for the promise app and mark the
-  // promise app as ready to be shown to the user.
-  ExpectNumUpdates(/*num_updates=*/3);
+  // cache. The second update is after we finish downloading all the icons for
+  // the promise app and mark the promise app as ready to be shown to the user.
+  ExpectNumUpdates(/*num_updates=*/2);
   service()->OnPromiseApp(std::make_unique<PromiseApp>(kTestPackageId));
 
   // Wait for all the updates to trigger.
@@ -242,7 +211,6 @@
 TEST_F(PromiseAppServiceTest, OnPromiseApp_FailedIconDownload) {
   proto::PromiseAppResponse response;
   response.set_package_id(kTestPackageId.ToString());
-  response.set_name("Name");
   response.add_icons();
   response.mutable_icons(0)->set_url("broken-url");
 
@@ -254,11 +222,9 @@
   // Confirm there aren't any icons for the package yet.
   EXPECT_FALSE(icon_cache()->DoesPackageIdHaveIcons(kTestPackageId));
 
-  // We expect 2 Promise App Registry Cache updates in this test:
-  // The first update is when we initially register the promise app in the
-  // cache. The second update is when we take the app name provided by the
-  // Almanac API response and save it to the promise app object.
-  ExpectNumUpdates(/*num_updates=*/2);
+  // We expect a Promise App Registry Cache update when we register the promise
+  // app in the cache.
+  ExpectNumUpdates(/*num_updates=*/1);
   service()->OnPromiseApp(std::make_unique<PromiseApp>(kTestPackageId));
 
   // Wait for all the updates to trigger.
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_update.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_update.cc
index 804a6d4..b94d2f8 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_update.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_update.cc
@@ -52,7 +52,6 @@
     return;
   }
 
-  SET_OPTIONAL_VALUE(name);
   SET_OPTIONAL_VALUE(progress);
   SET_ENUM_VALUE(status, PromiseStatus::kUnknown);
   SET_OPTIONAL_VALUE(should_show);
@@ -70,20 +69,6 @@
   }
 }
 
-absl::optional<std::string> PromiseAppUpdate::Name() const {
-  if (delta_ && delta_->name.has_value()) {
-    return *delta_->name;
-  }
-  if (state_ && state_->name.has_value()) {
-    return *state_->name;
-  }
-  return absl::nullopt;
-}
-
-bool PromiseAppUpdate::NameChanged() const {
-  RETURN_OPTIONAL_VALUE_CHANGED(name);
-}
-
 absl::optional<float> PromiseAppUpdate::Progress() const {
   if (delta_ && delta_->progress.has_value()) {
     return *delta_->progress;
@@ -116,8 +101,6 @@
 
 std::ostream& operator<<(std::ostream& out, const PromiseAppUpdate& update) {
   out << "Package_id: " << update.PackageId().ToString() << std::endl;
-  out << "- Name Changed: " << update.NameChanged() << std::endl;
-  out << "- Name: " << update.Name().value_or("N/A") << std::endl;
   out << "- Progress Changed: " << update.ProgressChanged() << std::endl;
   out << "- Progress: "
       << (update.Progress().has_value()
diff --git a/chrome/browser/apps/app_service/promise_apps/promise_app_update_unittest.cc b/chrome/browser/apps/app_service/promise_apps/promise_app_update_unittest.cc
index 764bc32..8519974 100644
--- a/chrome/browser/apps/app_service/promise_apps/promise_app_update_unittest.cc
+++ b/chrome/browser/apps/app_service/promise_apps/promise_app_update_unittest.cc
@@ -17,7 +17,6 @@
 
 TEST_F(PromiseAppUpdateTest, StateIsNonNull) {
   PromiseApp promise_app = PromiseApp(package_id);
-  promise_app.name = "Name";
   promise_app.progress = 0.1;
   promise_app.status = PromiseStatus::kPending;
   promise_app.should_show = true;
@@ -25,10 +24,6 @@
 
   EXPECT_EQ(package_id, u.PackageId());
 
-  EXPECT_TRUE(u.Name().has_value());
-  EXPECT_EQ(u.Name(), "Name");
-  EXPECT_EQ(u.NameChanged(), false);
-
   EXPECT_TRUE(u.Progress().has_value());
   EXPECT_FLOAT_EQ(u.Progress().value(), 0.1);
   EXPECT_EQ(u.ProgressChanged(), false);
@@ -42,7 +37,6 @@
 
 TEST_F(PromiseAppUpdateTest, DeltaIsNonNull) {
   PromiseApp promise_app = PromiseApp(package_id);
-  promise_app.name = "Name";
   promise_app.progress = 0.1;
   promise_app.status = PromiseStatus::kPending;
   promise_app.should_show = true;
@@ -50,10 +44,6 @@
 
   EXPECT_EQ(package_id, u.PackageId());
 
-  EXPECT_TRUE(u.Name().has_value());
-  EXPECT_EQ(u.Name(), "Name");
-  EXPECT_EQ(u.NameChanged(), true);
-
   EXPECT_TRUE(u.Progress().has_value());
   EXPECT_FLOAT_EQ(u.Progress().value(), 0.1);
   EXPECT_EQ(u.ProgressChanged(), true);
@@ -67,13 +57,11 @@
 
 TEST_F(PromiseAppUpdateTest, StateAndDeltaAreNonNull) {
   PromiseApp promise_app_old = PromiseApp(package_id);
-  promise_app_old.name = "Name";
   promise_app_old.progress = 0.1;
   promise_app_old.status = PromiseStatus::kPending;
   promise_app_old.should_show = false;
 
   PromiseApp promise_app_new = PromiseApp(package_id);
-  promise_app_new.name = "New name";
   promise_app_new.progress = 0.9;
   promise_app_new.status = PromiseStatus::kInstalling;
   promise_app_new.should_show = true;
@@ -82,10 +70,6 @@
 
   EXPECT_EQ(package_id, u.PackageId());
 
-  EXPECT_TRUE(u.Name().has_value());
-  EXPECT_EQ(u.Name(), "New name");
-  EXPECT_EQ(u.NameChanged(), true);
-
   EXPECT_TRUE(u.Progress().has_value());
   EXPECT_FLOAT_EQ(u.Progress().value(), 0.9);
   EXPECT_EQ(u.ProgressChanged(), true);
@@ -103,7 +87,6 @@
   state_1->should_show = false;
 
   auto state_2 = std::make_unique<PromiseApp>(package_id);
-  state_2->name = "Name";
   state_2->progress = 0.9;
   state_2->status = PromiseStatus::kInstalling;
   state_2->should_show = true;
diff --git a/chrome/browser/ash/app_list/app_service/app_service_promise_app_item.cc b/chrome/browser/ash/app_list/app_service/app_service_promise_app_item.cc
index 7d2df3cb..0a84bc1 100644
--- a/chrome/browser/ash/app_list/app_service/app_service_promise_app_item.cc
+++ b/chrome/browser/ash/app_list/app_service/app_service_promise_app_item.cc
@@ -61,18 +61,16 @@
 
 void AppServicePromiseAppItem::OnPromiseAppUpdate(
     const apps::PromiseAppUpdate& update) {
-  if (update.NameChanged() && update.Name().has_value()) {
-    SetName(update.Name().value());
-  }
-  if (update.ProgressChanged() && update.Progress().has_value()) {
-    SetProgress(update.Progress().value());
-  }
   // Each status has its own set of visual effects.
   if (update.StatusChanged()) {
     SetAppStatus(ShelfControllerHelper::ConvertPromiseStatusToAppStatus(
         update.Status()));
+    SetName(ShelfControllerHelper::GetLabelForPromiseStatus(update.Status()));
     LoadIcon();
   }
+  if (update.ProgressChanged() && update.Progress().has_value()) {
+    SetProgress(update.Progress().value());
+  }
 }
 
 void AppServicePromiseAppItem::LoadIcon() {
@@ -95,9 +93,8 @@
 
 void AppServicePromiseAppItem::InitializeItem(
     const apps::PromiseAppUpdate& update) {
-  CHECK(update.Name().has_value());
   CHECK(update.ShouldShow());
-  SetName(update.Name().value());
+  SetName(ShelfControllerHelper::GetLabelForPromiseStatus(update.Status()));
   if (update.Progress().has_value()) {
     SetProgress(update.Progress().value());
   }
diff --git a/chrome/browser/ash/app_list/app_service/app_service_promise_app_item_browsertest.cc b/chrome/browser/ash/app_list/app_service/app_service_promise_app_item_browsertest.cc
index f57378b..78a953c 100644
--- a/chrome/browser/ash/app_list/app_service/app_service_promise_app_item_browsertest.cc
+++ b/chrome/browser/ash/app_list/app_service/app_service_promise_app_item_browsertest.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/ash/app_list/app_list_test_util.h"
 #include "chrome/browser/ash/app_list/chrome_app_list_item.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/ash/shelf/shelf_controller_helper.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/services/app_service/public/cpp/app_types.h"
@@ -110,14 +111,12 @@
   // Update the promise app to allow showing in the Launcher.
   apps::PromiseAppPtr promise_app_update =
       std::make_unique<PromiseApp>(kTestPackageId);
-  promise_app_update->name = "Test";
   promise_app_update->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app_update));
 
   // Promise app item should now exist in the model.
   item = GetAppListItem(kTestPackageId.ToString());
   ASSERT_TRUE(item);
-  ASSERT_EQ(item->name(), "Test");
 
   // Verify that the promise app item is not added to local storage.
   const base::Value::Dict& local_items =
@@ -139,14 +138,14 @@
   // Register a promise app in the promise app registry cache.
   apps::PromiseAppPtr promise_app =
       std::make_unique<PromiseApp>(kTestPackageId);
-  promise_app->name = "Test";
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
 
   // Promise app item should exist in the model.
   ChromeAppListItem* item = GetChromeAppListItem(kTestPackageId);
   ASSERT_TRUE(item);
-  ASSERT_EQ(item->name(), "Test");
+  ASSERT_EQ(item->name(), ShelfControllerHelper::GetLabelForPromiseStatus(
+                              apps::PromiseStatus::kPending));
 
   // Retrieve the context menu.
   base::RunLoop run_loop;
@@ -189,14 +188,12 @@
 
   // Register a promise app in the promise app registry cache.
   apps::PromiseAppPtr promise_app = std::make_unique<PromiseApp>(package_id);
-  promise_app->name = "Test";
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
 
   // Promise app item should exist in the model.
   ash::AppListItem* item = GetAppListItem(package_id.ToString());
   ASSERT_TRUE(item);
-  ASSERT_EQ(item->name(), "Test");
 
   // Register (i.e. "install") an app with a matching package ID. This should
   // trigger removal of the promise app.
@@ -219,7 +216,6 @@
   // Register a promise app in the promise app registry cache.
   apps::PromiseAppPtr promise_app =
       std::make_unique<PromiseApp>(kTestPackageId);
-  promise_app->name = "Test";
   promise_app->status = PromiseStatus::kPending;
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
@@ -227,24 +223,22 @@
   // Promise app item should exist in the model.
   ChromeAppListItem* item = GetChromeAppListItem(kTestPackageId);
   ASSERT_TRUE(item);
-  EXPECT_EQ(item->name(), "Test");
   EXPECT_EQ(item->progress(), 0);
   EXPECT_EQ(item->app_status(), ash::AppStatus::kPending);
+  ASSERT_EQ(item->name(), ShelfControllerHelper::GetLabelForPromiseStatus(
+                              apps::PromiseStatus::kPending));
 
   // Update the promise app in the promise app registry cache.
   apps::PromiseAppPtr update = std::make_unique<PromiseApp>(kTestPackageId);
-  update->name = "Test2";
   update->progress = 0.3;
   update->status = PromiseStatus::kInstalling;
-  update->should_show = true;
   cache()->OnPromiseApp(std::move(update));
 
   // Promise app item should have updated fields.
-  item = GetChromeAppListItem(kTestPackageId);
-  ASSERT_TRUE(item);
-  EXPECT_EQ(item->name(), "Test2");
   EXPECT_EQ(item->progress(), 0.3f);
   EXPECT_EQ(item->app_status(), ash::AppStatus::kInstalling);
+  EXPECT_EQ(item->name(), ShelfControllerHelper::GetLabelForPromiseStatus(
+                              apps::PromiseStatus::kInstalling));
 }
 
 IN_PROC_BROWSER_TEST_F(AppServicePromiseAppItemBrowserTest, SetToSyncPosition) {
@@ -267,7 +261,6 @@
   // Register a promise app in the promise app registry cache.
   apps::PromiseAppPtr promise_app =
       std::make_unique<PromiseApp>(kTestPackageId);
-  promise_app->name = "Test";
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
 
@@ -277,4 +270,31 @@
   EXPECT_EQ(item->position(), ordinal);
 }
 
+IN_PROC_BROWSER_TEST_F(AppServicePromiseAppItemBrowserTest,
+                       LabelMatchesWithStatus) {
+  // Register test promise app.
+  PromiseAppPtr promise_app = std::make_unique<PromiseApp>(kTestPackageId);
+  promise_app->status = PromiseStatus::kPending;
+  promise_app->should_show = true;
+  cache()->OnPromiseApp(std::move(promise_app));
+
+  // Promise app item should now exist in the model.
+  ChromeAppListItem* item = GetChromeAppListItem(kTestPackageId);
+  ASSERT_TRUE(item);
+  ASSERT_EQ(item->app_status(), ash::AppStatus::kPending);
+  ASSERT_EQ(item->name(), ShelfControllerHelper::GetLabelForPromiseStatus(
+                              PromiseStatus::kPending));
+
+  // Push a status update to the promise app.
+  PromiseAppPtr update = std::make_unique<PromiseApp>(kTestPackageId);
+  update->status = PromiseStatus::kInstalling;
+  cache()->OnPromiseApp(std::move(update));
+
+  // Item should now reflect the new status and name.
+  EXPECT_TRUE(item);
+  EXPECT_EQ(item->app_status(), ash::AppStatus::kInstalling);
+  EXPECT_EQ(item->name(), ShelfControllerHelper::GetLabelForPromiseStatus(
+                              PromiseStatus::kInstalling));
+}
+
 }  // namespace apps
diff --git a/chrome/browser/ash/app_list/app_service/app_service_promise_app_model_builder_unittest.cc b/chrome/browser/ash/app_list/app_service/app_service_promise_app_model_builder_unittest.cc
index 932fa0a..9893bbb 100644
--- a/chrome/browser/ash/app_list/app_service/app_service_promise_app_model_builder_unittest.cc
+++ b/chrome/browser/ash/app_list/app_service/app_service_promise_app_model_builder_unittest.cc
@@ -81,13 +81,11 @@
     // Register two promise apps in the promise app registry cache.
     apps::PromiseAppPtr promise_app_1 = std::make_unique<apps::PromiseApp>(
         apps::PackageId(apps::AppType::kArc, "test1"));
-    promise_app_1->name = "Test 1";
     promise_app_1->should_show = true;
     cache()->OnPromiseApp(std::move(promise_app_1));
 
     apps::PromiseAppPtr promise_app_2 = std::make_unique<apps::PromiseApp>(
         apps::PackageId(apps::AppType::kArc, "test2"));
-    promise_app_2->name = "Test 2";
     promise_app_2->should_show = true;
     cache()->OnPromiseApp(std::move(promise_app_2));
   }
@@ -101,9 +99,7 @@
     // Confirm there are 2 launcher promise app items.
     EXPECT_EQ(model_updater()->ItemCount(), 2u);
     EXPECT_EQ(model_updater()->ItemAtForTest(0)->id(), "android:test1");
-    EXPECT_EQ(model_updater()->ItemAtForTest(0)->name(), "Test 1");
     EXPECT_EQ(model_updater()->ItemAtForTest(1)->id(), "android:test2");
-    EXPECT_EQ(model_updater()->ItemAtForTest(1)->name(), "Test 2");
   }
 
   AppListModelUpdater* model_updater() { return model_updater_.get(); }
diff --git a/chrome/browser/ash/bluetooth/bluetooth_integration_test.cc b/chrome/browser/ash/bluetooth/bluetooth_integration_test.cc
index 43cf5785..2ee65bc0 100644
--- a/chrome/browser/ash/bluetooth/bluetooth_integration_test.cc
+++ b/chrome/browser/ash/bluetooth/bluetooth_integration_test.cc
@@ -255,13 +255,6 @@
       WaitForToggleState(kOsSettingsElementId, kBluetoothToggleQuery, true),
 
       Log("Test complete"));
-
-  // Allow exploring the UI if --test-launcher-interactive is passed.
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kTestLauncherInteractive)) {
-    base::RunLoop loop;
-    loop.Run();
-  }
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest.cc b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
index 33e1bf59..d66ff42 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
@@ -1768,20 +1768,32 @@
     FilesAppBrowserTest,
     ::testing::Values(
         TestCase("directoryTreeActiveDirectory"),
+        TestCase("directoryTreeActiveDirectory").FilesExperimental(),
         TestCase("directoryTreeSelectedDirectory"),
-        // TODO(b/189173190): Enable
-        // TestCase("directoryTreeRecentsSubtypeScroll"),
+        TestCase("directoryTreeSelectedDirectory").FilesExperimental(),
         TestCase("directoryTreeHorizontalScroll"),
+        TestCase("directoryTreeHorizontalScroll").FilesExperimental(),
         TestCase("directoryTreeExpandHorizontalScroll"),
+        TestCase("directoryTreeExpandHorizontalScroll").FilesExperimental(),
         TestCase("directoryTreeExpandHorizontalScrollRTL"),
+        TestCase("directoryTreeExpandHorizontalScrollRTL").FilesExperimental(),
         TestCase("directoryTreeVerticalScroll"),
+        TestCase("directoryTreeVerticalScroll").FilesExperimental(),
         TestCase("directoryTreeExpandFolder"),
         TestCase("directoryTreeExpandFolder").FilesExperimental(),
         TestCase(
             "directoryTreeExpandFolderWithHiddenFileAndShowHiddenFilesOff"),
+        TestCase("directoryTreeExpandFolderWithHiddenFileAndShowHiddenFilesOff")
+            .FilesExperimental(),
         TestCase("directoryTreeExpandFolderWithHiddenFileAndShowHiddenFilesOn"),
+        TestCase("directoryTreeExpandFolderWithHiddenFileAndShowHiddenFilesOn")
+            .FilesExperimental(),
         TestCase("directoryTreeExpandFolderOnNonDelayExpansionVolume"),
-        TestCase("directoryTreeExpandFolderOnDelayExpansionVolume")));
+        TestCase("directoryTreeExpandFolderOnNonDelayExpansionVolume")
+            .FilesExperimental(),
+        TestCase("directoryTreeExpandFolderOnDelayExpansionVolume"),
+        TestCase("directoryTreeExpandFolderOnDelayExpansionVolume")
+            .FilesExperimental()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     DirectoryTreeContextMenu, /* directory_tree_context_menu.js */
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn b/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
index 72b6efb9..2503c931 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/BUILD.gn
@@ -26,6 +26,8 @@
     "//url",
   ]
   sources = [
+    "account_transfer_client_data.cc",
+    "account_transfer_client_data.h",
     "connection.cc",
     "connection.h",
     "fast_pair_advertiser.cc",
@@ -90,6 +92,7 @@
     "//url",
   ]
   sources = [
+    "account_transfer_client_data_unittest.cc",
     "connection_unittest.cc",
     "fast_pair_advertiser_unittest.cc",
     "handshake_helpers_unittest.cc",
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.cc
new file mode 100644
index 0000000..bdf011fd
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.cc
@@ -0,0 +1,47 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h"
+
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "url/gurl.h"
+
+namespace ash::quick_start {
+
+AccountTransferClientData::AccountTransferClientData(
+    Base64UrlString challenge_b64url)
+    : challenge_b64url_(challenge_b64url) {}
+
+AccountTransferClientData::~AccountTransferClientData() {}
+
+Base64UrlString AccountTransferClientData::GetChallengeBase64URLString() {
+  return challenge_b64url_;
+}
+
+std::string AccountTransferClientData::CreateJson() {
+  base::Value::Dict fido_collected_client_data;
+  url::Origin origin = url::Origin::Create(GURL(kOrigin));
+  fido_collected_client_data.Set(kClientDataOriginKey, origin.Serialize());
+  fido_collected_client_data.Set(kClientDataTypeKey, kCtapRequestType);
+  fido_collected_client_data.Set(
+      kClientDataChallengeKey,
+      std::string(challenge_b64url_->begin(), challenge_b64url_->end()));
+  fido_collected_client_data.Set(kClientDataCrossOriginKey, false);
+  std::string fido_client_data_json;
+  base::JSONWriter::Write(fido_collected_client_data, &fido_client_data_json);
+  return fido_client_data_json;
+}
+
+std::array<uint8_t, crypto::kSHA256Length>
+AccountTransferClientData::CreateHash() {
+  std::string json = CreateJson();
+  std::array<uint8_t, crypto::kSHA256Length> client_data_hash;
+  crypto::SHA256HashString(json, client_data_hash.data(),
+                           client_data_hash.size());
+
+  return client_data_hash;
+}
+
+}  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h
new file mode 100644
index 0000000..9c32a22
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h
@@ -0,0 +1,43 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_ACCOUNT_TRANSFER_CLIENT_DATA_H_
+#define CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_ACCOUNT_TRANSFER_CLIENT_DATA_H_
+
+#include <string>
+
+#include "chromeos/ash/components/quick_start/types.h"
+#include "crypto/sha2.h"
+#include "url/origin.h"
+
+namespace ash::quick_start {
+
+constexpr char kClientDataTypeKey[] = "type";
+constexpr char kClientDataChallengeKey[] = "challenge";
+constexpr char kClientDataOriginKey[] = "origin";
+constexpr char kClientDataCrossOriginKey[] = "crossOrigin";
+const char kCtapRequestType[] = "webauthn.get";
+
+const char kOrigin[] = "https://accounts.google.com";
+
+// AccountTransferClientData represents the client_data payload sent during the
+// FIDO Assertion flow, and handles encoding/decoding this payload into various
+// formats consumed in the flow (JSON, hash string, etc).
+class AccountTransferClientData {
+ public:
+  explicit AccountTransferClientData(Base64UrlString challenge_b64url);
+  ~AccountTransferClientData();
+
+  std::string CreateJson();
+  std::array<uint8_t, crypto::kSHA256Length> CreateHash();
+
+  Base64UrlString GetChallengeBase64URLString();
+
+ private:
+  Base64UrlString challenge_b64url_;
+};
+
+}  // namespace ash::quick_start
+
+#endif  // CHROME_BROWSER_ASH_LOGIN_OOBE_QUICK_START_CONNECTIVITY_ACCOUNT_TRANSFER_CLIENT_DATA_H_
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data_unittest.cc
new file mode 100644
index 0000000..893258c8
--- /dev/null
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data_unittest.cc
@@ -0,0 +1,63 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h"
+
+#include "base/json/json_reader.h"
+#include "chromeos/ash/components/quick_start/types.h"
+#include "crypto/sha2.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace ash::quick_start {
+
+namespace {
+const char kChallengeBase64Url[] = "my string";
+}  // namespace
+
+class AccountTransferClientDataTest : public testing::Test {
+ public:
+  AccountTransferClientDataTest() = default;
+  AccountTransferClientDataTest(AccountTransferClientDataTest&) = delete;
+  AccountTransferClientDataTest& operator=(AccountTransferClientDataTest&) =
+      delete;
+  ~AccountTransferClientDataTest() override = default;
+
+  Base64UrlString challenge_b64url_ = Base64UrlString(kChallengeBase64Url);
+};
+
+TEST_F(AccountTransferClientDataTest, CreateFidoAccountTransferClientDataJson) {
+  url::Origin origin = url::Origin::Create(GURL(kOrigin));
+
+  AccountTransferClientData data(challenge_b64url_);
+
+  std::string client_data_json = data.CreateJson();
+  absl::optional<base::Value> parsed_json =
+      base::JSONReader::Read(client_data_json);
+  ASSERT_TRUE(parsed_json);
+  ASSERT_TRUE(parsed_json->is_dict());
+  base::Value::Dict& parsed_json_dict = parsed_json.value().GetDict();
+  EXPECT_EQ(*parsed_json_dict.FindString(kClientDataTypeKey), kCtapRequestType);
+  EXPECT_EQ(*parsed_json_dict.FindString(kClientDataChallengeKey),
+            *Base64UrlString(kChallengeBase64Url));
+  EXPECT_EQ(*parsed_json_dict.FindString(kClientDataOriginKey),
+            origin.Serialize());
+  EXPECT_FALSE(parsed_json_dict.FindBool(kClientDataCrossOriginKey).value());
+}
+
+TEST_F(AccountTransferClientDataTest, CreateHash) {
+  AccountTransferClientData data(challenge_b64url_);
+
+  std::string client_data_json = data.CreateJson();
+
+  std::string json = data.CreateJson();
+  std::array<uint8_t, crypto::kSHA256Length> expected_hash;
+  crypto::SHA256HashString(json, expected_hash.data(), expected_hash.size());
+
+  std::array<uint8_t, crypto::kSHA256Length> result = data.CreateHash();
+
+  EXPECT_EQ(expected_hash, result);
+}
+
+}  // namespace ash::quick_start
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.cc
index d4248f12..e655d31 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.cc
@@ -143,6 +143,8 @@
 void Connection::RequestAccountTransferAssertion(
     const Base64UrlString& challenge,
     RequestAccountTransferAssertionCallback callback) {
+  client_data_ = std::make_unique<AccountTransferClientData>(challenge);
+
   auto parse_assertion_response =
       base::BindOnce(&Connection::OnRequestAccountTransferAssertionResponse,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback));
@@ -151,7 +153,7 @@
       base::IgnoreArgs<absl::optional<std::vector<uint8_t>>>(base::BindOnce(
           &Connection::SendMessageAndReadResponse,
           weak_ptr_factory_.GetWeakPtr(),
-          requests::BuildAssertionRequestMessage(challenge),
+          requests::BuildAssertionRequestMessage(client_data_->CreateHash()),
           QuickStartResponseType::kAssertion,
           std::move(parse_assertion_response), kDefaultRoundTripTimeout));
 
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h
index 60489c4b..c97f946 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection.h
@@ -15,6 +15,7 @@
 #include "base/timer/elapsed_timer.h"
 #include "base/timer/timer.h"
 #include "base/values.h"
+#include "chrome/browser/ash/login/oobe_quick_start/connectivity/account_transfer_client_data.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/fido_assertion_info.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/session_context.h"
 #include "chrome/browser/ash/login/oobe_quick_start/connectivity/target_device_connection_broker.h"
@@ -206,6 +207,7 @@
   mojo::SharedRemote<mojom::QuickStartDecoder> decoder_;
   std::unique_ptr<base::ElapsedTimer> message_elapsed_timer_;
   std::unique_ptr<base::ElapsedTimer> handshake_elapsed_timer_;
+  std::unique_ptr<AccountTransferClientData> client_data_;
 
   // Separate WeakPtrFactory for use with |OnResponseReceived()| to allow for
   // canceling the response.
diff --git a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
index 1bb6ed5..0d4ac36 100644
--- a/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/connectivity/connection_unittest.cc
@@ -139,6 +139,10 @@
     return connection_->response_timeout_timer_.IsRunning();
   }
 
+  AccountTransferClientData* GetClientData() {
+    return connection_->client_data_.get();
+  }
+
   void CallParseBootstrapConfigurationsResponse(
       base::OnceClosure callback,
       std::string cryptauth_device_id) {
@@ -388,6 +392,8 @@
       kChallenge_, base::BindOnce(&ConnectionTest::VerifyAssertionInfo,
                                   base::Unretained(this)));
 
+  EXPECT_EQ(GetClientData()->GetChallengeBase64URLString(), kChallenge_);
+
   std::vector<uint8_t> bootstrap_options_data =
       fake_nearby_connection_->GetWrittenData();
   QuickStartMessage::ReadResult read_result =
@@ -456,7 +462,8 @@
   absl::optional<std::vector<uint8_t>> get_assertion_command =
       base::Base64Decode(get_assertion_message_payload);
   EXPECT_TRUE(get_assertion_command);
-  cbor::Value request = requests::GenerateGetAssertionRequest(kChallenge_);
+  cbor::Value request = requests::GenerateGetAssertionRequest(
+      AccountTransferClientData(kChallenge_).CreateHash());
   std::vector<uint8_t> cbor_encoded_request =
       requests::CBOREncodeGetAssertionRequest(std::move(request));
   EXPECT_EQ(*get_assertion_command, cbor_encoded_request);
diff --git a/chrome/browser/ash/network_change_manager_client_browsertest.cc b/chrome/browser/ash/network_change_manager_client_browsertest.cc
index b784d953..6085b872 100644
--- a/chrome/browser/ash/network_change_manager_client_browsertest.cc
+++ b/chrome/browser/ash/network_change_manager_client_browsertest.cc
@@ -122,19 +122,13 @@
         network::mojom::ConnectionType::CONNECTION_ETHERNET);
 
     // Wait for all services to be removed.
-    service_client_ = ShillServiceClient::Get()->GetTestInterface();
-    service_client_->ClearServices();
+    ShillServiceClient::Get()->GetTestInterface()->ClearServices();
     base::RunLoop().RunUntilIdle();
   }
 
   ShillServiceClient::TestInterface* service_client() {
-    return service_client_;
+    return ShillServiceClient::Get()->GetTestInterface();
   }
-
- private:
-  raw_ptr<ShillServiceClient::TestInterface,
-          DanglingUntriaged | ExperimentalAsh>
-      service_client_;
 };
 
 // Tests that network changes from shill are received by both the
diff --git a/chrome/browser/autofill/BUILD.gn b/chrome/browser/autofill/BUILD.gn
index d558a512..a862aa1 100644
--- a/chrome/browser/autofill/BUILD.gn
+++ b/chrome/browser/autofill/BUILD.gn
@@ -30,6 +30,8 @@
     "merchant_promo_code_manager_factory.h",
     "personal_data_manager_factory.cc",
     "personal_data_manager_factory.h",
+    "shopping_service_delegate_impl.cc",
+    "shopping_service_delegate_impl.h",
     "strike_database_factory.cc",
     "strike_database_factory.h",
     "validation_rules_storage_factory.cc",
@@ -61,6 +63,7 @@
     "//chrome/browser/profiles",
     "//chrome/common:constants",
     "//components/autofill/content/browser",
+    "//components/commerce/core:shopping_service",
     "//components/image_fetcher/core",
     "//components/prefs",
     "//components/strings:components_strings_grit",
diff --git a/chrome/browser/autofill/autofill_offer_manager_factory.cc b/chrome/browser/autofill/autofill_offer_manager_factory.cc
index 4e924f4..ebd3b54 100644
--- a/chrome/browser/autofill/autofill_offer_manager_factory.cc
+++ b/chrome/browser/autofill/autofill_offer_manager_factory.cc
@@ -9,9 +9,12 @@
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "components/autofill/core/browser/payments/autofill_offer_manager.h"
 #if !BUILDFLAG(IS_ANDROID)
+#include "chrome/browser/autofill/shopping_service_delegate_impl.h"
 #include "chrome/browser/commerce/coupons/coupon_service.h"
 #include "chrome/browser/commerce/coupons/coupon_service_factory.h"
+#include "chrome/browser/commerce/shopping_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
+#include "components/commerce/core/shopping_service.h"
 #endif
 
 namespace autofill {
@@ -49,15 +52,21 @@
 KeyedService* AutofillOfferManagerFactory::BuildServiceInstanceFor(
     content::BrowserContext* context) const {
 #if !BUILDFLAG(IS_ANDROID)
-  CouponService* service =
+  CouponService* coupon_service =
       CouponServiceFactory::GetForProfile(Profile::FromBrowserContext(context));
+  commerce::ShoppingService* shopping_service =
+      commerce::ShoppingServiceFactory::GetForBrowserContext(context);
+  auto shopping_service_delegate =
+      std::make_unique<ShoppingServiceDelegateImpl>(shopping_service);
   return new AutofillOfferManager(
       PersonalDataManagerFactory::GetForBrowserContext(context),
-      static_cast<CouponServiceDelegate*>(service));
+      static_cast<CouponServiceDelegate*>(coupon_service),
+      std::move(shopping_service_delegate));
 #else
   return new AutofillOfferManager(
       PersonalDataManagerFactory::GetForBrowserContext(context),
-      /*coupon_service_delegate=*/nullptr);
+      /*coupon_service_delegate=*/nullptr,
+      /*shopping_service_delegate=*/nullptr);
 #endif
 }
 
diff --git a/chrome/browser/autofill/shopping_service_delegate_impl.cc b/chrome/browser/autofill/shopping_service_delegate_impl.cc
new file mode 100644
index 0000000..0151dc3
--- /dev/null
+++ b/chrome/browser/autofill/shopping_service_delegate_impl.cc
@@ -0,0 +1,30 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/autofill/shopping_service_delegate_impl.h"
+
+#include "components/commerce/core/shopping_service.h"
+
+namespace autofill {
+ShoppingServiceDelegateImpl::ShoppingServiceDelegateImpl(
+    commerce::ShoppingService* shopping_service)
+    : shopping_service_(shopping_service) {}
+
+ShoppingServiceDelegateImpl::~ShoppingServiceDelegateImpl() = default;
+
+bool ShoppingServiceDelegateImpl::IsDiscountEligibleToShowOnNavigation() {
+  return shopping_service_ &&
+         shopping_service_->IsDiscountEligibleToShowOnNavigation();
+}
+
+void ShoppingServiceDelegateImpl::GetDiscountInfoForUrls(
+    const std::vector<GURL>& urls,
+    commerce::DiscountInfoCallback callback) {
+  if (!shopping_service_) {
+    return;
+  }
+  shopping_service_->GetDiscountInfoForUrls(urls, std::move(callback));
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/autofill/shopping_service_delegate_impl.h b/chrome/browser/autofill/shopping_service_delegate_impl.h
new file mode 100644
index 0000000..1495dfd
--- /dev/null
+++ b/chrome/browser/autofill/shopping_service_delegate_impl.h
@@ -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.
+
+#ifndef CHROME_BROWSER_AUTOFILL_SHOPPING_SERVICE_DELEGATE_IMPL_H_
+#define CHROME_BROWSER_AUTOFILL_SHOPPING_SERVICE_DELEGATE_IMPL_H_
+
+#include "base/memory/raw_ptr.h"
+#include "components/autofill/core/browser/payments/autofill_offer_manager.h"
+#include "components/commerce/core/shopping_service.h"
+namespace autofill {
+
+// This class is used when constructing the AutofillOfferManager. The
+// ShoppingService can't be directly used because of crbug.com/1155712. We
+// should remove this and use the ShoppingService directly once this bug is
+// fixed.
+class ShoppingServiceDelegateImpl : public ShoppingServiceDelegate {
+ public:
+  explicit ShoppingServiceDelegateImpl(
+      commerce::ShoppingService* shopping_service);
+  ~ShoppingServiceDelegateImpl() override;
+
+  bool IsDiscountEligibleToShowOnNavigation() override;
+  void GetDiscountInfoForUrls(const std::vector<GURL>& urls,
+                              commerce::DiscountInfoCallback callback) override;
+
+ private:
+  raw_ptr<commerce::ShoppingService> shopping_service_;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_AUTOFILL_SHOPPING_SERVICE_DELEGATE_IMPL_H_
diff --git a/chrome/browser/commerce/shopping_service_factory.cc b/chrome/browser/commerce/shopping_service_factory.cc
index 892ffe7..b13115c 100644
--- a/chrome/browser/commerce/shopping_service_factory.cc
+++ b/chrome/browser/commerce/shopping_service_factory.cc
@@ -92,27 +92,4 @@
 bool ShoppingServiceFactory::ServiceIsNULLWhileTesting() const {
   return true;
 }
-
-KeyedService* ShoppingServiceFactory::SetTestingFactoryAndUse(
-    content::BrowserContext* context,
-    TestingFactory testing_factory) {
-  KeyedService* mock_shopping_service =
-      ProfileKeyedServiceFactory::SetTestingFactoryAndUse(
-          context, std::move(testing_factory));
-#if !BUILDFLAG(IS_ANDROID)
-  Profile* profile = Profile::FromBrowserContext(context);
-  Browser* browser = chrome::FindBrowserWithProfile(profile);
-  for (int i = 0; i < browser->tab_strip_model()->GetTabCount(); i++) {
-    CommerceTabHelper::FromWebContents(
-        browser->tab_strip_model()->GetWebContentsAt(i))
-        ->SetShoppingServiceForTesting(mock_shopping_service);  // IN-TEST
-  }
-#else
-  // TODO(crbug.com/1356028): Update the ShoppingService in CommerceTabHelper.
-  NOTIMPLEMENTED() << "No implementation for Android yet.";
-#endif
-
-  return mock_shopping_service;
-}
-
 }  // namespace commerce
diff --git a/chrome/browser/commerce/shopping_service_factory.h b/chrome/browser/commerce/shopping_service_factory.h
index 74916f3..f81a9c9 100644
--- a/chrome/browser/commerce/shopping_service_factory.h
+++ b/chrome/browser/commerce/shopping_service_factory.h
@@ -28,12 +28,6 @@
   static ShoppingService* GetForBrowserContextIfExists(
       content::BrowserContext* context);
 
-  // Associates |testing_factory| with |context| and immediately returns the
-  // created KeyedService. Since the factory will be used immediately, it may
-  // not be empty.
-  KeyedService* SetTestingFactoryAndUse(content::BrowserContext* context,
-                                        TestingFactory testing_factory);
-
  private:
   friend class base::NoDestructor<ShoppingServiceFactory>;
 
diff --git a/chrome/browser/companion/core/companion_url_builder.cc b/chrome/browser/companion/core/companion_url_builder.cc
index fbcaf63..6930e3a7 100644
--- a/chrome/browser/companion/core/companion_url_builder.cc
+++ b/chrome/browser/companion/core/companion_url_builder.cc
@@ -116,6 +116,7 @@
   url_params.set_is_vqs_enabled_on_chrome(base::FeatureList::IsEnabled(
       visual_search::features::kVisualSearchSuggestions));
   url_params.set_is_upload_dialog_supported(true);
+  url_params.set_is_hard_refresh_supported(true);
 #endif
 
   companion::proto::PromoState* promo_state = url_params.mutable_promo_state();
diff --git a/chrome/browser/companion/core/companion_url_builder_unittest.cc b/chrome/browser/companion/core/companion_url_builder_unittest.cc
index 8911bf7..7ca9c4b 100644
--- a/chrome/browser/companion/core/companion_url_builder_unittest.cc
+++ b/chrome/browser/companion/core/companion_url_builder_unittest.cc
@@ -186,6 +186,7 @@
   EXPECT_TRUE(proto.is_sign_in_allowed());
   EXPECT_FALSE(proto.has_msbb_enabled());
   EXPECT_TRUE(proto.is_upload_dialog_supported());
+  EXPECT_TRUE(proto.is_hard_refresh_supported());
 }
 
 TEST_F(CompanionUrlBuilderTest, MsbbOn) {
@@ -224,6 +225,7 @@
   EXPECT_TRUE(proto.links_open_in_new_tab());
   EXPECT_FALSE(proto.is_vqs_enabled_on_chrome());
   EXPECT_TRUE(proto.is_upload_dialog_supported());
+  EXPECT_TRUE(proto.is_hard_refresh_supported());
 
   // Verify promo state.
   EXPECT_TRUE(proto.has_promo_state());
diff --git a/chrome/browser/companion/core/mojom/companion.mojom b/chrome/browser/companion/core/mojom/companion.mojom
index 2eab135b..2d22fbbe 100644
--- a/chrome/browser/companion/core/mojom/companion.mojom
+++ b/chrome/browser/companion/core/mojom/companion.mojom
@@ -45,6 +45,9 @@
   // Method corresponding to `CompanionPageHandler.UpdateLoadingState`.
   kCompanionLoadingState = 11,
 
+  // Method corresponding to `CompanionPage.RefreshCompanionPage`.
+  kRefreshCompanionPage = 12,
+
   // Methods used in browser -> renderer communication.
   // Method corresponding to `CompanionPage.UpdateCompanionPage`.
   kUpdateCompanionPage = 31,
@@ -253,6 +256,9 @@
 
   // Called to update the browser with the appropriate companion loading state.
   OnLoadingState(LoadingState state);
+
+  // Does a hard refresh of the companion page with new state.
+  RefreshCompanionPage();
 };
 
 // WebUI page handler for request from Browser side. (C++ -> TypeScript)
diff --git a/chrome/browser/companion/core/proto/companion_url_params.proto b/chrome/browser/companion/core/proto/companion_url_params.proto
index 80f0b8e1..32d2242 100644
--- a/chrome/browser/companion/core/proto/companion_url_params.proto
+++ b/chrome/browser/companion/core/proto/companion_url_params.proto
@@ -24,7 +24,7 @@
 
 // This proto file is shared between Chrome and Server side and used to pack the
 // URL query params for the companion URL request.
-// Next ID: 12
+// Next ID: 13
 message CompanionUrlParams {
   reserved 4, 5;
 
@@ -54,4 +54,7 @@
 
   // Whether the upload dialog file chooser is supported on Chrome.
   optional bool is_upload_dialog_supported = 11;
+
+  // Whether the hard refresh protocol message is supported on Chrome.
+  optional bool is_hard_refresh_supported = 12;
 }
diff --git a/chrome/browser/extensions/api/settings_private/prefs_util.cc b/chrome/browser/extensions/api/settings_private/prefs_util.cc
index cfbd8a1..67da8a3 100644
--- a/chrome/browser/extensions/api/settings_private/prefs_util.cc
+++ b/chrome/browser/extensions/api/settings_private/prefs_util.cc
@@ -211,8 +211,6 @@
   (*s_allowlist)[::prefs::kPolicyThemeColor] =
       settings_api::PrefType::PREF_TYPE_NUMBER;
 #if BUILDFLAG(IS_LINUX)
-  (*s_allowlist)[::prefs::kUsesSystemThemeDeprecated] =
-      settings_api::PrefType::PREF_TYPE_BOOLEAN;
   (*s_allowlist)[::prefs::kSystemTheme] =
       settings_api::PrefType::PREF_TYPE_NUMBER;
 #endif
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index a150f99..5c19a78 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -6438,6 +6438,61 @@
       << errors[0]->message();
 }
 
+// Tests that an extension that doesn't have the `webView` permission cannot
+// manually create and add a WebRequestEvent that specifies a webViewInstanceId.
+// TODO(tjudkins): It would be good to also stop this on the JS layer by not
+// allowing extensions to manually create and add WebRequestEvents.
+// Regression test for crbug.com/1472830
+IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest,
+                       TestWebviewIdSpecifiedOnEvent_NoPermission) {
+  ASSERT_TRUE(StartEmbeddedTestServer());
+
+  static constexpr char kManifest[] =
+      R"({
+           "name": "MV3 WebRequest",
+           "version": "0.1",
+           "manifest_version": 3,
+           "permissions": ["webRequest"],
+           "host_permissions": [ "http://example.com/*" ],
+           "background": {"service_worker": "background.js"}
+         })";
+  // The extension tries to add a listener; this will fail asynchronously
+  // as a part of the webRequestInternal API trying to add the listener.
+  // This results in runtime.lastError being set, but since it's an
+  // internal API, there's no way for the extension to catch the error.
+  static constexpr char kBackgroundJs[] =
+      R"(let event = new chrome.webRequest.onBeforeRequest.constructor(
+             'webRequest.onBeforeRequest',
+             undefined,
+             undefined,
+             undefined,
+             1); // webViewInstanceId
+         event.addListener(() => {},
+         {urls: ['*://*.example.com/*']});)";
+
+  // Since we can't catch the error in the extension's JS, we instead listen to
+  // the error come into the error console.
+  ErrorConsoleTestObserver error_observer(1u, profile());
+  error_observer.EnableErrorCollection();
+
+  // Load the extension and wait for the error to come.
+  TestExtensionDir test_dir;
+  test_dir.WriteManifest(kManifest);
+  test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), kBackgroundJs);
+  const Extension* extension = LoadExtension(test_dir.UnpackedPath());
+
+  ASSERT_TRUE(extension);
+  error_observer.WaitForErrors();
+
+  const ErrorList& errors =
+      ErrorConsole::Get(profile())->GetErrorsForExtension(extension->id());
+  ASSERT_EQ(1u, errors.size());
+  EXPECT_EQ(u"Unchecked runtime.lastError: Missing webview permission.",
+            errors[0]->message());
+  EXPECT_EQ(0u, web_request_router()->GetListenerCountForTesting(
+                    profile(), "webRequest.onBeforeRequest"));
+}
+
 IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest, RecordUkmOnNavigation) {
   ASSERT_TRUE(StartEmbeddedTestServer());
   TestExtensionDir test_dir1;
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index a2bdc9f..ed506b2 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2174,11 +2174,6 @@
     "expiry_milestone": 130
   },
   {
-    "name": "enable-builtin-hls",
-    "owners": ["tmathmeyer", "videostack-eng@google.com"],
-    "expiry_milestone": 120
-  },
-  {
     "name": "enable-button-configuration-usage",
     "owners": [ "joemerramos", "ajuma", "bling-flags@google.com" ],
     "expiry_milestone": 120
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 17fe454..df20ce6 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4551,10 +4551,6 @@
 const char kEnableAndroidGamepadVibrationDescription[] =
     "Enables the ability to play vibration effects on supported gamepads.";
 
-const char kEnableBuiltinHlsName[] = "Builtin HLS player";
-const char kEnableBuiltinHlsDescription[] =
-    "Enables chrome's builtin HLS player instead of Android's MediaPlayer";
-
 const char kCormorantName[] = "Cormorant";
 const char kCormorantDescription[] = "Enables the experimental Cormorant UI.";
 
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 1725eab..fb20a2b 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2287,9 +2287,6 @@
 extern const char kEnableAndroidGamepadVibrationName[];
 extern const char kEnableAndroidGamepadVibrationDescription[];
 
-extern const char kEnableBuiltinHlsName[];
-extern const char kEnableBuiltinHlsDescription[];
-
 extern const char kEnableCommandLineOnNonRootedName[];
 extern const char kEnableCommandLineOnNoRootedDescription[];
 
diff --git a/chrome/browser/mac/install_from_dmg.mm b/chrome/browser/mac/install_from_dmg.mm
index 1427a04..b0b6e2e 100644
--- a/chrome/browser/mac/install_from_dmg.mm
+++ b/chrome/browser/mac/install_from_dmg.mm
@@ -116,7 +116,7 @@
     image_path->clear();
   }
 
-  if (base::mac::IsAtLeastOS12()) {
+  if (base::mac::MacOSMajorVersion() >= 12) {
     // Starting with macOS 12 "Monterey", the IOMedia has an ancestor of
     // type "AppleDiskImageDevice" that has a property "DiskImageURL" of string
     // type.
diff --git a/chrome/browser/metrics/desktop_session_duration/chrome_visibility_observer_interactive_uitest.cc b/chrome/browser/metrics/desktop_session_duration/chrome_visibility_observer_interactive_uitest.cc
index 293a1ec..15e8031 100644
--- a/chrome/browser/metrics/desktop_session_duration/chrome_visibility_observer_interactive_uitest.cc
+++ b/chrome/browser/metrics/desktop_session_duration/chrome_visibility_observer_interactive_uitest.cc
@@ -68,7 +68,7 @@
 IN_PROC_BROWSER_TEST_F(ChromeVisibilityObserverInteractiveTest,
                        VisibilityTest) {
 #if BUILDFLAG(IS_MAC)
-  if (base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() >= 13) {
     GTEST_SKIP() << "Broken on macOS 13: https://crbug.com/1447844";
   }
 #endif
diff --git a/chrome/browser/navigation_predictor/navigation_predictor.cc b/chrome/browser/navigation_predictor/navigation_predictor.cc
index e0a2519..601a5b02 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor.cc
@@ -13,6 +13,7 @@
 #include "base/rand_util.h"
 #include "base/ranges/algorithm.h"
 #include "base/system/sys_info.h"
+#include "base/time/default_tick_clock.h"
 #include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service.h"
 #include "chrome/browser/navigation_predictor/navigation_predictor_keyed_service_factory.h"
 #include "chrome/browser/navigation_predictor/preloading_model_keyed_service.h"
@@ -81,14 +82,43 @@
   return {path_length, path_depth, hash};
 }
 
+base::TimeDelta MLModelExecutionTimerStartDelay() {
+  static int timer_start_delay = base::GetFieldTrialParamByFeatureAsInt(
+      blink::features::kPreloadingHeuristicsMLModel, "timer_start_delay", 0);
+  return base::Milliseconds(timer_start_delay);
+}
+
+base::TimeDelta MLModelExecutionTimerInterval() {
+  static int timer_interval = base::GetFieldTrialParamByFeatureAsInt(
+      blink::features::kPreloadingHeuristicsMLModel, "timer_interval", 100);
+  return base::Milliseconds(timer_interval);
+}
+
+bool IsTargetURLTheSameAsDocument(
+    const blink::mojom::AnchorElementMetricsPtr& anchor) {
+  GURL::Replacements replacements;
+  replacements.ClearRef();
+  GURL document_url = anchor->source_url.ReplaceComponents(replacements);
+  GURL target_url = anchor->target_url.ReplaceComponents(replacements);
+  return target_url == document_url;
+}
+
 }  // namespace
 
+NavigationPredictor::AnchorElementData::AnchorElementData(
+    blink::mojom::AnchorElementMetricsPtr metrics,
+    base::TimeTicks first_report_timestamp)
+    : metrics(std::move(metrics)),
+      first_report_timestamp(first_report_timestamp) {}
+NavigationPredictor::AnchorElementData::~AnchorElementData() = default;
+
 NavigationPredictor::NavigationPredictor(
     content::RenderFrameHost& render_frame_host,
     mojo::PendingReceiver<AnchorElementMetricsHost> receiver)
     : content::DocumentService<blink::mojom::AnchorElementMetricsHost>(
           render_frame_host,
-          std::move(receiver)) {
+          std::move(receiver)),
+      clock_(base::DefaultTickClock::GetInstance()) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
   // When using content::Page::IsPrimary, bfcache can cause returning a false in
   // the back/forward navigation. So, DCHECK only checks if current page is
@@ -96,6 +126,7 @@
   // https://crbug.com/1239310.
   DCHECK(!IsPrerendering(render_frame_host));
 
+  navigation_start_ = NowTicks();
   ukm_recorder_ = ukm::UkmRecorder::Get();
   ukm_source_id_ = render_frame_host.GetMainFrame()->GetPageUkmSourceId();
 }
@@ -201,7 +232,8 @@
       new_predictions.push_back(target_url);
     }
 
-    anchors_.emplace(anchor_id, std::move(element));
+    anchors_.emplace(std::piecewise_construct, std::forward_as_tuple(anchor_id),
+                     std::forward_as_tuple(std::move(element), NowTicks()));
     tracked_anchor_id_to_index_[anchor_id] = tracked_anchor_id_to_index_.size();
   }
 
@@ -231,33 +263,51 @@
   render_frame_host().OnPreloadingHeuristicsModelDone(url, result.value());
 }
 
-// TODO(isaboori): Currently not all of ML model inputs are connected properly
-// and the model is executed for both kPointerOver and kPointerOut events. In
-// the next CL this will be fixed. We should also call the ML model periodically
-// while the mouse pointer is hovering over a link.
 void NavigationPredictor::ProcessPointerEventUsingMLModel(
     blink::mojom::AnchorElementPointerEventForMLModelPtr pointer_event) {
-  // Currently we only process mouse based events.
-  if (!pointer_event->is_mouse) {
-    return;
-  }
   // Find anchor elements data.
   AnchorId anchor_id(pointer_event->anchor_id);
   auto it = anchors_.find(anchor_id);
   if (it == anchors_.end()) {
     return;
   }
-  const blink::mojom::AnchorElementMetricsPtr& anchor = it->second;
 
-  // Ignore anchors pointing to the same document.
-  GURL::Replacements replacements;
-  replacements.ClearRef();
-  GURL document_url = anchor->source_url.ReplaceComponents(replacements);
-  GURL target_url = anchor->target_url.ReplaceComponents(replacements);
-  if (target_url == document_url) {
-    return;
+  AnchorElementData& anchor = it->second;
+  switch (pointer_event->user_interaction_event_type) {
+    case blink::mojom::AnchorElementUserInteractionEventForMLModelType::
+        kPointerOut: {
+      anchor.pointer_over_timestamp.reset();
+      ml_model_candidate_.reset();
+      break;
+    }
+    case blink::mojom::AnchorElementUserInteractionEventForMLModelType::
+        kPointerOver: {
+      // Currently we only process mouse based events.
+      if (!pointer_event->is_mouse) {
+        return;
+      }
+      // Ignore anchors pointing to the same document.
+      if (IsTargetURLTheSameAsDocument(anchor.metrics)) {
+        return;
+      }
+
+      anchor.pointer_over_timestamp = NowTicks();
+      anchor.pointer_hovering_over_count++;
+      ml_model_candidate_ = anchor_id;
+      if (!ml_model_execution_timer_.IsRunning()) {
+        ml_model_execution_timer_.Start(
+            FROM_HERE, MLModelExecutionTimerStartDelay(),
+            base::BindOnce(&NavigationPredictor::OnMLModelExecutionTimerFired,
+                           base::Unretained(this)));
+      }
+      break;
+    }
+    default:
+      break;
   }
+}
 
+void NavigationPredictor::OnMLModelExecutionTimerFired() {
   // Check whether preloading is enabled or not.
   Profile* profile =
       Profile::FromBrowserContext(render_frame_host().GetBrowserContext());
@@ -273,40 +323,71 @@
     return;
   }
 
+  if (!ml_model_candidate_.has_value()) {
+    return;
+  }
+  auto it = anchors_.find(ml_model_candidate_.value());
+  if (it == anchors_.end()) {
+    return;
+  }
+
+  AnchorElementData& anchor = it->second;
+
   PreloadingModelKeyedService::Inputs inputs;
-  inputs.contains_image = anchor->contains_image;
-  inputs.font_size = GetFontSizeFromPx(anchor->font_size_px);
-  inputs.has_text_sibling = anchor->has_text_sibling;
-  inputs.is_bold = IsBoldFont(anchor->font_weight);
-  inputs.is_in_iframe = anchor->is_in_iframe;
-  inputs.is_url_incremented_by_one = anchor->is_url_incremented_by_one;
-  // TODO(isaboori): set the input with correct value.
-  inputs.navigation_start_to_link_logged = base::TimeDelta();
-  auto path_info = GetUrlPathLengthDepthAndHash(anchor->target_url);
+  inputs.contains_image = anchor.metrics->contains_image;
+  inputs.font_size = GetFontSizeFromPx(anchor.metrics->font_size_px);
+  inputs.has_text_sibling = anchor.metrics->has_text_sibling;
+  inputs.is_bold = IsBoldFont(anchor.metrics->font_weight);
+  inputs.is_in_iframe = anchor.metrics->is_in_iframe;
+  inputs.is_url_incremented_by_one = anchor.metrics->is_url_incremented_by_one;
+  inputs.navigation_start_to_link_logged =
+      anchor.first_report_timestamp - navigation_start_;
+  auto path_info = GetUrlPathLengthDepthAndHash(anchor.metrics->target_url);
   inputs.path_length = path_info.path_length;
   inputs.path_depth = path_info.path_depth;
-
   // Convert the ratio area and ratio distance from [0,1] to [0,100].
-  int percent_ratio_area = static_cast<int>(anchor->ratio_area * 100);
   inputs.percent_clickable_area =
-      GetLinearBucketForRatioArea(percent_ratio_area);
+      static_cast<int>(anchor.metrics->ratio_area * 100);
 
-  int percent_ratio_distance_root_top =
-      static_cast<int>(anchor->ratio_distance_root_top * 100);
   inputs.percent_vertical_distance =
-      GetLinearBucketForLinkLocation(percent_ratio_distance_root_top);
+      static_cast<int>(anchor.metrics->ratio_distance_root_top * 100);
 
-  inputs.is_same_origin = anchor->is_same_host;
-  // TODO(isaboori): set the input to correct value.
-  inputs.entered_viewport_to_left_viewport = base::TimeDelta();
-  // TODO(isaboori): set the input to correct value.
-  inputs.hover_dwell_time = base::TimeDelta();
-  // TODO(isaboori): set the input to correct value.
-  inputs.pointer_hovering_over_count = 0;
+  inputs.is_same_origin = anchor.metrics->is_same_host;
+  auto to_timedelta = [this](absl::optional<base::TimeTicks> ts) {
+    return ts.has_value() ? NowTicks() - ts.value() : base::TimeDelta();
+  };
+  inputs.entered_viewport_to_left_viewport =
+      to_timedelta(anchor.entered_viewport_timestamp);
+  inputs.hover_dwell_time = to_timedelta(anchor.pointer_over_timestamp);
+  inputs.pointer_hovering_over_count = anchor.pointer_hovering_over_count;
+  if (model_score_callback_) {
+    std::move(model_score_callback_).Run(inputs);
+  }
   model_service->Score(
       &scoring_model_task_tracker_, inputs,
       base::BindOnce(&NavigationPredictor::OnPreloadingHeuristicsModelDone,
-                     weak_ptr_factory_.GetWeakPtr(), anchor->target_url));
+                     weak_ptr_factory_.GetWeakPtr(),
+                     anchor.metrics->target_url));
+
+  if (!ml_model_execution_timer_.IsRunning()) {
+    ml_model_execution_timer_.Start(
+        FROM_HERE, MLModelExecutionTimerInterval(),
+        base::BindOnce(&NavigationPredictor::OnMLModelExecutionTimerFired,
+                       base::Unretained(this)));
+  }
+}
+
+void NavigationPredictor::SetModelScoreCallbackForTesting(
+    ModelScoreCallbackForTesting callback) {
+  model_score_callback_ = std::move(callback);
+}
+
+void NavigationPredictor::SetTaskRunnerForTesting(
+    scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+    const base::TickClock* clock) {
+  ml_model_execution_timer_.SetTaskRunner(task_runner);
+  clock_ = clock;
+  navigation_start_ = NowTicks();
 }
 
 void NavigationPredictor::ReportAnchorElementClick(
@@ -384,7 +465,7 @@
   auto it = anchors_.find(anchor_id);
   if (it != anchors_.end()) {
     page_link_click.href_unchanged_ =
-        (it->second->target_url == click->target_url);
+        (it->second.metrics->target_url == click->target_url);
   }
   navigation_start_to_click_ = click->navigation_start_to_click;
   // navigation_start_to_click_ is set to click->navigation_start_to_click and
@@ -545,13 +626,9 @@
       // zero width/height, etc.
       continue;
     }
-    const auto& anchor = anchor_it->second;
+    const auto& anchor = anchor_it->second.metrics;
     // Collect the target URL if it is new, without ref (# fragment).
-    GURL::Replacements replacements;
-    replacements.ClearRef();
-    GURL document_url = anchor->source_url.ReplaceComponents(replacements);
-    GURL target_url = anchor->target_url.ReplaceComponents(replacements);
-    if (target_url == document_url) {
+    if (IsTargetURLTheSameAsDocument(anchor)) {
       // Ignore anchors pointing to the same document.
       continue;
     }
diff --git a/chrome/browser/navigation_predictor/navigation_predictor.h b/chrome/browser/navigation_predictor/navigation_predictor.h
index 92c4116..68625af 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor.h
+++ b/chrome/browser/navigation_predictor/navigation_predictor.h
@@ -5,7 +5,6 @@
 #ifndef CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_
 #define CHROME_BROWSER_NAVIGATION_PREDICTOR_NAVIGATION_PREDICTOR_H_
 
-#include <deque>
 #include <set>
 #include <unordered_map>
 #include <vector>
@@ -13,6 +12,8 @@
 #include "base/memory/raw_ptr.h"
 #include "base/sequence_checker.h"
 #include "base/task/cancelable_task_tracker.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/time/tick_clock.h"
 #include "base/time/time.h"
 #include "chrome/browser/navigation_predictor/preloading_model_keyed_service.h"
 #include "chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h"
@@ -39,6 +40,9 @@
 class NavigationPredictor
     : public content::DocumentService<blink::mojom::AnchorElementMetricsHost> {
  public:
+  using ModelScoreCallbackForTesting = base::OnceCallback<void(
+      const PreloadingModelKeyedService::Inputs& inputs)>;
+
   NavigationPredictor(const NavigationPredictor&) = delete;
   NavigationPredictor& operator=(const NavigationPredictor&) = delete;
 
@@ -46,6 +50,12 @@
   static void Create(content::RenderFrameHost* render_frame_host,
                      mojo::PendingReceiver<AnchorElementMetricsHost> receiver);
 
+  void SetModelScoreCallbackForTesting(ModelScoreCallbackForTesting callback);
+
+  void SetTaskRunnerForTesting(
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+      const base::TickClock* clock);
+
  private:
   friend class MockNavigationPredictorForTesting;
   using AnchorId = base::StrongAlias<class AnchorId, uint32_t>;
@@ -78,6 +88,8 @@
       blink::mojom::AnchorElementPointerEventForMLModelPtr pointer_event)
       override;
 
+  void OnMLModelExecutionTimerFired();
+
   // Computes and stores document level metrics, including |number_of_anchors_|
   // etc.
   void ComputeDocumentMetricsOnLoad(
@@ -109,14 +121,29 @@
       GURL url,
       PreloadingModelKeyedService::Result result);
 
+  base::TimeTicks NowTicks() const { return clock_->NowTicks(); }
+
   // A count of clicks to prevent reporting more than 10 clicks to UKM.
   size_t clicked_count_ = 0;
 
   // Stores the anchor element metrics for each anchor ID that we track.
-  std::unordered_map<AnchorId,
-                     blink::mojom::AnchorElementMetricsPtr,
-                     typename AnchorId::Hasher>
+  struct AnchorElementData {
+    AnchorElementData(blink::mojom::AnchorElementMetricsPtr metrics,
+                      base::TimeTicks first_report_timestamp);
+    ~AnchorElementData();
+    blink::mojom::AnchorElementMetricsPtr metrics;
+    // Following fields are used for computing timing inputs of the ML model.
+    base::TimeTicks first_report_timestamp;
+    absl::optional<base::TimeTicks> pointer_over_timestamp;
+    absl::optional<base::TimeTicks> entered_viewport_timestamp;
+    size_t pointer_hovering_over_count = 0u;
+  };
+  std::unordered_map<AnchorId, AnchorElementData, typename AnchorId::Hasher>
       anchors_;
+  // It is the anchor element that the user has recently interacted
+  // with and is a good candidate for the ML model to predict the next user
+  // click.
+  absl::optional<AnchorId> ml_model_candidate_;
 
   // The time between navigation start and the last time user clicked on a link.
   absl::optional<base::TimeDelta> navigation_start_to_click_;
@@ -142,6 +169,12 @@
   // `PreloadingModelKeyedService`.
   base::CancelableTaskTracker scoring_model_task_tracker_;
 
+  raw_ptr<const base::TickClock> clock_;
+
+  base::OneShotTimer ml_model_execution_timer_;
+
+  ModelScoreCallbackForTesting model_score_callback_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<NavigationPredictor> weak_ptr_factory_{this};
diff --git a/chrome/browser/navigation_predictor/navigation_predictor_unittest.cc b/chrome/browser/navigation_predictor/navigation_predictor_unittest.cc
index bf867c1..c7469c0 100644
--- a/chrome/browser/navigation_predictor/navigation_predictor_unittest.cc
+++ b/chrome/browser/navigation_predictor/navigation_predictor_unittest.cc
@@ -15,6 +15,7 @@
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_mock_time_task_runner.h"
 #include "chrome/browser/page_load_metrics/observers/page_anchors_metrics_observer.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
 #include "components/ukm/test_ukm_recorder.h"
@@ -36,7 +37,8 @@
 
 class NavigationPredictorTest : public ChromeRenderViewHostTestHarness {
  public:
-  NavigationPredictorTest() = default;
+  NavigationPredictorTest()
+      : task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>()) {}
   ~NavigationPredictorTest() override = default;
 
   // Helper function to generate mojom metrics.
@@ -95,7 +97,11 @@
         {});
   }
 
+  base::TestMockTimeTaskRunner* task_runner() { return task_runner_.get(); }
+
  private:
+  scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
+
   base::test::ScopedFeatureList scoped_feature_list_;
   mojo::Remote<blink::mojom::AnchorElementMetricsHost> predictor_service_;
 
@@ -1240,15 +1246,10 @@
   mojo::Remote<blink::mojom::AnchorElementMetricsHost> predictor_service;
   auto* predictor_service_host = MockNavigationPredictorForTesting::Create(
       main_rfh(), predictor_service.BindNewPipeAndPassReceiver());
+  predictor_service_host->SetTaskRunnerForTesting(
+      task_runner(), task_runner()->GetMockTickClock());
 
-  base::RunLoop run_loop;
-  predictor_service_host->SetOnPreloadingHeuristicsModelDoneCallback(
-      base::BindLambdaForTesting(
-          [&](PreloadingModelKeyedService::Result result) {
-            EXPECT_FALSE(result.has_value());
-            run_loop.Quit();
-          }));
-
+  task_runner()->AdvanceMockTickClock(base::Milliseconds(150));
   auto anchor_id = ReportNewAnchorElementWithDetails(
       predictor_service.get(),
       /*ratio_area=*/0.1,
@@ -1262,8 +1263,51 @@
       /*is_same_host=*/true,
       /*is_url_incremented_by_one=*/true,
       /*has_text_sibling=*/false,
-      /*font_size_px=*/1,
-      /*font_weight=*/1);
+      /*font_size_px=*/15,
+      /*font_weight=*/700);
+
+  // Make sure the ML model is periodically called while the mouse pointer is
+  // hovering over the link.
+  for (int i = 0; i < 5; i++) {
+    base::RunLoop run_loop;
+    predictor_service_host->SetOnPreloadingHeuristicsModelDoneCallback(
+        base::BindLambdaForTesting(
+            [&](PreloadingModelKeyedService::Result result) {
+              EXPECT_FALSE(result.has_value());
+              run_loop.Quit();
+            }));
+    predictor_service_host->SetModelScoreCallbackForTesting(
+        base::BindLambdaForTesting(
+            [&](const PreloadingModelKeyedService::Inputs& inputs) {
+              EXPECT_FLOAT_EQ(10.0f, inputs.percent_clickable_area);
+              EXPECT_EQ(2, inputs.font_size);
+              EXPECT_TRUE(inputs.is_bold);
+              EXPECT_FALSE(inputs.has_text_sibling);
+              EXPECT_EQ(base::Milliseconds(150),
+                        inputs.navigation_start_to_link_logged);
+              EXPECT_EQ(base::Milliseconds(i * 100), inputs.hover_dwell_time);
+            }));
+    if (i == 0) {
+      ProcessPointerEventUsingMLModel(
+          /*predictor_service=*/predictor_service.get(),
+          /*anchor_id=*/anchor_id,
+          /*is_mouse=*/true,
+          /*user_interaction_event_type=*/
+          blink::mojom::AnchorElementUserInteractionEventForMLModelType::
+              kPointerOver);
+    }
+    task_runner()->RunUntilIdle();
+    run_loop.Run();
+    task_runner()->AdvanceMockTickClock(base::Milliseconds(100));
+  }
+
+  // Make sure the model is not called after the mouse pointer out event.
+  bool did_ml_score_called = false;
+  predictor_service_host->SetModelScoreCallbackForTesting(
+      base::BindLambdaForTesting(
+          [&](const PreloadingModelKeyedService::Inputs& inputs) {
+            did_ml_score_called = true;
+          }));
 
   ProcessPointerEventUsingMLModel(
       /*predictor_service=*/predictor_service.get(),
@@ -1271,7 +1315,8 @@
       /*is_mouse=*/true,
       /*user_interaction_event_type=*/
       blink::mojom::AnchorElementUserInteractionEventForMLModelType::
-          kPointerOver);
-
-  run_loop.Run();
+          kPointerOut);
+  task_runner()->AdvanceMockTickClock(base::Milliseconds(200));
+  task_runner()->RunUntilIdle();
+  EXPECT_FALSE(did_ml_score_called);
 }
diff --git a/chrome/browser/obsolete_system/obsolete_system_mac.cc b/chrome/browser/obsolete_system/obsolete_system_mac.cc
index cccb655..eef1e11 100644
--- a/chrome/browser/obsolete_system/obsolete_system_mac.cc
+++ b/chrome/browser/obsolete_system/obsolete_system_mac.cc
@@ -20,10 +20,10 @@
 
 Obsoleteness OsObsoleteness() {
 #if CHROME_VERSION_MAJOR >= 114
-  // Use base::SysInfo::OperatingSystemVersionNumbers() here rather than the
-  // preferred base::mac::IsOS*() function because the IsOS functions for
-  // obsolete system versions are removed to help prevent obsolete code from
-  // existing in the Chromium codebase.
+  // Use base::SysInfo::OperatingSystemVersionNumbers() here because
+  // base::mac::MacOSVersion() didn't exist at the time that this code was
+  // written. The next time this is reused for the next obsolete OS, use
+  // base::mac::MacOSMajorVersion() instead.
   int32_t major, minor, bugfix;
   base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
 
diff --git a/chrome/browser/performance_manager/policies/background_tab_loading_policy_unittest.cc b/chrome/browser/performance_manager/policies/background_tab_loading_policy_unittest.cc
index 1bf92c32..0f4fdf73 100644
--- a/chrome/browser/performance_manager/policies/background_tab_loading_policy_unittest.cc
+++ b/chrome/browser/performance_manager/policies/background_tab_loading_policy_unittest.cc
@@ -344,7 +344,8 @@
   PageNode* raw_page_node;
 
   page_node = CreateNode<performance_manager::PageNodeImpl>(
-      WebContentsProxy(), std::string(), GURL(), false, false,
+      WebContentsProxy(), std::string(), GURL(),
+      performance_manager::PagePropertyFlags{},
       base::TimeTicks::Now() -
           (base::Seconds(1) + policy()->kMaxTimeSinceLastUseToLoad));
   raw_page_node = page_node.get();
@@ -367,7 +368,8 @@
   // Tab without notification permission.
   auto page_node_without_notification_permission =
       CreateNode<performance_manager::PageNodeImpl>(
-          WebContentsProxy(), std::string(), GURL(), false, false,
+          WebContentsProxy(), std::string(), GURL(),
+          performance_manager::PagePropertyFlags{},
           base::TimeTicks::Now() - base::Days(1));
   policy()->SetSiteDataReaderForPageNode(
       page_node_without_notification_permission.get(),
@@ -380,7 +382,8 @@
   // Tab with notification permission.
   auto page_node_with_notification_permission =
       CreateNode<performance_manager::PageNodeImpl>(
-          WebContentsProxy(), std::string(), GURL(), false, false,
+          WebContentsProxy(), std::string(), GURL(),
+          performance_manager::PagePropertyFlags{},
           base::TimeTicks::Now() - base::Days(1));
   policy()->SetSiteDataReaderForPageNode(
       page_node_with_notification_permission.get(), &site_data_reader_default);
@@ -427,7 +430,8 @@
 
   // Old
   page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>(
-      WebContentsProxy(), std::string(), GURL(), false, false,
+      WebContentsProxy(), std::string(), GURL(),
+      performance_manager::PagePropertyFlags{},
       base::TimeTicks::Now() - base::Days(30)));
   policy()->SetSiteDataReaderForPageNode(page_nodes.back().get(),
                                          &site_data_reader_default);
@@ -437,7 +441,8 @@
 
   // Recent
   page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>(
-      WebContentsProxy(), std::string(), GURL(), false, false,
+      WebContentsProxy(), std::string(), GURL(),
+      performance_manager::PagePropertyFlags{},
       base::TimeTicks::Now() - base::Seconds(1)));
   policy()->SetSiteDataReaderForPageNode(page_nodes.back().get(),
                                          &site_data_reader_default);
@@ -448,7 +453,8 @@
   // Slightly older tabs which were observed updating their title or favicon or
   // playing audio in the background
   page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>(
-      WebContentsProxy(), std::string(), GURL(), false, false,
+      WebContentsProxy(), std::string(), GURL(),
+      performance_manager::PagePropertyFlags{},
       base::TimeTicks::Now() - base::Seconds(2)));
   policy()->SetSiteDataReaderForPageNode(page_nodes.back().get(),
                                          &site_data_reader_title);
@@ -457,7 +463,8 @@
   to_load.push_back(title);
 
   page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>(
-      WebContentsProxy(), std::string(), GURL(), false, false,
+      WebContentsProxy(), std::string(), GURL(),
+      performance_manager::PagePropertyFlags{},
       base::TimeTicks::Now() - base::Seconds(3)));
   policy()->SetSiteDataReaderForPageNode(page_nodes.back().get(),
                                          &site_data_reader_favicon);
@@ -466,7 +473,8 @@
   to_load.push_back(favicon);
 
   page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>(
-      WebContentsProxy(), std::string(), GURL(), false, false,
+      WebContentsProxy(), std::string(), GURL(),
+      performance_manager::PagePropertyFlags{},
       base::TimeTicks::Now() - base::Seconds(4)));
   policy()->SetSiteDataReaderForPageNode(page_nodes.back().get(),
                                          &site_data_reader_audio);
@@ -476,7 +484,8 @@
 
   //  Internal page
   page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>(
-      WebContentsProxy(), std::string(), GURL("chrome://newtab"), false, false,
+      WebContentsProxy(), std::string(), GURL("chrome://newtab"),
+      performance_manager::PagePropertyFlags{},
       base::TimeTicks::Now() - base::Seconds(1)));
   policy()->SetSiteDataReaderForPageNode(page_nodes.back().get(),
                                          &site_data_reader_default);
@@ -486,7 +495,8 @@
 
   //  Page with notification permission
   page_nodes.push_back(CreateNode<performance_manager::PageNodeImpl>(
-      WebContentsProxy(), std::string(), GURL("chrome://newtab"), false, false,
+      WebContentsProxy(), std::string(), GURL("chrome://newtab"),
+      performance_manager::PagePropertyFlags{},
       base::TimeTicks::Now() - base::Seconds(1)));
   policy()->SetSiteDataReaderForPageNode(page_nodes.back().get(),
                                          &site_data_reader_default);
diff --git a/chrome/browser/performance_manager/public/user_tuning/battery_saver_mode_manager.h b/chrome/browser/performance_manager/public/user_tuning/battery_saver_mode_manager.h
index 5c9db11..7af8e11 100644
--- a/chrome/browser/performance_manager/public/user_tuning/battery_saver_mode_manager.h
+++ b/chrome/browser/performance_manager/public/user_tuning/battery_saver_mode_manager.h
@@ -36,6 +36,9 @@
   // would be `20` for 20%.
   static const uint64_t kLowBatteryThresholdPercent;
 
+  // Command line switch for overriding the device has battery flag.
+  static const char kForceDeviceHasBatterySwitch[];
+
   class FrameThrottlingDelegate {
    public:
     virtual void StartThrottlingAllFrameSinks() = 0;
@@ -151,9 +154,6 @@
 
   PrefChangeRegistrar pref_change_registrar_;
   base::ObserverList<Observer> observers_;
-
-  // Command line switch for overriding the device has battery flag.
-  static const char kForceDeviceHasBatterySwitch[];
 };
 
 }  // namespace performance_manager::user_tuning
diff --git a/chrome/browser/performance_manager/public/user_tuning/user_tuning_utils.h b/chrome/browser/performance_manager/public/user_tuning/user_tuning_utils.h
index 55f407cc..8411ebf 100644
--- a/chrome/browser/performance_manager/public/user_tuning/user_tuning_utils.h
+++ b/chrome/browser/performance_manager/public/user_tuning/user_tuning_utils.h
@@ -14,6 +14,9 @@
 // mode is currently active.
 bool IsRefreshRateThrottled();
 
+// Returns whether battery saver mode should be managed by the OS
+bool IsBatterySaverModeManagedByOS();
+
 // Helper for logic to get the memory footprint estimate for a discarded page.
 // This must be called from the |PerformanceManager| sequence.
 uint64_t GetDiscardedMemoryEstimateForPage(
diff --git a/chrome/browser/performance_manager/user_tuning/battery_saver_mode_manager.cc b/chrome/browser/performance_manager/user_tuning/battery_saver_mode_manager.cc
index bae0297..9462820e 100644
--- a/chrome/browser/performance_manager/user_tuning/battery_saver_mode_manager.cc
+++ b/chrome/browser/performance_manager/user_tuning/battery_saver_mode_manager.cc
@@ -310,6 +310,13 @@
     client->GetBatterySaverModeState(base::BindOnce(
         &ChromeOSBatterySaverProvider::OnInitialBatterySaverModeObtained,
         weak_ptr_factory_.GetWeakPtr()));
+
+    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+    if (command_line->HasSwitch(
+            BatterySaverModeManager::kForceDeviceHasBatterySwitch)) {
+      force_has_battery_ = true;
+      has_battery_ = true;
+    }
   }
 
   ~ChromeOSBatterySaverProvider() override = default;
@@ -333,8 +340,16 @@
     manager_->NotifyOnBatterySaverModeChanged(enabled_);
   }
 
+  void PowerChanged(
+      const power_manager::PowerSupplyProperties& proto) override {
+    bool device_has_battery =
+        proto.battery_state() !=
+        power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT;
+    has_battery_ = force_has_battery_ || device_has_battery;
+  }
+
   // BatterySaverProvider:
-  bool DeviceHasBattery() const override { return false; }
+  bool DeviceHasBattery() const override { return has_battery_; }
   bool IsBatterySaverActive() const override { return enabled_; }
   bool IsUsingBatteryPower() const override { return false; }
   base::Time GetLastBatteryUsageTimestamp() const override {
@@ -349,6 +364,8 @@
 
  private:
   bool enabled_ = false;
+  bool has_battery_ = false;
+  bool force_has_battery_ = false;
 
   base::ScopedObservation<chromeos::PowerManagerClient,
                           chromeos::PowerManagerClient::Observer>
diff --git a/chrome/browser/performance_manager/user_tuning/user_tuning_utils.cc b/chrome/browser/performance_manager/user_tuning/user_tuning_utils.cc
index 32db081..35bde4fe 100644
--- a/chrome/browser/performance_manager/user_tuning/user_tuning_utils.cc
+++ b/chrome/browser/performance_manager/user_tuning/user_tuning_utils.cc
@@ -15,6 +15,14 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/constants/ash_features.h"
+#endif
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#include "chromeos/startup/browser_params_proxy.h"
+#endif
+
 namespace performance_manager::user_tuning {
 
 bool IsRefreshRateThrottled() {
@@ -30,6 +38,16 @@
 #endif
 }
 
+bool IsBatterySaverModeManagedByOS() {
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  return ash::features::IsBatterySaverAvailable();
+#elif BUILDFLAG(IS_CHROMEOS_LACROS)
+  return chromeos::BrowserParamsProxy::Get()->IsCrosBatterySaverAvailable();
+#else
+  return false;
+#endif
+}
+
 uint64_t GetDiscardedMemoryEstimateForPage(const PageNode* node) {
   DCHECK_ON_GRAPH_SEQUENCE(node->GetGraph());
 
diff --git a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.cc b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.cc
index ffccb1a..88f8ffe 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.cc
@@ -25,6 +25,7 @@
 constexpr char kEncryptedRecordListKey[] = "encryptedRecord";
 constexpr char kAttachEncryptionSettingsKey[] = "attachEncryptionSettings";
 constexpr char kAttachConfigurationFile[] = "attachConfigurationFile";
+constexpr char kClientAutomatedTestPath[] = "clientAutomatedTest";
 
 // EncryptedRecordDictionaryBuilder strings
 constexpr char kEncryptedWrappedRecord[] = "encryptedWrappedRecord";
@@ -49,6 +50,12 @@
              "ShouldRequestConfigurationFile",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+// Feature used in the tast tests to let the server know that the events are
+// coming from an automated client test. Only used in tast tests.
+BASE_FEATURE(kClientAutomatedTest,
+             "ClientAutomatedTest",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 UploadEncryptedReportingRequestBuilder::UploadEncryptedReportingRequestBuilder(
     bool attach_encryption_settings) {
   result_.emplace();
@@ -62,6 +69,11 @@
   if (base::FeatureList::IsEnabled(kShouldRequestConfigurationFile)) {
     result_->Set(GetAttachConfigurationFilePath(), true);
   }
+
+  // This feature signals the server that this is an automated client test.
+  if (base::FeatureList::IsEnabled(kClientAutomatedTest)) {
+    result_->Set(GetClientAutomatedTestPath(), true);
+  }
 }
 
 UploadEncryptedReportingRequestBuilder::
@@ -143,6 +155,12 @@
   return kAttachConfigurationFile;
 }
 
+// static
+std::string_view
+UploadEncryptedReportingRequestBuilder::GetClientAutomatedTestPath() {
+  return kClientAutomatedTestPath;
+}
+
 EncryptedRecordDictionaryBuilder::EncryptedRecordDictionaryBuilder(
     EncryptedRecord record,
     ScopedReservation& scoped_reservation) {
diff --git a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h
index 10358e9..dd66bbf 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h
+++ b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.h
@@ -67,6 +67,11 @@
 //   // optional field, corresponding to the configuration file that the
 //   // server provides to the client.
 //   "attachConfigurationFile": true
+//   // optional field, only used by the client tast tests to signal to the
+//   // server that this is an automated test from the lab. In production, this
+//   // should always be absent. Even if it is erroneously present in production
+//   // code, server ignores it.
+//   "clientAutomatedTest": true
 // }
 //
 // This payload is added to the common payload of all reporting jobs, which
@@ -87,6 +92,7 @@
 // in record.proto.
 
 BASE_DECLARE_FEATURE(kShouldRequestConfigurationFile);
+BASE_DECLARE_FEATURE(kClientAutomatedTest);
 
 class UploadEncryptedReportingRequestBuilder {
  public:
@@ -120,6 +126,7 @@
   static std::string_view GetEncryptedRecordListPath();
   static std::string_view GetAttachEncryptionSettingsPath();
   static std::string_view GetAttachConfigurationFilePath();
+  static std::string_view GetClientAutomatedTestPath();
 
   absl::optional<base::Value::Dict> result_;
 };
diff --git a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc
index f7f7877..c0f1e65 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder_unittest.cc
@@ -353,6 +353,24 @@
               IsConfigurationFileRequestUploadRequestValid(false));
 }
 
+TEST_P(RecordUploadRequestBuilderTest, ClientAutomatedTestExperimentEnabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(kClientAutomatedTest);
+  UploadEncryptedReportingRequestBuilder builder(need_encryption_key());
+
+  EXPECT_THAT(builder.Build().value(),
+              IsClientAutomatedTestRequestUploadRequestValid(true));
+}
+
+TEST_P(RecordUploadRequestBuilderTest, ClientAutomatedTestExperimentDisabled) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(kClientAutomatedTest);
+  UploadEncryptedReportingRequestBuilder builder(need_encryption_key());
+
+  EXPECT_THAT(builder.Build().value(),
+              IsClientAutomatedTestRequestUploadRequestValid(false));
+}
+
 INSTANTIATE_TEST_SUITE_P(NeedOrNoNeedKey,
                          RecordUploadRequestBuilderTest,
                          testing::Bool());
diff --git a/chrome/browser/policy/messaging_layer/util/test_request_payload.cc b/chrome/browser/policy/messaging_layer/util/test_request_payload.cc
index 3f38e8c..ca63439 100644
--- a/chrome/browser/policy/messaging_layer/util/test_request_payload.cc
+++ b/chrome/browser/policy/messaging_layer/util/test_request_payload.cc
@@ -145,6 +145,56 @@
   return "no-attach-configuration-file-matcher";
 }
 
+bool ClientAutomatedTestMatcher::MatchAndExplain(
+    const base::Value::Dict& arg,
+    MatchResultListener* listener) const {
+  const auto client_automated_test = arg.FindBool("clientAutomatedTest");
+  if (!client_automated_test) {
+    *listener << "No key named \"clientAutomatedTest\" in the argument or "
+                 "the value is not of bool type.";
+    return false;
+  }
+  if (!client_automated_test.value()) {
+    *listener << "The value of \"clientAutomatedTest\" is false.";
+    return false;
+  }
+  return true;
+}
+
+void ClientAutomatedTestMatcher::DescribeTo(std::ostream* os) const {
+  *os << "has a valid clientAutomatedTest field.";
+}
+
+void ClientAutomatedTestMatcher::DescribeNegationTo(std::ostream* os) const {
+  *os << "has an invalid clientAutomatedTest field.";
+}
+
+std::string ClientAutomatedTestMatcher::Name() const {
+  return "client-automated-test-matcher";
+}
+
+bool NoClientAutomatedTestMatcher::MatchAndExplain(
+    const base::Value::Dict& arg,
+    MatchResultListener* listener) const {
+  if (arg.Find("clientAutomatedTest") != nullptr) {
+    *listener << "Found \"clientAutomatedTest\" in the argument.";
+    return false;
+  }
+  return true;
+}
+
+void NoClientAutomatedTestMatcher::DescribeTo(std::ostream* os) const {
+  *os << "expectedly has no clientAutomatedTest field.";
+}
+
+void NoClientAutomatedTestMatcher::DescribeNegationTo(std::ostream* os) const {
+  *os << "unexpectedly has an clientAutomatedTest field.";
+}
+
+std::string NoClientAutomatedTestMatcher::Name() const {
+  return "no-client-automated-test-matcher";
+}
+
 void CompressionInformationMatcher::DescribeTo(std::ostream* os) const {
   *os << "has a valid compression information field.";
 }
diff --git a/chrome/browser/policy/messaging_layer/util/test_request_payload.h b/chrome/browser/policy/messaging_layer/util/test_request_payload.h
index 8b473645..c82d4e4f3 100644
--- a/chrome/browser/policy/messaging_layer/util/test_request_payload.h
+++ b/chrome/browser/policy/messaging_layer/util/test_request_payload.h
@@ -82,6 +82,26 @@
   std::string Name() const override;
 };
 
+// clientAutomatedTest must be of bool type and true.
+class ClientAutomatedTestMatcher : public RequestValidityMatcherInterface {
+ public:
+  bool MatchAndExplain(const base::Value::Dict& arg,
+                       MatchResultListener* listener) const override;
+  void DescribeTo(std::ostream* os) const override;
+  void DescribeNegationTo(std::ostream* os) const override;
+  std::string Name() const override;
+};
+
+// clientAutomatedTest must be absent.
+class NoClientAutomatedTestMatcher : public RequestValidityMatcherInterface {
+ public:
+  bool MatchAndExplain(const base::Value::Dict& arg,
+                       MatchResultListener* listener) const override;
+  void DescribeTo(std::ostream* os) const override;
+  void DescribeNegationTo(std::ostream* os) const override;
+  std::string Name() const override;
+};
+
 // encryptedRecord must be a list. This matcher is recommended to be applies
 // before verifying the details of any record (e.g., via |RecordMatcher|) to
 // generate more readable error messages.
@@ -263,6 +283,22 @@
   }
 
   // Creates and returns a |RequestValidityMatcherBuilder| instance that
+  // contains a matcher that is suited for verifying a client automated test
+  // request. If client_automated_test is false the matcher will ensure the
+  // request does not include the field clientAutomatedTest.
+  static RequestValidityMatcherBuilder<T>
+  CreateClientAutomatedTestRequestUpload(bool client_automated_test) {
+    auto builder = RequestValidityMatcherBuilder<T>::CreateEmpty();
+    builder.AppendMatcher(RequestIdMatcher());
+    if (client_automated_test) {
+      builder.AppendMatcher(ClientAutomatedTestMatcher());
+    } else {
+      builder.AppendMatcher(NoClientAutomatedTestMatcher());
+    }
+    return builder;
+  }
+
+  // Creates and returns a |RequestValidityMatcherBuilder| instance that
   // contains a matcher that is suited for verifying a single record.
   static RequestValidityMatcherBuilder<T> CreateRecord() {
     return std::move(RequestValidityMatcherBuilder<T>::CreateEmpty()
@@ -385,6 +421,17 @@
       .Build();
 }
 
+// Match a configuration file request upload request that is valid. If
+// client_automated_test is false, this matcher will ensure the request does not
+// request a configuration file.
+template <class T = base::Value::Dict>
+Matcher<T> IsClientAutomatedTestRequestUploadRequestValid(
+    bool client_automated_test = false) {
+  return RequestValidityMatcherBuilder<
+             T>::CreateClientAutomatedTestRequestUpload(client_automated_test)
+      .Build();
+}
+
 // Match a gap upload request that is valid.
 template <class T = base::Value::Dict>
 Matcher<T> IsGapUploadRequestValid() {
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 3ff9d8c..8577952 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -1042,13 +1042,6 @@
   registry->RegisterBooleanPref(kTokenServiceDiceCompatible, false);
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 
-#if BUILDFLAG(IS_LINUX)
-  // Deprecated 08/2022.
-  // TODO(crbug.com/1476487): The pref `kUsesSystemThemeDeprecated` is still in
-  // use. Please remove once code is cleaned up.
-  registry->RegisterBooleanPref(prefs::kUsesSystemThemeDeprecated, false);
-#endif
-
   // Deprecated 09/2022
   registry->RegisterBooleanPref(kPrivacySandboxFirstPartySetsDataAccessAllowed,
                                 true);
@@ -2138,20 +2131,6 @@
   // TODO(crbug.com/1476489): Remove when unit test code is updated.
   profile_prefs->ClearPref(kTokenServiceDiceCompatible);
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
-#if BUILDFLAG(IS_LINUX)
-  // Added 08/2022.
-  // TODO(crbug.com/1476487): The pref `kUsesSystemThemeDeprecated` is still in
-  // use. Please remove once code is cleaned up.
-  if (profile_prefs->HasPrefPath(prefs::kUsesSystemThemeDeprecated)) {
-    auto migrated_theme =
-        profile_prefs->GetBoolean(prefs::kUsesSystemThemeDeprecated)
-            ? ui::SystemTheme::kGtk
-            : ui::SystemTheme::kDefault;
-    profile_prefs->SetInteger(prefs::kSystemTheme,
-                              static_cast<int>(migrated_theme));
-  }
-  profile_prefs->ClearPref(prefs::kUsesSystemThemeDeprecated);
-#endif
 
   // Added 09/2022.
   profile_prefs->ClearPref(kPrivacySandboxFirstPartySetsDataAccessAllowed);
diff --git a/chrome/browser/preloading/prerender/prerender_browsertest.cc b/chrome/browser/preloading/prerender/prerender_browsertest.cc
index 5c9f2b6e..130f7c4 100644
--- a/chrome/browser/preloading/prerender/prerender_browsertest.cc
+++ b/chrome/browser/preloading/prerender/prerender_browsertest.cc
@@ -590,7 +590,7 @@
   // Simulate a browser-initiated navigation.
   GetActiveWebContents()->OpenURL(content::OpenURLParams(
       prerender_url, content::Referrer(), WindowOpenDisposition::CURRENT_TAB,
-      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK),
+      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_BOOKMARK),
       /*is_renderer_initiated=*/false));
   activation_manager.WaitForNavigationFinished();
   EXPECT_TRUE(activation_manager.was_activated());
diff --git a/chrome/browser/preloading/prerender/prerender_manager.cc b/chrome/browser/preloading/prerender/prerender_manager.cc
index 3b62b79..9e8658f 100644
--- a/chrome/browser/preloading/prerender/prerender_manager.cc
+++ b/chrome/browser/preloading/prerender/prerender_manager.cc
@@ -406,7 +406,7 @@
   new_tab_page_prerender_handle_ = web_contents()->StartPrerendering(
       prerendering_url, content::PrerenderTriggerType::kEmbedder,
       prerender_utils::kNewTabPageMetricSuffix,
-      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK),
+      ui::PageTransitionFromInt(ui::PAGE_TRANSITION_AUTO_BOOKMARK),
       content::PreloadingHoldbackStatus::kUnspecified, preloading_attempt);
 
   return new_tab_page_prerender_handle_
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_button_row.ts b/chrome/browser/resources/ash/settings/device_page/customize_button_row.ts
index 42613157..69446ab 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_button_row.ts
+++ b/chrome/browser/resources/ash/settings/device_page/customize_button_row.ts
@@ -2,12 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/**
- * @fileoverview
- * 'keyboard-remap-key-row' contains a key with icon label and dropdown menu to
- * allow users to customize the remapped key.
- */
-
 import 'chrome://resources/cr_components/settings_prefs/prefs.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/cr_elements/md_select.css.js';
@@ -72,6 +66,15 @@
   return combinationLabel;
 }
 
+
+/**
+ * @fileoverview
+ * 'keyboard-remap-key-row' contains a key with icon label and dropdown menu to
+ * allow users to customize the remapped key.
+ */
+
+export type ShowRenamingDialogEvent = CustomEvent<{buttonIndex: number}>;
+
 const CustomizeButtonRowElementBase = I18nMixin(PolymerElement);
 
 export class CustomizeButtonRowElement extends CustomizeButtonRowElementBase {
@@ -249,7 +252,11 @@
    * Pops out the dialog to edit button label.
    */
   private onEditButtonLabelClicked_(): void {
-    // TODO(yyhyyh@): Implement edit icon clicked function.
+    this.dispatchEvent(new CustomEvent('show-renaming-dialog', {
+      bubbles: true,
+      composed: true,
+      detail: {buttonIndex: this.remappingIndex},
+    }));
   }
 }
 
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.html b/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.html
index 016a85b..c6b5784d4 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.html
+++ b/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.html
@@ -9,6 +9,14 @@
     color: var(--cros-text-color-secondary);
     margin-inline-start: 20px;
   }
+
+  #inputContainer {
+    height: 70px;
+  }
+
+  #renamingDialog {
+    --cr-dialog-width: 320px;
+  }
 </style>
 <template is="dom-repeat"
     items="[[buttonRemappingList]]"
@@ -18,4 +26,33 @@
       remapping-index="[[index]]"
       action-list$="[[actionList]]">
   </customize-button-row>
-</template>
\ No newline at end of file
+</template>
+<template is="dom-if" if="[[shouldShowRenamingDialog_]]" restamp>
+  <cr-dialog id="renamingDialog"
+      on-close="cancelRenamingDialogClicked_" show-on-attach>
+    <div slot="title">$i18n{buttonRenamingDialogTitle}</div>
+    <div slot="body">
+      <div id="inputContainer">
+        <span>$i18n{buttonRemappingDialogInputLabel}</span>
+        <cr-input
+          value="{{selectedButtonName_}}"
+          aria-label="$i18n{buttonRenamingDialogTitle}">
+      </cr-input>
+      </div>
+    </div>
+    <div slot="button-container">
+      <div>
+        <cr-button id="cancelButton"
+            on-click="cancelRenamingDialogClicked_">
+          $i18n{buttonRemappingDialogCancelLabel}
+        </cr-button>
+      </div>
+      <div>
+        <cr-button id="saveButton"
+            on-click="saveRenamingDialogClicked_">
+          $i18n{buttonRemappingDialogSaveLabel}
+        </cr-button>
+      </div>
+    </div>
+  </cr-dialog>
+</template>
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.ts b/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.ts
index 55d7f52..bb57d4f 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.ts
+++ b/chrome/browser/resources/ash/settings/device_page/customize_buttons_subsection.ts
@@ -8,6 +8,9 @@
  * elements that allow users to remap buttons to actions or key combinations.
  */
 
+import 'chrome://resources/cr_elements/cr_button/cr_button.js';
+import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
+import 'chrome://resources/cr_elements/cr_input/cr_input.js';
 import '../settings_shared.css.js';
 import './customize_button_row.js';
 
@@ -15,9 +18,17 @@
 import {PolymerElementProperties} from 'chrome://resources/polymer/v3_0/polymer/interfaces.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {ShowRenamingDialogEvent} from './customize_button_row.js';
 import {getTemplate} from './customize_buttons_subsection.html.js';
 import {ActionChoice, ButtonRemapping} from './input_device_settings_types.js';
 
+
+declare global {
+  interface HTMLElementEventMap {
+    'show-renaming-dialog': ShowRenamingDialogEvent;
+  }
+}
+
 const CustomizeButtonsSubsectionElementBase = I18nMixin(PolymerElement);
 
 export class CustomizeButtonsSubsectionElement extends
@@ -39,11 +50,56 @@
       buttonRemappingList: {
         type: Array,
       },
+
+      selectedButton_: {
+        type: Object,
+      },
+
+      shouldShowRenamingDialog_: {
+        type: Boolean,
+        value: false,
+      },
+
+      selectedButtonName_: {
+        type: String,
+        value: '',
+      },
     };
   }
 
   buttonRemappingList: ButtonRemapping[];
   actionList: ActionChoice[];
+  private selectedButton_: ButtonRemapping;
+  private shouldShowRenamingDialog_: boolean;
+  private selectedButtonName_: string;
+
+  override connectedCallback(): void {
+    super.connectedCallback();
+    this.addEventListener('show-renaming-dialog', this.showRenamingDialog_);
+  }
+
+  private showRenamingDialog_(e: ShowRenamingDialogEvent): void {
+    const selectedIndex = e.detail.buttonIndex;
+    this.selectedButton_ = this.buttonRemappingList[selectedIndex];
+    this.selectedButtonName_ = this.selectedButton_.name;
+    this.shouldShowRenamingDialog_ = true;
+  }
+
+  private cancelRenamingDialogClicked_(): void {
+    this.shouldShowRenamingDialog_ = false;
+  }
+
+  private saveRenamingDialogClicked_(): void {
+    if (this.selectedButton_.name !== this.selectedButtonName_) {
+      this.onSettingsChanged();
+    }
+    this.selectedButtonName_ = '';
+    this.shouldShowRenamingDialog_ = false;
+  }
+
+  onSettingsChanged(): void {
+    // TODO(yyhyyh@): Update changed buttonRemapping.
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_mouse_buttons_subpage.html b/chrome/browser/resources/ash/settings/device_page/customize_mouse_buttons_subpage.html
index f20f158b..d090b3be 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_mouse_buttons_subpage.html
+++ b/chrome/browser/resources/ash/settings/device_page/customize_mouse_buttons_subpage.html
@@ -11,5 +11,11 @@
   }
 </style>
 <div id="header">
-  <div id="description">[[mouse.name]]</div>
-</div>
\ No newline at end of file
+  <div id="description">[[selectedMouse.name]]</div>
+</div>
+<div class="subsection">
+  <customize-buttons-subsection
+      button-remapping-list="[[selectedMouse.settings.buttonRemappings]]"
+      action-list$="[[buttonActionList_]]">
+  </customize-buttons-subsection>
+</div>
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_mouse_buttons_subpage.ts b/chrome/browser/resources/ash/settings/device_page/customize_mouse_buttons_subpage.ts
index 97fc78c3..aa161f6d 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_mouse_buttons_subpage.ts
+++ b/chrome/browser/resources/ash/settings/device_page/customize_mouse_buttons_subpage.ts
@@ -20,7 +20,8 @@
 import {Route, Router, routes} from '../router.js';
 
 import {getTemplate} from './customize_mouse_buttons_subpage.html.js';
-import {Mouse} from './input_device_settings_types.js';
+import {getInputDeviceSettingsProvider} from './input_device_mojo_interface_provider.js';
+import {ActionChoice, InputDeviceSettingsProviderInterface, Mouse} from './input_device_settings_types.js';
 
 const SettingsCustomizeMouseButtonsSubpageElementBase =
     RouteObserverMixin(I18nMixin(PolymerElement));
@@ -37,11 +38,15 @@
 
   static get properties(): PolymerElementProperties {
     return {
-      mouse: {
+      selectedMouse: {
         type: Object,
       },
 
-      mice: {
+      mouseList: {
+        type: Array,
+      },
+
+      buttonActionList_: {
         type: Array,
       },
     };
@@ -49,12 +54,15 @@
 
   static get observers(): string[] {
     return [
-      'onMouseListUpdated(mice.*)',
+      'onMouseListUpdated(mouseList.*)',
     ];
   }
 
-  mouse: Mouse;
-  mice: Mouse[];
+  selectedMouse: Mouse;
+  mouseList: Mouse[];
+  private buttonActionList_: ActionChoice[];
+  private inputDeviceSettingsProvider_: InputDeviceSettingsProviderInterface =
+      getInputDeviceSettingsProvider();
 
   override currentRouteChanged(route: Route): void {
     // Does not apply to this page.
@@ -62,7 +70,8 @@
       return;
     }
     if (this.hasMice() &&
-        (!this.mouse || this.mouse.id !== this.getMouseIdFromUrl())) {
+        (!this.selectedMouse ||
+         this.selectedMouse.id !== this.getMouseIdFromUrl())) {
       this.initializeMouse();
     }
   }
@@ -71,11 +80,18 @@
    * Get the mouse to display according to the mouseId in the url query,
    * initializing the page and pref with the mouse data.
    */
-  private initializeMouse(): void {
+  private async initializeMouse(): Promise<void> {
+    // TODO(yyhyyh@): Remove the if condition after getActions functions is
+    // added in the mojo.
+    if (this.inputDeviceSettingsProvider_
+            .getActionsForMouseButtonCustomization) {
+      this.buttonActionList_ = await this.inputDeviceSettingsProvider_
+                                   .getActionsForMouseButtonCustomization();
+    }
     const mouseId = this.getMouseIdFromUrl();
     const searchedMouse =
-        this.mice.find((mouse: Mouse) => mouse.id === mouseId);
-    this.mouse = castExists(searchedMouse);
+        this.mouseList.find((mouse: Mouse) => mouse.id === mouseId);
+    this.selectedMouse = castExists(searchedMouse);
   }
 
   private getMouseIdFromUrl(): number {
@@ -83,11 +99,11 @@
   }
 
   private hasMice(): boolean {
-    return this.mice?.length > 0;
+    return this.mouseList?.length > 0;
   }
 
   private isMouseConnected(id: number): boolean {
-    return !!this.mice.find(mouse => mouse.id === id);
+    return !!this.mouseList.find(mouse => mouse.id === id);
   }
 
   onMouseListUpdated(): void {
diff --git a/chrome/browser/resources/ash/settings/device_page/device_page.html b/chrome/browser/resources/ash/settings/device_page/device_page.html
index cd4fb66..750d9a0 100644
--- a/chrome/browser/resources/ash/settings/device_page/device_page.html
+++ b/chrome/browser/resources/ash/settings/device_page/device_page.html
@@ -216,7 +216,7 @@
       route-path="/per-device-mouse/customizeButtons">
     <os-settings-subpage id="customizeMouseButtonsRow"
         page-title="$i18n{customizeMouseButtonsTitle}">
-      <settings-customize-mouse-buttons-subpage mice="[[mice]]">
+      <settings-customize-mouse-buttons-subpage mouse-list="[[mice]]">
       </settings-customize-mouse-buttons-subpage>
     </os-settings-subpage>
   </template>
diff --git a/chrome/browser/resources/ash/settings/device_page/fake_input_device_data.ts b/chrome/browser/resources/ash/settings/device_page/fake_input_device_data.ts
index 3a325c1b8..2b6191f 100644
--- a/chrome/browser/resources/ash/settings/device_page/fake_input_device_data.ts
+++ b/chrome/browser/resources/ash/settings/device_page/fake_input_device_data.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 {AcceleratorAction, ActionChoice, GraphicsTablet, Keyboard, MetaKey, ModifierKey, Mouse, PointingStick, SimulateRightClickModifier, SixPackKeyInfo, SixPackShortcutModifier, Stylus, Touchpad, Vkey} from './input_device_settings_types.js';
+import {AcceleratorAction, ActionChoice, CustomizableButton, GraphicsTablet, Keyboard, MetaKey, ModifierKey, Mouse, PointingStick, SimulateRightClickModifier, SixPackKeyInfo, SixPackShortcutModifier, Stylus, Touchpad, Vkey} from './input_device_settings_types.js';
 
 const defaultSixPackKeyRemappings: SixPackKeyInfo = {
   pageDown: SixPackShortcutModifier.kSearch,
@@ -241,7 +241,56 @@
       accelerationEnabled: true,
       scrollSensitivity: 5,
       scrollAcceleration: true,
-      buttonRemappings: [],
+      buttonRemappings: [
+        {
+          name: 'Back Button',
+          button: {
+            customizableButton: CustomizableButton.kBack,
+          },
+          remappingAction: {
+            action: AcceleratorAction.kCycleBackwardMru,
+          },
+        },
+        {
+          name: 'Forward Button',
+          button: {
+            customizableButton: CustomizableButton.kForward,
+          },
+          remappingAction: {
+            action: AcceleratorAction.kCycleForwardMru,
+          },
+        },
+        {
+          name: 'Undo',
+          button: {
+            customizableButton: CustomizableButton.kExtra,
+          },
+          remappingAction: {
+            keyEvent: {
+              vkey: Vkey.kKeyZ,
+              domCode: 0,
+              domKey: 0,
+              modifiers: 4,
+              keyDisplay: 'z',
+            },
+          },
+        },
+        {
+          name: 'Redo',
+          button: {
+            customizableButton: CustomizableButton.kSide,
+          },
+          remappingAction: {
+            keyEvent: {
+              vkey: Vkey.kKeyZ,
+              domCode: 0,
+              domKey: 0,
+              modifiers: 6,
+              keyDisplay: 'z',
+            },
+          },
+        },
+      ],
     },
   },
   {
@@ -256,7 +305,32 @@
       accelerationEnabled: false,
       scrollSensitivity: 1,
       scrollAcceleration: false,
-      buttonRemappings: [],
+      buttonRemappings: [
+        {
+          name: 'Chrome Vox',
+          button: {
+            customizableButton: CustomizableButton.kSide,
+          },
+          remappingAction: {
+            keyEvent: {
+              vkey: Vkey.kKeyZ,
+              domCode: 0,
+              domKey: 0,
+              modifiers: 10,
+              keyDisplay: 'z',
+            },
+          },
+        },
+        {
+          name: 'Open Clipboard',
+          button: {
+            customizableButton: CustomizableButton.kMiddle,
+          },
+          remappingAction: {
+            action: AcceleratorAction.kToggleClipboardHistory,
+          },
+        },
+      ],
     },
   },
 ];
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 7139709..2b6d776d4 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
@@ -49,4 +49,18 @@
 
 #keyLabel {
   padding-inline: 6px;
+}
+
+cr-dialog [slot='button-container'] {
+  padding: 0 32px 28px 32px;
+  display: flex;
+  gap: 8px;
+}
+
+cr-dialog [slot='body'] {
+  padding: 24px 32px;
+}
+
+cr-dialog [slot='title'] {
+  padding: 32px 32px 0 32px;
 }
\ No newline at end of file
diff --git a/chrome/browser/resources/ash/settings/device_page/input_device_settings_types.ts b/chrome/browser/resources/ash/settings/device_page/input_device_settings_types.ts
index 4213cbf..4d491ed 100644
--- a/chrome/browser/resources/ash/settings/device_page/input_device_settings_types.ts
+++ b/chrome/browser/resources/ash/settings/device_page/input_device_settings_types.ts
@@ -46,7 +46,9 @@
 
 export type Keyboard = InputDeviceSettingsTypes.Keyboard;
 export type Touchpad = InputDeviceSettingsTypes.Touchpad;
-export type Mouse = InputDeviceSettingsTypes.Mouse;
+export type Mouse = Omit<InputDeviceSettingsTypes.Mouse, 'settings'>&{
+  settings: MouseSettings,
+};
 export type PointingStick = InputDeviceSettingsTypes.PointingStick;
 
 export interface Stylus {
@@ -75,7 +77,10 @@
 
 export type KeyboardSettings = InputDeviceSettingsTypes.KeyboardSettings;
 export type TouchpadSettings = InputDeviceSettingsTypes.TouchpadSettings;
-export type MouseSettings = InputDeviceSettingsTypes.MouseSettings;
+export type MouseSettings =
+    Omit<InputDeviceSettingsTypes.MouseSettings, 'buttonRemappings'>&{
+      buttonRemappings: ButtonRemapping[],
+    };
 export type PointingStickSettings =
     InputDeviceSettingsTypes.PointingStickSettings;
 export type DeviceSettings =
@@ -107,22 +112,16 @@
   keyDisplay: string;
 }
 
-export interface FakeRemappingAction {
-  keyEvent?: FakeKeyEvent;
-  action?: AcceleratorActionTypes.AcceleratorAction;
-}
-
-export interface FakeButtonRemapping {
-  name: string;
-  button: InputDeviceSettingsTypes.Button;
-  remappingAction?: FakeRemappingAction;
-}
-
 export type ButtonRemapping =
-    Required<InputDeviceSettingsTypes.ButtonRemapping>&
-    Partial<FakeButtonRemapping>;
+    Omit<InputDeviceSettingsTypes.ButtonRemapping, 'remappingAction'>&{
+      remappingAction?: RemappingAction,
+    };
 
-export type RemappingAction = InputDeviceSettingsTypes.RemappingAction;
+export type RemappingAction =
+    Omit<InputDeviceSettingsTypes.RemappingAction, 'keyEvent'>&{
+      keyEvent?: KeyEvent,
+    };
+
 export type KeyEvent =
     Required<InputDeviceSettingsTypes.KeyEvent>&Partial<FakeKeyEvent>;
 
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.html b/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.html
index 8c07463..f93eb25 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.html
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.html
@@ -474,14 +474,20 @@
         </div>
 
         <!-- Hidden. -->
-        <settings-toggle-button id="hiddenToggle"
-            pref="{{hiddenPref_}}"
-            label="$i18n{networkHidden}"
-            sub-label="$i18n{networkHiddenSublabel}"
-            sub-label-icon="cr20:warning"
-            learn-more-url="$i18n{wifiHiddenNetworkLearnMoreUrl}"
-            deep-link-focus-id$="[[Setting.kWifiHidden]]">
-        </settings-toggle-button>
+        <template is="dom-if"
+            if="[[showHiddenNetworkToggle_(
+                    globalPolicy,
+                    managedNetworkAvailable,
+                    managedProperties_)]]">
+          <settings-toggle-button id="hiddenToggle"
+              pref="{{hiddenPref_}}"
+              label="$i18n{networkHidden}"
+              sub-label="$i18n{networkHiddenSublabel}"
+              sub-label-icon="cr20:warning"
+              learn-more-url="$i18n{wifiHiddenNetworkLearnMoreUrl}"
+              deep-link-focus-id$="[[Setting.kWifiHidden]]">
+          </settings-toggle-button>
+        </template>
       </iron-collapse>
     </template>
 
diff --git a/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.ts b/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.ts
index 4937755..6f15cac 100644
--- a/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.ts
+++ b/chrome/browser/resources/ash/settings/internet_page/internet_detail_subpage.ts
@@ -1859,7 +1859,7 @@
             managedProperties, globalPolicy, managedNetworkAvailable);
   }
 
-  private showHiddenNetwork_(): boolean {
+  private showHiddenNetworkToggle_(): boolean {
     if (!this.showHiddenToggle_) {
       return false;
     }
diff --git a/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.html b/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.html
index 880eebf..03f9bc8 100644
--- a/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.html
+++ b/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.html
@@ -60,6 +60,18 @@
     </template>
 
     <template is="dom-if"
+        if="[[shouldShowConfirmationDialog_(dialogType_, 'bulk-pinning-listing-files')]]"
+        restamp>
+      <settings-drive-confirmation-dialog
+          id="driveBulkPinningListingFilesDialog"
+          cancel-button-text="$i18n{googleDriveOkButtonText}"
+          title-text="$i18n{googleDriveFileSyncListingFilesTitle}"
+          body-text="[[getListingFilesDialogBody_(listedFiles_)]]"
+          on-close="onDriveConfirmationDialogClose_">
+      </settings-drive-confirmation-dialog>
+    </template>
+
+    <template is="dom-if"
         if="[[shouldShowConfirmationDialog_(dialogType_, 'bulk-pinning-not-enough-space')]]"
         restamp>
       <settings-drive-confirmation-dialog
diff --git a/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.ts b/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.ts
index 945d745..4b66f65 100644
--- a/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.ts
+++ b/chrome/browser/resources/ash/settings/os_files_page/google_drive_subpage.ts
@@ -45,6 +45,7 @@
 export enum ConfirmationDialogType {
   DISCONNECT = 'disconnect',
   BULK_PINNING_DISABLE = 'bulk-pinning-disable',
+  BULK_PINNING_LISTING_FILES = 'bulk-pinning-listing-files',
   BULK_PINNING_NOT_ENOUGH_SPACE = 'bulk-pinning-not-enough-space',
   BULK_PINNING_UNEXPECTED_ERROR = 'bulk-pinning-unexpected-error',
   BULK_PINNING_CLEAN_UP_STORAGE = 'bulk-pinning-clean-up-storage',
@@ -155,6 +156,18 @@
       ContentCacheSizeType.CALCULATING;
 
   /**
+   * The number of files that have currently been listed, this count is the one
+   * displayed in the UI which gets updated every 5s from the source at
+   * bulkPinningStatus_.listedFiles.
+   */
+  private listedFiles_: bigint = 0n;
+
+  /**
+   * The interval to update listedFiles_.
+   */
+  private updateListedFilesInterval_: number|undefined = undefined;
+
+  /**
    * Whether to show the spinner in the top right of the settings page.
    */
   private showSpinner: boolean = false;
@@ -202,6 +215,14 @@
   }
 
   /**
+   * Returns the current number of listed files.
+   * Used for testing.
+   */
+  get listedFiles() {
+    return this.listedFiles_;
+  }
+
+  /**
    * Returns the current confirmation dialog showing.
    */
   get dialogType() {
@@ -224,6 +245,8 @@
    */
   private onServiceUnavailable_() {
     this.bulkPinningServiceUnavailable_ = true;
+    clearInterval(this.updateListedFilesInterval_);
+    this.updateListedFilesInterval_ = undefined;
   }
 
   /**
@@ -231,10 +254,25 @@
    * This could also end up in an error state (e.g. no free space).
    */
   private onProgress_(status: Status) {
+    this.bulkPinningServiceUnavailable_ = false;
+
     if (status.stage !== this.bulkPinningStatus_?.stage ||
         status.freeSpace !== this.bulkPinningStatus_?.freeSpace ||
-        status.requiredSpace !== this.bulkPinningStatus_?.requiredSpace) {
+        status.requiredSpace !== this.bulkPinningStatus_?.requiredSpace ||
+        status.listedFiles !== this.bulkPinningStatus_?.listedFiles) {
       this.bulkPinningStatus_ = status;
+
+      if (!this.updateListedFilesInterval_ &&
+          status.stage === Stage.kListingFiles) {
+        this.listedFiles_ = this.bulkPinningStatus_?.listedFiles || 0n;
+        this.updateListedFilesInterval_ = setInterval(() => {
+          this.listedFiles_ = this.bulkPinningStatus_?.listedFiles || 0n;
+        }, 5000);
+      }
+    }
+
+    if (status.stage !== Stage.kListingFiles) {
+      this.stopUpdatingListedFilesAndClearDialog_();
     }
 
     let requiredSpace: number;
@@ -249,6 +287,21 @@
   }
 
   /**
+   * Whilst listing files an interval is maintained to not update the UI with
+   * too many changes. When listing files has finished, ensure the interval is
+   * cleared and the dialog is closed if it is kept open.
+   */
+  private stopUpdatingListedFilesAndClearDialog_() {
+    clearInterval(this.updateListedFilesInterval_);
+    this.updateListedFilesInterval_ = undefined;
+    this.listedFiles_ = 0n;
+    if (this.dialogType_ ===
+        ConfirmationDialogType.BULK_PINNING_LISTING_FILES) {
+      this.dialogType_ = ConfirmationDialogType.NONE;
+    }
+  }
+
+  /**
    * Retrieves the total pinned size of items in Drive and stores the total.
    */
   private async updateContentCacheSize_() {
@@ -427,18 +480,27 @@
         return;
       }
 
-      // When the device is offline, don't allow the user to enable the toggle.
-      if (this.bulkPinningStatus_?.stage === Stage.kPausedOffline) {
-        this.dialogType_ = ConfirmationDialogType.BULK_PINNING_OFFLINE;
-        return;
-      }
-
       // If an error occurs (that is not related to low disk space) surface an
       // unexpected error dialog.
       this.dialogType_ = ConfirmationDialogType.BULK_PINNING_UNEXPECTED_ERROR;
       return;
     }
 
+    // When the device is offline, don't allow the user to enable the toggle.
+    if (this.bulkPinningStatus_?.stage === Stage.kPausedOffline) {
+      target.checked = false;
+      this.dialogType_ = ConfirmationDialogType.BULK_PINNING_OFFLINE;
+      return;
+    }
+
+    // If currently enumerating the files, don't allow the user to enable file
+    // sync until we're certain the corpus will fit on the device.
+    if (this.bulkPinningStatus_?.stage === Stage.kListingFiles) {
+      target.checked = false;
+      this.dialogType_ = ConfirmationDialogType.BULK_PINNING_LISTING_FILES;
+      return;
+    }
+
     target.checked = true;
 
     // Turning the preference off should first spawn a dialog to have the user
@@ -477,6 +539,14 @@
     });
   }
 
+  private getListingFilesDialogBody_() {
+    return this.listedFiles_ > 0n ?
+        this.i18n(
+            'googleDriveFileSyncListingFilesItemsFoundBody',
+            this.listedFiles_.toLocaleString()) :
+        this.i18n('googleDriveFileSyncListingFilesBody');
+  }
+
   /**
    * When the "Clean up storage" button is clicked, should not clean up
    * immediately but show the confirmation dialog first.
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_rules.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_rules.js
index 93f5869b..b5d66ed 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_rules.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/output/output_rules.js
@@ -63,13 +63,10 @@
    * @return {boolean} true if the role was set, false otherwise.
    */
   populateRole(role, parentRole, formatName) {
-    const eventBlock = OutputRule.RULES[this.event_];
-    if (role && eventBlock[role] && eventBlock[role][formatName]) {
+    if (this.hasRule_(role, formatName) && role) {
       this.role_ = role;
       return true;
-    } else if (
-        parentRole && eventBlock[parentRole] &&
-        eventBlock[parentRole][formatName]) {
+    } else if (this.hasRule_(parentRole, formatName) && parentRole) {
       this.role_ = parentRole;
       return true;
     }
@@ -106,6 +103,19 @@
   get output() {
     return this.output_;
   }
+
+  // ========= Private methods =========
+
+  /**
+   * @param {ChromeVoxRole|undefined} role
+   * @param {string|undefined} format
+   * @return {boolean} Whether there is a rule for this role/format combo.
+   * @private
+   */
+  hasRule_(role, format) {
+    const eventBlock = OutputRule.RULES[this.event_];
+    return role && eventBlock[role] && eventBlock[role][format];
+  }
 }
 
 export class AncestryOutputRule extends OutputRule {
diff --git a/chrome/browser/resources/new_tab_page/modules/v2/drive/module.html b/chrome/browser/resources/new_tab_page/modules/v2/drive/module.html
index 0236c57..4b5a879 100644
--- a/chrome/browser/resources/new_tab_page/modules/v2/drive/module.html
+++ b/chrome/browser/resources/new_tab_page/modules/v2/drive/module.html
@@ -3,15 +3,33 @@
     background-color: var(--color-new-tab-page-module-background);
   }
 
+  @media (forced-colors: active) {
+    /* Set outline since background isn't visible in hcm */
+    ntp-module-header-v2,
+    a {
+      border-radius: var(--ntp-module-item-border-radius);
+      outline: var(--cr-border-hcm);
+     }
+
+    .file {
+      overflow: visible;
+    }
+  }
+
   ntp-module-header-v2 {
     margin: 8px;
   }
 
+  :host-context(.focus-outline-visible) a:focus,
+  a:focus-visible {
+    box-shadow: var(--ntp-focus-shadow);
+    outline: none;
+  }
+
   #files {
     background-color: var(--color-new-tab-page-module-item-background);
     border-radius: var(--ntp-module-item-border-radius);
     margin: 8px;
-    overflow: hidden;
   }
 
   .file {
@@ -20,11 +38,31 @@
     height: 52px;
     padding-bottom: 2px;
     padding-top: 2px;
+    position: relative;
     text-decoration: none;
+    overflow: hidden;
   }
 
-  .file:hover {
-    background-color: var(--color-new-tab-page-control-background-hovered);
+  .file:hover #hover-layer {
+    background: var(--color-new-tab-page-module-item-background-hovered);
+    display: block;
+    inset: 0;
+    pointer-events: none;
+    position: absolute;
+  }
+
+  #hover-layer {
+    display: none;
+  }
+
+  .file:first-of-type {
+    border-radius: var(--ntp-module-item-border-radius)
+        var(--ntp-module-item-border-radius) 0 0;
+  }
+
+  .file:last-of-type {
+    border-radius: 0 0 var(--ntp-module-item-border-radius)
+        var(--ntp-module-item-border-radius);
   }
 
   .file-icon {
@@ -72,6 +110,7 @@
 <div id="files">
   <template id="fileRepeat" is="dom-repeat" items="[[files]]">
     <a class="file" href="[[item.itemUrl.url]]" on-click="onFileClick_">
+      <div id="hover-layer"></div>
       <img is="cr-auto-img"
           class="file-icon"
           draggable="false"
diff --git a/chrome/browser/resources/settings/performance_page/battery_page.html b/chrome/browser/resources/settings/performance_page/battery_page.html
index b60327f..fa86457 100644
--- a/chrome/browser/resources/settings/performance_page/battery_page.html
+++ b/chrome/browser/resources/settings/performance_page/battery_page.html
@@ -3,32 +3,40 @@
     padding-block-end: var(--cr-section-vertical-padding);
   }
 </style>
-<settings-toggle-button id="toggleButton" on-change="onChange_"
-    pref="{{prefs.performance_tuning.battery_saver_mode.state}}"
-    label="$i18n{batterySaverModeLabel}"
-    sub-label="$i18n{batterySaverModeDescription}"
-    learn-more-url="$i18n{batterySaverLearnMoreUrl}"
-    numeric-unchecked-value="[[batterySaverModeStateEnum_.DISABLED]]"
-    numeric-checked-value="[[batterySaverModeStateEnum_.ENABLED_BELOW_THRESHOLD]]"
-    >
-</settings-toggle-button>
-<iron-collapse id="radioGroupCollapse"
-    opened="[[isBatterySaverModeEnabled_(
-    prefs.performance_tuning.battery_saver_mode.state.value)]]">
-  <div class="cr-row continuation battery-saver-radio-group">
-    <settings-radio-group id="radioGroup" on-change="onChange_"
-        pref="{{prefs.performance_tuning.battery_saver_mode.state}}"
-        group-aria-label="$i18n{batterySaverModeRadioGroupAriaLabel}">
-      <controlled-radio-button
-          label="$i18n{batterySaverModeEnabledBelowThresholdLabel}"
-          name="[[batterySaverModeStateEnum_.ENABLED_BELOW_THRESHOLD]]"
-          pref="[[prefs.performance_tuning.battery_saver_mode.state]]">
-      </controlled-radio-button>
-      <controlled-radio-button id="enabledOnBatteryButton"
-          label="$i18n{batterySaverModeEnabledOnBatteryLabel}"
-          name="[[batterySaverModeStateEnum_.ENABLED_ON_BATTERY]]"
-          pref="[[prefs.performance_tuning.battery_saver_mode.state]]">
-      </controlled-radio-button>
-    </settings-radio-group>
-  </div>
-</iron-collapse>
\ No newline at end of file
+<template is="dom-if" if="[[isBatterySaverModeManagedByOS_]]">
+  <cr-link-row id="batterySaverOSSettingsLinkRow"
+      label="$i18n{batterySaverModeLabel}"
+      sub-label="$i18n{batterySaverModeLinkOsDescription}"
+      on-click="openOsPowerSettings_"
+      external>
+  </cr-link-row>
+</template>
+<template is="dom-if" if="[[!isBatterySaverModeManagedByOS_]]">
+  <settings-toggle-button id="toggleButton" on-change="onChange_"
+      pref="{{prefs.performance_tuning.battery_saver_mode.state}}"
+      label="$i18n{batterySaverModeLabel}"
+      sub-label="$i18n{batterySaverModeDescription}"
+      learn-more-url="$i18n{batterySaverLearnMoreUrl}"
+      numeric-unchecked-value="[[batterySaverModeStateEnum_.DISABLED]]"
+      numeric-checked-value="[[batterySaverModeStateEnum_.ENABLED_BELOW_THRESHOLD]]">
+  </settings-toggle-button>
+  <iron-collapse id="radioGroupCollapse"
+      opened="[[isBatterySaverModeEnabled_(prefs.performance_tuning.battery_saver_mode.state.value)]]">
+    <div class="cr-row continuation battery-saver-radio-group">
+      <settings-radio-group id="radioGroup" on-change="onChange_"
+          pref="{{prefs.performance_tuning.battery_saver_mode.state}}"
+          group-aria-label="$i18n{batterySaverModeRadioGroupAriaLabel}">
+        <controlled-radio-button
+            label="$i18n{batterySaverModeEnabledBelowThresholdLabel}"
+            name="[[batterySaverModeStateEnum_.ENABLED_BELOW_THRESHOLD]]"
+            pref="[[prefs.performance_tuning.battery_saver_mode.state]]">
+        </controlled-radio-button>
+        <controlled-radio-button id="enabledOnBatteryButton"
+            label="$i18n{batterySaverModeEnabledOnBatteryLabel}"
+            name="[[batterySaverModeStateEnum_.ENABLED_ON_BATTERY]]"
+            pref="[[prefs.performance_tuning.battery_saver_mode.state]]">
+        </controlled-radio-button>
+      </settings-radio-group>
+    </div>
+  </iron-collapse>
+</template>
diff --git a/chrome/browser/resources/settings/performance_page/battery_page.ts b/chrome/browser/resources/settings/performance_page/battery_page.ts
index 46eb994..250dacf 100644
--- a/chrome/browser/resources/settings/performance_page/battery_page.ts
+++ b/chrome/browser/resources/settings/performance_page/battery_page.ts
@@ -4,6 +4,7 @@
 
 import 'chrome://resources/cr_elements/cr_shared_style.css.js';
 import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
+import 'chrome://resources/cr_elements/cr_link_row/cr_link_row.js';
 import '/shared/settings/controls/controlled_radio_button.js';
 import '/shared/settings/controls/settings_radio_group.js';
 import '/shared/settings/controls/settings_toggle_button.js';
@@ -13,9 +14,12 @@
 import {SettingsRadioGroupElement} from '/shared/settings/controls/settings_radio_group.js';
 import {SettingsToggleButtonElement} from '/shared/settings/controls/settings_toggle_button.js';
 import {PrefsMixin} from 'chrome://resources/cr_components/settings_prefs/prefs_mixin.js';
+import {OpenWindowProxyImpl} from 'chrome://resources/js/open_window_proxy.js';
 import {IronCollapseElement} from 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {loadTimeData} from '../i18n_setup.js';
+
 import {getTemplate} from './battery_page.html.js';
 import {BatterySaverModeState, PerformanceMetricsProxy, PerformanceMetricsProxyImpl} from './performance_metrics_proxy.js';
 
@@ -49,6 +53,13 @@
         type: Object,
         value: BatterySaverModeState,
       },
+
+      isBatterySaverModeManagedByOS_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean('isBatterySaverModeManagedByOS');
+        },
+      },
     };
   }
 
@@ -63,6 +74,13 @@
     this.metricsProxy_.recordBatterySaverModeChanged(
         this.getPref<number>(BATTERY_SAVER_MODE_PREF).value);
   }
+
+  // <if expr="is_chromeos">
+  private openOsPowerSettings_() {
+    OpenWindowProxyImpl.getInstance().openUrl(
+        loadTimeData.getString('osPowerSettingsUrl'));
+  }
+  // </if>
 }
 
 declare global {
diff --git a/chrome/browser/resources/side_panel/companion/companion.ts b/chrome/browser/resources/side_panel/companion/companion.ts
index b94b4dc..9ba6c3c 100644
--- a/chrome/browser/resources/side_panel/companion/companion.ts
+++ b/chrome/browser/resources/side_panel/companion/companion.ts
@@ -283,6 +283,8 @@
   } else if (methodType === MethodType.kCompanionLoadingState) {
     companionProxy.handler.onLoadingState(
         data[ParamType.COMPANION_LOADING_STATE]);
+  } else if (methodType === MethodType.kRefreshCompanionPage) {
+    companionProxy.handler.refreshCompanionPage();
   }
 }
 
diff --git a/chrome/browser/resources/side_panel/customize_chrome/app.html b/chrome/browser/resources/side_panel/customize_chrome/app.html
index 331b3b88..2bb8e27 100644
--- a/chrome/browser/resources/side_panel/customize_chrome/app.html
+++ b/chrome/browser/resources/side_panel/customize_chrome/app.html
@@ -4,6 +4,7 @@
     color: var(--cr-primary-text-color);
     height: 100%;
     overflow: auto;
+    position: relative;
   }
 
   :host-context([chrome-refresh-2023]) #container {
diff --git a/chrome/browser/resources/side_panel/read_anything/BUILD.gn b/chrome/browser/resources/side_panel/read_anything/BUILD.gn
index 9780236..7f771b1 100644
--- a/chrome/browser/resources/side_panel/read_anything/BUILD.gn
+++ b/chrome/browser/resources/side_panel/read_anything/BUILD.gn
@@ -31,6 +31,7 @@
   ts_definitions = [
     "read_anything.d.ts",
     "//tools/typescript/definitions/pending.d.ts",
+    "//tools/typescript/definitions/metrics_private.d.ts",
   ]
   ts_path_mappings =
       [ "//read-anything-side-panel.top-chrome/shared/*|" +
diff --git a/chrome/browser/resources/side_panel/read_anything/app.ts b/chrome/browser/resources/side_panel/read_anything/app.ts
index 94b70d4..4b06403 100644
--- a/chrome/browser/resources/side_panel/read_anything/app.ts
+++ b/chrome/browser/resources/side_panel/read_anything/app.ts
@@ -16,6 +16,7 @@
 import {PolymerElement} from '//resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {getTemplate} from './app.html.js';
+import {ReadAnythingToolbar} from './read_anything_toolbar.js';
 
 const ReadAnythingElementBase = WebUiListenerMixin(PolymerElement);
 
@@ -412,8 +413,7 @@
         // Continue speaking with the next block of text.
         readAnythingApp.playMessage(text.substring(maxTextLength, text.length));
       } else {
-        // TODO(crbug.com/1474951): Ensure the play button resets when we run
-        // out of text to speak.
+        readAnythingApp?.onSpeechStopped();
       }
     };
 
@@ -435,6 +435,16 @@
     this.synth.speak(message);
   }
 
+  private onSpeechStopped() {
+    const shadowRoot = this.shadowRoot;
+    assert(shadowRoot);
+    const toolbar = shadowRoot.getElementById('toolbar');
+    assert(toolbar);
+    if (toolbar instanceof ReadAnythingToolbar) {
+      toolbar.updateUiForPausing();
+    }
+  }
+
   // TODO(b/1465029): Once the IsReadAnythingWebUIEnabled flag is removed
   // this should be renamed to just validatedFontName_ and the current
   // validatedFontName_ method can be removed.
diff --git a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
index 91bb32e..e13df96 100644
--- a/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
+++ b/chrome/browser/resources/side_panel/read_anything/read_anything_toolbar.ts
@@ -39,6 +39,22 @@
   VERY_WIDE = 10,
 }
 
+// Enum for logging when a text style setting is changed.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused.
+enum ReadAnythingSettingsChange {
+  FONT_CHANGE = 0,
+  FONT_SIZE_CHANGE = 1,
+  THEME_CHANGE = 2,
+  LINE_HEIGHT_CHANGE = 3,
+  LETTER_SPACING_CHANGE = 4,
+
+  // Must be last.
+  COUNT = 5,
+}
+
+const SETTINGS_CHANGE_UMA = 'Accessibility.ReadAnything.SettingsChange';
+
 
 const ReadAnythingToolbarBase = WebUiListenerMixin(PolymerElement);
 export class ReadAnythingToolbar extends ReadAnythingToolbarBase {
@@ -122,6 +138,9 @@
           // Do nothing;
       }
 
+      chrome.metricsPrivate.recordEnumerationValue(
+          SETTINGS_CHANGE_UMA, ReadAnythingSettingsChange.LINE_HEIGHT_CHANGE,
+          ReadAnythingSettingsChange.COUNT);
       if (this.contentPage && data) {
         this.contentPage.updateLineSpacing(
             chrome.readingMode.getLineSpacingValue(data));
@@ -150,6 +169,9 @@
           // Do nothing;
       }
 
+      chrome.metricsPrivate.recordEnumerationValue(
+          SETTINGS_CHANGE_UMA, ReadAnythingSettingsChange.LETTER_SPACING_CHANGE,
+          ReadAnythingSettingsChange.COUNT);
       if (this.contentPage && data) {
         this.contentPage.updateLetterSpacing(
             chrome.readingMode.getLetterSpacingValue(data));
@@ -186,6 +208,9 @@
           // Do nothing;
       }
 
+      chrome.metricsPrivate.recordEnumerationValue(
+          SETTINGS_CHANGE_UMA, ReadAnythingSettingsChange.THEME_CHANGE,
+          ReadAnythingSettingsChange.COUNT);
       if (this.contentPage && (colorSuffix !== undefined)) {
         this.contentPage.updateThemeFromWebUi(colorSuffix);
       }
@@ -204,6 +229,24 @@
     this.removeOnClickListeners(this.$.letterSpacingSubmenu);
   }
 
+  updateUiForPlaying() {
+    const shadowRoot = this.shadowRoot;
+    assert(shadowRoot);
+    const button = shadowRoot.getElementById('play-pause');
+    assert(button);
+    button.setAttribute('iron-icon', 'read-anything:pause');
+    this.isPaused = false;
+  }
+
+  updateUiForPausing() {
+    const shadowRoot = this.shadowRoot;
+    assert(shadowRoot);
+    const button = shadowRoot.getElementById('play-pause');
+    assert(button);
+    button.setAttribute('iron-icon', 'read-anything:play');
+    this.isPaused = true;
+  }
+
   private removeOnClickListeners(menu: CrActionMenuElement) {
     const nodes = Array.from(menu.children);
     nodes.forEach((element) => {
@@ -278,6 +321,9 @@
   }
 
   private onFontClick_(fontName: string) {
+    chrome.metricsPrivate.recordEnumerationValue(
+        SETTINGS_CHANGE_UMA, ReadAnythingSettingsChange.FONT_CHANGE,
+        ReadAnythingSettingsChange.COUNT);
     chrome.readingMode.onFontChange(fontName);
     if (this.contentPage) {
       this.contentPage.updateFont(fontName);
@@ -294,6 +340,9 @@
     this.updateFontSize_(false);
   }
   private updateFontSize_(increase: boolean) {
+    chrome.metricsPrivate.recordEnumerationValue(
+        SETTINGS_CHANGE_UMA, ReadAnythingSettingsChange.FONT_SIZE_CHANGE,
+        ReadAnythingSettingsChange.COUNT);
     if (this.contentPage) {
       this.contentPage.updateFontSize(increase);
     }
@@ -301,20 +350,13 @@
   }
 
   private onPlayPauseClick_() {
-    const shadowRoot = this.shadowRoot;
-    assert(shadowRoot);
-    const button = shadowRoot.getElementById('play-pause');
-    assert(button);
-
     if (this.isPaused) {
-      button.setAttribute('iron-icon', 'read-anything:pause');
-      this.isPaused = false;
+      this.updateUiForPlaying();
       if (this.contentPage) {
         this.contentPage.playSpeech();
       }
     } else {
-      button.setAttribute('iron-icon', 'read-anything:play');
-      this.isPaused = true;
+      this.updateUiForPausing();
       if (this.contentPage) {
         this.contentPage.stopSpeech();
       }
diff --git a/chrome/browser/safe_browsing/client_side_detection_service_browsertest.cc b/chrome/browser/safe_browsing/client_side_detection_service_browsertest.cc
index 2a3d65f2..3b70230 100644
--- a/chrome/browser/safe_browsing/client_side_detection_service_browsertest.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_service_browsertest.cc
@@ -111,7 +111,7 @@
 IN_PROC_BROWSER_TEST_F(ClientSideDetectionServiceBrowserTest,
                        MAYBE_ModelUpdatesPropagated) {
 #if BUILDFLAG(IS_MAC)
-  if (base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() >= 13) {
     GTEST_SKIP() << "Flaky on macOS 13: https://crbug.com/1433315";
   }
 #endif
@@ -196,7 +196,7 @@
 IN_PROC_BROWSER_TEST_F(ClientSideDetectionServiceBrowserTest,
                        MAYBE_TfLiteClassification) {
 #if BUILDFLAG(IS_MAC)
-  if (base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() >= 13) {
     GTEST_SKIP() << "Flaky on macOS 13: https://crbug.com/1433315";
   }
 #endif
@@ -296,7 +296,7 @@
 IN_PROC_BROWSER_TEST_F(ClientSideDetectionServiceBrowserTest,
                        MAYBE_TfLiteClassificationAfterTwoModelUploads) {
 #if BUILDFLAG(IS_MAC)
-  if (base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() >= 13) {
     GTEST_SKIP() << "Flaky on macOS 13: https://crbug.com/1433315";
   }
 #endif
diff --git a/chrome/browser/safe_browsing/ohttp_key_service_factory.cc b/chrome/browser/safe_browsing/ohttp_key_service_factory.cc
index ccd54c1..98743bd2 100644
--- a/chrome/browser/safe_browsing/ohttp_key_service_factory.cc
+++ b/chrome/browser/safe_browsing/ohttp_key_service_factory.cc
@@ -38,7 +38,8 @@
   DependsOn(NetworkContextServiceFactory::GetInstance());
 }
 
-KeyedService* OhttpKeyServiceFactory::BuildServiceInstanceFor(
+std::unique_ptr<KeyedService>
+OhttpKeyServiceFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* context) const {
   // TODO(crbug.com/1441654) [Also TODO(thefrog)]: For now we simply return
   // nullptr for Android. If it becomes settled that Android should not use this
@@ -59,7 +60,7 @@
       std::make_unique<network::CrossThreadPendingSharedURLLoaderFactory>(
           g_browser_process->safe_browsing_service()->GetURLLoaderFactory(
               profile));
-  return new OhttpKeyService(
+  return std::make_unique<OhttpKeyService>(
       network::SharedURLLoaderFactory::Create(std::move(url_loader_factory)),
       profile->GetPrefs());
 #endif
diff --git a/chrome/browser/safe_browsing/ohttp_key_service_factory.h b/chrome/browser/safe_browsing/ohttp_key_service_factory.h
index 33bab202..e700602 100644
--- a/chrome/browser/safe_browsing/ohttp_key_service_factory.h
+++ b/chrome/browser/safe_browsing/ohttp_key_service_factory.h
@@ -41,7 +41,7 @@
   ~OhttpKeyServiceFactory() override = default;
 
   // BrowserContextKeyedServiceFactory:
-  KeyedService* BuildServiceInstanceFor(
+  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
       content::BrowserContext* context) const override;
   bool ServiceIsCreatedWithBrowserContext() const override;
 };
diff --git a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.cc b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.cc
index 7c31203a..0d83df16 100644
--- a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.cc
+++ b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.h"
 
 #include "base/metrics/histogram_functions.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -68,6 +69,11 @@
                               profile->GetPrefs()),
       profile_(profile) {
   AddObserver(this);
+  if (base::FeatureList::IsEnabled(
+          safe_browsing::kTailoredSecurityRetryForSyncUsers)) {
+    retry_timer_.Start(FROM_HERE, kRetryAttemptStartupDelay, this,
+                       &ChromeTailoredSecurityService::MaybeRetryForSyncUsers);
+  }
 }
 
 ChromeTailoredSecurityService::~ChromeTailoredSecurityService() {
@@ -250,4 +256,58 @@
   }
 }
 
+void ChromeTailoredSecurityService::MaybeRetryForSyncUsers() {
+  if (ShouldRetryForSyncUsers()) {
+    TailoredSecurityTimestampUpdateCallback();
+  }
+}
+
+bool ChromeTailoredSecurityService::ShouldRetryForSyncUsers() {
+  PrefService* prefs = profile_->GetPrefs();
+  if (prefs->GetTime(prefs::kAccountTailoredSecurityUpdateTimestamp) ==
+      base::Time()) {
+    // Do nothing because we can still rely on the user setting the tailored
+    // security bit on their account settings in the future.
+    return false;
+  }
+  if (prefs->GetInteger(prefs::kTailoredSecuritySyncFlowRetryState) ==
+      safe_browsing::NO_RETRY_NEEDED) {
+    return false;
+  }
+  if (prefs->GetInteger(prefs::kTailoredSecuritySyncFlowRetryState) ==
+      safe_browsing::RETRY_NEEDED) {
+    if (base::Time::Now() >=
+        prefs->GetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp)) {
+      // Set the next attempt time to a future point in time so that if this
+      // retry attempt fails, enough time passes before retrying again.
+      prefs->SetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp,
+                     base::Time::Now() + kRetryNextAttemptDelay);
+      return true;
+    }
+    return false;
+  }
+  if (prefs->GetInteger(prefs::kTailoredSecuritySyncFlowRetryState) ==
+      safe_browsing::UNSET) {
+    // The stateful version of `ChromeTailoredSecurityService` has not run yet,
+    // and we don't know if a previous version of the service showed the dialog
+    // to the user in the past, so we need to ensure that we wait long enough
+    // before retrying.
+    if (prefs->GetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp) ==
+        base::Time()) {
+      prefs->SetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp,
+                     base::Time::Now() + kWaitingPeriodInterval);
+      return false;
+    }
+    if (base::Time::Now() >=
+        prefs->GetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp)) {
+      // Set the next attempt time to a future point in time so that if this
+      // retry attempt fails, enough time passes before retrying again.
+      prefs->SetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp,
+                     base::Time::Now() + kRetryNextAttemptDelay);
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.h b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.h
index 9e2c47f..afb7fd8b 100644
--- a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.h
+++ b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service.h
@@ -6,6 +6,8 @@
 #define CHROME_BROWSER_SAFE_BROWSING_TAILORED_SECURITY_CHROME_TAILORED_SECURITY_SERVICE_H_
 
 #include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "build/build_config.h"
 #include "components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.h"
 #include "components/safe_browsing/core/browser/tailored_security_service/tailored_security_service_observer.h"
@@ -35,6 +37,18 @@
 #endif
 {
  public:
+  // The amount of time to wait after construction before checking if a retry is
+  // needed.
+  static constexpr const base::TimeDelta kRetryAttemptStartupDelay =
+      base::Minutes(2);
+  // The amount of time to wait between retry attempts.
+  static constexpr const base::TimeDelta kRetryNextAttemptDelay = base::Days(1);
+  // Length of time that the retry mechanism will wait before running. This
+  // delay is used for the case where the tailored security service can't tell
+  // if it succeeded in the past.
+  static constexpr const base::TimeDelta kWaitingPeriodInterval =
+      base::Days(90);
+
   explicit ChromeTailoredSecurityService(Profile* profile);
   ~ChromeTailoredSecurityService() override;
 
@@ -78,9 +92,12 @@
   TailoredSecurityDesktopDialogManager dialog_manager_;
 #endif
 
+  void MaybeRetryForSyncUsers();
   void SaveRetryState(TailoredSecurityRetryState result);
+  bool ShouldRetryForSyncUsers();
 
   raw_ptr<Profile> profile_;
+  base::OneShotTimer retry_timer_;
 };
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service_unittest.cc b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service_unittest.cc
index 0644ddf..de5e3d00 100644
--- a/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service_unittest.cc
+++ b/chrome/browser/safe_browsing/tailored_security/chrome_tailored_security_service_unittest.cc
@@ -89,9 +89,26 @@
                                                        previous_update);
   }
 
+  // Sets the value that is expected to be returned by the remote server.
+  void SetTailoredSecurityServiceValue(bool tailored_security_bit_value) {
+    tailored_security_service_value_ = tailored_security_bit_value;
+  }
+
+  bool GetTailoredSecurityServiceValue() {
+    return tailored_security_service_value_;
+  }
+
+  // Overridden to skip calling the remote server. It supplies the value that
+  // was most recently set through `SetTailoredSecurityServiceValue`.
+  void TailoredSecurityTimestampUpdateCallback() override {
+    MaybeNotifySyncUser(tailored_security_service_value_, base::Time::Now());
+  }
+
  private:
   bool previous_show_enable_dialog_value_ = false;
   int times_display_desktop_dialog_called_ = 0;
+  // Represents the value that we want the remote service to provide.
+  bool tailored_security_service_value_ = true;
 };
 }  // namespace
 
@@ -122,8 +139,9 @@
     identity_test_env_adaptor_ =
         std::make_unique<IdentityTestEnvironmentProfileAdaptor>(profile_);
     GetIdentityTestEnv()->SetTestURLLoaderFactory(&test_url_loader_factory_);
-    // TODO(crbug.com/1466447): `ConsentLevel::kSync` is deprecated and should
-    // be removed. See `ConsentLevel::kSync` documentation for details.
+    // TODO(crbug.com/1466447): `ConsentLevel::kSync` is deprecated and
+    // should be removed. See `ConsentLevel::kSync` documentation for
+    // details.
     GetIdentityTestEnv()->MakePrimaryAccountAvailable(
         "test@foo.com", signin::ConsentLevel::kSync);
     prefs_ = profile_->GetTestingPrefService();
@@ -225,13 +243,29 @@
     profile_manager_.DeleteTestingProfile("primary_account");
   }
 
+  void SetAccountTailoredSecurityTimestamp(base::Time time) {
+    // Changing prefs::kAccountTailoredSecurityUpdateTimestamp triggers the
+    // preference observer, so here we prevent the preference observer method
+    // from doing anything by having the server bit value match the safe
+    // browsing level in preferences.
+    bool original_tailored_security_service_value =
+        tailored_security_service()->GetTailoredSecurityServiceValue();
+
+    tailored_security_service()->SetTailoredSecurityServiceValue(
+        IsEnhancedProtectionEnabled(*prefs()));
+    prefs()->SetTime(prefs::kAccountTailoredSecurityUpdateTimestamp, time);
+    tailored_security_service()->SetTailoredSecurityServiceValue(
+        original_tailored_security_service_value);
+  }
+
   base::test::ScopedFeatureList scoped_feature_list_;
+  content::BrowserTaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
 
  private:
   // Must be declared before anything that may make use of the
   // directory so as to ensure files are closed before cleanup.
   base::ScopedTempDir temp_dir_;
-  content::BrowserTaskEnvironment task_environment_;
   // This is required to create browser tabs in the tests.
   content::RenderViewHostTestEnabler rvh_test_enabler_;
   raw_ptr<sync_preferences::TestingPrefServiceSyncable, DanglingUntriaged>
@@ -480,8 +514,217 @@
             TailoredSecurityRetryState::NO_RETRY_NEEDED);
 }
 
+TEST_F(ChromeTailoredSecurityServiceTest, RunsRetryLogicAfterStartupDelay) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+  // Setup the state so that a dialog will be displayed because that is what we
+  // will use to check if the startup task ran at the correct time.
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+  int initial_times_displayed =
+      tailored_security_service()->times_dialog_displayed();
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::RETRY_NEEDED);
+  prefs()->SetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp,
+                   base::Time::Now());
+
+  // The logic should run after the startup delay, so check that it does not run
+  // before that.
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay -
+      base::Seconds(1));
+  EXPECT_EQ(tailored_security_service()->times_dialog_displayed(),
+            initial_times_displayed);
+  // Startup delay has passed, so verify that the retry ran.
+  task_environment_.FastForwardBy(base::Seconds(1));
+  // We're checking if the dialog was displayed as a proxy to checking if the
+  // logic ran because we don't have a direct way of checking this.
+  EXPECT_EQ(tailored_security_service()->times_dialog_displayed(),
+            initial_times_displayed + 1);
+}
+
+TEST_F(ChromeTailoredSecurityServiceTest,
+       TailoredSecurityUpdateTimeNotSetDoesNotRetry) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+  int initial_times_displayed =
+      tailored_security_service()->times_dialog_displayed();
+
+  SetAccountTailoredSecurityTimestamp(base::Time());
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+
+  // Verify that notification was not shown.
+  EXPECT_EQ(tailored_security_service()->times_dialog_displayed(),
+            initial_times_displayed);
+}
+
+TEST_F(ChromeTailoredSecurityServiceTest,
+       WhenRetryNeededButNotEnoughTimeHasPassedDoesNotRetry) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+
+  int initial_times_displayed =
+      tailored_security_service()->times_dialog_displayed();
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::RETRY_NEEDED);
+
+  // set next sync flow to after when the retry check will happen.
+  prefs()->SetTime(
+      prefs::kTailoredSecurityNextSyncFlowTimestamp,
+      base::Time::Now() +
+          ChromeTailoredSecurityService::kRetryAttemptStartupDelay +
+          base::Seconds(1));
+
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+
+  EXPECT_EQ(tailored_security_service()->times_dialog_displayed(),
+            initial_times_displayed);
+}
+
+TEST_F(ChromeTailoredSecurityServiceTest,
+       WhenRetryNeededAndEnoughTimeHasPassedRetries) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+  int initial_times_displayed =
+      tailored_security_service()->times_dialog_displayed();
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::RETRY_NEEDED);
+  prefs()->SetTime(
+      prefs::kTailoredSecurityNextSyncFlowTimestamp,
+      base::Time::Now() +
+          ChromeTailoredSecurityService::kRetryAttemptStartupDelay -
+          base::Seconds(1));
+
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+
+  // Verify that notification was shown.
+  EXPECT_EQ(tailored_security_service()->times_dialog_displayed(),
+            initial_times_displayed + 1);
+}
+
+TEST_F(
+    ChromeTailoredSecurityServiceTest,
+    WhenRetryNeededAndEnoughTimeHasPassedUpdatesNextSyncFlowTimestampByNextAttemptDelay) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::RETRY_NEEDED);
+
+  prefs()->SetTime(
+      prefs::kTailoredSecurityNextSyncFlowTimestamp,
+      base::Time::Now() +
+          ChromeTailoredSecurityService::kRetryAttemptStartupDelay -
+          base::Seconds(1));
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+
+  EXPECT_EQ(prefs()->GetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp),
+            base::Time::Now() +
+                ChromeTailoredSecurityService::kRetryNextAttemptDelay);
+}
+
+TEST_F(
+    ChromeTailoredSecurityServiceTest,
+    WhenRetryNotSetAndNextSyncFlowNotSetSetsNextSyncFlowToWaitingIntervalFromNow) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::UNSET);
+  prefs()->SetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp, base::Time());
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+  EXPECT_EQ(prefs()->GetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp),
+            base::Time::Now() +
+                ChromeTailoredSecurityService::kWaitingPeriodInterval);
+}
+
+TEST_F(ChromeTailoredSecurityServiceTest,
+       WhenRetryNotSetAndNextSyncFlowHasPassedRunsRetry) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+
+  int initial_times_displayed =
+      tailored_security_service()->times_dialog_displayed();
+
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::UNSET);
+  prefs()->SetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp,
+                   base::Time::Now());
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+  // Verify that notification was shown.
+  EXPECT_EQ(tailored_security_service()->times_dialog_displayed(),
+            initial_times_displayed + 1);
+}
+
+TEST_F(ChromeTailoredSecurityServiceTest,
+       WhenRetryNotSetAndNextSyncFlowHasPassedSetsNextSyncFlowToTomorrow) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::UNSET);
+  prefs()->SetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp,
+                   base::Time::Now());
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+
+  // Next sync flow time should be tomorrow.
+  EXPECT_EQ(prefs()->GetTime(prefs::kTailoredSecurityNextSyncFlowTimestamp),
+            base::Time::Now() +
+                ChromeTailoredSecurityService::kRetryNextAttemptDelay);
+}
+
+TEST_F(ChromeTailoredSecurityServiceTest, WhenNoRetryNeededDoesNotRetry) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+  int initial_times_displayed =
+      tailored_security_service()->times_dialog_displayed();
+
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::NO_RETRY_NEEDED);
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+
+  // Verify that notification was not shown.
+  EXPECT_EQ(tailored_security_service()->times_dialog_displayed(),
+            initial_times_displayed);
+}
+
 TEST_F(ChromeTailoredSecurityServiceRetryForSyncUsersDisabledTest,
-       WhenRetryDisabledOnSuccessDoesNotUpdateRetryStatePref) {
+       OnSuccessDoesNotUpdateRetryStatePref) {
   SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
   auto original_value =
       prefs()->GetInteger(prefs::kTailoredSecuritySyncFlowRetryState);
@@ -491,4 +734,23 @@
             original_value);
 }
 
+TEST_F(ChromeTailoredSecurityServiceRetryForSyncUsersDisabledTest,
+       WhenRetryForSyncUsersIsDisabledDoesNotRunRetryLogicAfterStartupDelay) {
+  const GURL google_url("https://www.google.com");
+  AddTab(google_url);
+  SetAccountTailoredSecurityTimestamp(base::Time::Now());
+  tailored_security_service()->SetTailoredSecurityServiceValue(true);
+  SetSafeBrowsingState(prefs(), SafeBrowsingState::STANDARD_PROTECTION);
+  int initial_times_displayed =
+      tailored_security_service()->times_dialog_displayed();
+
+  prefs()->SetInteger(prefs::kTailoredSecuritySyncFlowRetryState,
+                      safe_browsing::RETRY_NEEDED);
+  task_environment_.FastForwardBy(
+      ChromeTailoredSecurityService::kRetryAttemptStartupDelay);
+
+  EXPECT_EQ(tailored_security_service()->times_dialog_displayed(),
+            initial_times_displayed);
+}
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/search_engines/template_url_fetcher_factory.cc b/chrome/browser/search_engines/template_url_fetcher_factory.cc
index da1b2d0..35f3884 100644
--- a/chrome/browser/search_engines/template_url_fetcher_factory.cc
+++ b/chrome/browser/search_engines/template_url_fetcher_factory.cc
@@ -42,8 +42,9 @@
 
 TemplateURLFetcherFactory::~TemplateURLFetcherFactory() = default;
 
-KeyedService* TemplateURLFetcherFactory::BuildServiceInstanceFor(
+std::unique_ptr<KeyedService>
+TemplateURLFetcherFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* profile) const {
-  return new TemplateURLFetcher(
+  return std::make_unique<TemplateURLFetcher>(
       TemplateURLServiceFactory::GetForProfile(static_cast<Profile*>(profile)));
 }
diff --git a/chrome/browser/search_engines/template_url_fetcher_factory.h b/chrome/browser/search_engines/template_url_fetcher_factory.h
index 8da7cac..7be3979 100644
--- a/chrome/browser/search_engines/template_url_fetcher_factory.h
+++ b/chrome/browser/search_engines/template_url_fetcher_factory.h
@@ -35,7 +35,7 @@
   ~TemplateURLFetcherFactory() override;
 
   // BrowserContextKeyedServiceFactory:
-  KeyedService* BuildServiceInstanceFor(
+  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
       content::BrowserContext* profile) const override;
 };
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
index cdf6893..08b1b0b1 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteController.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.omnibox.suggestions;
 
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
@@ -12,6 +11,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.lifetime.Destroyable;
+import org.chromium.chrome.browser.omnibox.OmniboxMetrics;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler.VoiceResult;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
@@ -184,6 +184,14 @@
         AutocompleteControllerJni.get().resetSession(mNativeController);
     }
 
+    private boolean hasValidNativeObjectRef(
+            AutocompleteMatch match, @VerificationPoint int reason) {
+        // Skip suggestions from cache.
+        OmniboxMetrics.recordUsedSuggestionFromCache(match.getNativeObjectRef() == 0L);
+        if (match.getNativeObjectRef() == 0L) return false;
+        return mAutocompleteResult.verifyCoherency(AutocompleteResult.NO_SUGGESTION_INDEX, reason);
+    }
+
     /**
      * Partially deletes an omnibox suggestion.
      * This call should be used by compound suggestion types (such as carousel) that host multiple
@@ -194,10 +202,8 @@
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     public void deleteMatchElement(AutocompleteMatch match, int elementIndex) {
         if (mNativeController == 0) return;
-        if (!mAutocompleteResult.verifyCoherency(
-                    AutocompleteResult.NO_SUGGESTION_INDEX, VerificationPoint.DELETE_MATCH)) {
-            return;
-        }
+        if (!hasValidNativeObjectRef(match, VerificationPoint.DELETE_MATCH)) return;
+
         // Skip suggestions from cache.
         if (match.getNativeObjectRef() == 0L) return;
         AutocompleteControllerJni.get().deleteMatchElement(
@@ -211,10 +217,8 @@
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     public void deleteMatch(AutocompleteMatch match) {
         if (mNativeController == 0) return;
-        if (!mAutocompleteResult.verifyCoherency(
-                    AutocompleteResult.NO_SUGGESTION_INDEX, VerificationPoint.DELETE_MATCH)) {
-            return;
-        }
+        if (!hasValidNativeObjectRef(match, VerificationPoint.DELETE_MATCH)) return;
+
         // Skip suggestions from cache.
         if (match.getNativeObjectRef() == 0L) return;
         AutocompleteControllerJni.get().deleteMatch(mNativeController, match.getNativeObjectRef());
@@ -258,8 +262,8 @@
             @NonNull GURL currentPageUrl, int pageClassification, long elapsedTimeSinceModified,
             int completedLength, @Nullable WebContents webContents) {
         if (mNativeController == 0) return;
-        // Skip suggestions from cache.
-        if (match.getNativeObjectRef() == 0L) return;
+        if (!hasValidNativeObjectRef(match, VerificationPoint.SELECT_MATCH)) return;
+
         AutocompleteControllerJni.get().onSuggestionSelected(mNativeController,
                 match.getNativeObjectRef(), suggestionLine, disposition, currentPageUrl.getSpec(),
                 pageClassification, elapsedTimeSinceModified, completedLength, webContents);
@@ -268,17 +272,18 @@
     /**
      * Called when the user touches down on a suggestion. Only called for search suggestions.
      *
-     * @param matchIndex The position at which the match is located.
-     * @param webContents The web contents for the tab where suggestion could be used.
-     * @return Whether or not a prefetch was started.
+     * @param match the match that received the touch
+     * @param matchIndex the vertical position at which the match is located
+     * @param webContents the web contents for the tab where suggestion could be used
+     * @return whether or not a prefetch was started
      */
-    public boolean onSuggestionTouchDown(int matchIndex, @Nullable WebContents webContents) {
+    public boolean onSuggestionTouchDown(
+            AutocompleteMatch match, int matchIndex, @Nullable WebContents webContents) {
         if (mNativeController == 0) return false;
-        if (!mAutocompleteResult.verifyCoherency(matchIndex, VerificationPoint.ON_TOUCH_MATCH)) {
-            return false;
-        }
+        if (!hasValidNativeObjectRef(match, VerificationPoint.ON_TOUCH_MATCH)) return false;
+
         return AutocompleteControllerJni.get().onSuggestionTouchDown(
-                mNativeController, matchIndex, webContents);
+                mNativeController, match.getNativeObjectRef(), matchIndex, webContents);
     }
 
     /**
@@ -340,10 +345,8 @@
             long elapsedTimeSinceInputChange, @Nullable String newQueryText,
             @Nullable List<String> newQueryParams) {
         if (mNativeController == 0) return null;
-        if (!mAutocompleteResult.verifyCoherency(
-                    AutocompleteResult.NO_SUGGESTION_INDEX, VerificationPoint.UPDATE_MATCH)) {
-            return null;
-        }
+        if (!hasValidNativeObjectRef(match, VerificationPoint.UPDATE_MATCH)) return null;
+
         // Skip suggestions from cache.
         if (match.getNativeObjectRef() == 0) return null;
         return AutocompleteControllerJni.get()
@@ -356,17 +359,16 @@
 
     /**
      * Retrieves matching tab for suggestion at specific index.
-     * TODO(crbug.com/1266558): move this to AutocompleteMatch object when Tab is no longer part
-     * of the //chrome/browser directory.
      *
-     * @param matchIndex Index of the suggestion to retrieve Tab info for.
-     * @return Tab that hosts matching URL.
+     * @param match the AutocompleteMatch to retrieve Tab info for
+     * @return tab that hosts matching URL
      */
     @Nullable
-    Tab getMatchingTabForSuggestion(int matchIndex) {
+    Tab getMatchingTabForSuggestion(AutocompleteMatch match) {
         if (mNativeController == 0) return null;
+        if (!hasValidNativeObjectRef(match, VerificationPoint.GET_MATCHING_TAB)) return null;
         return AutocompleteControllerJni.get().getMatchingTabForSuggestion(
-                mNativeController, matchIndex);
+                mNativeController, match.getNativeObjectRef());
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
@@ -384,8 +386,8 @@
                 long nativeAutocompleteMatch, int matchIndex, int disposition,
                 String currentPageUrl, int pageClassification, long elapsedTimeSinceModified,
                 int completedLength, WebContents webContents);
-        boolean onSuggestionTouchDown(
-                long nativeAutocompleteControllerAndroid, int matchIndex, WebContents webContents);
+        boolean onSuggestionTouchDown(long nativeAutocompleteControllerAndroid,
+                long nativeAutocompleteMatch, int matchIndex, WebContents webContents);
         void onOmniboxFocused(long nativeAutocompleteControllerAndroid, String omniboxText,
                 String currentUrl, int pageClassification, String currentTitle);
         void deleteMatchElement(long nativeAutocompleteControllerAndroid,
@@ -394,7 +396,8 @@
         GURL updateMatchDestinationURLWithAdditionalAssistedQueryStats(
                 long nativeAutocompleteControllerAndroid, long nativeAutocompleteMatch,
                 long elapsedTimeSinceInputChange, String newQueryText, String[] newQueryParams);
-        Tab getMatchingTabForSuggestion(long nativeAutocompleteControllerAndroid, int matchIndex);
+        Tab getMatchingTabForSuggestion(
+                long nativeAutocompleteControllerAndroid, long nativeAutocompleteMatch);
         void setVoiceMatches(long nativeAutocompleteControllerAndroid, String[] matches,
                 float[] confidenceScores);
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
index fc47e87..ca4b5a8b 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediator.java
@@ -461,7 +461,8 @@
         mNumTouchDownEventForwardedInOmniboxSession++;
         WebContents webContents =
                 mDataProvider.hasTab() ? mDataProvider.getTab().getWebContents() : null;
-        boolean wasPrefetchStarted = mAutocomplete.onSuggestionTouchDown(matchIndex, webContents);
+        boolean wasPrefetchStarted =
+                mAutocomplete.onSuggestionTouchDown(suggestion, matchIndex, webContents);
         if (wasPrefetchStarted) {
             mNumPrefetchesStartedInOmniboxSession++;
             mLastPrefetchStartedSuggestion = suggestion;
@@ -503,17 +504,17 @@
     }
 
     @Override
-    public void onSwitchToTab(AutocompleteMatch suggestion, int matchIndex) {
-        if (maybeSwitchToTab(matchIndex)) {
-            recordMetrics(suggestion, matchIndex, WindowOpenDisposition.SWITCH_TO_TAB);
+    public void onSwitchToTab(AutocompleteMatch match, int matchIndex) {
+        if (maybeSwitchToTab(match)) {
+            recordMetrics(match, matchIndex, WindowOpenDisposition.SWITCH_TO_TAB);
         } else {
-            onSuggestionClicked(suggestion, matchIndex, suggestion.getUrl());
+            onSuggestionClicked(match, matchIndex, match.getUrl());
         }
     }
 
     @VisibleForTesting
-    public boolean maybeSwitchToTab(int matchIndex) {
-        Tab tab = mAutocomplete.getMatchingTabForSuggestion(matchIndex);
+    public boolean maybeSwitchToTab(AutocompleteMatch match) {
+        Tab tab = mAutocomplete.getMatchingTabForSuggestion(match);
         if (tab == null || !mTabWindowManagerSupplier.hasValue()) return false;
 
         // When invoked directly from a browser, we want to trigger switch to tab animation.
@@ -584,7 +585,18 @@
     public void showDeleteDialog(@NonNull AutocompleteMatch suggestion, @NonNull String titleText,
             Runnable deleteAction) {
         RecordUserAction.record("MobileOmniboxDeleteGesture");
+
+        // Prevent updates to the shown omnibox suggestions list while the dialog is open.
+        // Each update invalidates previous result set, making it impossible to perform the delete
+        // action (there is no native match to delete). Calling `stopAutocomplete()` here will
+        // ensure that suggestions don't change the moment the User is presented with the dialog,
+        // allowing us to complete the deletion.
+        stopAutocomplete(/*clear=*/false);
         if (!suggestion.isDeletable()) return;
+        // Do not attempt to delete matches that have been detached from their native counterpart.
+        // These matches likely come from cache, or the delete request came for a previous set of
+        // matches.
+        if (suggestion.getNativeObjectRef() == 0) return;
 
         ModalDialogManager manager = mModalDialogManagerSupplier.get();
         if (manager == null) {
@@ -630,8 +642,6 @@
                         .with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, true)
                         .build();
 
-        // Prevent updates to the shown omnibox suggestions list while the dialog is open.
-        stopAutocomplete(false);
         manager.showDialog(mDeleteDialogModel, ModalDialogManager.ModalDialogType.APP);
     }
 
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
index 49c8d35..293defd 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteMediatorUnitTest.java
@@ -693,8 +693,8 @@
         mMediator.setAutocompleteProfile(mProfile);
 
         // There is no Tab to switch to.
-        doReturn(null).when(mAutocompleteController).getMatchingTabForSuggestion(anyInt());
-        Assert.assertFalse(mMediator.maybeSwitchToTab(0));
+        doReturn(null).when(mAutocompleteController).getMatchingTabForSuggestion(any());
+        Assert.assertFalse(mMediator.maybeSwitchToTab(null));
     }
 
     @Test
@@ -703,8 +703,8 @@
         mMediator.setAutocompleteProfile(mProfile);
 
         // We have a tab, but no tab manager.
-        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(anyInt());
-        Assert.assertFalse(mMediator.maybeSwitchToTab(0));
+        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
+        Assert.assertFalse(mMediator.maybeSwitchToTab(null));
     }
 
     @Test
@@ -713,11 +713,11 @@
         mMediator.setAutocompleteProfile(mProfile);
 
         // We have a tab, and tab manager. The tab is part of the stopped activity.
-        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(anyInt());
+        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
         mTabWindowManagerSupplier.set(mTabManager);
         doReturn(mMockWindowAndroid).when(mTab).getWindowAndroid();
         doReturn(ActivityState.STOPPED).when(mMockWindowAndroid).getActivityState();
-        Assert.assertTrue(mMediator.maybeSwitchToTab(0));
+        Assert.assertTrue(mMediator.maybeSwitchToTab(null));
     }
 
     @Test
@@ -728,12 +728,12 @@
         // We have a tab, and tab manager. The tab is part of the running activity.
         // The tab is not a part of the model though (eg. it has just been closed).
         // https://crbug.com/1300447
-        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(anyInt());
+        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
         mTabWindowManagerSupplier.set(mTabManager);
         doReturn(mMockWindowAndroid).when(mTab).getWindowAndroid();
         doReturn(ActivityState.RESUMED).when(mMockWindowAndroid).getActivityState();
         doReturn(null).when(mTabManager).getTabModelForTab(any());
-        Assert.assertFalse(mMediator.maybeSwitchToTab(0));
+        Assert.assertFalse(mMediator.maybeSwitchToTab(null));
     }
 
     @Test
@@ -744,7 +744,7 @@
         // We have a tab, and tab manager. The tab is part of the running activity.
         // The tab reports association with an existing model, but the model thinks otherwise.
         // https://crbug.com/1300447
-        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(anyInt());
+        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
         mTabWindowManagerSupplier.set(mTabManager);
         doReturn(mMockWindowAndroid).when(mTab).getWindowAndroid();
         doReturn(ActivityState.RESUMED).when(mMockWindowAndroid).getActivityState();
@@ -753,7 +753,7 @@
         // Make sure that this indeed returns no association.
         Assert.assertEquals(
                 TabModel.INVALID_TAB_INDEX, TabModelUtils.getTabIndexById(mTabModel, mTab.getId()));
-        Assert.assertFalse(mMediator.maybeSwitchToTab(0));
+        Assert.assertFalse(mMediator.maybeSwitchToTab(null));
     }
 
     @Test
@@ -763,14 +763,14 @@
 
         // We have a tab, and tab manager. The tab is part of the running activity.
         // The tab reports association with an existing model; the model confirms this.
-        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(anyInt());
+        doReturn(mTab).when(mAutocompleteController).getMatchingTabForSuggestion(any());
         mTabWindowManagerSupplier.set(mTabManager);
         doReturn(mMockWindowAndroid).when(mTab).getWindowAndroid();
         doReturn(ActivityState.RESUMED).when(mMockWindowAndroid).getActivityState();
         doReturn(mTabModel).when(mTabManager).getTabModelForTab(any());
         doReturn(1).when(mTabModel).getCount();
         doReturn(mTab).when(mTabModel).getTabAt(anyInt());
-        Assert.assertTrue(mMediator.maybeSwitchToTab(0));
+        Assert.assertTrue(mMediator.maybeSwitchToTab(null));
     }
 
     /**
@@ -1010,7 +1010,8 @@
                         .build();
         mMediator.setAutocompleteProfile(mProfile);
         when(mLocationBarDataProvider.hasTab()).thenReturn(false);
-        when(mAutocompleteController.onSuggestionTouchDown(0, null)).thenReturn(true);
+        when(mAutocompleteController.onSuggestionTouchDown(any(), anyInt(), any()))
+                .thenReturn(true);
         setSuggestionNativeObjectRef();
         mMediator.onNativeInitialized();
 
@@ -1018,7 +1019,8 @@
         mMediator.onSuggestionTouchDown(mSuggestionsList.get(0), /*matchIndex=*/0);
 
         // Ensure that no extra signals are sent to native.
-        verify(mAutocompleteController, times(1)).onSuggestionTouchDown(anyInt(), any());
+        verify(mAutocompleteController, times(1))
+                .onSuggestionTouchDown(mSuggestionsList.get(0), 0, null);
 
         // Simulate a navigation to the suggestion that was prefetched. This causes metrics about
         // prefetch to be recorded.
@@ -1046,7 +1048,8 @@
                         .build();
         mMediator.setAutocompleteProfile(mProfile);
         when(mLocationBarDataProvider.hasTab()).thenReturn(false);
-        when(mAutocompleteController.onSuggestionTouchDown(0, null)).thenReturn(true);
+        when(mAutocompleteController.onSuggestionTouchDown(any(), anyInt(), any()))
+                .thenReturn(true);
         setSuggestionNativeObjectRef();
         mMediator.onNativeInitialized();
 
@@ -1054,7 +1057,8 @@
         mMediator.onSuggestionTouchDown(mSuggestionsList.get(0), /*matchIndex=*/0);
 
         // Ensure that no extra signals are sent to native.
-        verify(mAutocompleteController, times(1)).onSuggestionTouchDown(anyInt(), any());
+        verify(mAutocompleteController, times(1))
+                .onSuggestionTouchDown(mSuggestionsList.get(0), 0, null);
 
         // Simulate a navigation to a suggestion that was not prefetched. This causes metrics about
         // prefetch to be recorded.
@@ -1086,13 +1090,15 @@
         mMediator.onNativeInitialized();
 
         // This will simulate the touch down trigger not starting a prefetch.
-        when(mAutocompleteController.onSuggestionTouchDown(0, null)).thenReturn(false);
+        when(mAutocompleteController.onSuggestionTouchDown(any(), anyInt(), any()))
+                .thenReturn(false);
 
         // Simulate a suggestion being touched down.
         mMediator.onSuggestionTouchDown(mSuggestionsList.get(0), /*matchIndex=*/0);
 
         // Ensure that no extra signals are sent to native.
-        verify(mAutocompleteController, times(1)).onSuggestionTouchDown(anyInt(), any());
+        verify(mAutocompleteController, times(1))
+                .onSuggestionTouchDown(mSuggestionsList.get(0), 0, null);
 
         // Simulate a navigation to the suggestion that was not prefetched. This causes metrics
         // about prefetch to be recorded.
@@ -1121,7 +1127,8 @@
                         .build();
         mMediator.setAutocompleteProfile(mProfile);
         when(mLocationBarDataProvider.hasTab()).thenReturn(false);
-        when(mAutocompleteController.onSuggestionTouchDown(anyInt(), any())).thenReturn(true);
+        when(mAutocompleteController.onSuggestionTouchDown(any(), anyInt(), any()))
+                .thenReturn(true);
         setSuggestionNativeObjectRef();
         mMediator.onNativeInitialized();
 
@@ -1136,7 +1143,7 @@
         // Ensure that no extra signals are sent to native.
         verify(mAutocompleteController,
                 times(OmniboxFeatures.DEFAULT_MAX_PREFETCHES_PER_OMNIBOX_SESSION))
-                .onSuggestionTouchDown(anyInt(), any());
+                .onSuggestionTouchDown(any(), anyInt(), any());
 
         // Ends the omnibox session to reset state of touch down prefetch, and record metrics.
         mMediator.onUrlFocusChange(false);
@@ -1145,7 +1152,7 @@
         mMediator.onSuggestionTouchDown(mSuggestionsList.get(0), 0);
         verify(mAutocompleteController,
                 times(OmniboxFeatures.DEFAULT_MAX_PREFETCHES_PER_OMNIBOX_SESSION + 1))
-                .onSuggestionTouchDown(anyInt(), any());
+                .onSuggestionTouchDown(any(), anyInt(), any());
         mMediator.onUrlFocusChange(false);
 
         histogramWatcher.assertExpected();
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
index 33653d499..5c5658f 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.cc
@@ -15,7 +15,10 @@
 #include "ash/shell.h"
 #include "ash/system/video_conference/fake_video_conference_tray_controller.h"
 #include "ash/system/video_conference/video_conference_tray_controller.h"
+#include "base/check.h"
+#include "base/check_op.h"
 #include "base/command_line.h"
+#include "base/functional/callback.h"
 #include "base/scoped_observation.h"
 #include "base/trace_event/trace_event.h"
 #include "chrome/browser/ash/app_list/app_list_client_impl.h"
@@ -97,6 +100,10 @@
 #include "chrome/browser/exo_parts.h"
 #endif
 
+namespace {
+ChromeBrowserMainExtraPartsAsh* g_instance = nullptr;
+}  // namespace
+
 namespace internal {
 
 // Creates a ChromeShelfController on the first active session notification.
@@ -147,9 +154,20 @@
 
 }  // namespace internal
 
-ChromeBrowserMainExtraPartsAsh::ChromeBrowserMainExtraPartsAsh() = default;
+// static
+ChromeBrowserMainExtraPartsAsh* ChromeBrowserMainExtraPartsAsh::Get() {
+  return g_instance;
+}
 
-ChromeBrowserMainExtraPartsAsh::~ChromeBrowserMainExtraPartsAsh() = default;
+ChromeBrowserMainExtraPartsAsh::ChromeBrowserMainExtraPartsAsh() {
+  CHECK(!g_instance);
+  g_instance = this;
+}
+
+ChromeBrowserMainExtraPartsAsh::~ChromeBrowserMainExtraPartsAsh() {
+  CHECK_EQ(g_instance, this);
+  g_instance = nullptr;
+}
 
 void ChromeBrowserMainExtraPartsAsh::PreCreateMainMessageLoop() {
   user_profile_loaded_observer_ = std::make_unique<UserProfileLoadedObserver>();
@@ -352,6 +370,11 @@
 
 void ChromeBrowserMainExtraPartsAsh::PostBrowserStart() {
   mobile_data_notifications_ = std::make_unique<MobileDataNotifications>();
+
+  did_post_browser_start_ = true;
+  if (post_browser_start_callback_) {
+    std::move(post_browser_start_callback_).Run();
+  }
 }
 
 void ChromeBrowserMainExtraPartsAsh::PostMainMessageLoopRun() {
diff --git a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h
index 25a592a..b0ee86e9 100644
--- a/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h
+++ b/chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h
@@ -6,7 +6,9 @@
 #define CHROME_BROWSER_UI_ASH_CHROME_BROWSER_MAIN_EXTRA_PARTS_ASH_H_
 
 #include <memory>
+#include <utility>
 
+#include "base/functional/callback.h"
 #include "chrome/browser/chrome_browser_main_extra_parts.h"
 #include "chrome/browser/ui/ash/in_session_auth_token_provider_impl.h"
 #include "chrome/common/buildflags.h"
@@ -74,6 +76,10 @@
 // intitialization (e.g. initialization of chrome/browser/ui/ash classes).
 class ChromeBrowserMainExtraPartsAsh : public ChromeBrowserMainExtraParts {
  public:
+  // Returns the single instance. Returns null early in startup and late in
+  // shutdown.
+  static ChromeBrowserMainExtraPartsAsh* Get();
+
   ChromeBrowserMainExtraPartsAsh();
 
   ChromeBrowserMainExtraPartsAsh(const ChromeBrowserMainExtraPartsAsh&) =
@@ -90,6 +96,12 @@
   void PostBrowserStart() override;
   void PostMainMessageLoopRun() override;
 
+  void set_post_browser_start_callback(base::OnceClosure callback) {
+    post_browser_start_callback_ = std::move(callback);
+  }
+
+  bool did_post_browser_start() const { return did_post_browser_start_; }
+
  private:
   class UserProfileLoadedObserver;
 
@@ -154,6 +166,12 @@
   std::unique_ptr<MobileDataNotifications> mobile_data_notifications_;
   std::unique_ptr<ash::NightLightClient> night_light_client_;
   std::unique_ptr<AmbientClientImpl> ambient_client_;
+
+  // Boolean that is set to true after PostBrowserStart() executes.
+  bool did_post_browser_start_ = false;
+
+  // Callback invoked at the end of PostBrowserStart().
+  base::OnceClosure post_browser_start_callback_;
 };
 
 #endif  // CHROME_BROWSER_UI_ASH_CHROME_BROWSER_MAIN_EXTRA_PARTS_ASH_H_
diff --git a/chrome/browser/ui/ash/quick_settings_integration_test.cc b/chrome/browser/ui/ash/quick_settings_integration_test.cc
index 0240ef7..7b18dbe 100644
--- a/chrome/browser/ui/ash/quick_settings_integration_test.cc
+++ b/chrome/browser/ui/ash/quick_settings_integration_test.cc
@@ -9,12 +9,18 @@
 #include "ash/system/model/system_tray_model.h"
 #include "base/test/gtest_tags.h"
 #include "base/test/scoped_feature_list.h"
-#include "chrome/browser/ash/crosapi/browser_manager_observer.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/base/chromeos/crosier/interactive_ash_test.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/aura/env_observer.h"
+#include "ui/aura/window.h"
+#include "ui/aura/window_observer.h"
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/interaction/state_observer.h"
+#include "ui/base/l10n/l10n_util.h"
 
 namespace ash {
 namespace {
@@ -88,28 +94,55 @@
 // Testing with Lacros requires a VM or DUT.
 #if BUILDFLAG(IS_CHROMEOS_DEVICE)
 
-// Observes the crosapi browser manager to detect Lacros startup. Signals with a
-// boolean that is true if lacros is running, false otherwise.
-class TestBrowserManagerObserver : public ui::test::ObservationStateObserver<
-                                       bool,
-                                       crosapi::BrowserManager,
-                                       crosapi::BrowserManagerObserver> {
+// Observes the aura environment to detect the Lacros window title.
+class LacrosWindowTitleObserver
+    : public ui::test::ObservationStateObserver<std::u16string,
+                                                aura::Env,
+                                                aura::EnvObserver>,
+      public aura::WindowObserver {
  public:
-  TestBrowserManagerObserver()
-      : ObservationStateObserver(crosapi::BrowserManager::Get()) {}
+  LacrosWindowTitleObserver()
+      : ObservationStateObserver(aura::Env::GetInstance()) {}
+
+  ~LacrosWindowTitleObserver() override {
+    if (lacros_window_) {
+      lacros_window_->RemoveObserver(this);
+      lacros_window_ = nullptr;
+    }
+  }
 
   // ui::test::ObservationStateObserver:
-  bool GetStateObserverInitialState() const override {
+  std::u16string GetStateObserverInitialState() const override {
     // Tests in this suite do not have a lacros browser window open at start.
-    return false;
+    return {};
   }
 
-  // crosapi::BrowserManagerObserver:
-  void OnStateChanged() override {
-    const bool is_lacros_running =
-        crosapi::BrowserManager::Get()->IsRunningOrWillRun();
-    OnStateObserverStateChanged(is_lacros_running);
+  // aura::EnvObserver:
+  void OnWindowInitialized(aura::Window* window) override {
+    CHECK(window);
+    if (crosapi::browser_util::IsLacrosWindow(window)) {
+      lacros_window_ = window;
+      OnStateObserverStateChanged(lacros_window_->GetTitle());
+      // Observe for window title changes (the initial window title is set
+      // asynchronously).
+      lacros_window_->AddObserver(this);
+    }
   }
+
+  // aura::WindowObserver:
+  void OnWindowTitleChanged(aura::Window* window) override {
+    CHECK_EQ(window, lacros_window_);
+    OnStateObserverStateChanged(lacros_window_->GetTitle());
+  }
+
+  void OnWindowDestroying(aura::Window* window) override {
+    CHECK_EQ(window, lacros_window_);
+    lacros_window_->RemoveObserver(this);
+    lacros_window_ = nullptr;
+  }
+
+ private:
+  raw_ptr<aura::Window> lacros_window_ = nullptr;
 };
 
 // Tests of ash quick settings that assume the primary browser is Lacros.
@@ -119,6 +152,12 @@
     feature_list_.InitAndEnableFeature(features::kLacrosOnly);
   }
 
+  // InteractiveAshTest:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    QuickSettingsIntegrationTest::SetUpCommandLine(command_line);
+    SetUpCommandLineForLacros(command_line);
+  }
+
  private:
   base::test::ScopedFeatureList feature_list_;
 };
@@ -136,7 +175,14 @@
       DeviceEnterpriseInfo{"example.com", /*active_directory_managed=*/false,
                            ManagementDeviceMode::kChromeEnterprise});
 
-  DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(TestBrowserManagerObserver, kLacrosState);
+  DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(LacrosWindowTitleObserver,
+                                      kLacrosWindowTitle);
+
+  // The test will launch Lacros, so ensure the wayland server is
+  // running and crosapi is ready.
+  WaitForAshFullyStarted();
+  ASSERT_TRUE(crosapi::CrosapiManager::Get());
+  ASSERT_TRUE(crosapi::CrosapiManager::Get()->crosapi_ash());
 
   RunTestSequence(
       Log("Opening quick settings bubble"),
@@ -144,14 +190,13 @@
       WaitForShow(kQuickSettingsViewElementId),
 
       Log("Pressing enterprise managed view"),
-      ObserveState(kLacrosState,
-                   std::make_unique<TestBrowserManagerObserver>()),
+      ObserveState(kLacrosWindowTitle,
+                   std::make_unique<LacrosWindowTitleObserver>()),
       PressButton(kEnterpriseManagedView),
 
-      Log("Waiting for the lacros browser to start"),
-      WaitForState(kLacrosState, true),
-
-      // TODO(jamescook): Verify that Lacros loaded the management URL.
+      Log("Waiting for the lacros browser to load the management page"),
+      WaitForState(kLacrosWindowTitle,
+                   l10n_util::GetStringUTF16(IDS_MANAGEMENT_TITLE)),
 
       Log("Test complete"));
 }
diff --git a/chrome/browser/ui/ash/shelf/app_service/app_service_promise_app_shelf_context_menu_browsertest.cc b/chrome/browser/ui/ash/shelf/app_service/app_service_promise_app_shelf_context_menu_browsertest.cc
index d052fadb..e2ef394 100644
--- a/chrome/browser/ui/ash/shelf/app_service/app_service_promise_app_shelf_context_menu_browsertest.cc
+++ b/chrome/browser/ui/ash/shelf/app_service/app_service_promise_app_shelf_context_menu_browsertest.cc
@@ -35,8 +35,6 @@
   void AddTestPromiseApp(const apps::PackageId& package_id) {
     apps::PromiseAppPtr promise_app =
         std::make_unique<apps::PromiseApp>(package_id);
-    promise_app->progress = 0.7;
-    promise_app->name = "Name";
     promise_app->should_show = true;
     apps::AppServiceProxyFactory::GetForProfile(browser()->profile())
         ->PromiseAppRegistryCache()
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
index 8b6f62e..3cdc2db 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
@@ -1062,15 +1062,14 @@
     return;
   }
   ash::ShelfItem item = model_->items()[index];
-  if (update.Name().has_value()) {
-    item.title = base::UTF8ToUTF16(update.Name().value());
-  }
   if (update.Progress().has_value()) {
     item.progress = update.Progress().value();
   }
   if (update.StatusChanged()) {
     item.app_status =
         ShelfControllerHelper::ConvertPromiseStatusToAppStatus(update.Status());
+    item.title = base::UTF8ToUTF16(
+        ShelfControllerHelper::GetLabelForPromiseStatus(update.Status()));
   }
   model_->Set(index, item);
 }
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index d776149..6ef098fc 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -6258,7 +6258,6 @@
       apps::PackageId(apps::AppType::kArc, "com.example.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
-  promise_app->name = "Name";
   promise_app->status = apps::PromiseStatus::kPending;
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
@@ -6271,22 +6270,24 @@
   EXPECT_TRUE(model_->IsAppPinned(package_id.ToString()));
   ash::ShelfID id(package_id.ToString());
   const ash::ShelfItem* item = shelf_controller_->GetItem(id);
-  EXPECT_EQ(item->title, std::u16string(u"Name"));
+  EXPECT_EQ(item->title,
+            base::UTF8ToUTF16(ShelfControllerHelper::GetLabelForPromiseStatus(
+                apps::PromiseStatus::kPending)));
   EXPECT_EQ(item->progress, 0);
   EXPECT_EQ(item->app_status, ash::AppStatus::kPending);
 
   // Push an progress and status update to the promise app.
   apps::PromiseAppPtr update = std::make_unique<apps::PromiseApp>(package_id);
-  update->name = "NewName";
   update->progress = 0.3;
   update->status = apps::PromiseStatus::kInstalling;
   cache()->OnPromiseApp(std::move(update));
 
   // Verify that the shelf item has updated details.
-  const ash::ShelfItem* item_after_update = shelf_controller_->GetItem(id);
-  EXPECT_EQ(item_after_update->title, std::u16string(u"NewName"));
-  EXPECT_EQ(item_after_update->progress, 0.3f);
-  EXPECT_EQ(item_after_update->app_status, ash::AppStatus::kInstalling);
+  EXPECT_EQ(item->title,
+            base::UTF8ToUTF16(ShelfControllerHelper::GetLabelForPromiseStatus(
+                apps::PromiseStatus::kInstalling)));
+  EXPECT_EQ(item->progress, 0.3f);
+  EXPECT_EQ(item->app_status, ash::AppStatus::kInstalling);
 }
 
 TEST_F(ChromeShelfControllerPromiseAppsTest,
@@ -6296,7 +6297,7 @@
       apps::PackageId(apps::AppType::kArc, "main.package.for.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
-  promise_app->name = "Name";
+  promise_app->status = apps::PromiseStatus::kPending;
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
 
@@ -6306,7 +6307,7 @@
       apps::PackageId(apps::AppType::kArc, "other.package");
   apps::PromiseAppPtr other_promise_app =
       std::make_unique<apps::PromiseApp>(other_package_id);
-  other_promise_app->name = "Other";
+  other_promise_app->status = apps::PromiseStatus::kPending;
   other_promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(other_promise_app));
 
@@ -6315,31 +6316,28 @@
   PinAppWithIDToShelf(package_id.ToString());
   PinAppWithIDToShelf(other_package_id.ToString());
 
-  // Verify the name of the main shelf item.
+  // Verify the status of the main shelf item.
   EXPECT_TRUE(model_->IsAppPinned(package_id.ToString()));
   ash::ShelfID id(package_id.ToString());
   const ash::ShelfItem* item = shelf_controller_->GetItem(id);
-  EXPECT_EQ(item->title, std::u16string(u"Name"));
+  EXPECT_EQ(item->app_status, ash::AppStatus::kPending);
 
-  // Verify the name of the other shelf item.
+  // Verify the status of the other shelf item.
   EXPECT_TRUE(model_->IsAppPinned(other_package_id.ToString()));
   ash::ShelfID other_id(other_package_id.ToString());
   const ash::ShelfItem* other_item = shelf_controller_->GetItem(other_id);
-  EXPECT_EQ(other_item->title, std::u16string(u"Other"));
+  EXPECT_EQ(other_item->app_status, ash::AppStatus::kPending);
 
   // Push an update to the main promise app.
   apps::PromiseAppPtr update = std::make_unique<apps::PromiseApp>(package_id);
-  update->name = "NewName";
+  update->status = apps::PromiseStatus::kInstalling;
   cache()->OnPromiseApp(std::move(update));
 
-  // Verify that the main shelf item has an updated name.
-  const ash::ShelfItem* item_after_update = shelf_controller_->GetItem(id);
-  EXPECT_EQ(item_after_update->title, std::u16string(u"NewName"));
+  // Verify that the main shelf item has an updated status.
+  EXPECT_EQ(item->app_status, ash::AppStatus::kInstalling);
 
   // Verify that the other shelf item remains the same.
-  const ash::ShelfItem* other_item_after_update =
-      shelf_controller_->GetItem(other_id);
-  EXPECT_EQ(other_item_after_update->title, std::u16string(u"Other"));
+  EXPECT_EQ(other_item->app_status, ash::AppStatus::kPending);
 }
 
 TEST_F(ChromeShelfControllerPromiseAppsTest, ShelfItemFetchesAndUpdatesIcon) {
@@ -6348,7 +6346,6 @@
       apps::PackageId(apps::AppType::kArc, "com.example.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
-  promise_app->name = "Name";
   promise_app->status = apps::PromiseStatus::kPending;
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
@@ -6405,7 +6402,6 @@
   apps::PackageId package_id(app_type, identifier);
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
-  promise_app->name = "Name";
   promise_app->progress = 0.9;
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
@@ -6444,7 +6440,6 @@
       apps::PackageId(apps::AppType::kArc, "com.example.test");
   apps::PromiseAppPtr promise_app =
       std::make_unique<apps::PromiseApp>(package_id);
-  promise_app->name = "Name";
   promise_app->should_show = true;
   cache()->OnPromiseApp(std::move(promise_app));
 
diff --git a/chrome/browser/ui/ash/shelf/shelf_controller_helper.cc b/chrome/browser/ui/ash/shelf/shelf_controller_helper.cc
index f928954..c4c7ad1 100644
--- a/chrome/browser/ui/ash/shelf/shelf_controller_helper.cc
+++ b/chrome/browser/ui/ash/shelf/shelf_controller_helper.cc
@@ -47,6 +47,10 @@
 
 namespace {
 
+// TODO(b/297453039): Replace with correct UXW when available.
+constexpr char kPendingString[] = "Waiting...";
+constexpr char kInstallingString[] = "Installing...";
+
 constexpr float kProgressNone = 0;
 constexpr float kProgressNotApplicable = -1;
 
@@ -68,6 +72,18 @@
 
 ShelfControllerHelper::~ShelfControllerHelper() {}
 
+std::string ShelfControllerHelper::GetLabelForPromiseStatus(
+    apps::PromiseStatus status) {
+  switch (status) {
+    case apps::PromiseStatus::kUnknown:
+    case apps::PromiseStatus::kPending:
+      return kPendingString;
+    case apps::PromiseStatus::kInstalling:
+    case apps::PromiseStatus::kRemove:
+      return kInstallingString;
+  }
+}
+
 // static
 std::u16string ShelfControllerHelper::GetAppTitle(Profile* profile,
                                                   const std::string& app_id) {
@@ -182,12 +198,11 @@
       apps::AppServiceProxyFactory::GetForProfile(profile)
           ->PromiseAppRegistryCache()
           ->GetPromiseAppForStringPackageId(string_package_id);
-  if (!promise_app || !promise_app->name.has_value() ||
-      promise_app->name->empty()) {
+  if (!promise_app) {
     return std::u16string();
   }
 
-  return base::UTF8ToUTF16(promise_app->name.value());
+  return base::UTF8ToUTF16(GetLabelForPromiseStatus(promise_app->status));
 }
 
 // static
diff --git a/chrome/browser/ui/ash/shelf/shelf_controller_helper.h b/chrome/browser/ui/ash/shelf/shelf_controller_helper.h
index fc824b0c..1471a90 100644
--- a/chrome/browser/ui/ash/shelf/shelf_controller_helper.h
+++ b/chrome/browser/ui/ash/shelf/shelf_controller_helper.h
@@ -35,6 +35,10 @@
 
   ~ShelfControllerHelper() override;
 
+  // Get the item label that should be shown for the specified promise app
+  // status.
+  static std::string GetLabelForPromiseStatus(apps::PromiseStatus status);
+
   // Helper function to return the title associated with |app_id|.
   // Returns an empty title if no matching extension can be found.
   static std::u16string GetAppTitle(Profile* profile,
diff --git a/chrome/browser/ui/browser_focus_uitest.cc b/chrome/browser/ui/browser_focus_uitest.cc
index d0bc18d..782cb19 100644
--- a/chrome/browser/ui/browser_focus_uitest.cc
+++ b/chrome/browser/ui/browser_focus_uitest.cc
@@ -318,7 +318,7 @@
   // TODO(https://crbug.com/1446127): Re-enable when child widget focus manager
   // relationship is fixed.
 #if BUILDFLAG(IS_MAC)
-  if (base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() >= 13) {
     GTEST_SKIP() << "Broken on macOS 13: https://crbug.com/1446127";
   }
 #endif
diff --git a/chrome/browser/ui/cocoa/accelerators_cocoa.mm b/chrome/browser/ui/cocoa/accelerators_cocoa.mm
index 9bfbe01..243f90e6 100644
--- a/chrome/browser/ui/cocoa/accelerators_cocoa.mm
+++ b/chrome/browser/ui/cocoa/accelerators_cocoa.mm
@@ -117,7 +117,7 @@
   int modifiers = ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN;
 
   // The default keyboard accelerator for Enter Full Screen changed in macOS 12.
-  if (base::mac::IsAtLeastOS12()) {
+  if (base::mac::MacOSMajorVersion() >= 12) {
     modifiers = ui::EF_FUNCTION_DOWN;
   }
 
diff --git a/chrome/browser/ui/cocoa/main_menu_builder.mm b/chrome/browser/ui/cocoa/main_menu_builder.mm
index 698dc21..bcf4697b 100644
--- a/chrome/browser/ui/cocoa/main_menu_builder.mm
+++ b/chrome/browser/ui/cocoa/main_menu_builder.mm
@@ -273,7 +273,7 @@
                 Item(IDS_ENTER_FULLSCREEN_MAC)
                     .action(@selector(toggleFullScreen:))
                     .is_alternate()
-                    .remove_if(base::mac::IsAtMostOS11())
+                    .remove_if(base::mac::MacOSMajorVersion() <= 11)
                     .key_equivalent(@"f", NSEventModifierFlagCommand |
                                               NSEventModifierFlagControl),
                 Item(IDS_TEXT_DEFAULT_MAC).command_id(IDC_ZOOM_NORMAL),
diff --git a/chrome/browser/ui/side_panel/read_anything/read_anything_side_panel_controller_utils.h b/chrome/browser/ui/side_panel/read_anything/read_anything_side_panel_controller_utils.h
index 6f45bee..a25b197 100644
--- a/chrome/browser/ui/side_panel/read_anything/read_anything_side_panel_controller_utils.h
+++ b/chrome/browser/ui/side_panel/read_anything/read_anything_side_panel_controller_utils.h
@@ -7,13 +7,8 @@
 
 class Browser;
 
-namespace content {
-class WebContents;
-}  // namespace content
-
 // Used for reading mode option in context menu.
 void ShowReadAnythingSidePanel(Browser* browser);
 bool IsReadAnythingEntryShowing(Browser* browser);
-void CreateAndRegisterEntry(content::WebContents* web_contents);
 
 #endif  // CHROME_BROWSER_UI_SIDE_PANEL_READ_ANYTHING_READ_ANYTHING_SIDE_PANEL_CONTROLLER_UTILS_H_
diff --git a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_interactive_uitest.cc b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_interactive_uitest.cc
index d35e84b..f1691b5 100644
--- a/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_interactive_uitest.cc
+++ b/chrome/browser/ui/views/autofill/payments/offer_notification_bubble_views_interactive_uitest.cc
@@ -9,6 +9,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/commerce/shopping_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/page_action/page_action_icon_type.h"
@@ -27,6 +28,9 @@
 #include "components/autofill/core/browser/ui/payments/payments_bubble_closed_reasons.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
 #include "components/commerce/core/commerce_feature_list.h"
+#include "components/commerce/core/mock_shopping_service.h"
+#include "components/commerce/core/test_utils.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -75,6 +79,22 @@
   OfferNotificationBubbleViewsInteractiveUiTest& operator=(
       const OfferNotificationBubbleViewsInteractiveUiTest&) = delete;
 
+  void SetUpInProcessBrowserTestFixture() override {
+    create_services_subscription_ =
+        BrowserContextDependencyManager::GetInstance()
+            ->RegisterCreateServicesCallbackForTesting(base::BindRepeating(
+                &OfferNotificationBubbleViewsInteractiveUiTest::
+                    OnWillCreateBrowserContextServices,
+                base::Unretained(this)));
+  }
+
+  void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
+    commerce::ShoppingServiceFactory::GetInstance()->SetTestingFactory(
+        context, base::BindRepeating([](content::BrowserContext* context) {
+          return commerce::MockShoppingService::Build();
+        }));
+  }
+
   void ShowBubbleForOfferAndVerify() {
     switch (test_offer_type_) {
       case AutofillOfferData::OfferType::GPAY_CARD_LINKED_OFFER:
@@ -170,6 +190,7 @@
   TestAutofillClock test_clock_;
   const AutofillOfferData::OfferType test_offer_type_;
   base::test::ScopedFeatureList feature_list_;
+  base::CallbackListSubscription create_services_subscription_;
 };
 
 // TODO(https://crbug.com/1334806): Split parameterized tests that are
@@ -719,4 +740,48 @@
           IDS_AUTOFILL_OFFERS_REMINDER_ICON_TOOLTIP_TEXT));
 }
 
+IN_PROC_BROWSER_TEST_P(OfferNotificationBubbleViewsInteractiveUiTest,
+                       ShowShoppingServiceFreeListingOffer) {
+  const std::string domain_url = "www.merchantsite1.com";
+  const GURL with_offer_url = GetUrl(domain_url, "/product1");
+  const GURL without_offer_url = GetUrl(domain_url, "/product2");
+  const std::string detail = "Discount description detail";
+  const std::string discount_code = "discount-code";
+  const int64_t discount_id = 123;
+  const double expiry_time_sec =
+      (AutofillClock::Now() + base::Days(2)).ToDoubleT();
+
+  auto* mock_shopping_service = static_cast<commerce::MockShoppingService*>(
+      commerce::ShoppingServiceFactory::GetForBrowserContext(
+          browser()->profile()));
+  mock_shopping_service->SetIsDiscountEligibleToShowOnNavigation(true);
+  // Expect to call this once on every navigation, this test is navigated 3
+  // times.
+  EXPECT_CALL(*mock_shopping_service, IsDiscountEligibleToShowOnNavigation)
+      .Times(3);
+  EXPECT_CALL(*mock_shopping_service, GetDiscountInfoForUrls).Times(3);
+
+  NavigateToAndWaitForForm(GetUrl(domain_url, "/"));
+  EXPECT_FALSE(IsIconVisible());
+  EXPECT_FALSE(GetOfferNotificationBubbleViews());
+
+  // Simulate FreeListingOffer for a product page on the `domain_url`.
+  mock_shopping_service->SetResponseForGetDiscountInfoForUrls(
+      {{with_offer_url,
+        {commerce::CreateValidDiscountInfo(
+            detail, /*terms_and_conditions=*/"",
+            /*value_in_text=*/"$10 off", discount_code, discount_id,
+            /*is_merchant_wide=*/false, expiry_time_sec)}}});
+
+  NavigateToAndWaitForForm(with_offer_url);
+  EXPECT_TRUE(IsIconVisible());
+  EXPECT_TRUE(GetOfferNotificationBubbleViews());
+
+  // Navigates to URL without offers will dismiss the icon.
+  mock_shopping_service->SetResponseForGetDiscountInfoForUrls({});
+  NavigateToAndWaitForForm(without_offer_url);
+  EXPECT_FALSE(IsIconVisible());
+  EXPECT_FALSE(GetOfferNotificationBubbleViews());
+}
+
 }  // namespace autofill
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_browsertest.cc b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_browsertest.cc
index 7bedd6bc..30cf1a3 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_browsertest.cc
@@ -24,6 +24,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/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "content/public/test/browser_test.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
@@ -40,25 +41,38 @@
   ~BaseBookmarkBubbleViewBrowserTest() override = default;
 
   void SetUpOnMainThread() override {
+    EXPECT_TRUE(is_browser_context_services_created);
+    mock_shopping_service_ = static_cast<commerce::MockShoppingService*>(
+        commerce::ShoppingServiceFactory::GetForBrowserContext(
+            browser()->profile()));
+
     auto* helper = commerce::ShoppingListUiTabHelper::FromWebContents(
         browser()->tab_strip_model()->GetActiveWebContents());
 
-    // Clear the original shopping service before we replace it with the new one
-    // so we're not dealing with dangling pointers on destruction (of both the
-    // service itself and its observers).
-    helper->SetShoppingServiceForTesting(nullptr);
-
-    mock_shopping_service_ = static_cast<commerce::MockShoppingService*>(
-        commerce::ShoppingServiceFactory::GetInstance()
-            ->SetTestingFactoryAndUse(
-                browser()->profile(),
-                base::BindRepeating([](content::BrowserContext* context) {
-                  return commerce::MockShoppingService::Build();
-                })));
-
     helper->SetShoppingServiceForTesting(mock_shopping_service_);
   }
 
+  void SetUpInProcessBrowserTestFixture() override {
+    create_services_subscription_ =
+        BrowserContextDependencyManager::GetInstance()
+            ->RegisterCreateServicesCallbackForTesting(
+                base::BindRepeating(&BaseBookmarkBubbleViewBrowserTest::
+                                        OnWillCreateBrowserContextServices,
+                                    weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    is_browser_context_services_created = false;
+  }
+
+  void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
+    is_browser_context_services_created = true;
+    commerce::ShoppingServiceFactory::GetInstance()->SetTestingFactory(
+        context, base::BindRepeating([](content::BrowserContext* context) {
+          return commerce::MockShoppingService::Build();
+        }));
+  }
+
   // DialogBrowserTest:
   void ShowUi(const std::string& name) override {
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
@@ -109,6 +123,10 @@
  private:
   raw_ptr<commerce::MockShoppingService, DanglingUntriaged>
       mock_shopping_service_;
+  base::CallbackListSubscription create_services_subscription_;
+  bool is_browser_context_services_created{false};
+  base::WeakPtrFactory<BaseBookmarkBubbleViewBrowserTest> weak_ptr_factory_{
+      this};
 };
 
 class BookmarkBubbleViewBrowserTest : public BaseBookmarkBubbleViewBrowserTest {
diff --git a/chrome/browser/ui/views/bubble/bubble_contents_wrapper_service_factory.cc b/chrome/browser/ui/views/bubble/bubble_contents_wrapper_service_factory.cc
index fe1fceb..b4192b3 100644
--- a/chrome/browser/ui/views/bubble/bubble_contents_wrapper_service_factory.cc
+++ b/chrome/browser/ui/views/bubble/bubble_contents_wrapper_service_factory.cc
@@ -33,9 +33,11 @@
               .WithGuest(ProfileSelection::kOriginalOnly)
               .Build()) {}
 
-KeyedService* BubbleContentsWrapperServiceFactory::BuildServiceInstanceFor(
+std::unique_ptr<KeyedService>
+BubbleContentsWrapperServiceFactory::BuildServiceInstanceForBrowserContext(
     content::BrowserContext* context) const {
-  return new BubbleContentsWrapperService(Profile::FromBrowserContext(context));
+  return std::make_unique<BubbleContentsWrapperService>(
+      Profile::FromBrowserContext(context));
 }
 
 BubbleContentsWrapperServiceFactory::~BubbleContentsWrapperServiceFactory() =
diff --git a/chrome/browser/ui/views/bubble/bubble_contents_wrapper_service_factory.h b/chrome/browser/ui/views/bubble/bubble_contents_wrapper_service_factory.h
index 721e702..065ce9b7 100644
--- a/chrome/browser/ui/views/bubble/bubble_contents_wrapper_service_factory.h
+++ b/chrome/browser/ui/views/bubble/bubble_contents_wrapper_service_factory.h
@@ -29,7 +29,7 @@
   ~BubbleContentsWrapperServiceFactory() override;
 
   // BrowserContextKeyedServiceFactory:
-  KeyedService* BuildServiceInstanceFor(
+  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
       content::BrowserContext* context) const override;
 };
 
diff --git a/chrome/browser/ui/views/commerce/price_insights_icon_view_interactive_uitest.cc b/chrome/browser/ui/views/commerce/price_insights_icon_view_interactive_uitest.cc
index 36dac35..b8db020 100644
--- a/chrome/browser/ui/views/commerce/price_insights_icon_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/commerce/price_insights_icon_view_interactive_uitest.cc
@@ -60,33 +60,44 @@
     SetUpTabHelperAndShoppingService();
   }
 
+  void SetUpInProcessBrowserTestFixture() override {
+    create_services_subscription_ =
+        BrowserContextDependencyManager::GetInstance()
+            ->RegisterCreateServicesCallbackForTesting(
+                base::BindRepeating(&PriceInsightsIconViewInteractiveTest::
+                                        OnWillCreateBrowserContextServices,
+                                    weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    is_browser_context_services_created = false;
+  }
+
+  void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
+    is_browser_context_services_created = true;
+    commerce::ShoppingServiceFactory::GetInstance()->SetTestingFactory(
+        context, base::BindRepeating([](content::BrowserContext* context) {
+          return commerce::MockShoppingService::Build();
+        }));
+  }
+
  protected:
   raw_ptr<commerce::MockShoppingService, AcrossTasksDanglingUntriaged>
       mock_shopping_service_;
   raw_ptr<MockShoppingListUiTabHelper, AcrossTasksDanglingUntriaged>
       mock_tab_helper_;
   absl::optional<commerce::PriceInsightsInfo> price_insights_info_;
+  base::CallbackListSubscription create_services_subscription_;
+  bool is_browser_context_services_created{false};
 
  private:
   base::test::ScopedFeatureList test_features_{commerce::kPriceInsights};
 
   void SetUpTabHelperAndShoppingService() {
-    // Remove the original tab helper so we don't get into a bad situation when
-    // we go to replace the shopping service with the mock one. The old tab
-    // helper is still holding a reference to the original shopping service and
-    // other dependencies which we switch out below (leaving some dangling
-    // pointers on destruction).
-    browser()->tab_strip_model()->GetActiveWebContents()->RemoveUserData(
-        commerce::ShoppingListUiTabHelper::UserDataKey());
-
+    EXPECT_TRUE(is_browser_context_services_created);
     mock_shopping_service_ = static_cast<commerce::MockShoppingService*>(
-        commerce::ShoppingServiceFactory::GetInstance()
-            ->SetTestingFactoryAndUse(
-                browser()->profile(),
-                base::BindRepeating([](content::BrowserContext* context) {
-                  return commerce::MockShoppingService::Build();
-                })));
-
+        commerce::ShoppingServiceFactory::GetForBrowserContext(
+            browser()->profile()));
     MockShoppingListUiTabHelper::CreateForWebContents(
         browser()->tab_strip_model()->GetActiveWebContents());
     mock_tab_helper_ = static_cast<MockShoppingListUiTabHelper*>(
@@ -127,6 +138,9 @@
     mock_shopping_service_->SetResponseForGetPriceInsightsInfoForUrl(
         price_insights_info);
   }
+
+  base::WeakPtrFactory<PriceInsightsIconViewInteractiveTest> weak_ptr_factory_{
+      this};
 };
 
 IN_PROC_BROWSER_TEST_F(PriceInsightsIconViewInteractiveTest,
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 2302295..88254e87 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
@@ -68,6 +68,27 @@
     SetUpTabHelperAndShoppingService();
   }
 
+  void SetUpInProcessBrowserTestFixture() override {
+    create_services_subscription_ =
+        BrowserContextDependencyManager::GetInstance()
+            ->RegisterCreateServicesCallbackForTesting(base::BindRepeating(
+                &PriceTrackingEmailDialogConsentViewInteractiveTest::
+                    OnWillCreateBrowserContextServices,
+                weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  void TearDownInProcessBrowserTestFixture() override {
+    is_browser_context_services_created_ = false;
+  }
+
+  void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
+    is_browser_context_services_created_ = true;
+    commerce::ShoppingServiceFactory::GetInstance()->SetTestingFactory(
+        context, base::BindRepeating([](content::BrowserContext* context) {
+          return commerce::MockShoppingService::Build();
+        }));
+  }
+
   void ApplyMetaToBookmark() {
     bookmarks::BookmarkModel* model =
         BookmarkModelFactory::GetForBrowserContext(browser()->profile());
@@ -82,24 +103,14 @@
  private:
   base::test::ScopedFeatureList test_features_;
   feature_engagement::test::ScopedIphFeatureList test_iph_features_;
+  base::CallbackListSubscription create_services_subscription_;
+  bool is_browser_context_services_created_{false};
 
   void SetUpTabHelperAndShoppingService() {
-    // Remove the original tab helper so we don't get into a bad situation when
-    // we go to replace the shopping service with the mock one. The old tab
-    // helper is still holding a reference to the original shopping service and
-    // other dependencies which we switch out below (leaving some dangling
-    // pointers on destruction).
-    browser()->tab_strip_model()->GetActiveWebContents()->RemoveUserData(
-        commerce::ShoppingListUiTabHelper::UserDataKey());
-
+    EXPECT_TRUE(is_browser_context_services_created_);
     auto* mock_shopping_service = static_cast<commerce::MockShoppingService*>(
-        commerce::ShoppingServiceFactory::GetInstance()
-            ->SetTestingFactoryAndUse(
-                browser()->profile(),
-                base::BindRepeating([](content::BrowserContext* context) {
-                  return commerce::MockShoppingService::Build();
-                })));
-
+        commerce::ShoppingServiceFactory::GetForBrowserContext(
+            browser()->profile()));
     MockShoppingListUiTabHelper::CreateForWebContents(
         browser()->tab_strip_model()->GetActiveWebContents());
     MockShoppingListUiTabHelper* mock_tab_helper =
@@ -121,6 +132,9 @@
     mock_shopping_service->SetIsSubscribedCallbackValue(true);
     mock_shopping_service->SetIsClusterIdTrackedByUserResponse(true);
   }
+
+  base::WeakPtrFactory<PriceTrackingEmailDialogConsentViewInteractiveTest>
+      weak_ptr_factory_{this};
 };
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingEmailDialogConsentViewInteractiveTest,
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 09c7e52b..928e92fc 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
@@ -77,33 +77,28 @@
         BookmarkModelFactory::GetForBrowserContext(browser()->profile());
     bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model);
 
-    // Remove the original tab helper so we don't get into a bad situation when
-    // we go to replace the shopping service with the mock one. The old tab
-    // helper is still holding a reference to the original shopping service and
-    // other dependencies which we switch out below (leaving some dangling
-    // pointers on destruction).
-    browser()->tab_strip_model()->GetActiveWebContents()->RemoveUserData(
-        commerce::ShoppingListUiTabHelper::UserDataKey());
+    SetUpTabHelperAndShoppingService();
+  }
 
-    mock_shopping_service_ = static_cast<commerce::MockShoppingService*>(
-        commerce::ShoppingServiceFactory::GetInstance()
-            ->SetTestingFactoryAndUse(
-                browser()->profile(),
-                base::BindRepeating([](content::BrowserContext* context) {
-                  return commerce::MockShoppingService::Build();
-                })));
+  void SetUpInProcessBrowserTestFixture() override {
+    create_services_subscription_ =
+        BrowserContextDependencyManager::GetInstance()
+            ->RegisterCreateServicesCallbackForTesting(
+                base::BindRepeating(&PriceTrackingIconViewInteractiveTest::
+                                        OnWillCreateBrowserContextServices,
+                                    weak_ptr_factory_.GetWeakPtr()));
+  }
 
-    MockShoppingListUiTabHelper::CreateForWebContents(
-        browser()->tab_strip_model()->GetActiveWebContents());
-    mock_tab_helper_ = static_cast<MockShoppingListUiTabHelper*>(
-        MockShoppingListUiTabHelper::FromWebContents(
-            browser()->tab_strip_model()->GetActiveWebContents()));
-    mock_tab_helper_->SetShoppingServiceForTesting(mock_shopping_service_);
+  void TearDownInProcessBrowserTestFixture() override {
+    is_browser_context_services_created_ = false;
+  }
 
-    const gfx::Image image = mock_tab_helper_->GetValidProductImage();
-    ON_CALL(*mock_tab_helper_, GetProductImage)
-        .WillByDefault(
-            testing::ReturnRef(mock_tab_helper_->GetValidProductImage()));
+  void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
+    is_browser_context_services_created_ = true;
+    commerce::ShoppingServiceFactory::GetInstance()->SetTestingFactory(
+        context, base::BindRepeating([](content::BrowserContext* context) {
+          return commerce::MockShoppingService::Build();
+        }));
   }
 
   PriceTrackingIconView* GetChip() {
@@ -176,6 +171,30 @@
 
  private:
   feature_engagement::test::ScopedIphFeatureList test_features_;
+  base::CallbackListSubscription create_services_subscription_;
+  bool is_browser_context_services_created_{false};
+
+  void SetUpTabHelperAndShoppingService() {
+    EXPECT_TRUE(is_browser_context_services_created_);
+
+    mock_shopping_service_ = static_cast<commerce::MockShoppingService*>(
+        commerce::ShoppingServiceFactory::GetForBrowserContext(
+            browser()->profile()));
+    MockShoppingListUiTabHelper::CreateForWebContents(
+        browser()->tab_strip_model()->GetActiveWebContents());
+    mock_tab_helper_ = static_cast<MockShoppingListUiTabHelper*>(
+        MockShoppingListUiTabHelper::FromWebContents(
+            browser()->tab_strip_model()->GetActiveWebContents()));
+    mock_tab_helper_->SetShoppingServiceForTesting(mock_shopping_service_);
+
+    const gfx::Image image = mock_tab_helper_->GetValidProductImage();
+    ON_CALL(*mock_tab_helper_, GetProductImage)
+        .WillByDefault(
+            testing::ReturnRef(mock_tab_helper_->GetValidProductImage()));
+  }
+
+  base::WeakPtrFactory<PriceTrackingIconViewInteractiveTest> weak_ptr_factory_{
+      this};
 };
 
 IN_PROC_BROWSER_TEST_F(PriceTrackingIconViewInteractiveTest,
diff --git a/chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.cc b/chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.cc
index dfffe920..9138cb3e 100644
--- a/chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.cc
+++ b/chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.cc
@@ -21,6 +21,7 @@
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/layout/flex_layout.h"
@@ -45,7 +46,8 @@
 
 constexpr int kPromoCardIconSizeDip = 48;
 
-constexpr int kContainerPaddingDip = 16;
+constexpr gfx::Insets kPromoCardInsets = gfx::Insets::VH(12, 16);
+
 constexpr int kContainerMinWidthDip = 368;
 
 // Spacing between this view and the anchor view (context menu).
@@ -141,7 +143,7 @@
   layout->SetOrientation(views::LayoutOrientation::kHorizontal)
       .SetCrossAxisAlignment(views::LayoutAlignment::kStart)
       .SetCollapseMargins(true)
-      .SetDefault(views::kMarginsKey, gfx::Insets(kContainerPaddingDip));
+      .SetDefault(views::kMarginsKey, kPromoCardInsets);
 
   // Icon.
   auto* icon = AddChildView(std::make_unique<views::ImageView>());
@@ -152,43 +154,43 @@
   // The main view, which shows the promo card text and buttons.
   auto* main_view = AddChildView(std::make_unique<views::FlexLayoutView>());
   main_view->SetOrientation(views::LayoutOrientation::kVertical);
-  main_view->SetCollapseMargins(true);
-  main_view->SetIgnoreDefaultMainAxisMargins(true);
-  main_view->SetDefault(views::kMarginsKey,
-                        gfx::Insets::VH(kContainerPaddingDip, 0));
+  main_view->SetProperty(
+      views::kFlexBehaviorKey,
+      views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToZero,
+                               views::MaximumFlexSizeRule::kUnbounded));
 
-  InitTextContainer(main_view);
-  InitButtonBar(main_view);
+  AddTitle(main_view);
+  AddDescription(main_view);
+  AddButtonBar(main_view);
 }
 
-void EditorMenuPromoCardView::InitTextContainer(views::View* main_view) {
-  // Text container layout.
-  auto* text_container =
-      main_view->AddChildView(std::make_unique<views::FlexLayoutView>());
-  text_container->SetOrientation(views::LayoutOrientation::kVertical);
-  text_container->SetCollapseMargins(true);
-  text_container->SetIgnoreDefaultMainAxisMargins(true);
-
-  const int vertical_spacing = views::LayoutProvider::Get()->GetDistanceMetric(
-      views::DISTANCE_RELATED_CONTROL_VERTICAL);
-  text_container->SetDefault(views::kMarginsKey,
-                             gfx::Insets::VH(vertical_spacing, 0));
-
-  // Title.
-  auto* title = text_container->AddChildView(std::make_unique<views::Label>(
-      kTitleTextPlaceholder, views::style::CONTEXT_DIALOG_TITLE));
+void EditorMenuPromoCardView::AddTitle(views::View* main_view) {
+  auto* title = main_view->AddChildView(std::make_unique<views::Label>(
+      kTitleTextPlaceholder, views::style::CONTEXT_DIALOG_TITLE,
+      views::style::STYLE_HEADLINE_5));
   title->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   title->SetMultiLine(true);
-
-  // Description.
-  auto* description =
-      text_container->AddChildView(std::make_unique<views::Label>(
-          kDescriptionTextPlaceholder, views::style::CONTEXT_DIALOG_BODY_TEXT));
-  description->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  description->SetMultiLine(true);
+  title->SetEnabledColorId(ui::kColorSysOnSurface);
 }
 
-void EditorMenuPromoCardView::InitButtonBar(views::View* main_view) {
+void EditorMenuPromoCardView::AddDescription(views::View* main_view) {
+  auto* description = main_view->AddChildView(std::make_unique<views::Label>(
+      kDescriptionTextPlaceholder, views::style::CONTEXT_DIALOG_BODY_TEXT,
+      views::style::STYLE_BODY_3));
+  description->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  description->SetMultiLine(true);
+  description->SetEnabledColorId(ui::kColorSysOnSurfaceSubtle);
+  description->SetProperty(
+      views::kMarginsKey,
+      gfx::Insets::TLBR(views::LayoutProvider::Get()->GetDistanceMetric(
+                            views::DISTANCE_DIALOG_CONTENT_MARGIN_TOP_TEXT),
+                        0,
+                        views::LayoutProvider::Get()->GetDistanceMetric(
+                            views::DISTANCE_DIALOG_CONTENT_MARGIN_BOTTOM_TEXT),
+                        0));
+}
+
+void EditorMenuPromoCardView::AddButtonBar(views::View* main_view) {
   // Button bar layout.
   auto* button_bar =
       main_view->AddChildView(std::make_unique<views::FlexLayoutView>());
@@ -196,26 +198,29 @@
   button_bar->SetMainAxisAlignment(views::LayoutAlignment::kEnd);
   button_bar->SetCollapseMargins(true);
   button_bar->SetIgnoreDefaultMainAxisMargins(true);
-
-  const int button_spacing = views::LayoutProvider::Get()->GetDistanceMetric(
-      views::DISTANCE_RELATED_BUTTON_HORIZONTAL);
-  button_bar->SetDefault(views::kMarginsKey,
-                         gfx::Insets::VH(0, button_spacing));
+  button_bar->SetDefault(
+      views::kMarginsKey,
+      gfx::Insets::VH(0, views::LayoutProvider::Get()->GetDistanceMetric(
+                             views::DISTANCE_RELATED_BUTTON_HORIZONTAL)));
 
   // Dismiss button.
   dismiss_button_ =
-      button_bar->AddChildView(std::make_unique<views::LabelButton>(
+      button_bar->AddChildView(std::make_unique<views::MdTextButton>(
           base::BindRepeating(&EditorMenuPromoCardView::OnDismissButtonPressed,
                               weak_factory_.GetWeakPtr()),
           l10n_util::GetStringUTF16(
               IDS_EDITOR_MENU_PROMO_CARD_VIEW_DISMISS_BUTTON)));
+  dismiss_button_->SetStyle(ui::ButtonStyle::kText);
 
   // Tell me more button.
-  button_bar->AddChildView(std::make_unique<views::LabelButton>(
-      base::BindRepeating(&EditorMenuPromoCardView::OnTellMeMoreButtonPressed,
-                          weak_factory_.GetWeakPtr()),
-      l10n_util::GetStringUTF16(
-          IDS_EDITOR_MENU_PROMO_CARD_VIEW_TELL_ME_MORE_BUTTON)));
+  tell_me_more_button_ =
+      button_bar->AddChildView(std::make_unique<views::MdTextButton>(
+          base::BindRepeating(
+              &EditorMenuPromoCardView::OnTellMeMoreButtonPressed,
+              weak_factory_.GetWeakPtr()),
+          l10n_util::GetStringUTF16(
+              IDS_EDITOR_MENU_PROMO_CARD_VIEW_TELL_ME_MORE_BUTTON)));
+  tell_me_more_button_->SetStyle(ui::ButtonStyle::kProminent);
 }
 
 void EditorMenuPromoCardView::OnDismissButtonPressed() {
diff --git a/chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.h b/chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.h
index bf85e578..cba511bf 100644
--- a/chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.h
+++ b/chrome/browser/ui/views/editor_menu/editor_menu_promo_card_view.h
@@ -13,7 +13,7 @@
 #include "ui/views/widget/widget_observer.h"
 
 namespace views {
-class LabelButton;
+class MdTextButton;
 }
 
 namespace chromeos::editor_menu {
@@ -52,8 +52,9 @@
 
  private:
   void InitLayout();
-  void InitTextContainer(views::View* main_view);
-  void InitButtonBar(views::View* main_view);
+  void AddTitle(views::View* main_view);
+  void AddDescription(views::View* main_view);
+  void AddButtonBar(views::View* main_view);
 
   void OnDismissButtonPressed();
   void OnTellMeMoreButtonPressed();
@@ -63,7 +64,8 @@
   // `delegate_` outlives `this`.
   raw_ptr<EditorMenuViewDelegate> delegate_ = nullptr;
 
-  raw_ptr<views::LabelButton> dismiss_button_ = nullptr;
+  raw_ptr<views::MdTextButton> dismiss_button_ = nullptr;
+  raw_ptr<views::MdTextButton> tell_me_more_button_ = nullptr;
 
   base::ScopedObservation<views::Widget, views::WidgetObserver>
       widget_observation_{this};
diff --git a/chrome/browser/ui/views/editor_menu/editor_menu_view.cc b/chrome/browser/ui/views/editor_menu/editor_menu_view.cc
index 9dd45b41..b6ac65be 100644
--- a/chrome/browser/ui/views/editor_menu/editor_menu_view.cc
+++ b/chrome/browser/ui/views/editor_menu/editor_menu_view.cc
@@ -26,7 +26,11 @@
 #include "ui/compositor/layer.h"
 #include "ui/compositor/layer_owner.h"
 #include "ui/display/screen.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rounded_corners_f.h"
+#include "ui/gfx/skia_paint_util.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/image_button.h"
@@ -52,6 +56,13 @@
 
 constexpr gfx::Insets kTitleContainerInsets = gfx::Insets::TLBR(10, 16, 10, 10);
 
+constexpr char16_t kBadgeText[] = u"New";
+constexpr gfx::Insets kBadgeInsets = gfx::Insets::VH(0, 8);
+constexpr int kBadgeHorizontalPaddingDip = 8;
+constexpr int kBadgeVerticalPaddingDip = 8;
+constexpr SkColor kBadgeBackgroundColorStart = SkColorSetRGB(0xB5, 0xC4, 0xFF);
+constexpr SkColor kBadgeBackgroundColorEnd = SkColorSetRGB(0xB3, 0xEF, 0xD4);
+
 constexpr char16_t kSettingsTooltipString[] = u"Settings";
 constexpr int kSettingsButtonSizeDip = 32;
 constexpr int kSettingsIconSizeDip = 20;
@@ -73,6 +84,48 @@
     u"chip label 4", u"chip label 5", u"chip label 6",
 };
 
+// A background that fills the canvas with rounded corners and gradient colors.
+class GradientRoundedRectBackground : public views::Background {
+ public:
+  GradientRoundedRectBackground(float radius,
+                                SkColor start_color,
+                                SkColor end_color)
+      : radii_(gfx::RoundedCornersF{radius}),
+        start_color_(start_color),
+        end_color_(end_color) {}
+
+  GradientRoundedRectBackground(const GradientRoundedRectBackground&) = delete;
+  GradientRoundedRectBackground& operator=(
+      const GradientRoundedRectBackground&) = delete;
+
+  ~GradientRoundedRectBackground() override = default;
+
+  // views::Background:
+  void Paint(gfx::Canvas* canvas, views::View* view) const override {
+    const auto& bounds = view->GetContentsBounds();
+    gfx::Rect rect(bounds);
+    SkPath path;
+    SkScalar radii[8] = {radii_.upper_left(),  radii_.upper_left(),
+                         radii_.upper_right(), radii_.upper_right(),
+                         radii_.lower_right(), radii_.lower_right(),
+                         radii_.lower_left(),  radii_.lower_left()};
+    path.addRoundRect(gfx::RectToSkRect(rect), radii);
+
+    cc::PaintFlags flags;
+    flags.setBlendMode(SkBlendMode::kSrcOver);
+    flags.setShader(gfx::CreateGradientShader(
+        bounds.left_center(), bounds.right_center(), start_color_, end_color_));
+    flags.setAntiAlias(true);
+    flags.setStyle(cc::PaintFlags::kFill_Style);
+    canvas->DrawPath(path, flags);
+  }
+
+ private:
+  const gfx::RoundedCornersF radii_;
+  const SkColor start_color_;
+  const SkColor end_color_;
+};
+
 }  // namespace
 
 EditorMenuView::EditorMenuView(const gfx::Rect& anchor_view_bounds,
@@ -194,6 +247,19 @@
   auto* title = title_container_->AddChildView(
       std::make_unique<views::Label>(kContainerTitle));
 
+  auto* badge =
+      title_container_->AddChildView(std::make_unique<views::FlexLayoutView>());
+  badge->SetMainAxisAlignment(views::LayoutAlignment::kCenter);
+  badge->SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
+  badge->SetProperty(views::kMarginsKey, kBadgeInsets);
+  auto* text = badge->AddChildView(std::make_unique<views::Label>(kBadgeText));
+  badge->SetPreferredSize(gfx::Size(
+      text->GetPreferredSize().width() + 2 * kBadgeHorizontalPaddingDip,
+      text->GetPreferredSize().height() + 2 * kBadgeVerticalPaddingDip));
+  float radius = badge->GetPreferredSize().height() / 2.0f;
+  badge->SetBackground(std::make_unique<GradientRoundedRectBackground>(
+      radius, kBadgeBackgroundColorStart, kBadgeBackgroundColorEnd));
+
   auto* spacer =
       title_container_->AddChildView(std::make_unique<views::View>());
   layout->SetFlexForView(spacer, 1);
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
index c29925f..c04ce53 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm
@@ -449,7 +449,7 @@
 
 gfx::Insets BrowserNonClientFrameViewMac::GetCaptionButtonInsets() const {
   const int kCaptionButtonInset =
-      base::mac::IsOS10_15()
+      base::mac::MacOSMajorVersion() < 11
           ? kCaptionButtonsInsetsCatalinaOrOlder
           : (kCaptionButtonsWidth + (kCaptionButtonsLeadingPadding * 2) -
              TabStyle::Get()->GetBottomCornerRadius());
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_mac_interactive_uitest.mm b/chrome/browser/ui/views/frame/immersive_mode_controller_mac_interactive_uitest.mm
index 6d0fdd4..d3408c8 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_mac_interactive_uitest.mm
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_mac_interactive_uitest.mm
@@ -197,7 +197,7 @@
 
   // Only on macOS 13 and higher will the contentView no longer live in the
   // window.
-  if (base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() >= 13) {
     EXPECT_NE([overlay_widget_window contentView], overlay_widget_content_view);
   }
 
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
index 39b6c24..acdf8df 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.cc
@@ -21,6 +21,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/ui/frame/frame_utils.h"
 #include "components/omnibox/browser/location_bar_model_impl.h"
+#include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/browser/document_picture_in_picture_window_controller.h"
 #include "content/public/browser/web_contents.h"
@@ -109,10 +110,13 @@
 
   explicit BackToTabButton(PressedCallback callback)
       : OverlayWindowImageButton(std::move(callback)) {
+    auto* icon = &vector_icons::kBackToTabIcon;
+    if (OmniboxFieldTrial::IsChromeRefreshIconsEnabled()) {
+      icon = &vector_icons::kBackToTabChromeRefreshIcon;
+    }
     SetImageModel(views::Button::STATE_NORMAL,
-                  ui::ImageModel::FromVectorIcon(vector_icons::kBackToTabIcon,
-                                                 kColorPipWindowForeground,
-                                                 kBackToTabImageSize));
+                  ui::ImageModel::FromVectorIcon(
+                      *icon, kColorPipWindowForeground, kBackToTabImageSize));
 
     const std::u16string back_to_tab_button_label = l10n_util::GetStringUTF16(
         IDS_PICTURE_IN_PICTURE_BACK_TO_TAB_CONTROL_TEXT);
@@ -293,6 +297,12 @@
       CONTEXT_OMNIBOX_PRIMARY, views::style::STYLE_PRIMARY);
   location_icon_view_ = top_bar_container_view_->AddChildView(
       std::make_unique<LocationIconView>(font_list, this, this));
+  if (OmniboxFieldTrial::IsChromeRefreshIconsEnabled()) {
+    // The PageInfo icon should be 8px from the left of the window and 4px from
+    // the right of the origin.
+    location_icon_view_->SetProperty(views::kMarginsKey,
+                                     gfx::Insets::TLBR(0, 8, 0, 4));
+  }
 
   // Creates the window title.
   top_bar_container_view_->AddChildView(
@@ -601,6 +611,10 @@
   show_close_button_animation_.SetContainer(animation_container);
   hide_close_button_animation_.SetContainer(animation_container);
 
+  // TODO(https://crbug.com/1475419): Don't force dark mode once we support a
+  // light mode window.
+  GetWidget()->SetColorModeOverride(ui::ColorProviderKey::ColorMode::kDark);
+
   BrowserNonClientFrameView::AddedToWidget();
 }
 
@@ -717,11 +731,32 @@
 
 ui::ImageModel PictureInPictureBrowserFrameView::GetLocationIcon(
     LocationIconView::Delegate::IconFetchedCallback on_icon_fetched) const {
+  ui::ColorId foreground_color_id = kColorOmniboxSecurityChipSecure;
+
+  if (OmniboxFieldTrial::IsChromeRefreshIconsEnabled()) {
+    // If we're animating between colors, use the current color value.
+    if (current_foreground_color_.has_value()) {
+      return ui::ImageModel::FromVectorIcon(
+          location_bar_model_->GetVectorIcon(), *current_foreground_color_,
+          kWindowIconImageSize);
+    }
+
+    foreground_color_id = (top_bar_color_animation_.GetCurrentValue() == 0)
+                              ? kColorPipWindowForegroundInactive
+                              : kColorPipWindowForeground;
+  }
+
   return ui::ImageModel::FromVectorIcon(location_bar_model_->GetVectorIcon(),
-                                        kColorOmniboxSecurityChipSecure,
+                                        foreground_color_id,
                                         kWindowIconImageSize);
 }
 
+absl::optional<ui::ColorId>
+PictureInPictureBrowserFrameView::GetLocationIconBackgroundColorOverride()
+    const {
+  return kColorPipWindowTopBarBackground;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // IconLabelBubbleView::Delegate implementations:
 
@@ -780,6 +815,14 @@
 ///////////////////////////////////////////////////////////////////////////////
 // gfx::AnimationDelegate implementations:
 
+void PictureInPictureBrowserFrameView::AnimationEnded(
+    const gfx::Animation* animation) {
+  if (animation == &top_bar_color_animation_) {
+    current_foreground_color_ = absl::nullopt;
+    location_icon_view_->Update(/*suppress_animations=*/false);
+  }
+}
+
 void PictureInPictureBrowserFrameView::AnimationProgressed(
     const gfx::Animation* animation) {
   if (animation == &top_bar_color_animation_) {
@@ -791,6 +834,10 @@
     for (ContentSettingImageView* view : content_setting_views_) {
       view->SetIconColor(color);
     }
+    if (OmniboxFieldTrial::IsChromeRefreshIconsEnabled()) {
+      current_foreground_color_ = color;
+      location_icon_view_->Update(/*suppress_animations=*/false);
+    }
     return;
   }
 
diff --git a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
index 8f78c22..203e1563 100644
--- a/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
+++ b/chrome/browser/ui/views/frame/picture_in_picture_browser_frame_view.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/ui/views/overlay/close_image_button.h"
 #include "components/omnibox/browser/location_bar_model.h"
 #include "content/public/browser/web_contents.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/gfx/animation/multi_animation.h"
 #include "ui/gfx/animation/slide_animation.h"
@@ -102,6 +103,8 @@
   LocationBarModel* GetLocationBarModel() const override;
   ui::ImageModel GetLocationIcon(LocationIconView::Delegate::IconFetchedCallback
                                      on_icon_fetched) const override;
+  absl::optional<ui::ColorId> GetLocationIconBackgroundColorOverride()
+      const override;
 
   // IconLabelBubbleView::Delegate:
   SkColor GetIconLabelBubbleSurroundingForegroundColor() const override;
@@ -118,6 +121,7 @@
   void OnWidgetDestroying(views::Widget* widget) override;
 
   // gfx::AnimationDelegate:
+  void AnimationEnded(const gfx::Animation* animation) override;
   void AnimationProgressed(const gfx::Animation* animation) override;
 
   // views::View:
@@ -245,6 +249,10 @@
   gfx::MultiAnimation show_close_button_animation_;
   gfx::MultiAnimation hide_close_button_animation_;
 
+  // The foreground color given the current state of the
+  // `top_bar_color_animation_`.
+  absl::optional<SkColor> current_foreground_color_;
+
 #if BUILDFLAG(IS_LINUX)
   // Used to draw window frame borders and shadow on Linux when GTK theme is
   // enabled.
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view.cc b/chrome/browser/ui/views/location_bar/location_icon_view.cc
index 0c4e369..c8ca3a7 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_icon_view.cc
@@ -54,6 +54,11 @@
 using content::WebContents;
 using security_state::SecurityLevel;
 
+absl::optional<ui::ColorId>
+LocationIconView::Delegate::GetLocationIconBackgroundColorOverride() const {
+  return absl::nullopt;
+}
+
 LocationIconView::LocationIconView(
     const gfx::FontList& font_list,
     IconLabelBubbleView::Delegate* parent_delegate,
@@ -297,9 +302,11 @@
     const bool is_text_dangerous =
         display_text == l10n_util::GetStringUTF16(IDS_DANGEROUS_VERBOSE_STATE);
 
-    ui::ColorId id = is_text_dangerous
-                         ? kColorOmniboxSecurityChipDangerousBackground
-                         : kColorPageInfoBackground;
+    const ui::ColorId id =
+        delegate_->GetLocationIconBackgroundColorOverride().value_or(
+            is_text_dangerous ? kColorOmniboxSecurityChipDangerousBackground
+                              : kColorPageInfoBackground);
+
     SetBackground(views::CreateRoundedRectBackground(
         GetColorProvider()->GetColor(id), height() / 2));
 
diff --git a/chrome/browser/ui/views/location_bar/location_icon_view.h b/chrome/browser/ui/views/location_bar/location_icon_view.h
index cb503ce5..09e34bb 100644
--- a/chrome/browser/ui/views/location_bar/location_icon_view.h
+++ b/chrome/browser/ui/views/location_bar/location_icon_view.h
@@ -8,6 +8,7 @@
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/ui/views/location_bar/icon_label_bubble_view.h"
 #include "components/omnibox/browser/location_bar_model.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 
 namespace content {
@@ -58,6 +59,11 @@
     // Gets an icon for the location bar icon chip.
     virtual ui::ImageModel GetLocationIcon(
         IconFetchedCallback on_icon_fetched) const = 0;
+
+    // Gets an optional background color override for the location bar icon
+    // chip.
+    virtual absl::optional<ui::ColorId> GetLocationIconBackgroundColorOverride()
+        const;
   };
 
   LocationIconView(const gfx::FontList& font_list,
diff --git a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_browsertest.cc b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_browsertest.cc
index 072e8fed..27828b47 100644
--- a/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_browsertest.cc
+++ b/chrome/browser/ui/views/media_router/media_router_dialog_controller_views_browsertest.cc
@@ -4,6 +4,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/media/router/media_router_feature.h"
 #include "chrome/browser/ui/browser.h"
@@ -63,7 +64,12 @@
   void ShowDialogForPresentation() {
     dialog_controller_->ShowMediaRouterDialogForPresentation(
         CreateStartPresentationContext(initiator_));
-    base::RunLoop().RunUntilIdle();
+    base::RunLoop run_loop;
+    // RunUntilIdle() should work here, but it was somehow leading to flakiness,
+    // so we add some delay instead. See crbug.com/1444794.
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), base::Seconds(1));
+    run_loop.Run();
   }
 
   raw_ptr<WebContents, AcrossTasksDanglingUntriaged> initiator_;
diff --git a/chrome/browser/ui/views/overlay/close_image_button.cc b/chrome/browser/ui/views/overlay/close_image_button.cc
index 371830d..af0616d9 100644
--- a/chrome/browser/ui/views/overlay/close_image_button.cc
+++ b/chrome/browser/ui/views/overlay/close_image_button.cc
@@ -7,6 +7,8 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/omnibox/browser/omnibox_field_trial.h"
+#include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/image_model.h"
@@ -24,9 +26,13 @@
 CloseImageButton::CloseImageButton(PressedCallback callback)
     : OverlayWindowImageButton(std::move(callback)) {
   SetSize(gfx::Size(kCloseButtonSize, kCloseButtonSize));
+
+  auto* icon = &views::kIcCloseIcon;
+  if (OmniboxFieldTrial::IsChromeRefreshIconsEnabled()) {
+    icon = &vector_icons::kCloseChromeRefreshIcon;
+  }
   SetImageModel(views::Button::STATE_NORMAL,
-                ui::ImageModel::FromVectorIcon(views::kIcCloseIcon,
-                                               kColorPipWindowForeground,
+                ui::ImageModel::FromVectorIcon(*icon, kColorPipWindowForeground,
                                                kCloseButtonIconSize));
 
   // Accessibility.
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.cc
index 74878723..89d108a4 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_controller.cc
@@ -12,6 +12,7 @@
 #include "chrome/browser/ui/webui/side_panel/read_anything/read_anything_prefs.h"
 #include "chrome/common/accessibility/read_anything_constants.h"
 #include "components/prefs/pref_service.h"
+#include "ui/accessibility/accessibility_features.h"
 
 ReadAnythingController::ReadAnythingController(ReadAnythingModel* model,
                                                Browser* browser)
@@ -25,8 +26,11 @@
   if (!model_->GetFontModel()->IsValidFontIndex(new_index))
     return;
 
-  base::UmaHistogramEnumeration(string_constants::kSettingsChangeHistogramName,
-                                ReadAnythingSettingsChange::kFontChange);
+  if (!features::IsReadAnythingWebUIToolbarEnabled()) {
+    base::UmaHistogramEnumeration(
+        string_constants::kSettingsChangeHistogramName,
+        ReadAnythingSettingsChange::kFontChange);
+  }
   model_->SetSelectedFontByIndex(new_index);
 
   browser_->profile()->GetPrefs()->SetString(
@@ -49,8 +53,11 @@
     model_->DecreaseTextSize();
   }
 
-  base::UmaHistogramEnumeration(string_constants::kSettingsChangeHistogramName,
-                                ReadAnythingSettingsChange::kFontSizeChange);
+  if (!features::IsReadAnythingWebUIToolbarEnabled()) {
+    base::UmaHistogramEnumeration(
+        string_constants::kSettingsChangeHistogramName,
+        ReadAnythingSettingsChange::kFontSizeChange);
+  }
   browser_->profile()->GetPrefs()->SetDouble(
       prefs::kAccessibilityReadAnythingFontScale, model_->GetFontScale());
 }
@@ -63,8 +70,11 @@
     return;
   }
 
-  base::UmaHistogramEnumeration(string_constants::kSettingsChangeHistogramName,
-                                ReadAnythingSettingsChange::kThemeChange);
+  if (!features::IsReadAnythingWebUIToolbarEnabled()) {
+    base::UmaHistogramEnumeration(
+        string_constants::kSettingsChangeHistogramName,
+        ReadAnythingSettingsChange::kThemeChange);
+  }
   model_->SetSelectedColorsByIndex(new_index);
 
   prefs->SetInteger(prefs::kAccessibilityReadAnythingColorInfo, new_index);
@@ -78,8 +88,11 @@
   if (!model_->GetLineSpacingModel()->IsValidIndex(new_index))
     return;
 
-  base::UmaHistogramEnumeration(string_constants::kSettingsChangeHistogramName,
-                                ReadAnythingSettingsChange::kLineHeightChange);
+  if (!features::IsReadAnythingWebUIToolbarEnabled()) {
+    base::UmaHistogramEnumeration(
+        string_constants::kSettingsChangeHistogramName,
+        ReadAnythingSettingsChange::kLineHeightChange);
+  }
   model_->SetSelectedLineSpacingByIndex(new_index);
 
   // Saved preferences correspond to LineSpacing. However, since it contains a
@@ -99,9 +112,11 @@
   if (!model_->GetLetterSpacingModel()->IsValidIndex(new_index))
     return;
 
-  base::UmaHistogramEnumeration(
-      string_constants::kSettingsChangeHistogramName,
-      ReadAnythingSettingsChange::kLetterSpacingChange);
+  if (!features::IsReadAnythingWebUIToolbarEnabled()) {
+    base::UmaHistogramEnumeration(
+        string_constants::kSettingsChangeHistogramName,
+        ReadAnythingSettingsChange::kLetterSpacingChange);
+  }
   model_->SetSelectedLetterSpacingByIndex(new_index);
 
   // Saved preferences correspond to LetterSpacing. However, since it contains a
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 3726dad..20d6aef0 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
@@ -13,11 +13,11 @@
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/language/language_model_manager_factory.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/side_panel/read_anything/read_anything_side_panel_controller_utils.h"
 #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_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/webui/side_panel/read_anything/read_anything_prefs.h"
@@ -55,14 +55,12 @@
 
   browser->tab_strip_model()->AddObserver(this);
   Observe(GetActiveWebContents());
-  CreateAndRegisterEntriesForExistingWebContents(browser->tab_strip_model());
 }
 
 void ReadAnythingCoordinator::InitModelWithUserPrefs() {
   Browser* browser = &GetBrowser();
-  if (!browser->profile() || !browser->profile()->GetPrefs()) {
+  if (!browser->profile() || !browser->profile()->GetPrefs())
     return;
-  }
 
   // Get user's default language to check for compatible fonts.
   language::LanguageModel* language_model =
@@ -113,16 +111,19 @@
   // Read Anything as a side panel entry observer.
   Browser* browser = &GetBrowser();
   BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
-  if (!browser_view) {
+  if (!browser_view)
     return;
-  }
+  SidePanelRegistry* global_registry =
+      SidePanelCoordinator::GetGlobalSidePanelRegistry(browser);
+  global_registry->Deregister(
+      SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything));
 
   browser->tab_strip_model()->RemoveObserver(this);
   Observe(nullptr);
 }
 
-void ReadAnythingCoordinator::CreateAndRegisterSidePanelEntry(
-    SidePanelRegistry* registry) {
+void ReadAnythingCoordinator::CreateAndRegisterEntry(
+    SidePanelRegistry* global_registry) {
   auto side_panel_entry = std::make_unique<SidePanelEntry>(
       SidePanelEntry::Id::kReadAnything,
       l10n_util::GetStringUTF16(IDS_READING_MODE_TITLE),
@@ -131,14 +132,7 @@
       base::BindRepeating(&ReadAnythingCoordinator::CreateContainerView,
                           base::Unretained(this)));
   side_panel_entry->AddObserver(this);
-  registry->Register(std::move(side_panel_entry));
-}
-
-void ReadAnythingCoordinator::CreateAndRegisterEntriesForExistingWebContents(
-    TabStripModel* tab_strip_model) {
-  for (int index = 0; index < tab_strip_model->GetTabCount(); index++) {
-    CreateAndRegisterEntry(tab_strip_model->GetWebContentsAt(index));
-  }
+  global_registry->Register(std::move(side_panel_entry));
 }
 
 ReadAnythingController* ReadAnythingCoordinator::GetController() {
@@ -220,17 +214,6 @@
     TabStripModel* tab_strip_model,
     const TabStripModelChange& change,
     const TabStripSelectionChange& selection) {
-  if (change.type() == TabStripModelChange::Type::kInserted) {
-    for (const auto& inserted_tab : change.GetInsert()->contents) {
-      CreateAndRegisterEntry(inserted_tab.contents);
-    }
-  }
-  if (change.type() == TabStripModelChange::Type::kReplaced) {
-    raw_ptr<content::WebContents> new_contents =
-        change.GetReplace()->new_contents;
-    CHECK(new_contents);
-    CreateAndRegisterEntry(new_contents);
-  }
   if (!selection.active_tab_changed()) {
     return;
   }
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
index 10faabd..36e7e603 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h
@@ -51,7 +51,7 @@
   ReadAnythingCoordinator& operator=(const ReadAnythingCoordinator&) = delete;
   ~ReadAnythingCoordinator() override;
 
-  void CreateAndRegisterSidePanelEntry(SidePanelRegistry* registry);
+  void CreateAndRegisterEntry(SidePanelRegistry* global_registry);
   ReadAnythingController* GetController();
   ReadAnythingModel* GetModel();
 
@@ -64,10 +64,6 @@
   friend class BrowserUserData<ReadAnythingCoordinator>;
   friend class ReadAnythingCoordinatorTest;
 
-  void CreateAndRegisterEntriesForExistingWebContents(
-      TabStripModel* tab_strip_model);
-  void DeregisterEntriesForExistingWebContents(TabStripModel* tab_strip_model);
-
   // Used during construction to initialize the model with saved user prefs.
   void InitModelWithUserPrefs();
 
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
index 9310e2f..1a52e321aa 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator_unittest.cc
@@ -35,41 +35,12 @@
     scoped_feature_list_.InitWithFeatures({features::kReadAnything}, {});
     TestWithBrowserView::SetUp();
 
-    // Ensure a kReadAnything entry is added to the contextual registry for the
-    // first tab.
-    AddTab(browser_view()->browser(), GURL("http://foo1.com"));
-    content::WebContents* active_contents =
-        browser_view()->GetActiveWebContents();
-    auto* tab_one_registry = SidePanelRegistry::Get(active_contents);
     side_panel_coordinator_ =
         SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser());
+    side_panel_registry_ =
+        SidePanelCoordinator::GetGlobalSidePanelRegistry(browser());
     read_anything_coordinator_ =
         ReadAnythingCoordinator::GetOrCreateForBrowser(browser());
-    contextual_registries_.push_back(tab_one_registry);
-
-    // Ensure a kReadAnything entry is added to the contextual registry for the
-    // second tab.
-    AddTab(browser_view()->browser(), GURL("http://foo2.com"));
-    active_contents = browser_view()->GetActiveWebContents();
-    auto* tab_two_registry = SidePanelRegistry::Get(active_contents);
-    contextual_registries_.push_back(tab_two_registry);
-
-    // Verify the first tab has one entry, kReadAnything.
-    browser_view()->browser()->tab_strip_model()->ActivateTabAt(0);
-    active_contents = browser_view()->GetActiveWebContents();
-    SidePanelRegistry* contextual_registry =
-        SidePanelRegistry::Get(active_contents);
-    ASSERT_EQ(contextual_registry->entries().size(), 1u);
-    EXPECT_EQ(contextual_registry->entries()[0]->key().id(),
-              SidePanelEntry::Id::kReadAnything);
-
-    // Verify the second tab has one entry, kReadAnything.
-    browser_view()->browser()->tab_strip_model()->ActivateTabAt(1);
-    active_contents = browser_view()->GetActiveWebContents();
-    contextual_registry = SidePanelRegistry::Get(active_contents);
-    ASSERT_EQ(contextual_registry->entries().size(), 1u);
-    EXPECT_EQ(contextual_registry->entries()[0]->key().id(),
-              SidePanelEntry::Id::kReadAnything);
   }
 
   // Wrapper methods around the ReadAnythingCoordinator. These do nothing more
@@ -94,8 +65,7 @@
  protected:
   raw_ptr<SidePanelCoordinator, DanglingUntriaged> side_panel_coordinator_ =
       nullptr;
-  std::vector<raw_ptr<SidePanelRegistry, DanglingUntriaged>>
-      contextual_registries_;
+  raw_ptr<SidePanelRegistry, DanglingUntriaged> side_panel_registry_ = nullptr;
   raw_ptr<ReadAnythingCoordinator, DanglingUntriaged>
       read_anything_coordinator_ = nullptr;
 
@@ -147,7 +117,7 @@
 TEST_F(ReadAnythingCoordinatorTest,
        ActivateCalled_ShowAndHideReadAnythingEntry) {
   AddObserver(&coordinator_observer_);
-  SidePanelEntry* entry = contextual_registries_[0]->GetEntryForKey(
+  SidePanelEntry* entry = side_panel_registry_->GetEntryForKey(
       SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything));
 
   EXPECT_CALL(coordinator_observer_, Activate(true)).Times(1);
diff --git a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller_utils.cc b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller_utils.cc
index ce9e732c..7f5e3798e 100644
--- a/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller_utils.cc
+++ b/chrome/browser/ui/views/side_panel/read_anything/read_anything_side_panel_controller_utils.cc
@@ -4,11 +4,7 @@
 
 #include "chrome/browser/ui/side_panel/read_anything/read_anything_side_panel_controller_utils.h"
 
-#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/side_panel/side_panel_ui.h"
-#include "chrome/browser/ui/views/side_panel/read_anything/read_anything_coordinator.h"
-#include "chrome/browser/ui/views/side_panel/side_panel_registry.h"
-#include "content/public/browser/web_contents_observer.h"
 
 void ShowReadAnythingSidePanel(Browser* browser) {
   SidePanelUI* side_panel_ui = SidePanelUI::GetSidePanelUIForBrowser(browser);
@@ -28,18 +24,3 @@
          (side_panel_ui->GetCurrentEntryId() ==
           SidePanelEntryId::kReadAnything);
 }
-
-void CreateAndRegisterEntry(content::WebContents* web_contents) {
-  auto* registry = SidePanelRegistry::Get(web_contents);
-  Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
-  CHECK(registry);
-  CHECK(browser);
-  // If the web contents is already registered to read anything, do nothing.
-  if (registry->GetEntryForKey(
-          SidePanelEntry::Key(SidePanelEntry::Id::kReadAnything))) {
-    return;
-  }
-
-  auto* coordinator = ReadAnythingCoordinator::GetOrCreateForBrowser(browser);
-  coordinator->CreateAndRegisterSidePanelEntry(registry);
-}
diff --git a/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc b/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
index de6eefd..a73fe22c 100644
--- a/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
+++ b/chrome/browser/ui/views/side_panel/search_companion/companion_page_browsertest.cc
@@ -1719,6 +1719,29 @@
       static_cast<int>(SidePanelOpenTrigger::kPinnedEntryToolbarButton));
 }
 
+IN_PROC_BROWSER_TEST_F(CompanionPageBrowserTest,
+                       RefreshCompanionPageMessageDoesReload) {
+  EnableSignInMsbbExps(/*signed_in=*/true, /*msbb=*/true, /*exps=*/true);
+
+  // Load a page on the active tab and open companion side panel
+  ASSERT_TRUE(
+      ui_test_utils::NavigateToURL(browser(), CreateUrl(kHost, kRelativeUrl1)));
+  side_panel_coordinator()->Show(SidePanelEntry::Id::kSearchCompanion);
+  WaitForCompanionToBeLoaded();
+  auto proto = GetLastCompanionProtoFromUrlLoad();
+  EXPECT_TRUE(proto.has_value());
+  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl1));
+
+  // Simulate a message to refresh companion page.
+  CompanionScriptBuilder builder(MethodType::kRefreshCompanionPage);
+  EXPECT_TRUE(ExecJs(builder.Build()));
+
+  WaitForCompanionIframeReload();
+  proto = GetLastCompanionProtoFromUrlLoad();
+  EXPECT_TRUE(proto.has_value());
+  EXPECT_EQ(proto->page_url(), CreateUrl(kHost, kRelativeUrl1));
+}
+
 class CompanionPageDisabledBrowserTest : public CompanionPageBrowserTest {
  public:
   CompanionPageDisabledBrowserTest() : CompanionPageBrowserTest() {
diff --git a/chrome/browser/ui/views/side_panel/side_panel_util.cc b/chrome/browser/ui/views/side_panel/side_panel_util.cc
index 98c92ce1..4443ed9 100644
--- a/chrome/browser/ui/views/side_panel/side_panel_util.cc
+++ b/chrome/browser/ui/views/side_panel/side_panel_util.cc
@@ -88,7 +88,8 @@
 
   // Add read anything.
   if (features::IsReadAnythingEnabled()) {
-    ReadAnythingCoordinator::GetOrCreateForBrowser(browser);
+    ReadAnythingCoordinator::GetOrCreateForBrowser(browser)
+        ->CreateAndRegisterEntry(global_registry);
   }
 
   // Create Search Companion coordinator.
diff --git a/chrome/browser/ui/views/toolbar/toolbar_view.cc b/chrome/browser/ui/views/toolbar/toolbar_view.cc
index 43c668cc..2ea09fd 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_view.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_view.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/companion/core/features.h"
 #include "chrome/browser/download/bubble/download_bubble_prefs.h"
 #include "chrome/browser/media/router/media_router_feature.h"
+#include "chrome/browser/performance_manager/public/user_tuning/user_tuning_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profiles_state.h"
 #include "chrome/browser/share/share_features.h"
@@ -123,14 +124,6 @@
 #include "chrome/browser/ui/bookmarks/bookmark_bubble_sign_in_delegate.h"
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "ash/constants/ash_features.h"
-#endif
-
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-#include "chromeos/startup/browser_params_proxy.h"
-#endif
-
 #if BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
 #include "chrome/browser/ui/views/frame/webui_tab_strip_container_view.h"
 #endif  // BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
@@ -200,17 +193,6 @@
 
   const raw_ptr<BrowserView> browser_view_;
 };
-
-bool IsCrosBatterySaverAvailable() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
-  return ash::features::IsBatterySaverAvailable();
-#elif BUILDFLAG(IS_CHROMEOS_LACROS)
-  return chromeos::BrowserParamsProxy::Get()->IsCrosBatterySaverAvailable();
-#else
-  return false;
-#endif
-}
-
 }  // namespace
 
 class ToolbarView::ContainerView : public views::View {
@@ -436,7 +418,7 @@
 
   // Only show the Battery Saver button when it is not controlled by the OS. On
   // ChromeOS the battery icon in the shelf shows the same information.
-  if (!IsCrosBatterySaverAvailable()) {
+  if (!performance_manager::user_tuning::IsBatterySaverModeManagedByOS()) {
     battery_saver_button_ = container_view_->AddChildView(
         std::make_unique<BatterySaverButton>(browser_view_));
   }
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 27afdfc..6610ea2 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
@@ -2540,7 +2540,7 @@
       EXPECT_TRUE(base::WriteFile(bin_path, bin_contents));
 
       // Since we modified the binary, we need to re-sign it.
-      if (base::mac::IsAtLeastOS12()) {
+      if (base::mac::MacOSMajorVersion() >= 12) {
         std::string codesign_output;
         std::vector<std::string> codesign_argv = {
             "codesign", "--force", "--sign", "-", bin_path.value()};
diff --git a/chrome/browser/ui/views/web_dialog_view_browsertest.cc b/chrome/browser/ui/views/web_dialog_view_browsertest.cc
index db3c669..1010f2b 100644
--- a/chrome/browser/ui/views/web_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/web_dialog_view_browsertest.cc
@@ -135,8 +135,9 @@
   // used for window modals like this dialog, always centers them within the
   // parent window regardless of the requested origin. The size is still
   // honored.
-  if (base::mac::IsAtLeastOS11())
+  if (base::mac::MacOSMajorVersion() >= 11) {
     centered_in_window = true;
+  }
 #endif
 
   gfx::Rect set_bounds = view_->GetWidget()->GetClientAreaBoundsInScreen();
diff --git a/chrome/browser/ui/webui/ash/enterprise_reporting/OWNER b/chrome/browser/ui/webui/ash/enterprise_reporting/OWNER
deleted file mode 100644
index 28e4985..0000000
--- a/chrome/browser/ui/webui/ash/enterprise_reporting/OWNER
+++ /dev/null
@@ -1 +0,0 @@
-file://components/reporting/OWNERS
diff --git a/chrome/browser/ui/webui/ash/enterprise_reporting/OWNERS b/chrome/browser/ui/webui/ash/enterprise_reporting/OWNERS
new file mode 100644
index 0000000..f298905f
--- /dev/null
+++ b/chrome/browser/ui/webui/ash/enterprise_reporting/OWNERS
@@ -0,0 +1,5 @@
+file://components/reporting/OWNERS
+
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
diff --git a/chrome/browser/ui/webui/settings/ash/device_section.cc b/chrome/browser/ui/webui/settings/ash/device_section.cc
index 0fea93d..4092faf 100644
--- a/chrome/browser/ui/webui/settings/ash/device_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/device_section.cc
@@ -1901,6 +1901,14 @@
 void DeviceSection::AddCustomizeButtonsPageStrings(
     content::WebUIDataSource* html_source) const {
   static constexpr webui::LocalizedString kCustomizeButtonsPageStrings[] = {
+      {"buttonRemappingDialogInputLabel",
+       IDS_SETTINGS_CUSTOMIZE_BUTTONS_RENAMING_DIALOG_INPUT_LABEL},
+      {"buttonRemappingDialogCancelLabel",
+       IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_CANCEL},
+      {"buttonRemappingDialogSaveLabel",
+       IDS_SETTINGS_CUSTOMIZE_BUTTONS_DIALOG_SAVE},
+      {"buttonRenamingDialogTitle",
+       IDS_SETTINGS_CUSTOMIZE_BUTTONS_RENAMING_DIALOG_TITLE},
       {"customizeMouseButtonsTitle",
        IDS_SETTINGS_CUSTOMIZE_MOUSE_BUTTONS_TITLE},
       {"keyCombinationOptionLabel", IDS_SETTINGS_KEY_COMBINATION_OPTION_LABEL},
diff --git a/chrome/browser/ui/webui/settings/ash/files_page/google_drive_page_handler.cc b/chrome/browser/ui/webui/settings/ash/files_page/google_drive_page_handler.cc
index c559a66..2dab688 100644
--- a/chrome/browser/ui/webui/settings/ash/files_page/google_drive_page_handler.cc
+++ b/chrome/browser/ui/webui/settings/ash/files_page/google_drive_page_handler.cc
@@ -33,6 +33,7 @@
           ? base::UTF16ToUTF8(ui::FormatBytes(progress.free_space))
           : "";
   status->stage = progress.stage;
+  status->listed_files = progress.listed_files;
   status->is_error = progress.IsError();
   return status;
 }
diff --git a/chrome/browser/ui/webui/settings/ash/files_page/mojom/google_drive_handler.mojom b/chrome/browser/ui/webui/settings/ash/files_page/mojom/google_drive_handler.mojom
index bdeb307..2a48e8c 100644
--- a/chrome/browser/ui/webui/settings/ash/files_page/mojom/google_drive_handler.mojom
+++ b/chrome/browser/ui/webui/settings/ash/files_page/mojom/google_drive_handler.mojom
@@ -20,6 +20,9 @@
   // The current stage the pin manager is going through.
   drivefs.pin_manager_types.mojom.Stage stage;
 
+  // The number of listed files during the `kListingFiles` stage.
+  uint64 listed_files;
+
   // Whether the stage returned is an error.
   bool is_error;
 };
diff --git a/chrome/browser/ui/webui/settings/ash/files_section.cc b/chrome/browser/ui/webui/settings/ash/files_section.cc
index ac1f95e5..ba6478eb 100644
--- a/chrome/browser/ui/webui/settings/ash/files_section.cc
+++ b/chrome/browser/ui/webui/settings/ash/files_section.cc
@@ -146,6 +146,12 @@
        IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_TURN_OFF_TITLE_TEXT},
       {"googleDriveFileSyncTurnOffBody",
        IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_TURN_OFF_BODY_TEXT},
+      {"googleDriveFileSyncListingFilesTitle",
+       IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_TITLE_TEXT},
+      {"googleDriveFileSyncListingFilesBody",
+       IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_BODY_TEXT},
+      {"googleDriveFileSyncListingFilesItemsFoundBody",
+       IDS_SETTINGS_GOOGLE_DRIVE_FILE_SYNC_LISTING_FILES_ITEMS_FOUND_BODY_TEXT},
       {"googleDriveNotEnoughSpaceTitle",
        IDS_SETTINGS_GOOGLE_DRIVE_BULK_PINNING_NOT_ENOUGH_SPACE_TITLE_TEXT},
       {"googleDriveNotEnoughSpaceBody",
diff --git a/chrome/browser/ui/webui/settings/performance_settings_interactive_uitest.cc b/chrome/browser/ui/webui/settings/performance_settings_interactive_uitest.cc
index 464295c..eaa03dd 100644
--- a/chrome/browser/ui/webui/settings/performance_settings_interactive_uitest.cc
+++ b/chrome/browser/ui/webui/settings/performance_settings_interactive_uitest.cc
@@ -10,6 +10,8 @@
 #include "base/test/scoped_feature_list.h"
 #include "build/branding_buildflags.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/performance_manager/public/user_tuning/battery_saver_mode_manager.h"
+#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/webui/feedback/feedback_dialog.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/test/base/ui_test_utils.h"
@@ -20,6 +22,11 @@
 #include "content/public/test/browser_test.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/constants/ash_features.h"
+#include "chrome/test/base/chromeos/crosier/interactive_ash_test.h"
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 using performance_manager::user_tuning::prefs::BatterySaverModeState;
 using performance_manager::user_tuning::prefs::HighEfficiencyModeState;
 
@@ -55,8 +62,10 @@
 class PerformanceSettingsInteractiveTest : public InteractiveBrowserTest {
  public:
   void SetUp() override {
-    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
     SetUpFakeBatterySampler();
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+    scoped_feature_list_.InitAndDisableFeature(ash::features::kBatterySaver);
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
     InteractiveBrowserTest::SetUp();
   }
 
@@ -65,6 +74,7 @@
     performance_manager::user_tuning::UserPerformanceTuningManager::
         GetInstance()
             ->SetHighEfficiencyModeEnabled(true);
+    ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
     embedded_test_server()->StartAcceptingConnections();
   }
 
@@ -176,6 +186,7 @@
   raw_ptr<base::test::TestBatteryLevelProvider, DanglingUntriaged>
       battery_level_provider_;
   std::unique_ptr<base::BatteryStateSampler> battery_state_sampler_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(PerformanceSettingsInteractiveTest,
@@ -365,6 +376,69 @@
 
 #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+class BatterySettingsInteractiveTest : public InteractiveAshTest {
+ public:
+  BatterySettingsInteractiveTest()
+      : scoped_feature_list_(ash::features::kBatterySaver) {}
+
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitch(
+        performance_manager::user_tuning::BatterySaverModeManager::
+            kForceDeviceHasBatterySwitch);
+  }
+
+  auto WaitForElementToRender(const ui::ElementIdentifier& contents_id,
+                              const DeepQuery& element) {
+    StateChange element_renders;
+    element_renders.event = kElementRenders;
+    element_renders.where = element;
+    element_renders.type = StateChange::Type::kExistsAndConditionTrue;
+    element_renders.test_function =
+        "(el) => { return el !== null && el.clientWidth > 0 && el.clientHeight "
+        "> 0; }";
+
+    return WaitForStateChange(contents_id, element_renders);
+  }
+
+  auto ClickElement(const ui::ElementIdentifier& contents_id,
+                    const DeepQuery& element) {
+    return Steps(WaitForElementToRender(contents_id, element),
+                 MoveMouseTo(contents_id, element), ClickMouse());
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(BatterySettingsInteractiveTest,
+                       BatterySaverSettingsLinksToOSSettings) {
+  SetupContextWidget();
+  InstallSystemApps();
+
+  DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kOsSettingsElementId);
+
+  const DeepQuery battery_saver_link_row = {
+      "settings-ui", "settings-main", "settings-basic-page",
+      "settings-battery-page", "cr-link-row#batterySaverOSSettingsLinkRow"};
+
+  CreateBrowserWindow(GURL(chrome::kChromeUIPerformanceSettingsURL));
+  Browser* const browser = chrome::FindLastActive();
+  ASSERT_NE(browser, nullptr);
+
+  RunTestSequence(
+      InContext(browser->window()->GetElementContext(),
+                InstrumentTab(kPerformanceSettingsPage)),
+      WaitForElementToRender(kPerformanceSettingsPage, battery_saver_link_row),
+      InstrumentNextTab(kOsSettingsElementId, AnyBrowser()),
+      ClickElement(kPerformanceSettingsPage, battery_saver_link_row),
+      WaitForShow(kOsSettingsElementId),
+      WaitForWebContentsReady(kOsSettingsElementId,
+                              GURL("chrome://os-settings/power")));
+}
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 class PerformanceSettingsMultiStateModeInteractiveTest
     : public PerformanceSettingsInteractiveTest {
  public:
@@ -372,7 +446,7 @@
     scoped_feature_list_.InitAndEnableFeature(
         performance_manager::features::kHighEfficiencyMultistateMode);
 
-    PerformanceSettingsInteractiveTest::SetUp();
+    InteractiveBrowserTest::SetUp();
   }
 
   auto WaitForDisabledStateChange(const ui::ElementIdentifier& contents_id,
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 1df17dfa..3f984bf 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -173,6 +173,14 @@
 namespace settings {
 namespace {
 
+#if BUILDFLAG(IS_CHROMEOS)
+std::string BuildOSSettingsUrl(const std::string& sub_page) {
+  std::string os_settings_url = chrome::kChromeUIOSSettingsURL;
+  os_settings_url.append(sub_page);
+  return os_settings_url;
+}
+#endif
+
 void AddCommonStrings(content::WebUIDataSource* html_source, Profile* profile) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
     {"add", IDS_ADD},
@@ -709,6 +717,8 @@
        IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_SETTING},
       {"batterySaverModeDescription",
        IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_SETTING_DESCRIPTION},
+      {"batterySaverModeLinkOsDescription",
+       IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_LINK_OS_SETTING_DESCRIPTION},
       {"batterySaverModeEnabledOnBatteryLabel",
        IDS_SETTINGS_PERFORMANCE_BATTERY_SAVER_MODE_ON_BATTERY_LABEL},
       {"batterySaverModeRadioGroupAriaLabel",
@@ -792,6 +802,12 @@
                          chrome::kHighEfficiencyModeLearnMoreUrl);
   html_source->AddString("batterySaverLearnMoreUrl",
                          chrome::kBatterySaverModeLearnMoreUrl);
+
+#if BUILDFLAG(IS_CHROMEOS)
+  html_source->AddString(
+      "osPowerSettingsUrl",
+      BuildOSSettingsUrl(chromeos::settings::mojom::kPowerSubpagePath));
+#endif
 }
 
 void AddLanguagesStrings(content::WebUIDataSource* html_source,
@@ -1596,14 +1612,6 @@
   html_source->AddLocalizedStrings(kLocalizedStrings);
 }
 
-#if BUILDFLAG(IS_CHROMEOS)
-std::string BuildOSSettingsUrl(const std::string& sub_page) {
-  std::string os_settings_url = chrome::kChromeUIOSSettingsURL;
-  os_settings_url.append(sub_page);
-  return os_settings_url;
-}
-#endif
-
 void AddBrowserSyncPageStrings(content::WebUIDataSource* html_source) {
   static constexpr webui::LocalizedString kLocalizedStrings[] = {
     {"peopleSignInSyncPagePromptSecondaryWithAccount",
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index 2a5de574..0cb83379 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/commerce/shopping_service_factory.h"
 #include "chrome/browser/download/bubble/download_bubble_prefs.h"
 #include "chrome/browser/performance_manager/public/user_tuning/user_performance_tuning_manager.h"
+#include "chrome/browser/performance_manager/public/user_tuning/user_tuning_utils.h"
 #include "chrome/browser/preloading/preloading_features.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service.h"
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.h"
@@ -523,6 +524,9 @@
   html_source->AddBoolean("isPerformanceSettingsPreloadingSubpageEnabled",
                           base::FeatureList::IsEnabled(
                               features::kPerformanceSettingsPreloadingSubpage));
+  html_source->AddBoolean(
+      "isBatterySaverModeManagedByOS",
+      performance_manager::user_tuning::IsBatterySaverModeManagedByOS());
 
   html_source->AddBoolean(
       "enablePermissionStorageAccessApi",
diff --git a/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.cc b/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.cc
index ecf6776..a5f05258 100644
--- a/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.cc
@@ -460,6 +460,10 @@
   signin_delegate_->OpenUrlInBrowser(url_to_open.value(), use_new_tab);
 }
 
+void CompanionPageHandler::RefreshCompanionPage() {
+  NotifyURLChanged(/*is_full_reload*/ true);
+}
+
 void CompanionPageHandler::OnNavigationError() {
   page_->OnNavigationError();
 }
diff --git a/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.h b/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.h
index 703c010..df406f822 100644
--- a/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/companion/companion_page_handler.h
@@ -68,6 +68,7 @@
   void OpenUrlInBrowser(const absl::optional<GURL>& url_to_open,
                         bool use_new_tab) override;
   void OnLoadingState(side_panel::mojom::LoadingState loading_state) override;
+  void RefreshCompanionPage() override;
 
   // content::WebContentsObserver overrides.
   void DidFinishNavigation(
diff --git a/chrome/browser/usb/chrome_usb_browsertest.cc b/chrome/browser/usb/chrome_usb_browsertest.cc
index a47889b..a26765b 100644
--- a/chrome/browser/usb/chrome_usb_browsertest.cc
+++ b/chrome/browser/usb/chrome_usb_browsertest.cc
@@ -1149,16 +1149,6 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 };
 
-// Test fixture with kEnableWebUsbOnExtensionServiceWorker enabled.
-class WebUsbExtensionFeatureEnabledBrowserTest
-    : public WebUsbExtensionBrowserTest {
- public:
-  WebUsbExtensionFeatureEnabledBrowserTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {features::kEnableWebUsbOnExtensionServiceWorker}, {});
-  }
-};
-
 // Test fixture with kEnableWebUsbOnExtensionServiceWorker disabled.
 class WebUsbExtensionFeatureDisabledBrowserTest
     : public WebUsbExtensionBrowserTest {
@@ -1169,20 +1159,6 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(WebUsbExtensionBrowserTest, FeatureDefaultDisabled) {
-  constexpr base::StringPiece kBackgroundJs = R"(
-    chrome.test.sendMessage("ready", async () => {
-      try {
-        chrome.test.assertEq(navigator.usb, undefined);
-        chrome.test.notifyPass();
-      } catch (e) {
-        chrome.test.fail(e.name + ':' + e.message);
-      }
-    });
-  )";
-  LoadExtensionAndRunTest(kBackgroundJs);
-}
-
 IN_PROC_BROWSER_TEST_F(WebUsbExtensionFeatureDisabledBrowserTest,
                        FeatureDisabled) {
   constexpr base::StringPiece kBackgroundJs = R"(
@@ -1198,7 +1174,7 @@
   LoadExtensionAndRunTest(kBackgroundJs);
 }
 
-IN_PROC_BROWSER_TEST_F(WebUsbExtensionFeatureEnabledBrowserTest, GetDevices) {
+IN_PROC_BROWSER_TEST_F(WebUsbExtensionBrowserTest, GetDevices) {
   constexpr base::StringPiece kBackgroundJs = R"(
     chrome.test.sendMessage("ready", async () => {
       try {
@@ -1214,8 +1190,7 @@
   LoadExtensionAndRunTest(kBackgroundJs);
 }
 
-IN_PROC_BROWSER_TEST_F(WebUsbExtensionFeatureEnabledBrowserTest,
-                       RequestDevice) {
+IN_PROC_BROWSER_TEST_F(WebUsbExtensionBrowserTest, RequestDevice) {
   constexpr base::StringPiece kBackgroundJs = R"(
     chrome.test.sendMessage("ready", async () => {
       try {
@@ -1229,8 +1204,7 @@
   LoadExtensionAndRunTest(kBackgroundJs);
 }
 
-IN_PROC_BROWSER_TEST_F(WebUsbExtensionFeatureEnabledBrowserTest,
-                       UsbConnectionTracker) {
+IN_PROC_BROWSER_TEST_F(WebUsbExtensionBrowserTest, UsbConnectionTracker) {
   constexpr char kBackgroundJs[] = R"(
     // |device| is a global variable to store UsbDevice object being tested in
     // case the local one is garbage collected, which can close the connection.
@@ -1259,7 +1233,7 @@
 
 // Test the scenario of waking up the service worker upon device events and
 // the service worker being kept alive with active device session.
-IN_PROC_BROWSER_TEST_F(WebUsbExtensionFeatureEnabledBrowserTest,
+IN_PROC_BROWSER_TEST_F(WebUsbExtensionBrowserTest,
                        DeviceConnectAndOpenDeviceWhenServiceWorkerStopped) {
   content::ServiceWorkerContext* context = browser()
                                                ->profile()
diff --git a/chrome/browser/usb/chrome_usb_delegate_unittest.cc b/chrome/browser/usb/chrome_usb_delegate_unittest.cc
index 2a621cee..d5f3a87 100644
--- a/chrome/browser/usb/chrome_usb_delegate_unittest.cc
+++ b/chrome/browser/usb/chrome_usb_delegate_unittest.cc
@@ -805,12 +805,13 @@
 };
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
-class EnableWebUsbOnExtensionServiceWorkerHelper {
+class DisableWebUsbOnExtensionServiceWorkerHelper {
  public:
-  EnableWebUsbOnExtensionServiceWorkerHelper() {
+  DisableWebUsbOnExtensionServiceWorkerHelper() {
     scoped_feature_list_.InitWithFeatures(
-        /*enabled_features=*/{features::kEnableWebUsbOnExtensionServiceWorker},
-        /*disabled_features=*/{});
+        /*enabled_features=*/{},
+        /*disabled_features=*/{
+            features::kEnableWebUsbOnExtensionServiceWorker});
   }
 
  private:
@@ -820,12 +821,18 @@
 class ChromeUsbDelegateExtensionRenderFrameTest
     : public ChromeUsbDelegateRenderFrameTestBase {
  public:
+  ChromeUsbDelegateExtensionRenderFrameTest() {
+    supports_usb_connection_tracker_ = true;
+  }
   void SetUpOriginUrl() override { SetUpExtensionOriginUrl(kExtensionId); }
 };
 
 class ChromeUsbDelegateImprivataExtensionRenderFrameTest
     : public ChromeUsbDelegateRenderFrameTestBase {
  public:
+  ChromeUsbDelegateImprivataExtensionRenderFrameTest() {
+    supports_usb_connection_tracker_ = true;
+  }
   void SetUpOriginUrl() override {
     SetUpExtensionOriginUrl(kAllowlistedImprivataExtensionId);
   }
@@ -834,17 +841,22 @@
 class ChromeUsbDelegateSmartCardExtensionRenderFrameTest
     : public ChromeUsbDelegateRenderFrameTestBase {
  public:
+  ChromeUsbDelegateSmartCardExtensionRenderFrameTest() {
+    supports_usb_connection_tracker_ = true;
+  }
   void SetUpOriginUrl() override {
     SetUpExtensionOriginUrl(kAllowlistedSmartCardExtensionId);
   }
 };
 
-class ChromeUsbDelegateExtensionRenderFrameFeatureEnabledTest
+class ChromeUsbDelegateExtensionRenderFrameFeatureDisabledTest
     : public ChromeUsbDelegateRenderFrameTestBase,
-      public EnableWebUsbOnExtensionServiceWorkerHelper {
+      public DisableWebUsbOnExtensionServiceWorkerHelper {
  public:
-  ChromeUsbDelegateExtensionRenderFrameFeatureEnabledTest() {
-    supports_usb_connection_tracker_ = true;
+  ChromeUsbDelegateExtensionRenderFrameFeatureDisabledTest() {
+    // There is no usb connection tracker activity when
+    // features::kEnableWebUsbOnExtensionServiceWorker is disabled.
+    supports_usb_connection_tracker_ = false;
   }
   void SetUpOriginUrl() override { SetUpExtensionOriginUrl(kExtensionId); }
 };
@@ -852,24 +864,28 @@
 class ChromeUsbDelegateExtensionServiceWorkerTest
     : public ChromeUsbDelegateServiceWorkerTestBase {
  public:
-  void SetUpOriginUrl() override { SetUpExtensionOriginUrl(kExtensionId); }
-};
-
-class ChromeUsbDelegateExtensionServiceWorkerFeatureEnabledTest
-    : public ChromeUsbDelegateServiceWorkerTestBase,
-      public EnableWebUsbOnExtensionServiceWorkerHelper {
- public:
-  ChromeUsbDelegateExtensionServiceWorkerFeatureEnabledTest() {
+  ChromeUsbDelegateExtensionServiceWorkerTest() {
     supports_usb_connection_tracker_ = true;
   }
   void SetUpOriginUrl() override { SetUpExtensionOriginUrl(kExtensionId); }
 };
 
-class ChromeUsbDelegateImprivataExtensionServiceWorkerFeatureEnabledTest
+class ChromeUsbDelegateExtensionServiceWorkerFeatureDisabledTest
     : public ChromeUsbDelegateServiceWorkerTestBase,
-      public EnableWebUsbOnExtensionServiceWorkerHelper {
+      public DisableWebUsbOnExtensionServiceWorkerHelper {
  public:
-  ChromeUsbDelegateImprivataExtensionServiceWorkerFeatureEnabledTest() {
+  ChromeUsbDelegateExtensionServiceWorkerFeatureDisabledTest() {
+    // There is no usb connection tracker activity when
+    // features::kEnableWebUsbOnExtensionServiceWorker is disabled.
+    supports_usb_connection_tracker_ = false;
+  }
+  void SetUpOriginUrl() override { SetUpExtensionOriginUrl(kExtensionId); }
+};
+
+class ChromeUsbDelegateImprivataExtensionServiceWorkerTest
+    : public ChromeUsbDelegateServiceWorkerTestBase {
+ public:
+  ChromeUsbDelegateImprivataExtensionServiceWorkerTest() {
     supports_usb_connection_tracker_ = true;
   }
   void SetUpOriginUrl() override {
@@ -877,11 +893,10 @@
   }
 };
 
-class ChromeUsbDelegateSmartCardExtensionServiceWorkerFeatureEnabledTest
-    : public ChromeUsbDelegateServiceWorkerTestBase,
-      public EnableWebUsbOnExtensionServiceWorkerHelper {
+class ChromeUsbDelegateSmartCardExtensionServiceWorkerTest
+    : public ChromeUsbDelegateServiceWorkerTestBase {
  public:
-  ChromeUsbDelegateSmartCardExtensionServiceWorkerFeatureEnabledTest() {
+  ChromeUsbDelegateSmartCardExtensionServiceWorkerTest() {
     supports_usb_connection_tracker_ = true;
   }
   void SetUpOriginUrl() override {
@@ -952,54 +967,48 @@
   TestAllowlistedSmartCardConnectorExtension(web_contents());
 }
 
-TEST_F(ChromeUsbDelegateExtensionRenderFrameFeatureEnabledTest,
+TEST_F(ChromeUsbDelegateExtensionRenderFrameFeatureDisabledTest,
        OpenAndCloseDevice) {
   TestOpenAndCloseDevice(web_contents());
 }
 
-TEST_F(ChromeUsbDelegateExtensionRenderFrameFeatureEnabledTest,
+TEST_F(ChromeUsbDelegateExtensionRenderFrameFeatureDisabledTest,
        OpenAndDisconnectDevice) {
   TestOpenAndDisconnectDevice(web_contents());
 }
 
-TEST_F(ChromeUsbDelegateExtensionServiceWorkerTest, WebUsbServiceNotConnected) {
+TEST_F(ChromeUsbDelegateExtensionServiceWorkerFeatureDisabledTest,
+       WebUsbServiceNotConnected) {
   TestWebUsbServiceNotConnected();
 }
-TEST_F(ChromeUsbDelegateImprivataExtensionServiceWorkerFeatureEnabledTest,
+
+TEST_F(ChromeUsbDelegateImprivataExtensionServiceWorkerTest,
        AllowlistedImprivataExtension) {
   TestAllowlistedImprivataExtension(nullptr);
 }
 
-TEST_F(ChromeUsbDelegateSmartCardExtensionServiceWorkerFeatureEnabledTest,
-       // TODO(crbug.com/1475901): Re-enable this test
-       DISABLED_AllowlistedSmartCardConnectorExtension) {
+TEST_F(ChromeUsbDelegateSmartCardExtensionServiceWorkerTest,
+       AllowlistedSmartCardConnectorExtension) {
   TestAllowlistedSmartCardConnectorExtension(nullptr);
 }
 
-TEST_F(ChromeUsbDelegateExtensionServiceWorkerFeatureEnabledTest,
-       // TODO(crbug.com/1475901): Re-enable this test
-       DISABLED_NoPermissionDevice) {
+TEST_F(ChromeUsbDelegateExtensionServiceWorkerTest, NoPermissionDevice) {
   TestNoPermissionDevice();
 }
 
-TEST_F(ChromeUsbDelegateExtensionServiceWorkerFeatureEnabledTest,
-       ReconnectDeviceManager) {
+TEST_F(ChromeUsbDelegateExtensionServiceWorkerTest, ReconnectDeviceManager) {
   TestReconnectDeviceManager();
 }
 
-TEST_F(ChromeUsbDelegateExtensionServiceWorkerFeatureEnabledTest,
-       RevokeDevicePermission) {
+TEST_F(ChromeUsbDelegateExtensionServiceWorkerTest, RevokeDevicePermission) {
   TestRevokeDevicePermission();
 }
 
-TEST_F(ChromeUsbDelegateExtensionServiceWorkerFeatureEnabledTest,
-       OpenAndCloseDevice) {
+TEST_F(ChromeUsbDelegateExtensionServiceWorkerTest, OpenAndCloseDevice) {
   TestOpenAndCloseDevice(/*web_contents=*/nullptr);
 }
 
-TEST_F(ChromeUsbDelegateExtensionServiceWorkerFeatureEnabledTest,
-       // TODO(crbug.com/1475901): Re-enable this test
-       DISABLED_OpenAndDisconnectDevice) {
+TEST_F(ChromeUsbDelegateExtensionServiceWorkerTest, OpenAndDisconnectDevice) {
   TestOpenAndDisconnectDevice(/*web_contents=*/nullptr);
 }
 
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model.cc b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
index a0790fd..6b3b749 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model.cc
@@ -69,7 +69,11 @@
     case AuthenticatorTransport::kUsbHumanInterfaceDevice:
       return IDS_WEBAUTHN_TRANSPORT_USB;
     case AuthenticatorTransport::kInternal:
+#if BUILDFLAG(IS_MAC)
+      return IDS_WEBAUTHN_YOUR_CHROME_PROFILE;
+#else
       return IDS_WEBAUTHN_TRANSPORT_INTERNAL;
+#endif
     case AuthenticatorTransport::kHybrid:
       return IDS_WEBAUTHN_TRANSPORT_CABLE;
     case AuthenticatorTransport::kAndroidAccessory:
@@ -1776,6 +1780,9 @@
         transport_availability_.has_platform_authenticator_credential ==
             device::FidoRequestHandlerBase::RecognizedCredential::
                 kNoRecognizedCredential &&
+        transport_availability_.has_icloud_keychain_credential ==
+            device::FidoRequestHandlerBase::RecognizedCredential::
+                kNoRecognizedCredential &&
         paired_phones_.size() == 1 && !use_conditional_mediation_ &&
         transport_availability_.is_only_hybrid_or_internal;
     if (skip_to_phone_confirmation) {
diff --git a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
index 8c8f4ae..414865b5 100644
--- a/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
+++ b/chrome/browser/webauthn/authenticator_request_dialog_model_unittest.cc
@@ -145,6 +145,7 @@
   kBleDisabled,
   kBleAccessDenied,
   kHasICloudKeychain,
+  kHasICloudKeychainCreds,
   kCreateInICloudKeychain,
   kNoTouchId,
 };
@@ -190,6 +191,8 @@
       return "kBleAccessDenied";
     case TransportAvailabilityParam::kHasICloudKeychain:
       return "kHasICloudKeychain";
+    case TransportAvailabilityParam::kHasICloudKeychainCreds:
+      return "kHasICloudKeychainCreds";
     case TransportAvailabilityParam::kCreateInICloudKeychain:
       return "kCreateInICloudKeychain";
     case TransportAvailabilityParam::kNoTouchId:
@@ -281,6 +284,7 @@
   const auto cred2 = CredentialInfoFrom(kCred2);
   const auto phonecred1 = CredentialInfoFrom(kPhoneCred1);
   const auto phonecred2 = CredentialInfoFrom(kPhoneCred2);
+  const auto ickc_cred1 = CredentialInfoFrom(kCred1FromICloudKeychain);
   const auto v1 = TransportAvailabilityParam::kHasCableV1Extension;
   const auto v2 = TransportAvailabilityParam::kHasCableV2Extension;
   const auto has_winapi =
@@ -310,6 +314,8 @@
       TransportAvailabilityParam::kCreateInICloudKeychain;
   [[maybe_unused]] const auto no_touchid =
       TransportAvailabilityParam::kNoTouchId;
+  [[maybe_unused]] const auto ickc_creds =
+      TransportAvailabilityParam::kHasICloudKeychainCreds;
   using c = AuthenticatorRequestDialogModel::Mechanism::Credential;
   using t = AuthenticatorRequestDialogModel::Mechanism::Transport;
   using p = AuthenticatorRequestDialogModel::Mechanism::Phone;
@@ -806,6 +812,14 @@
        {pqr("a")},
        {p("a"), add},
        mss},
+      // iCloud Keychain counts as a recognised platform credential too.
+      {L,
+       ga,
+       {cable, internal},
+       {only_hybrid_or_internal, has_ickc, ickc_creds},
+       {pqr("a")},
+       {c(ickc_cred1), p("a"), add},
+       plat_ui},
 #else
       // Phone confirmation sheet: Get assertion should jump to it if there is
       // a single phone paired.
@@ -1053,6 +1067,19 @@
     }
 
     if (base::Contains(test.params,
+                       TransportAvailabilityParam::kHasICloudKeychainCreds)) {
+      transports_info.has_icloud_keychain_credential =
+          device::FidoRequestHandlerBase::RecognizedCredential::
+              kHasRecognizedCredential;
+      transports_info.recognized_credentials.emplace_back(
+          kCred1FromICloudKeychain);
+    } else {
+      transports_info.has_icloud_keychain_credential =
+          device::FidoRequestHandlerBase::RecognizedCredential::
+              kNoRecognizedCredential;
+    }
+
+    if (base::Contains(test.params,
                        TransportAvailabilityParam::kOneRecognizedCred)) {
       transports_info.recognized_credentials = {kCred1};
     } else if (base::Contains(
@@ -1148,6 +1175,9 @@
     }
 #endif
 
+    model.SetAccountPreselectedCallback(
+        base::BindRepeating([](device::PublicKeyCredentialDescriptor cred) {}));
+
     if (has_v2_cable_extension.has_value() || !test.phones.empty() ||
         base::Contains(test.transports,
                        device::FidoTransportProtocol::kHybrid)) {
diff --git a/chrome/build/lacros64.pgo.txt b/chrome/build/lacros64.pgo.txt
index 24e3dd9..4ad0d5c7 100644
--- a/chrome/build/lacros64.pgo.txt
+++ b/chrome/build/lacros64.pgo.txt
@@ -1 +1 @@
-chrome-chromeos-amd64-generic-main-1693223885-3d1cfb022438f5ea54903002310dde594ddd0723.profdata
+chrome-chromeos-amd64-generic-main-1693267185-f1a828a802fec6b73f059f1a8f2aa0cb3e679ff9.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 4eb9674..706f803 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1693252748-b702fd908a4e86c978ca9c2e86fee4979a2c0e96.profdata
+chrome-mac-arm-main-1693259895-f4f566002afb1c728421b8b664c96c1c793ea1a1.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index c0777af..1733877e 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1693234781-d14c62267c9dc68d344e48947422b562a026049d.profdata
+chrome-win32-main-1693254218-d25431719d846a18cdb9cd7e9446f49ca171c4de.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 0b2c49eb..56e9257 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1693234781-8c8e704089f580ed1e03fbb24b25e2451a51f774.profdata
+chrome-win64-main-1693254218-f5959cf0ce63e721c2b9be4a61097d71aaec23bf.profdata
diff --git a/chrome/common/accessibility/read_anything_constants.h b/chrome/common/accessibility/read_anything_constants.h
index 9ef70bb..9f5d7f2e 100644
--- a/chrome/common/accessibility/read_anything_constants.h
+++ b/chrome/common/accessibility/read_anything_constants.h
@@ -58,6 +58,7 @@
 // Enum for logging when a text style setting is changed.
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
+// TODO(crbug.com/1465029): Remove this enum once the views toolbar is removed.
 enum class ReadAnythingSettingsChange {
   kFontChange = 0,
   kFontSizeChange = 1,
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 8f975e15..5af24957 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -405,7 +405,7 @@
 // Enable WebUSB on extension service workers.
 BASE_FEATURE(kEnableWebUsbOnExtensionServiceWorker,
              "EnableWebUsbOnExtensionServiceWorker",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enable extended descriptions for key settings in Chrome settings.
 BASE_FEATURE(kExtendedSettingsDescriptions,
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index fd6ee75..14cdc8a 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -1318,10 +1318,6 @@
 // of lacros-chrome is complete.
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)
 // Linux specific preference on whether we should match the system theme.
-// TODO(crbug.com/1476487): The pref `kUsesSystemThemeDeprecated` is still in
-// use. Please remove once code is cleaned up.
-inline constexpr char kUsesSystemThemeDeprecated[] =
-    "extensions.theme.use_system";
 inline constexpr char kSystemTheme[] = "extensions.theme.system_theme";
 #endif
 inline constexpr char kCurrentThemePackFilename[] = "extensions.theme.pack";
diff --git a/chrome/services/mac_notifications/mac_notification_service_ns.mm b/chrome/services/mac_notifications/mac_notification_service_ns.mm
index bb7fed20..31d44ad 100644
--- a/chrome/services/mac_notifications/mac_notification_service_ns.mm
+++ b/chrome/services/mac_notifications/mac_notification_service_ns.mm
@@ -108,7 +108,7 @@
   // A default close button label is provided by the platform but we explicitly
   // override it in case the user decides to not use the OS language in Chrome.
   // macOS 11 already shows a close button in the top-left corner.
-  if (!base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() < 11) {
     [notification setOtherButtonTitle:l10n_util::GetNSString(
                                           IDS_NOTIFICATION_BUTTON_CLOSE)];
   }
@@ -149,7 +149,7 @@
   // will always show "Options" via this API. Setting actionButtonTitle just
   // appends another button into the overflow menu. Only the new UNNotification
   // API allows overriding this title on macOS 11.
-  if (base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() >= 11) {
     [notification setValue:@NO forKey:@"_hasActionButton"];
   } else {
     [notification setActionButtonTitle:l10n_util::GetNSString(
diff --git a/chrome/services/mac_notifications/notification_category_manager.mm b/chrome/services/mac_notifications/notification_category_manager.mm
index 7c2e8658a..f2962322 100644
--- a/chrome/services/mac_notifications/notification_category_manager.mm
+++ b/chrome/services/mac_notifications/notification_category_manager.mm
@@ -183,8 +183,9 @@
                    options:UNNotificationActionOptionNone];
 
   // macOS 11 shows a close button in the top-left corner.
-  if (!base::mac::IsAtLeastOS11())
+  if (base::mac::MacOSMajorVersion() < 11) {
     [buttons_array addObject:close_button];
+  }
 
   // We only support up to two user action buttons.
   DCHECK_LE(buttons.size(), 2u);
@@ -206,7 +207,7 @@
   // be set as [button, Close] so that close is on top. If there are more than 2
   // buttons or we're on macOS 11, the buttons end up in an overflow menu which
   // shows them the correct way around.
-  if (!base::mac::IsAtLeastOS11() && buttons_array.count == 2) {
+  if (base::mac::MacOSMajorVersion() < 11 && buttons_array.count == 2) {
     // Remove the close button and move it to the end of the array.
     [buttons_array removeObject:close_button];
     [buttons_array addObject:close_button];
@@ -225,7 +226,7 @@
   // both alerts and banners, and modifies its content so that it is consistent
   // with the rest of the notification buttons. Otherwise, the text inside the
   // close button will come from the Apple API.
-  if (!base::mac::IsAtLeastOS11() &&
+  if (base::mac::MacOSMajorVersion() < 11 &&
       [category respondsToSelector:@selector(alternateAction)]) {
     [buttons_array removeObject:close_button];
     [category setValue:buttons_array forKey:@"actions"];
diff --git a/chrome/services/mac_notifications/notification_category_manager_unittest.mm b/chrome/services/mac_notifications/notification_category_manager_unittest.mm
index 8ebe9b06..2a25f22 100644
--- a/chrome/services/mac_notifications/notification_category_manager_unittest.mm
+++ b/chrome/services/mac_notifications/notification_category_manager_unittest.mm
@@ -69,7 +69,7 @@
   EXPECT_NSEQ(category_id, [category identifier]);
 
   // Test contents of the category
-  if (base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() >= 11) {
     EXPECT_EQ("Settings",
               base::SysNSStringToUTF8([[[category actions] lastObject] title]));
     EXPECT_EQ(
@@ -119,7 +119,7 @@
   EXPECT_NSEQ(category_id, [category identifier]);
 
   // Test contents of the category
-  if (base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() >= 11) {
     EXPECT_EQ("Button1",
               base::SysNSStringToUTF8([[category actions][0] title]));
     EXPECT_EQ(base::SysNSStringToUTF8(kNotificationButtonOne),
@@ -181,7 +181,7 @@
   UNNotificationAction* action_2 = nullptr;
 
   // Test contents of the category
-  if (base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() >= 11) {
     ASSERT_EQ(3ul, [[category actions] count]);
     action_1 = [category actions][0];
     action_2 = [category actions][1];
@@ -243,7 +243,7 @@
   EXPECT_NSEQ(category_id, [category identifier]);
 
   // Test contents of the category
-  if (base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() >= 11) {
     EXPECT_EQ(0ul, [[category actions] count]);
   } else if ([category respondsToSelector:@selector(alternateAction)]) {
     EXPECT_EQ("Close", base::SysNSStringToUTF8(
@@ -276,7 +276,7 @@
   EXPECT_NSEQ(category_id, [category identifier]);
 
   // Test contents of the category
-  if (base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() >= 11) {
     EXPECT_EQ("Button1",
               base::SysNSStringToUTF8([[category actions][0] title]));
     EXPECT_EQ(base::SysNSStringToUTF8(kNotificationButtonOne),
diff --git a/chrome/test/base/chromeos/crosier/interactive_ash_test.cc b/chrome/test/base/chromeos/crosier/interactive_ash_test.cc
index cf98401..6fec1ef 100644
--- a/chrome/test/base/chromeos/crosier/interactive_ash_test.cc
+++ b/chrome/test/base/chromeos/crosier/interactive_ash_test.cc
@@ -4,17 +4,29 @@
 
 #include "chrome/test/base/chromeos/crosier/interactive_ash_test.h"
 
+#include "ash/constants/ash_switches.h"
 #include "ash/root_window_controller.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/system/status_area_widget.h"
 #include "base/check.h"
+#include "base/command_line.h"
+#include "base/environment.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
+#include "base/test/test_switches.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "chrome/browser/ash/app_restore/full_restore_app_launch_handler.h"
 #include "chrome/browser/ash/dbus/ash_dbus_helper.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/lifetime/application_lifetime.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#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 "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
@@ -117,3 +129,61 @@
   params.window_action = NavigateParams::SHOW_WINDOW;
   return Navigate(&params);
 }
+
+void InteractiveAshTest::SetUpCommandLineForLacros(
+    base::CommandLine* command_line) {
+  CHECK(command_line);
+
+  // Enable the Wayland server.
+  command_line->AppendSwitch(ash::switches::kAshEnableWaylandServer);
+
+  // Set up XDG_RUNTIME_DIR for Wayland.
+  std::unique_ptr<base::Environment> env(base::Environment::Create());
+  CHECK(scoped_temp_dir_xdg_.CreateUniqueTempDir());
+  env->SetVar("XDG_RUNTIME_DIR", scoped_temp_dir_xdg_.GetPath().AsUTF8Unsafe());
+}
+
+void InteractiveAshTest::WaitForAshFullyStarted() {
+  CHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
+      ash::switches::kAshEnableWaylandServer))
+      << "Did you forget to call SetUpCommandLineForLacros?";
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  base::FilePath xdg_path = scoped_temp_dir_xdg_.GetPath();
+  base::RepeatingTimer timer;
+  base::RunLoop run_loop1;
+  timer.Start(FROM_HERE, base::Milliseconds(100),
+              base::BindLambdaForTesting([&]() {
+                if (base::PathExists(xdg_path.Append("wayland-0")) &&
+                    base::PathExists(xdg_path.Append("wayland-0.lock"))) {
+                  run_loop1.Quit();
+                }
+              }));
+  base::ThreadPool::PostDelayedTask(FROM_HERE, run_loop1.QuitClosure(),
+                                    TestTimeouts::action_max_timeout());
+  run_loop1.Run();
+  CHECK(base::PathExists(xdg_path.Append("wayland-0")));
+  CHECK(base::PathExists(xdg_path.Append("wayland-0.lock")));
+
+  // Wait for ChromeBrowserMainExtraParts::PostBrowserStart() to execute so that
+  // crosapi is initialized.
+  auto* extra_parts = ChromeBrowserMainExtraPartsAsh::Get();
+  CHECK(extra_parts);
+  if (!extra_parts->did_post_browser_start()) {
+    base::RunLoop run_loop2;
+    extra_parts->set_post_browser_start_callback(run_loop2.QuitClosure());
+    run_loop2.Run();
+  }
+  CHECK(extra_parts->did_post_browser_start());
+}
+
+void InteractiveAshTest::TearDownOnMainThread() {
+  // Passing --test-launcher-interactive leaves the browser running after the
+  // end of the test.
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kTestLauncherInteractive)) {
+    base::RunLoop loop;
+    loop.Run();
+  }
+  InteractiveBrowserTestT<
+      MixinBasedInProcessBrowserTest>::TearDownOnMainThread();
+}
diff --git a/chrome/test/base/chromeos/crosier/interactive_ash_test.h b/chrome/test/base/chromeos/crosier/interactive_ash_test.h
index 62353a1b..355b3fb 100644
--- a/chrome/test/base/chromeos/crosier/interactive_ash_test.h
+++ b/chrome/test/base/chromeos/crosier/interactive_ash_test.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/files/scoped_temp_dir.h"
 #include "base/memory/weak_ptr.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/test/base/mixin_based_in_process_browser_test.h"
@@ -20,6 +21,10 @@
 class GURL;
 class Profile;
 
+namespace base {
+class CommandLine;
+}
+
 namespace content {
 class NavigationHandle;
 }
@@ -75,6 +80,21 @@
   // browser_navigator.h.
   base::WeakPtr<content::NavigationHandle> CreateBrowserWindow(const GURL& url);
 
+  // Sets up the command line and environment variables to support Lacros (by
+  // enabling the Wayland server in ash). Call this from SetUpCommandLine() if
+  // your test starts Lacros.
+  void SetUpCommandLineForLacros(base::CommandLine* command_line);
+
+  // Waits for Ash to be ready for Lacros, including starting the "Exo" Wayland
+  // server. Call this method if your test starts Lacros, otherwise Exo may not
+  // be ready and Lacros may not start.
+  // TODO(http://b/297930282): Ensure we compile ToT Lacros and use it when
+  // testing ToT ash. The rootfs Lacros may be too old to run with ToT ash.
+  void WaitForAshFullyStarted();
+
+  // MixinBasedInProcessBrowserTest:
+  void TearDownOnMainThread() override;
+
  private:
 #if BUILDFLAG(IS_CHROMEOS_DEVICE)
   // This test runs on linux-chromeos in interactive_ui_tests and on a DUT in
@@ -88,6 +108,9 @@
   std::unique_ptr<FakeSessionManagerClientBrowserHelper>
       fake_session_manager_client_helper_;
 #endif
+
+  // Directory used by Wayland/Lacros in environment variable XDG_RUNTIME_DIR.
+  base::ScopedTempDir scoped_temp_dir_xdg_;
 };
 
 #endif  // CHROME_TEST_BASE_CHROMEOS_CROSIER_INTERACTIVE_ASH_TEST_H_
diff --git a/chrome/test/base/chromeos/crosier/interactive_ash_test_uitest.cc b/chrome/test/base/chromeos/crosier/interactive_ash_test_uitest.cc
index 1ed0ffb7..264a8636 100644
--- a/chrome/test/base/chromeos/crosier/interactive_ash_test_uitest.cc
+++ b/chrome/test/base/chromeos/crosier/interactive_ash_test_uitest.cc
@@ -2,14 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/test/test_switches.h"
 #include "chrome/test/base/chromeos/crosier/interactive_ash_test.h"
 #include "url/gurl.h"
 
 namespace ash {
 namespace {
 
-using InteractiveAshTestUITest = InteractiveAshTest;
+class InteractiveAshTestUITest : public InteractiveAshTest {
+ public:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    InteractiveAshTest::SetUpCommandLine(command_line);
+    SetUpCommandLineForLacros(command_line);
+  }
+};
 
 IN_PROC_BROWSER_TEST_F(InteractiveAshTestUITest, Basics) {
   SetupContextWidget();
@@ -17,6 +22,9 @@
   // Verify that installing system apps doesn't crash or flake.
   InstallSystemApps();
 
+  // Verify that the Wayland server starts and doesn't crash or flake.
+  WaitForAshFullyStarted();
+
   // Verify an active user exists.
   ASSERT_TRUE(GetActiveUserProfile());
 
@@ -27,14 +35,6 @@
   // Open a second browser window.
   GURL blank_url("about:blank");
   ASSERT_TRUE(CreateBrowserWindow(blank_url));
-
-  // You don't need this for your tests. This is just to prevent the test from
-  // exiting so you can play with the browser windows.
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          switches::kTestLauncherInteractive)) {
-    base::RunLoop loop;
-    loop.Run();
-  }
 }
 
 }  // namespace
diff --git a/chrome/test/data/extensions/api_test/user_scripts/get_scripts/worker.js b/chrome/test/data/extensions/api_test/user_scripts/get_scripts/worker.js
index 1d9023ff..99d07b3f 100644
--- a/chrome/test/data/extensions/api_test/user_scripts/get_scripts/worker.js
+++ b/chrome/test/data/extensions/api_test/user_scripts/get_scripts/worker.js
@@ -43,7 +43,6 @@
         allFrames: false,
         runAt: 'document_end'
       }
-
     ];
 
     await chrome.userScripts.register(userScriptsToRegister);
@@ -57,11 +56,35 @@
     scripts = await chrome.userScripts.getScripts({});
     chrome.test.assertEq(expectedUserScripts, scripts);
 
+    chrome.test.succeed();
+  },
+
+  // Tests that calling getScripts with empty filter ids returns zero scripts.
+  async function getScripts_EmptyFilterIds() {
+    await chrome.userScripts.unregister();
+
+    const userScriptsToRegister = [
+      {
+        id: 'script1',
+        matches: ['*://*/*'],
+        excludeMatches: ['*://abc.com/*'],
+        allFrames: true,
+        js: [{file: 'empty.js'}]
+      },
+      {
+        id: 'script2',
+        matches: ['*://requested.com/*'],
+        js: [{file: 'empty2.js'}],
+        runAt: 'document_end'
+      }
+    ];
+
+
+    await chrome.userScripts.register(userScriptsToRegister);
+
     // Calling getScripts with empty ids in filter returns no scripts.
-    // TODO(crbug.com/385165): Move to its separate test after implementing
-    // userScripts.unregister(), so we can unregister scripts in between tests.
-    scripts = await chrome.userScripts.getScripts({ids: []});
-    chrome.test.assertEq([], scripts);
+    const scripts = await chrome.userScripts.getScripts({ ids: [] });
+    chrome.test.assertEq(0, scripts.length);
 
     chrome.test.succeed();
   },
@@ -69,6 +92,8 @@
   // Tests that calling getScripts with a given filter returns only scripts
   // matching the filter.
   async function getScripts_Filter() {
+    await chrome.userScripts.unregister();
+
     const scriptsToRegister = [
       {id: 'script3', matches: ['*://*/*'], js: [{file: 'empty.js'}]},
       {id: 'script4', matches: ['*://*/*'], js: [{file: 'empty2.js'}]}
diff --git a/chrome/test/data/extensions/api_test/user_scripts/register/worker.js b/chrome/test/data/extensions/api_test/user_scripts/register/worker.js
index 9c69059..34a89806 100644
--- a/chrome/test/data/extensions/api_test/user_scripts/register/worker.js
+++ b/chrome/test/data/extensions/api_test/user_scripts/register/worker.js
@@ -5,10 +5,6 @@
 import {openTab} from '/_test_resources/test_util/tabs_util.js';
 
 chrome.test.runTests([
-
-  // TODO(crbug.com/1385165): Clear all registered user scripts at the beginning
-  // of each test once userScripts.unregister() is implemented.
-
   // Tests that an error is returned when multiple user script entries in
   // userScripts.register share the same ID.
   async function duplicateScriptId_DuplicatesInSameCall() {
@@ -30,6 +26,8 @@
   // succession, the first call will successfully register the script and the
   // second call with return with an error.
   async function duplicateScriptId_DuplicateInPendingRegistration() {
+    await chrome.userScripts.unregister();
+
     const scriptId = 'script2';
     const scripts = [
       {id: scriptId, matches: ['*://notused.com/*'], js: [{file: 'script.js'}]}
@@ -50,6 +48,8 @@
   // Tests that an error is returned when a user script to be registered has
   // the same ID as a previously registered user script.
   async function duplicateScriptId_DuplicatePreviouslyRegistered() {
+    await chrome.userScripts.unregister();
+
     const scriptId = 'script3';
     const scripts = [
       {id: scriptId, matches: ['*://notused.com/*'], js: [{file: 'script.js'}]}
@@ -66,6 +66,8 @@
   // Tests that an error is returned if a user script is specified with an
   // invalid ID.
   async function emptyScriptId() {
+    await chrome.userScripts.unregister();
+
     const scripts =
         [{id: '', matches: ['*://notused.com/*'], js: [{file: 'script.js'}]}];
 
@@ -79,9 +81,11 @@
   // Test that no scripts are registered when an empty array of scripts is
   // passed to userScripts.register.
   async function emptyScripts() {
+    await chrome.userScripts.unregister();
+
     await chrome.userScripts.register([]);
-    // TODO(crbug.com/1459670): Verify there are no registered scripts once
-    // userScripts.getScripts() is implemented.
+    const registeredUserScripts = await chrome.userScripts.getScripts();
+    chrome.test.assertEq(0, registeredUserScripts.length);
 
     chrome.test.succeed();
   },
@@ -89,6 +93,8 @@
   // Test that an error is returned if a user script is specified with a file
   // that cannot be read.
   async function scriptFileError() {
+    await chrome.userScripts.unregister();
+
     const scriptFile = 'nonexistent.js';
     const scripts = [
       {id: 'script4', matches: ['*://notused.com/*'], js: [{file: scriptFile}]}
@@ -104,6 +110,8 @@
   // Test that an error is returned if a user script does not specify any
   // script source to inject.
   async function invalidScriptSource_EmptyJs() {
+    await chrome.userScripts.unregister();
+
     const scriptId = 'empty';
     const scripts = [{id: scriptId, matches: ['*://notused.com/*'], js: []}];
 
@@ -117,6 +125,8 @@
   // Test that an error is returned if a user script source does not specify
   // either code or file.
   async function invalidScriptSource_EmptySource() {
+    await chrome.userScripts.unregister();
+
     const scriptId = 'script5';
     const scripts = [{id: scriptId, matches: ['*://notused.com/*'], js: [{}]}];
 
@@ -131,6 +141,8 @@
   // Test that an error is returned if a user script source specifies both
   // code and file.
   async function invalidScriptSource_MultipleSource() {
+    await chrome.userScripts.unregister();
+
     const scriptId = 'script6';
     const scripts = [{
       id: scriptId,
@@ -148,6 +160,8 @@
 
   // Test that a user script must specify a list of match patterns.
   async function matchPatternsNotSpecified() {
+    await chrome.userScripts.unregister();
+
     const scriptId = 'script7';
     const scripts = [{id: scriptId, js: [{file: 'script.js'}]}];
 
@@ -161,6 +175,8 @@
   // Test that an error is returned if a user script specifies a malformed
   // match pattern.
   async function invalidMatchPattern() {
+    await chrome.userScripts.unregister();
+
     const scripts = [{
       id: 'invalidMatchPattern',
       matches: ['invalid**match////'],
@@ -178,6 +194,8 @@
   // Tests that a (valid) script is registered and injected into a frame where
   // the extension has host permissions for.
   async function scriptRegistered_HostPermissions() {
+    await chrome.userScripts.unregister();
+
     const scripts = [{
       id: 'hostPerms',
       matches: ['*://requested.com/*'],
@@ -202,6 +220,8 @@
   // Tests that a registered user script will not be injected into a frame
   // where the extension does not have the host permissions for.
   async function scriptRegistered_NoHostPermissions() {
+    await chrome.userScripts.unregister();
+
     const scripts = [{
       id: 'noHostPerms',
       matches: ['*://non-requested.com/*'],
@@ -226,6 +246,8 @@
 
   // Tests that a file can be used both as a user script and content script.
   async function fileUsedAsContentScript() {
+    await chrome.userScripts.unregister();
+
     const file = 'script.js'
     const contentScripts =
         [{id: 'contentScript', matches: ['*://*/*'], js: [file]}];
@@ -235,13 +257,11 @@
     await chrome.scripting.registerContentScripts(contentScripts);
     await chrome.userScripts.register(userScripts);
 
-    let registered_content_scripts =
+    const registerContentScripts =
         await chrome.scripting.getRegisteredContentScripts();
-    chrome.test.assertEq('contentScript', registered_content_scripts[0].id);
-    // TODO(crbug.com/1385165): Assert user script was registered once we add
-    // a User Scripts API method for retrieving user scripts and unregistering
-    // them in between tests (otherwise we would retrieve here all user scripts
-    // across tests).
+    chrome.test.assertEq('contentScript', registerContentScripts[0].id);
+    const registeredUserScripts = await chrome.userScripts.getScripts();
+    chrome.test.assertEq('userScript', registeredUserScripts[0].id);
 
     chrome.test.succeed();
   },
diff --git a/chrome/test/data/extensions/api_test/web_authentication_proxy/util.js b/chrome/test/data/extensions/api_test/web_authentication_proxy/util.js
index c0a9afa..deadabc 100644
--- a/chrome/test/data/extensions/api_test/web_authentication_proxy/util.js
+++ b/chrome/test/data/extensions/api_test/web_authentication_proxy/util.js
@@ -11,7 +11,10 @@
   "authenticatorAttachment": "cross-platform",
   "response": {
     "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjE5FMp0DogaNHK9_e7CulU5rDmJZdF8y9IKfdQ8FAR-cJBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKnIoE6PUxtEEyfXqdBqSnQ6yPhGtof1L50MYa1JOtmfS5XD0Q7BzH-yYKi1D-BrdMMquwW8DBfzxAtUatWsSFGlAQIDJiABIVggqInVFbKi0k_Qd2WH9kK4hZnhXPjhWlRqTtQxoyros1IiWCCo9UskSZuzG14q_dREih7thij6Kj-YvwSd86USfrV5fA",
+    "authenticatorData": "5FMp0DogaNHK9_e7CulU5rDmJZdF8y9IKfdQ8FAR-cJBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKnIoE6PUxtEEyfXqdBqSnQ6yPhGtof1L50MYa1JOtmfS5XD0Q7BzH-yYKi1D-BrdMMquwW8DBfzxAtUatWsSFGlAQIDJiABIVggqInVFbKi0k_Qd2WH9kK4hZnhXPjhWlRqTtQxoyros1IiWCCo9UskSZuzG14q_dREih7thij6Kj-YvwSd86USfrV5fA",
     "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiZEdWemRBIiwib3JpZ2luIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
+    "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqInVFbKi0k_Qd2WH9kK4hZnhXPjhWlRqTtQxoyros1Ko9UskSZuzG14q_dREih7thij6Kj-YvwSd86USfrV5fA",
+    "publicKeyAlgorithm": -7,
     "transports": ["usb"]
   },
   "clientExtensionResults": {}
diff --git a/chrome/test/data/extensions/webui_untrusted/sanity_check_available_apis_read_anything.js b/chrome/test/data/extensions/webui_untrusted/sanity_check_available_apis_read_anything.js
index 5ea27b4..bf70253 100644
--- a/chrome/test/data/extensions/webui_untrusted/sanity_check_available_apis_read_anything.js
+++ b/chrome/test/data/extensions/webui_untrusted/sanity_check_available_apis_read_anything.js
@@ -15,6 +15,9 @@
   // Deprecated proprietary Chrome APIs unrelated to Extensions.
   'csi',
   'loadTimes',
+  // For logging various activity in
+  // chrome-untrusted://read-anything-side-panel.top-chrome.
+  'metricsPrivate',
   // chrome.readingMode is available in
   // chrome-untrusted://read-anything-side-panel.top-chrome.
   'readingMode',
diff --git a/chrome/test/data/webui/cr_components/theme_color_picker/theme_hue_slider_dialog_test.ts b/chrome/test/data/webui/cr_components/theme_color_picker/theme_hue_slider_dialog_test.ts
index 2d269d6..4c083cc0 100644
--- a/chrome/test/data/webui/cr_components/theme_color_picker/theme_hue_slider_dialog_test.ts
+++ b/chrome/test/data/webui/cr_components/theme_color_picker/theme_hue_slider_dialog_test.ts
@@ -75,8 +75,45 @@
     const anchor = document.createElement('div');
     document.body.appendChild(anchor);
     element.showAt(anchor);
-    assertTrue(element.$.crActionMenu.getDialog().open);
+    assertTrue(element.$.dialog.open);
     element.hide();
-    assertFalse(element.$.crActionMenu.getDialog().open);
+    assertFalse(element.$.dialog.open);
+  });
+
+  test('PositionsCorrectly', () => {
+    const windowHeight = 1000;
+    const dialogWidth = 100;
+    const dialogHeight = 200;
+    const anchorWidth = 50;
+    const anchorHeight = 25;
+    const anchorTop = 300;
+    const anchorLeft = 400;
+
+    // Force some dimensions to testing is more predictable.
+    const anchor = document.createElement('div');
+    anchor.style.position = 'fixed';
+    anchor.style.top = `${anchorTop}px`;
+    anchor.style.height = `${anchorHeight}px`;
+    anchor.style.left = `${anchorLeft}px`;
+    anchor.style.width = `${anchorWidth}px`;
+    element.$.dialog.style.width = `${dialogWidth}px`;
+    element.$.dialog.style.height = `${dialogHeight}px`;
+    window.innerHeight = windowHeight;
+
+    document.body.appendChild(anchor);
+    element.showAt(anchor);
+
+    assertEquals(`${anchorTop + anchorHeight}px`, element.$.dialog.style.top);
+    assertEquals(
+        `${anchorLeft + anchorWidth - dialogWidth}px`,
+        element.$.dialog.style.left);
+    element.hide();
+
+    // Test that the top position changes if anchor is near bottom of window.
+    const newAnchorTop = windowHeight;
+    anchor.style.top = `${newAnchorTop}px`;
+    element.showAt(anchor);
+    assertEquals(
+        `${newAnchorTop - dialogHeight}px`, element.$.dialog.style.top);
   });
 });
diff --git a/chrome/test/data/webui/settings/battery_page_test.ts b/chrome/test/data/webui/settings/battery_page_test.ts
index 8c23dff..31099c8a 100644
--- a/chrome/test/data/webui/settings/battery_page_test.ts
+++ b/chrome/test/data/webui/settings/battery_page_test.ts
@@ -5,7 +5,8 @@
 import 'chrome://settings/settings.js';
 
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {BATTERY_SAVER_MODE_PREF, BatterySaverModeState, PerformanceBrowserProxyImpl, PerformanceMetricsProxyImpl, SettingsBatteryPageElement} from 'chrome://settings/settings.js';
+import {IronCollapseElement, SettingsRadioGroupElement} from 'chrome://settings/lazy_load.js';
+import {BATTERY_SAVER_MODE_PREF, BatterySaverModeState, ControlledRadioButtonElement, PerformanceBrowserProxyImpl, PerformanceMetricsProxyImpl, SettingsBatteryPageElement, SettingsToggleButtonElement} from 'chrome://settings/settings.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 
 import {TestPerformanceBrowserProxy} from './test_performance_browser_proxy.js';
@@ -44,16 +45,20 @@
         BATTERY_SAVER_MODE_PREF, BatterySaverModeState.ENABLED_ON_BATTERY);
     flush();
     assertTrue(
-        batteryPage.$.toggleButton.checked,
+        batteryPage.shadowRoot!
+            .querySelector<SettingsToggleButtonElement>(
+                '#toggleButton')!.checked,
         'toggle should be checked when battery saver mode is enabled on ' +
             'battery');
     assertTrue(
-        batteryPage.$.radioGroupCollapse.opened,
+        batteryPage.shadowRoot!
+            .querySelector<IronCollapseElement>('#radioGroupCollapse')!.opened,
         'collapse should be open when battery saver mode is enabled on ' +
             'battery');
     assertEquals(
         String(BatterySaverModeState.ENABLED_ON_BATTERY),
-        batteryPage.$.radioGroup.selected,
+        batteryPage.shadowRoot!
+            .querySelector<SettingsRadioGroupElement>('#radioGroup')!.selected,
         'selected radio button should be enabled on battery');
   });
 
@@ -62,16 +67,20 @@
         BATTERY_SAVER_MODE_PREF, BatterySaverModeState.ENABLED_BELOW_THRESHOLD);
     flush();
     assertTrue(
-        batteryPage.$.toggleButton.checked,
+        batteryPage.shadowRoot!
+            .querySelector<SettingsToggleButtonElement>(
+                '#toggleButton')!.checked,
         'toggle should be checked when battery saver mode is enabled below ' +
             'threshold');
     assertTrue(
-        batteryPage.$.radioGroupCollapse.opened,
+        batteryPage.shadowRoot!
+            .querySelector<IronCollapseElement>('#radioGroupCollapse')!.opened,
         'collapse should be open when battery saver mode is enabled below ' +
             'threshold');
     assertEquals(
         String(BatterySaverModeState.ENABLED_BELOW_THRESHOLD),
-        batteryPage.$.radioGroup.selected,
+        batteryPage.shadowRoot!
+            .querySelector<SettingsRadioGroupElement>('#radioGroup')!.selected,
         'selected radio button should be enabled below threshold');
   });
 
@@ -79,10 +88,13 @@
     batteryPage.setPrefValue(
         BATTERY_SAVER_MODE_PREF, BatterySaverModeState.DISABLED);
     assertFalse(
-        batteryPage.$.toggleButton.checked,
+        batteryPage.shadowRoot!
+            .querySelector<SettingsToggleButtonElement>(
+                '#toggleButton')!.checked,
         'toggle should be unchecked when battery saver mode is disabled');
     assertFalse(
-        batteryPage.$.radioGroupCollapse.opened,
+        batteryPage.shadowRoot!
+            .querySelector<IronCollapseElement>('#radioGroupCollapse')!.opened,
         'collapse should be closed when battery saver mode is disabled');
   });
 
@@ -90,19 +102,23 @@
     batteryPage.setPrefValue(
         BATTERY_SAVER_MODE_PREF, BatterySaverModeState.DISABLED);
 
-    batteryPage.$.toggleButton.click();
+    batteryPage.shadowRoot!
+        .querySelector<SettingsToggleButtonElement>('#toggleButton')!.click();
     let state = await performanceMetricsProxy.whenCalled(
         'recordBatterySaverModeChanged');
     assertEquals(BatterySaverModeState.ENABLED_BELOW_THRESHOLD, state);
 
     performanceMetricsProxy.reset();
-    batteryPage.$.enabledOnBatteryButton.click();
+    batteryPage.shadowRoot!
+        .querySelector<ControlledRadioButtonElement>(
+            '#enabledOnBatteryButton')!.click();
     state = await performanceMetricsProxy.whenCalled(
         'recordBatterySaverModeChanged');
     assertEquals(BatterySaverModeState.ENABLED_ON_BATTERY, state);
 
     performanceMetricsProxy.reset();
-    batteryPage.$.toggleButton.click();
+    batteryPage.shadowRoot!
+        .querySelector<SettingsToggleButtonElement>('#toggleButton')!.click();
     state = await performanceMetricsProxy.whenCalled(
         'recordBatterySaverModeChanged');
     assertEquals(BatterySaverModeState.DISABLED, state);
diff --git a/chrome/test/data/webui/settings/chromeos/device_page/customize_buttons_subsection_test.ts b/chrome/test/data/webui/settings/chromeos/device_page/customize_buttons_subsection_test.ts
index 69c51d0..810feab 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page/customize_buttons_subsection_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/device_page/customize_buttons_subsection_test.ts
@@ -7,8 +7,9 @@
 
 import {CustomizeButtonsSubsectionElement} from 'chrome://os-settings/lazy_load.js';
 import {fakeGraphicsTabletButtonActions, fakeGraphicsTablets} from 'chrome://os-settings/os_settings.js';
+import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
-import {assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
 suite('<customize-buttons-subsection>', () => {
@@ -43,5 +44,25 @@
     await initializeCustomizeButtonsSubsection();
     assertTrue(!!customizeButtonsSubsection);
     assertTrue(!!customizeButtonsSubsection.get('buttonRemappingList'));
+
+    // Verify that renaming dialog will pop out when setting
+    // shouldShowRenamingDialog_ to true.
+    customizeButtonsSubsection.set('shouldShowRenamingDialog_', true);
+    customizeButtonsSubsection.set(
+        'selectedButton_',
+        fakeGraphicsTablets[0]!.settings!.penButtonRemappings[0]);
+    await flushTasks();
+    assertTrue(!!customizeButtonsSubsection.shadowRoot!.querySelector(
+        '#renamingDialog'));
+
+    // Verify that renaming dialog will disappear after clicking save button.
+    const saveButton: CrButtonElement|null =
+        customizeButtonsSubsection.shadowRoot!.querySelector('#saveButton');
+    assertTrue(!!saveButton);
+    saveButton.click();
+    await flushTasks();
+    assertFalse(customizeButtonsSubsection.get('shouldShowRenamingDialog_'));
+    assertFalse(!!customizeButtonsSubsection.shadowRoot!.querySelector(
+        '#renamingDialog'));
   });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/device_page/customize_mouse_buttons_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/device_page/customize_mouse_buttons_subpage_test.ts
index 11ffd0e8..5545c6f 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page/customize_mouse_buttons_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/device_page/customize_mouse_buttons_subpage_test.ts
@@ -2,9 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://os-settings/lazy_load.js';
+
 import {SettingsCustomizeMouseButtonsSubpageElement} from 'chrome://os-settings/lazy_load.js';
-import {fakeMice, Mouse, Router, routes} from 'chrome://os-settings/os_settings.js';
-import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {fakeMice, fakeMouseButtonActions, Mouse, Router, routes, setupFakeInputDeviceSettingsProvider} from 'chrome://os-settings/os_settings.js';
+import {assertDeepEquals, assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
 suite('<settings-customize-mouse-buttons-subpage>', () => {
@@ -12,7 +14,8 @@
 
   setup(async () => {
     page = document.createElement('settings-customize-mouse-buttons-subpage');
-    page.mice = fakeMice;
+    page.mouseList = fakeMice;
+    setupFakeInputDeviceSettingsProvider();
     // Set the current route with mouseId as search param and notify
     // the observer to update mouse settings.
     const url =
@@ -33,12 +36,22 @@
   test('navigate to device page when mouse detached', async () => {
     assertEquals(
         Router.getInstance().currentRoute, routes.CUSTOMIZE_MOUSE_BUTTONS);
-    const mouse: Mouse = page.mouse;
+    const mouse: Mouse = page.selectedMouse;
     assertTrue(!!mouse);
     assertEquals(mouse.id, fakeMice[0]!.id);
     // Remove fakeMice[0] from the mouse list.
-    page.mice = [fakeMice[1]!];
+    page.mouseList = [fakeMice[1]!];
     await flushTasks();
     assertEquals(Router.getInstance().currentRoute, routes.DEVICE);
   });
+
+  test('button action list fetched from provider', async () => {
+    const mouse: Mouse = page.selectedMouse;
+    assertTrue(!!mouse);
+    assertEquals(mouse.id, fakeMice[0]!.id);
+
+    const buttonActionList = page.get('buttonActionList_');
+    const expectedActionList = fakeMouseButtonActions;
+    assertDeepEquals(buttonActionList, expectedActionList);
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_tests.js b/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_tests.js
index d7b6c716..b677175 100644
--- a/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/internet_page/internet_detail_subpage_tests.js
@@ -421,6 +421,28 @@
       });
     });
 
+    test('Hidden toggle hidden for non-WiFi networks', function() {
+      init();
+      mojoApi_.resetForTest();
+      for (const networkType
+               of [NetworkType.kCellular, NetworkType.kEthernet,
+                   NetworkType.kTether, NetworkType.kVPN]) {
+        mojoApi_.setNetworkTypeEnabledState(networkType, true);
+        const networkTypeString = OncMojo.getNetworkTypeString(networkType);
+        const networkGuid = 'network_guid_' + networkTypeString;
+        const networkName = 'network_name_' + networkTypeString;
+        const network = getManagedProperties(networkType, networkName);
+
+        mojoApi_.setManagedPropertiesForTest(network);
+
+        internetDetailPage.init(networkGuid, networkTypeString, networkName);
+        return flushAsync().then(() => {
+          const hiddenToggle = getHiddenToggle();
+          assertFalse(!!hiddenToggle);
+        });
+      }
+    });
+
     test('Proxy Unshared', function() {
       init();
       mojoApi_.resetForTest();
diff --git a/chrome/test/data/webui/settings/chromeos/os_files_page/google_drive_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_files_page/google_drive_page_test.ts
index 2fa62df1..75e6f2e 100644
--- a/chrome/test/data/webui/settings/chromeos/os_files_page/google_drive_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_files_page/google_drive_page_test.ts
@@ -272,6 +272,43 @@
         });
 
     test(
+        'clicking the toggle whilst listing files shows a dialog', async () => {
+          page.setPrefValue('drivefs.bulk_pinning_enabled', false);
+          flush();
+
+          testBrowserProxy.observerRemote.onProgress({
+            freeSpace: '1,024 KB',
+            requiredSpace: '512 MB',
+            stage: Stage.kListingFiles,
+            listedFiles: BigInt(100),
+            isError: false,
+          });
+          testBrowserProxy.observerRemote.$.flushForTesting();
+          flush();
+
+          // Wait until the `onProgress` changes have been received.
+          await assertAsync(() => page.listedFiles === 100n);
+
+          // Toggle the bulk pinning toggle.
+          bulkPinningToggle.click();
+
+          // Wait for the clisting files dialog to appear and then close it.
+          await assertAsync(
+              () => page.dialogType ===
+                  ConfirmationDialogType.BULK_PINNING_LISTING_FILES,
+              5000);
+          await clickConfirmationDialogButton('.cancel-button');
+
+          // Assert the bulk pinning pref was not enabled and the toggle was not
+          // checked.
+          assertFalse(
+              page.getPref('drivefs.bulk_pinning_enabled').value,
+              'Pinning pref should be false');
+          assertFalse(
+              bulkPinningToggle.checked, 'Pinning toggle should be false');
+        });
+
+    test(
         'progress sent via the browser proxy updates the sub title text',
         async () => {
           page.setPrefValue('drivefs.bulk_pinning_enabled', false);
@@ -303,6 +340,7 @@
             freeSpace: '1,024 KB',
             requiredSpace: '512 MB',
             stage: Stage.kSuccess,
+            listedFiles: BigInt(100),
             isError: false,
           });
           testBrowserProxy.observerRemote.$.flushForTesting();
@@ -317,6 +355,7 @@
             freeSpace: '1,024 KB',
             requiredSpace: '512 MB',
             stage: Stage.kCannotGetFreeSpace,
+            listedFiles: BigInt(0),
             isError: true,
           });
           testBrowserProxy.observerRemote.$.flushForTesting();
@@ -370,6 +409,7 @@
             freeSpace: '512 MB',
             requiredSpace: '1,024 MB',
             stage: Stage.kNotEnoughSpace,
+            listedFiles: BigInt(100),
             isError: true,
           });
           testBrowserProxy.observerRemote.$.flushForTesting();
@@ -414,6 +454,7 @@
             freeSpace: 'x',
             requiredSpace: 'y',
             stage: Stage.kCannotGetFreeSpace,
+            listedFiles: BigInt(0),
             isError: true,
           });
           testBrowserProxy.observerRemote.$.flushForTesting();
@@ -456,6 +497,7 @@
         freeSpace: 'x',
         requiredSpace: 'y',
         stage: Stage.kStopped,
+        listedFiles: BigInt(100),
         isError: false,
       });
       testBrowserProxy.observerRemote.$.flushForTesting();
@@ -469,6 +511,7 @@
         freeSpace: 'x',
         requiredSpace: 'y',
         stage: Stage.kSyncing,
+        listedFiles: BigInt(100),
         isError: false,
       });
       testBrowserProxy.observerRemote.$.flushForTesting();
diff --git a/chromeos/ash/components/network/network_state.cc b/chromeos/ash/components/network/network_state.cc
index 3b2f5905..46dd25d 100644
--- a/chromeos/ash/components/network/network_state.cc
+++ b/chromeos/ash/components/network/network_state.cc
@@ -759,11 +759,11 @@
 }
 
 std::ostream& operator<<(std::ostream& out,
-                         const NetworkState::PortalState& state) {
-  using PortalState = NetworkState::PortalState;
+                         const NetworkState::PortalState state) {
+  using State = NetworkState::PortalState;
   switch (state) {
-#define PRINT(s)          \
-  case PortalState::k##s: \
+#define PRINT(s)    \
+  case State::k##s: \
     return out << #s;
     PRINT(Unknown)
     PRINT(Online)
@@ -775,7 +775,28 @@
   }
 
   return out << "PortalState("
-             << static_cast<std::underlying_type_t<PortalState>>(state) << ")";
+             << static_cast<std::underlying_type_t<State>>(state) << ")";
+}
+
+std::ostream& operator<<(std::ostream& out,
+                         const NetworkState::NetworkTechnologyType type) {
+  using Type = NetworkState::NetworkTechnologyType;
+  switch (type) {
+#define PRINT(s)   \
+  case Type::k##s: \
+    return out << #s;
+    PRINT(Cellular)
+    PRINT(Ethernet)
+    PRINT(EthernetEap)
+    PRINT(WiFi)
+    PRINT(Tether)
+    PRINT(VPN)
+    PRINT(Unknown)
+#undef PRINT
+  }
+
+  return out << "NetworkTechnologyType("
+             << static_cast<std::underlying_type_t<Type>>(type) << ")";
 }
 
 }  // namespace ash
diff --git a/chromeos/ash/components/network/network_state.h b/chromeos/ash/components/network/network_state.h
index fd074ced..9ce2ee1e 100644
--- a/chromeos/ash/components/network/network_state.h
+++ b/chromeos/ash/components/network/network_state.h
@@ -69,8 +69,8 @@
     kNoInternet,
     kMaxValue = kNoInternet  // For UMA_HISTOGRAM_ENUMERATION
   };
-  friend std::ostream& operator<<(std::ostream& stream,
-                                  const PortalState& portal_state);
+
+  friend std::ostream& operator<<(std::ostream& stream, PortalState state);
 
   // ManagedState overrides
   // If you change this method, update GetProperties too.
@@ -276,6 +276,10 @@
     kUnknown = 6,
     kMaxValue = kUnknown,
   };
+
+  friend std::ostream& operator<<(std::ostream& stream,
+                                  NetworkTechnologyType type);
+
   NetworkTechnologyType GetNetworkTechnologyType() const;
 
   // Setters for testing.
@@ -410,9 +414,13 @@
   bool connect_requested_ = false;
 };
 
-std::ostream& COMPONENT_EXPORT(CHROMEOS_NETWORK) operator<<(
-    std::ostream& stream,
-    const NetworkState::PortalState& portal_state);
+COMPONENT_EXPORT(CHROMEOS_NETWORK)
+std::ostream& operator<<(std::ostream& stream,
+                         NetworkState::PortalState portal_state);
+
+COMPONENT_EXPORT(CHROMEOS_NETWORK)
+std::ostream& operator<<(std::ostream& stream,
+                         NetworkState::NetworkTechnologyType type);
 
 }  // namespace ash
 
diff --git a/chromeos/ash/components/quick_start/quick_start_requests.cc b/chromeos/ash/components/quick_start/quick_start_requests.cc
index a47eee8..6c1e1c7 100644
--- a/chromeos/ash/components/quick_start/quick_start_requests.cc
+++ b/chromeos/ash/components/quick_start/quick_start_requests.cc
@@ -5,13 +5,11 @@
 #include "chromeos/ash/components/quick_start/quick_start_requests.h"
 
 #include "base/base64.h"
-#include "base/json/json_writer.h"
 #include "chromeos/ash/components/quick_start/quick_start_message.h"
 #include "chromeos/ash/components/quick_start/quick_start_message_type.h"
 #include "chromeos/ash/components/quick_start/types.h"
 #include "components/cbor/values.h"
 #include "components/cbor/writer.h"
-#include "crypto/aead.h"
 #include "crypto/sha2.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -43,7 +41,6 @@
 constexpr int kFlowTypeTargetChallenge = 2;
 
 const char kRelyingPartyId[] = "google.com";
-const char kCtapRequestType[] = "webauthn.get";
 const char kOrigin[] = "https://accounts.google.com";
 
 // Maps to CBOR byte labelling FIDO request as GetAssertion.
@@ -90,8 +87,8 @@
 }
 
 std::unique_ptr<QuickStartMessage> BuildAssertionRequestMessage(
-    const Base64UrlString& challenge) {
-  cbor::Value request = GenerateGetAssertionRequest(challenge);
+    std::array<uint8_t, crypto::kSHA256Length> client_data_hash) {
+  cbor::Value request = GenerateGetAssertionRequest(client_data_hash);
   std::vector<uint8_t> ctap_request_command =
       CBOREncodeGetAssertionRequest(std::move(request));
 
@@ -130,26 +127,11 @@
   return message;
 }
 
-std::string CreateFidoClientDataJson(const url::Origin& origin,
-                                     const Base64UrlString& challenge) {
-  base::Value::Dict fido_collected_client_data;
-  fido_collected_client_data.Set("type", kCtapRequestType);
-  fido_collected_client_data.Set("challenge", *challenge);
-  fido_collected_client_data.Set("origin", origin.Serialize());
-  fido_collected_client_data.Set("crossOrigin", false);
-  std::string fido_client_data_json;
-  base::JSONWriter::Write(fido_collected_client_data, &fido_client_data_json);
-  return fido_client_data_json;
-}
-
-cbor::Value GenerateGetAssertionRequest(const Base64UrlString& challenge) {
+cbor::Value GenerateGetAssertionRequest(
+    std::array<uint8_t, crypto::kSHA256Length> client_data_hash) {
   url::Origin origin = url::Origin::Create(GURL(kOrigin));
-  std::string client_data_json = CreateFidoClientDataJson(origin, challenge);
   cbor::Value::MapValue cbor_map;
   cbor_map.insert_or_assign(cbor::Value(0x01), cbor::Value(kRelyingPartyId));
-  std::array<uint8_t, crypto::kSHA256Length> client_data_hash;
-  crypto::SHA256HashString(client_data_json, client_data_hash.data(),
-                           client_data_hash.size());
   cbor_map.insert_or_assign(cbor::Value(0x02), cbor::Value(client_data_hash));
   cbor::Value::MapValue option_map;
   option_map.insert_or_assign(cbor::Value(kUserPresenceMapKey),
diff --git a/chromeos/ash/components/quick_start/quick_start_requests.h b/chromeos/ash/components/quick_start/quick_start_requests.h
index db39adf..458723c 100644
--- a/chromeos/ash/components/quick_start/quick_start_requests.h
+++ b/chromeos/ash/components/quick_start/quick_start_requests.h
@@ -8,18 +8,16 @@
 #include <array>
 #include <string>
 
-#include "base/values.h"
-#include "chromeos/ash/components/quick_start/types.h"
 #include "components/cbor/values.h"
+#include "crypto/sha2.h"
 #include "quick_start_message.h"
-#include "url/origin.h"
 
 namespace ash::quick_start::requests {
 
 std::unique_ptr<QuickStartMessage> BuildBootstrapOptionsRequest();
 
 std::unique_ptr<QuickStartMessage> BuildAssertionRequestMessage(
-    const Base64UrlString& challenge);
+    std::array<uint8_t, crypto::kSHA256Length> client_data_hash);
 
 std::unique_ptr<QuickStartMessage> BuildGetInfoRequestMessage();
 
@@ -29,10 +27,8 @@
 
 std::vector<uint8_t> CBOREncodeGetAssertionRequest(const cbor::Value& request);
 
-std::string CreateFidoClientDataJson(const url::Origin& origin,
-                                     const Base64UrlString& challenge);
-
-cbor::Value GenerateGetAssertionRequest(const Base64UrlString& challenge);
+cbor::Value GenerateGetAssertionRequest(
+    std::array<uint8_t, crypto::kSHA256Length> client_data_hash);
 
 std::unique_ptr<QuickStartMessage> BuildNotifySourceOfUpdateMessage(
     int32_t session_id,
diff --git a/chromeos/ash/components/quick_start/quick_start_requests_unittest.cc b/chromeos/ash/components/quick_start/quick_start_requests_unittest.cc
index b95027b..2ee066b1 100644
--- a/chromeos/ash/components/quick_start/quick_start_requests_unittest.cc
+++ b/chromeos/ash/components/quick_start/quick_start_requests_unittest.cc
@@ -4,21 +4,14 @@
 
 #include "chromeos/ash/components/quick_start/quick_start_requests.h"
 
-#include "base/json/json_reader.h"
-#include "chromeos/ash/components/quick_start/types.h"
 #include "components/cbor/reader.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "url/gurl.h"
-#include "url/origin.h"
 
 using ash::quick_start::requests::CBOREncodeGetAssertionRequest;
-using ash::quick_start::requests::CreateFidoClientDataJson;
 using ash::quick_start::requests::GenerateGetAssertionRequest;
 
 namespace {
-const char kChallengeBase64[] = "aQ==";
-const char kTestOrigin[] = "https://google.com";
-const char kCtapRequestType[] = "webauthn.get";
+const char kTestClientDataString[] = "test_client_data";
 }  // namespace
 
 class QuickStartRequestTest : public testing::Test {
@@ -30,29 +23,13 @@
 
  protected:
   void SetUp() override {}
-
-  const ash::quick_start::Base64UrlString kChallenge_ =
-      *ash::quick_start::Base64UrlTranscode(
-          ash::quick_start::Base64String(kChallengeBase64));
 };
 
-TEST_F(QuickStartRequestTest, CreateFidoClientDataJson) {
-  url::Origin test_origin = url::Origin::Create(GURL(kTestOrigin));
-  std::string client_data_json =
-      CreateFidoClientDataJson(test_origin, kChallenge_);
-  absl::optional<base::Value> parsed_json =
-      base::JSONReader::Read(client_data_json);
-  ASSERT_TRUE(parsed_json);
-  ASSERT_TRUE(parsed_json->is_dict());
-  base::Value::Dict& parsed_json_dict = parsed_json.value().GetDict();
-  EXPECT_EQ(*parsed_json_dict.FindString("type"), kCtapRequestType);
-  EXPECT_EQ(*parsed_json_dict.FindString("challenge"), *kChallenge_);
-  EXPECT_EQ(*parsed_json_dict.FindString("origin"), kTestOrigin);
-  EXPECT_FALSE(parsed_json_dict.FindBool("crossOrigin").value());
-}
-
 TEST_F(QuickStartRequestTest, GenerateGetAssertionRequest_ValidChallenge) {
-  cbor::Value request = GenerateGetAssertionRequest(kChallenge_);
+  std::array<uint8_t, crypto::kSHA256Length> client_data_hash;
+  crypto::SHA256HashString(kTestClientDataString, client_data_hash.data(),
+                           client_data_hash.size());
+  cbor::Value request = GenerateGetAssertionRequest(client_data_hash);
   ASSERT_TRUE(request.is_map());
   const cbor::Value::MapValue& request_map = request.GetMap();
   // CBOR Index 0x01 stores the relying_party_id for the GetAssertionRequest.
@@ -70,7 +47,10 @@
 }
 
 TEST_F(QuickStartRequestTest, CBOREncodeGetAssertionRequest) {
-  cbor::Value request = GenerateGetAssertionRequest(kChallenge_);
+  std::array<uint8_t, crypto::kSHA256Length> client_data_hash;
+  crypto::SHA256HashString(kTestClientDataString, client_data_hash.data(),
+                           client_data_hash.size());
+  cbor::Value request = GenerateGetAssertionRequest(client_data_hash);
   std::vector<uint8_t> cbor_encoded_request =
       CBOREncodeGetAssertionRequest(std::move(request));
   absl::optional<cbor::Value> cbor;
diff --git a/chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.cc b/chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.cc
index 0a8e54f3..b42770f 100644
--- a/chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.cc
+++ b/chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.cc
@@ -14,6 +14,10 @@
          system_state == mojom::BluetoothSystemState::kEnabling;
 }
 
+bool IsBluetoothEnabled(const mojom::BluetoothSystemState system_state) {
+  return system_state == mojom::BluetoothSystemState::kEnabled;
+}
+
 std::u16string GetPairedDeviceName(
     const mojom::PairedBluetoothDevicePropertiesPtr& paired_device_properties) {
   if (paired_device_properties->nickname.has_value())
diff --git a/chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h b/chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h
index d8a7191..560fec5 100644
--- a/chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h
+++ b/chromeos/ash/services/bluetooth_config/public/cpp/cros_bluetooth_config_util.h
@@ -16,6 +16,9 @@
 bool IsBluetoothEnabledOrEnabling(
     const mojom::BluetoothSystemState system_state);
 
+// Returns |true| if the given Bluetooth state |system_state| is enabled.
+bool IsBluetoothEnabled(const mojom::BluetoothSystemState system_state);
+
 // Returns the nickname of the provided device if it is set, otherwise returns
 // the public device name.
 std::u16string GetPairedDeviceName(
diff --git a/chromeos/profiles/arm-exp.afdo.newest.txt b/chromeos/profiles/arm-exp.afdo.newest.txt
index 369780c..d312ef00 100644
--- a/chromeos/profiles/arm-exp.afdo.newest.txt
+++ b/chromeos/profiles/arm-exp.afdo.newest.txt
@@ -1 +1 @@
-chromeos-chrome-arm-exp-118-5938.13-1692612678-benchmark-118.0.5973.0-r1-redacted.afdo.xz
+chromeos-chrome-arm-exp-118-5938.22-1693215733-benchmark-118.0.5974.0-r1-redacted.afdo.xz
diff --git a/clank b/clank
index 4375123..7aa5067 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 4375123d4e1ac4d9e10d3ddf8878ca5972bca19d
+Subproject commit 7aa5067e7b4b11457c64ba3fa44255d05ae6c638
diff --git a/components/autofill/core/DEPS b/components/autofill/core/DEPS
index 7cba5f3..dd442c0 100644
--- a/components/autofill/core/DEPS
+++ b/components/autofill/core/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/commerce",
   "+components/os_crypt/sync",
   "+components/pref_registry",
   "+components/unified_consent",
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 70c1803..ab65e6d 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -628,6 +628,7 @@
     "//base:i18n",
     "//build:branding_buildflags",
     "//build:chromeos_buildflags",
+    "//components/commerce/core:commerce_types",
     "//components/device_reauth",
     "//components/feature_engagement",
     "//components/google/core/common",
@@ -1147,6 +1148,7 @@
     "//base/test:test_support",
     "//build:chromeos_buildflags",
     "//components/autofill/core/common",
+    "//components/commerce/core:shopping_service_test_support",
     "//components/device_reauth:test_support",
     "//components/feature_engagement",
     "//components/image_fetcher/core:core",
diff --git a/components/autofill/core/browser/autofill_suggestion_generator_unittest.cc b/components/autofill/core/browser/autofill_suggestion_generator_unittest.cc
index cbbc47b..e5930d2 100644
--- a/components/autofill/core/browser/autofill_suggestion_generator_unittest.cc
+++ b/components/autofill/core/browser/autofill_suggestion_generator_unittest.cc
@@ -89,7 +89,7 @@
     autofill_client_.set_autofill_offer_manager(
         std::make_unique<AutofillOfferManager>(
             personal_data(),
-            /*coupon_service_delegate=*/nullptr));
+            /*coupon_service_delegate=*/nullptr, /*shopping_service=*/nullptr));
   }
 
   CreditCard CreateServerCard(
diff --git a/components/autofill/core/browser/metrics/autofill_metrics_test_base.cc b/components/autofill/core/browser/metrics/autofill_metrics_test_base.cc
index 9e6905f..565dbdc 100644
--- a/components/autofill/core/browser/metrics/autofill_metrics_test_base.cc
+++ b/components/autofill/core/browser/metrics/autofill_metrics_test_base.cc
@@ -65,7 +65,8 @@
           /*iban_save_manager=*/nullptr, &personal_data(), "en-US"));
   autofill_client_->set_autofill_offer_manager(
       std::make_unique<AutofillOfferManager>(
-          &personal_data(), /*coupon_service_delegate=*/nullptr));
+          &personal_data(), /*coupon_service_delegate=*/nullptr,
+          /*shopping_service=*/nullptr));
 
   auto browser_autofill_manager = std::make_unique<TestBrowserAutofillManager>(
       autofill_driver_.get(), autofill_client_.get());
diff --git a/components/autofill/core/browser/payments/autofill_offer_manager.cc b/components/autofill/core/browser/payments/autofill_offer_manager.cc
index b3649c4..f9ceb27 100644
--- a/components/autofill/core/browser/payments/autofill_offer_manager.cc
+++ b/components/autofill/core/browser/payments/autofill_offer_manager.cc
@@ -15,17 +15,28 @@
 #include "components/autofill/core/browser/ui/popup_item_ids.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
+#include "components/commerce/core/commerce_types.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 
 namespace autofill {
+AutofillOfferData ToAutofillOfferData(
+    const GURL& url,
+    const commerce::DiscountInfo& discount_info) {
+  return AutofillOfferData::FreeListingCouponOffer(
+      discount_info.id, base::Time::FromDoubleT(discount_info.expiry_time_sec),
+      {url}, url, DisplayStrings{discount_info.description_detail},
+      discount_info.discount_code);
+}
 
 AutofillOfferManager::AutofillOfferManager(
     PersonalDataManager* personal_data,
-    CouponServiceDelegate* coupon_service_delegate)
+    CouponServiceDelegate* coupon_service_delegate,
+    std::unique_ptr<ShoppingServiceDelegate> shopping_service_delegate)
     : personal_data_(personal_data),
-      coupon_service_delegate_(coupon_service_delegate) {
+      coupon_service_delegate_(coupon_service_delegate),
+      shopping_service_delegate_(std::move(shopping_service_delegate)) {
   personal_data_manager_observation.Observe(personal_data_);
   UpdateEligibleMerchantDomains();
 }
@@ -47,8 +58,9 @@
       last_committed_primary_main_frame_url.DeprecatedGetOriginAsURL();
 
   if (!base::Contains(eligible_merchant_domains_,
-                      last_committed_primary_main_frame_origin))
+                      last_committed_primary_main_frame_origin)) {
     return {};
+  }
 
   const std::vector<AutofillOfferData*> offers =
       personal_data_->GetAutofillOffers();
@@ -58,12 +70,14 @@
   for (AutofillOfferData* offer : offers) {
     // Ensure the offer is valid.
     if (!offer->IsActiveAndEligibleForOrigin(
-            last_committed_primary_main_frame_origin))
+            last_committed_primary_main_frame_origin)) {
       continue;
+    }
 
     // Ensure the offer is a card-linked offer.
-    if (!offer->IsCardLinkedOffer())
+    if (!offer->IsCardLinkedOffer()) {
       continue;
+    }
 
     for (const CreditCard* card : cards) {
       // If card has an offer, add the card's guid id to the map. There is
@@ -109,9 +123,23 @@
       return offer;
     }
   }
+
   return nullptr;
 }
 
+void AutofillOfferManager::GetShoppingServiceOfferForUrl(
+    const GURL& url,
+    AsyncOfferCallback callback) {
+  if (!shopping_service_delegate_ ||
+      !shopping_service_delegate_->IsDiscountEligibleToShowOnNavigation()) {
+    return;
+  }
+  shopping_service_delegate_->GetDiscountInfoForUrls(
+      {url},
+      base::BindOnce(&AutofillOfferManager::HandleShoppingServiceResponse,
+                     weak_ptr_factory_.GetWeakPtr(), url, std::move(callback)));
+}
+
 void AutofillOfferManager::UpdateEligibleMerchantDomains() {
   eligible_merchant_domains_.clear();
   std::vector<AutofillOfferData*> offers = personal_data_->GetAutofillOffers();
@@ -122,4 +150,18 @@
   }
 }
 
+void AutofillOfferManager::HandleShoppingServiceResponse(
+    const GURL& url,
+    AsyncOfferCallback callback,
+    const commerce::DiscountsMap& discounts) {
+  if (discounts.empty()) {
+    return;
+  }
+
+  CHECK(discounts.size() == 1 && discounts.count(url) == 1 &&
+        discounts.at(url).size() > 0);
+
+  std::move(callback).Run(url, ToAutofillOfferData(url, discounts.at(url)[0]));
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/autofill_offer_manager.h b/components/autofill/core/browser/payments/autofill_offer_manager.h
index aef440b..bfd3050 100644
--- a/components/autofill/core/browser/payments/autofill_offer_manager.h
+++ b/components/autofill/core/browser/payments/autofill_offer_manager.h
@@ -14,9 +14,11 @@
 
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "components/autofill/core/browser/payments/offer_notification_handler.h"
 #include "components/autofill/core/browser/personal_data_manager_observer.h"
+#include "components/commerce/core/commerce_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "url/gurl.h"
 
@@ -27,6 +29,19 @@
 class OfferNotificationHandler;
 class PersonalDataManager;
 
+// A delegate class that exposes ShoppingService functionalities.
+// This component can't depend on commerce:shopping_service component target
+// because of crbug.com/1155712. We should remove this and use the shopping
+// service directly once this is fixed.
+class ShoppingServiceDelegate {
+ public:
+  virtual ~ShoppingServiceDelegate() = default;
+  virtual bool IsDiscountEligibleToShowOnNavigation() = 0;
+  virtual void GetDiscountInfoForUrls(
+      const std::vector<GURL>& urls,
+      commerce::DiscountInfoCallback callback) = 0;
+};
+
 // A delegate class to expose relevant CouponService functionalities.
 class CouponServiceDelegate {
  public:
@@ -51,9 +66,15 @@
  public:
   // Mapping from credit card guid id to offer data.
   using CardLinkedOffersMap = std::map<std::string, AutofillOfferData*>;
+  // Callbacks for getting offers asynchronously, primarily used by calls to the
+  // ShoppingService.
+  using AsyncOfferCallback =
+      base::OnceCallback<void(const GURL&, const AutofillOfferData&)>;
 
-  AutofillOfferManager(PersonalDataManager* personal_data,
-                       CouponServiceDelegate* coupon_service_delegate);
+  AutofillOfferManager(
+      PersonalDataManager* personal_data,
+      CouponServiceDelegate* coupon_service_delegate,
+      std::unique_ptr<ShoppingServiceDelegate> shopping_service_delegate);
   ~AutofillOfferManager() override;
   AutofillOfferManager(const AutofillOfferManager&) = delete;
   AutofillOfferManager& operator=(const AutofillOfferManager&) = delete;
@@ -78,6 +99,11 @@
   AutofillOfferData* GetOfferForUrl(
       const GURL& last_committed_primary_main_frame_url);
 
+  // Get offer for `url` asynchronously from ShoppingService. If there are any
+  // available offers, `callback` will be called.
+  void GetShoppingServiceOfferForUrl(const GURL& url,
+                                     AsyncOfferCallback callback);
+
  private:
   FRIEND_TEST_ALL_PREFIXES(
       AutofillOfferManagerTest,
@@ -90,8 +116,13 @@
   // |eligible_merchant_domains_|
   void UpdateEligibleMerchantDomains();
 
+  void HandleShoppingServiceResponse(const GURL& url,
+                                     AsyncOfferCallback callback,
+                                     const commerce::DiscountsMap& discounts);
+
   raw_ptr<PersonalDataManager> personal_data_;
   raw_ptr<CouponServiceDelegate> coupon_service_delegate_;
+  std::unique_ptr<ShoppingServiceDelegate> shopping_service_delegate_;
 
   // This set includes all the eligible domains where offers are applicable.
   // This is used as a local cache and will be updated whenever the data in the
@@ -104,6 +135,8 @@
   // The handler for offer notification UI. It is a sub-level component of
   // AutofillOfferManager to decide whether to show the offer notification.
   OfferNotificationHandler notification_handler_{this};
+
+  base::WeakPtrFactory<AutofillOfferManager> weak_ptr_factory_{this};
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc b/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc
index fac4250..1a658ec 100644
--- a/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc
+++ b/components/autofill/core/browser/payments/autofill_offer_manager_unittest.cc
@@ -7,6 +7,7 @@
 
 #include "base/functional/bind.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
@@ -18,6 +19,8 @@
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/autofill/core/common/autofill_clock.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
+#include "components/commerce/core/commerce_types.h"
+#include "components/commerce/core/test_utils.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/sync/test/test_sync_service.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -62,8 +65,12 @@
                                 /*strike_database=*/nullptr,
                                 /*image_fetcher=*/nullptr);
     personal_data_manager_.SetPrefService(autofill_client_.GetPrefs());
+    auto mock_shopping_service_delegate =
+        std::make_unique<MockShoppingServiceDelegate>();
+    mock_shopping_service_delegate_ = mock_shopping_service_delegate.get();
     autofill_offer_manager_ = std::make_unique<AutofillOfferManager>(
-        &personal_data_manager_, &coupon_service_delegate_);
+        &personal_data_manager_, &coupon_service_delegate_,
+        std::move(mock_shopping_service_delegate));
   }
 
   CreditCard CreateCreditCard(std::string guid,
@@ -130,6 +137,14 @@
     MOCK_METHOD1(IsUrlEligible, bool(const GURL& url));
   };
 
+  class MockShoppingServiceDelegate : public ShoppingServiceDelegate {
+   public:
+    MOCK_METHOD0(IsDiscountEligibleToShowOnNavigation, bool());
+    MOCK_METHOD2(GetDiscountInfoForUrls,
+                 void(const std::vector<GURL>&,
+                      commerce::DiscountInfoCallback));
+  };
+
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   TestAutofillClient autofill_client_;
@@ -139,6 +154,7 @@
   std::unique_ptr<AutofillOfferManager> autofill_offer_manager_;
   base::test::ScopedFeatureList scoped_feature_list_;
   MockCouponServiceDelegate coupon_service_delegate_;
+  raw_ptr<MockShoppingServiceDelegate> mock_shopping_service_delegate_;
 };
 
 // Verify that a card linked offer is returned for an eligible url.
@@ -301,4 +317,63 @@
   EXPECT_EQ(offer2, *result);
 }
 
+TEST_F(AutofillOfferManagerTest, GetShoppingServiceOfferForUrl_ReturnOffer) {
+  const GURL url("http://www.example.com");
+  const std::string detail = "Discount description detail";
+  const std::string discount_code = "discount-code";
+  const int64_t discount_id = 123;
+  const double expiry_time_sec =
+      (AutofillClock::Now() + base::Days(2)).ToDoubleT();
+
+  const AutofillOfferData expected_autofill_offer_data =
+      AutofillOfferData::FreeListingCouponOffer(
+          discount_id, base::Time::FromDoubleT(expiry_time_sec), {url}, url,
+          DisplayStrings{detail}, discount_code);
+
+  ON_CALL(*mock_shopping_service_delegate_, GetDiscountInfoForUrls)
+      .WillByDefault([&](const std::vector<GURL>& urls,
+                         commerce::DiscountInfoCallback callback) {
+        const commerce::DiscountsMap discounts_map{
+            {url,
+             {commerce::CreateValidDiscountInfo(
+                 detail, /*terms_and_conditions=*/"",
+                 /*value_in_text=*/"$10 off", discount_code, discount_id,
+                 /*is_merchant_wide=*/false, expiry_time_sec)}}};
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindOnce(std::move(callback), discounts_map));
+      });
+  EXPECT_CALL(*mock_shopping_service_delegate_,
+              IsDiscountEligibleToShowOnNavigation())
+      .WillOnce(testing::Return(true));
+  EXPECT_CALL(*mock_shopping_service_delegate_, GetDiscountInfoForUrls);
+
+  base::MockCallback<AutofillOfferManager::AsyncOfferCallback> callback;
+  EXPECT_CALL(callback, Run(url, expected_autofill_offer_data));
+
+  autofill_offer_manager_->GetShoppingServiceOfferForUrl(url, callback.Get());
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(AutofillOfferManagerTest,
+       GetShoppingServiceOfferForUrl_ReturnEmptyResult) {
+  const GURL url("http://www.example.com");
+  ON_CALL(*mock_shopping_service_delegate_, GetDiscountInfoForUrls)
+      .WillByDefault([](const std::vector<GURL>& urls,
+                        commerce::DiscountInfoCallback callback) {
+        const commerce::DiscountsMap discounts_map{};
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindOnce(std::move(callback), discounts_map));
+      });
+  EXPECT_CALL(*mock_shopping_service_delegate_,
+              IsDiscountEligibleToShowOnNavigation())
+      .WillOnce(testing::Return(true));
+  EXPECT_CALL(*mock_shopping_service_delegate_, GetDiscountInfoForUrls);
+
+  base::MockCallback<AutofillOfferManager::AsyncOfferCallback> callback;
+  EXPECT_CALL(callback, Run(testing::_, testing::_)).Times(0);
+
+  autofill_offer_manager_->GetShoppingServiceOfferForUrl(url, callback.Get());
+  task_environment_.RunUntilIdle();
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/offer_notification_handler.cc b/components/autofill/core/browser/payments/offer_notification_handler.cc
index e5bd02b1..e52b170 100644
--- a/components/autofill/core/browser/payments/offer_notification_handler.cc
+++ b/components/autofill/core/browser/payments/offer_notification_handler.cc
@@ -9,17 +9,20 @@
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/browser/payments/autofill_offer_manager.h"
 #include "components/autofill/core/common/autofill_payments_features.h"
+#include "url/gurl.h"
 
 namespace autofill {
 
 namespace {
 
 bool IsOfferValid(AutofillOfferData* offer) {
-  if (!offer)
+  if (!offer) {
     return false;
+  }
 
-  if (offer->GetMerchantOrigins().empty())
+  if (offer->GetMerchantOrigins().empty()) {
     return false;
+  }
 
   if (offer->IsPromoCodeOffer() &&
       !base::FeatureList::IsEnabled(
@@ -27,8 +30,9 @@
     return false;
   }
 
-  if (offer->GetOfferType() == AutofillOfferData::OfferType::UNKNOWN)
+  if (offer->GetOfferType() == AutofillOfferData::OfferType::UNKNOWN) {
     return false;
+  }
 
   return true;
 }
@@ -43,29 +47,32 @@
 
 void OfferNotificationHandler::UpdateOfferNotificationVisibility(
     AutofillClient* client) {
-  GURL url = client->GetLastCommittedPrimaryMainFrameURL();
-  if (!offer_manager_->IsUrlEligible(url)) {
-    client->DismissOfferNotification();
-    return;
-  }
+  const GURL url = client->GetLastCommittedPrimaryMainFrameURL();
 
-  // Try to show offer notification when the last committed URL has the domain
-  // that an offer is applicable for.
-  // TODO(crbug.com/1203811): GetOfferForUrl needs to know whether to give
-  //   precedence to card-linked offers or promo code offers. Eventually, promo
-  //   code offers should take precedence if a bubble is shown. Currently, if a
-  //   url has both types of offers and the promo code offer is selected, no
-  //   bubble will end up being shown (due to not yet being implemented).
-  AutofillOfferData* offer = offer_manager_->GetOfferForUrl(url);
-  if (!IsOfferValid(offer)) {
-    client->DismissOfferNotification();
-    return;
-  }
+  if (ValidOfferExistsForUrl(url)) {
+    // Try to show offer notification when the last committed URL has the domain
+    // that an offer is applicable for.
+    // TODO(crbug.com/1203811): GetOfferForUrl needs to know whether to give
+    //   precedence to card-linked offers or promo code offers. Eventually,
+    //   promo code offers should take precedence if a bubble is shown.
+    //   Currently, if a url has both types of offers and the promo code offer
+    //   is selected, no bubble will end up being shown (due to not yet being
+    //   implemented).
+    AutofillOfferData* offer = offer_manager_->GetOfferForUrl(url);
+    CHECK(IsOfferValid(offer));
 
-  client->UpdateOfferNotification(
-      offer, shown_notification_ids_.contains(offer->GetOfferId()),
-      /*expand_notification_icon=*/false);
-  shown_notification_ids_.insert(offer->GetOfferId());
+    client->UpdateOfferNotification(
+        offer, shown_notification_ids_.contains(offer->GetOfferId()),
+        /*expand_notification_icon=*/false);
+    shown_notification_ids_.insert(offer->GetOfferId());
+  } else {
+    client->DismissOfferNotification();
+
+    offer_manager_->GetShoppingServiceOfferForUrl(
+        url, base::BindOnce(&OfferNotificationHandler::
+                                UpdateOfferNotificationForShoppingServiceOffer,
+                            weak_ptr_factory_.GetWeakPtr(), client));
+  }
 }
 
 void OfferNotificationHandler::ClearShownNotificationIdForTesting() {
@@ -77,4 +84,24 @@
   shown_notification_ids_.insert(shown_notification_id);
 }
 
+bool OfferNotificationHandler::ValidOfferExistsForUrl(const GURL& url) {
+  return offer_manager_->IsUrlEligible(url) &&
+         IsOfferValid(offer_manager_->GetOfferForUrl(url));
+}
+
+void OfferNotificationHandler::UpdateOfferNotificationForShoppingServiceOffer(
+    AutofillClient* client,
+    const GURL& url,
+    const AutofillOfferData& offer) {
+  if (!client || url != client->GetLastCommittedPrimaryMainFrameURL()) {
+    return;
+  }
+
+  int64_t offer_id = offer.GetOfferId();
+  client->UpdateOfferNotification(&offer,
+                                  shown_notification_ids_.contains(offer_id),
+                                  /*expand_notification_icon=*/true);
+  shown_notification_ids_.insert(offer_id);
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/offer_notification_handler.h b/components/autofill/core/browser/payments/offer_notification_handler.h
index 30fd1bf..7c96d1e 100644
--- a/components/autofill/core/browser/payments/offer_notification_handler.h
+++ b/components/autofill/core/browser/payments/offer_notification_handler.h
@@ -7,10 +7,13 @@
 
 #include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "url/gurl.h"
 
 namespace autofill {
 
 class AutofillClient;
+class AutofillOfferData;
 class AutofillOfferManager;
 
 // The class to handle actions related to the offer notifications. It is owned
@@ -30,6 +33,14 @@
   void AddShownNotificationIdForTesting(int64_t shown_notification_id);
 
  private:
+  bool ValidOfferExistsForUrl(const GURL& url);
+  // Updates the offer notification for a ShoppingService offer if a
+  // ShoppingService offer is available.
+  void UpdateOfferNotificationForShoppingServiceOffer(
+      AutofillClient* client,
+      const GURL& url,
+      const AutofillOfferData& offer);
+
   // The reference to the offer manager that owns |this|.
   raw_ptr<AutofillOfferManager> offer_manager_;
 
@@ -37,6 +48,8 @@
   // current browser context. It serves as a cross-tab status tracker for the
   // notification UI.
   base::flat_set<int64_t> shown_notification_ids_;
+
+  base::WeakPtrFactory<OfferNotificationHandler> weak_ptr_factory_{this};
 };
 
 }  // namespace autofill
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_credential_sync_bridge.cc b/components/autofill/core/browser/webdata/autofill_wallet_credential_sync_bridge.cc
index 60034d2..780180b 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_credential_sync_bridge.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_credential_sync_bridge.cc
@@ -170,7 +170,16 @@
 
 void AutofillWalletCredentialSyncBridge::ApplyDisableSyncChanges(
     std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) {
-  NOTIMPLEMENTED();
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // For this data type, we want to delete all the data (not just the metadata)
+  // when the type is disabled!
+  if (AutofillTable* table = GetAutofillTable();
+      !table || !table->ClearServerCvcs()) {
+    change_processor()->ReportError(
+        {FROM_HERE, "Failed to delete wallet credential data from the table."});
+  }
+  web_data_backend_->CommitChanges();
+  web_data_backend_->NotifyOfMultipleAutofillChanges();
 }
 
 bool AutofillWalletCredentialSyncBridge::IsEntityDataValid(
diff --git a/components/autofill/core/browser/webdata/autofill_wallet_credential_sync_bridge_unittest.cc b/components/autofill/core/browser/webdata/autofill_wallet_credential_sync_bridge_unittest.cc
index 439d32c..b03e762 100644
--- a/components/autofill/core/browser/webdata/autofill_wallet_credential_sync_bridge_unittest.cc
+++ b/components/autofill/core/browser/webdata/autofill_wallet_credential_sync_bridge_unittest.cc
@@ -373,4 +373,24 @@
   bridge()->ServerCvcChanged(change);
 }
 
+// Test to verify all the server cvc data is deleted/cleared when the sync is
+// disabled.
+TEST_F(AutofillWalletCredentialSyncBridgeTest, ApplyDisableSyncChanges) {
+  const ServerCvc server_cvc =
+      ServerCvc(1, u"123", base::Time::UnixEpoch() + base::Milliseconds(25000));
+
+  StartSyncing({AutofillWalletCredentialSpecificsFromStructData(server_cvc)});
+
+  EXPECT_THAT(GetAllServerCvcDataFromTable(),
+              testing::UnorderedElementsAre(server_cvc));
+  EXPECT_CALL(mock_processor(), Delete).Times(0);
+  EXPECT_CALL(mock_processor(), Put).Times(0);
+  EXPECT_CALL(backend(), CommitChanges());
+  EXPECT_CALL(backend(), NotifyOfMultipleAutofillChanges());
+
+  bridge()->ApplyDisableSyncChanges(bridge()->CreateMetadataChangeList());
+
+  EXPECT_TRUE(GetAllServerCvcDataFromTable().empty());
+}
+
 }  // namespace autofill
diff --git a/components/commerce/core/mock_shopping_service.cc b/components/commerce/core/mock_shopping_service.cc
index 665fc123..d80a271 100644
--- a/components/commerce/core/mock_shopping_service.cc
+++ b/components/commerce/core/mock_shopping_service.cc
@@ -221,6 +221,12 @@
       .WillByDefault(testing::Return(is_eligible));
 }
 
+void MockShoppingService::SetIsDiscountEligibleToShowOnNavigation(
+    bool is_eligible) {
+  ON_CALL(*this, IsDiscountEligibleToShowOnNavigation)
+      .WillByDefault(testing::Return(is_eligible));
+}
+
 void MockShoppingService::SetResponseForGetDiscountInfoForUrls(
     const DiscountsMap& discounts_map) {
   ON_CALL(*this, GetDiscountInfoForUrls)
diff --git a/components/commerce/core/mock_shopping_service.h b/components/commerce/core/mock_shopping_service.h
index d152027..d4585afe8 100644
--- a/components/commerce/core/mock_shopping_service.h
+++ b/components/commerce/core/mock_shopping_service.h
@@ -102,6 +102,7 @@
               (override));
   MOCK_METHOD(bool, IsMerchantViewerEnabled, (), (override));
   MOCK_METHOD(bool, IsPriceInsightsEligible, (), (override));
+  MOCK_METHOD(bool, IsDiscountEligibleToShowOnNavigation, (), (override));
   MOCK_METHOD(void,
               GetDiscountInfoForUrls,
               (const std::vector<GURL>& urls, DiscountInfoCallback callback),
@@ -130,6 +131,7 @@
   void SetGetAllShoppingBookmarksValue(
       std::vector<const bookmarks::BookmarkNode*> bookmarks);
   void SetIsPriceInsightsEligible(bool is_eligible);
+  void SetIsDiscountEligibleToShowOnNavigation(bool is_eligible);
   void SetResponseForGetDiscountInfoForUrls(const DiscountsMap& discounts_map);
 };
 
diff --git a/components/commerce/core/shopping_service.h b/components/commerce/core/shopping_service.h
index 26f46e88..9c08514 100644
--- a/components/commerce/core/shopping_service.h
+++ b/components/commerce/core/shopping_service.h
@@ -167,6 +167,45 @@
 using BookmarkProductInfoUpdatedCallback = base::RepeatingCallback<
     void(const base::Uuid&, const GURL&, absl::optional<ProductInfo>)>;
 
+// Under Desktop browser test or interactive ui test, use
+// ShoppingServiceFactory::SetTestingFactory to create a
+// MockShoppingService for testing. The test should use
+// BrowserContextDependencyManager to register a callback to create the
+// MockShoppingService when the BrowserContext is created.
+//
+// Example of an InteractiveBrowserTest setup for using a MockShoppingService:
+//
+// clang-format off
+// #include "components/commerce/core/mock_shopping_service.h"
+// #include "components/keyed_service/content/browser_context_dependency_manager.h"
+//
+// class MyTest : public InteractiveBrowserTest {
+//   void SetUpInProcessBrowserTestFixture() override {
+//     create_services_subscription_ =
+//         BrowserContextDependencyManager::GetInstance()
+//             ->RegisterCreateServicesCallbackForTesting(base::BindRepeating(
+//                 &MyTest::OnWillCreateBrowserContextServices,
+//                 weak_ptr_factory_.GetWeakPtr()));
+//   }
+//
+//   void OnWillCreateBrowserContextServices(content::BrowserContext* context) {
+//     commerce::ShoppingServiceFactory::GetInstance()->SetTestingFactory(
+//         context, base::BindRepeating([](content::BrowserContext* context) {
+//           return commerce::MockShoppingService::Build();
+//         }));
+//   }
+//
+//  private:
+//   base::CallbackListSubscription create_services_subscription_;
+//   base::WeakPtrFactory<MyTest> weak_ptr_factory_{this};
+// };
+//
+// To get the MockShoppingService:
+// auto* mock_shopping_service = static_cast<commerce::MockShoppingService*>(
+//     commerce::ShoppingServiceFactory::GetForBrowserContext(
+//         browser()->profile()));
+// clang-format on
+
 class ShoppingService : public KeyedService, public base::SupportsUserData {
  public:
   ShoppingService(
diff --git a/components/commerce/core/test_utils.cc b/components/commerce/core/test_utils.cc
index 1ac4658..06dd18c 100644
--- a/components/commerce/core/test_utils.cc
+++ b/components/commerce/core/test_utils.cc
@@ -118,4 +118,24 @@
   return info;
 }
 
+DiscountInfo CreateValidDiscountInfo(const std::string& detail,
+                                     const std::string& terms_and_conditions,
+                                     const std::string& value_in_text,
+                                     const std::string& discount_code,
+                                     int64_t id,
+                                     bool is_merchant_wide,
+                                     double expiry_time_sec) {
+  DiscountInfo discount_info;
+
+  discount_info.description_detail = detail;
+  discount_info.terms_and_conditions.emplace(terms_and_conditions);
+  discount_info.value_in_text = value_in_text;
+  discount_info.discount_code = discount_code;
+  discount_info.id = id;
+  discount_info.is_merchant_wide = is_merchant_wide;
+  discount_info.expiry_time_sec = expiry_time_sec;
+
+  return discount_info;
+}
+
 }  // namespace commerce
diff --git a/components/commerce/core/test_utils.h b/components/commerce/core/test_utils.h
index 3964227..a28c2b6 100644
--- a/components/commerce/core/test_utils.h
+++ b/components/commerce/core/test_utils.h
@@ -83,6 +83,14 @@
     bool has_price_history_data = false,
     PriceBucket price_bucket = PriceBucket::kUnknown);
 
+DiscountInfo CreateValidDiscountInfo(const std::string& detail,
+                                     const std::string& terms_and_conditions,
+                                     const std::string& value_in_text,
+                                     const std::string& discount_code,
+                                     int64_t id,
+                                     bool is_merchant_wide,
+                                     double expiry_time_sec);
+
 }  // namespace commerce
 
 #endif  // COMPONENTS_COMMERCE_CORE_TEST_UTILS_H_
diff --git a/components/global_media_controls/public/views/media_notification_view_ash_impl.cc b/components/global_media_controls/public/views/media_notification_view_ash_impl.cc
index 606b1001..e45d846 100644
--- a/components/global_media_controls/public/views/media_notification_view_ash_impl.cc
+++ b/components/global_media_controls/public/views/media_notification_view_ash_impl.cc
@@ -46,8 +46,8 @@
 constexpr int kChevronIconSize = 15;
 constexpr int kPlayPauseIconSize = 26;
 constexpr int kControlsIconSize = 20;
-constexpr int kBackgroundCornerRadius = 12;
-constexpr int kArtworkCornerRadius = 10;
+constexpr int kBackgroundCornerRadius = 16;
+constexpr int kArtworkCornerRadius = 12;
 constexpr int kSourceLineHeight = 18;
 constexpr int kTitleArtistLineHeight = 20;
 constexpr int kNotMediaActionButtonId = -1;
@@ -77,7 +77,6 @@
         icon_size_(button_id == static_cast<int>(MediaSessionAction::kPlay)
                        ? kPlayPauseIconSize
                        : kControlsIconSize),
-        foreground_color_id_(foreground_color_id),
         foreground_disabled_color_id_(foreground_disabled_color_id) {
     views::ConfigureVectorImageButton(this);
     SetFlipCanvasOnPaintForRTLUI(false);
@@ -93,18 +92,19 @@
     SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
     views::FocusRing::Get(this)->SetColorId(focus_ring_color_id);
 
-    Update(button_id, vector_icon, tooltip_text_id);
+    Update(button_id, vector_icon, tooltip_text_id, foreground_color_id);
   }
 
   void Update(int button_id,
               const gfx::VectorIcon& vector_icon,
-              int tooltip_text_id) {
+              int tooltip_text_id,
+              ui::ColorId foreground_color_id) {
     if (button_id != kNotMediaActionButtonId) {
       SetID(button_id);
     }
     SetTooltipText(l10n_util::GetStringUTF16(tooltip_text_id));
     views::SetImageFromVectorIconWithColorId(
-        this, vector_icon, foreground_color_id_, foreground_disabled_color_id_,
+        this, vector_icon, foreground_color_id, foreground_disabled_color_id_,
         icon_size_);
   }
 
@@ -114,7 +114,6 @@
 
  private:
   const int icon_size_;
-  const ui::ColorId foreground_color_id_;
   const ui::ColorId foreground_disabled_color_id_;
 };
 
@@ -240,7 +239,8 @@
       media_message_center::kPlayArrowIcon,
       IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PLAY);
   play_pause_button_->SetBackground(views::CreateThemedRoundedRectBackground(
-      theme_.system_container_color_id, kPlayPauseButtonSize.height() / 2));
+      theme_.play_button_container_color_id,
+      kPlayPauseButtonSize.height() / 2));
 
   // |controls_row| holds all the available media action buttons and the
   // progress view.
@@ -258,8 +258,11 @@
   // Create the squiggly progress view.
   squiggly_progress_view_ = controls_row->AddChildView(
       std::make_unique<media_message_center::MediaSquigglyProgressView>(
-          theme_.primary_container_color_id,
-          theme_.secondary_container_color_id, theme_.focus_ring_color_id,
+          theme_.playing_progress_foreground_color_id,
+          theme_.playing_progress_background_color_id,
+          theme_.paused_progress_foreground_color_id,
+          theme_.paused_progress_background_color_id,
+          theme_.focus_ring_color_id,
           base::BindRepeating(&MediaNotificationViewAshImpl::OnProgressDragging,
                               base::Unretained(this)),
           base::BindRepeating(&MediaNotificationViewAshImpl::SeekTo,
@@ -340,12 +343,20 @@
     play_pause_button_->Update(
         static_cast<int>(MediaSessionAction::kPause),
         media_message_center::kPauseIcon,
-        IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PAUSE);
+        IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PAUSE,
+        theme_.pause_button_foreground_color_id);
+    play_pause_button_->SetBackground(views::CreateThemedRoundedRectBackground(
+        theme_.pause_button_container_color_id,
+        kPlayPauseButtonSize.height() / 2));
   } else {
     play_pause_button_->Update(
         static_cast<int>(MediaSessionAction::kPlay),
         media_message_center::kPlayArrowIcon,
-        IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PLAY);
+        IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_PLAY,
+        theme_.play_button_foreground_color_id);
+    play_pause_button_->SetBackground(views::CreateThemedRoundedRectBackground(
+        theme_.play_button_container_color_id,
+        kPlayPauseButtonSize.height() / 2));
   }
 
   in_picture_in_picture_ =
@@ -356,12 +367,14 @@
     picture_in_picture_button_->Update(
         static_cast<int>(MediaSessionAction::kExitPictureInPicture),
         media_message_center::kMediaExitPipIcon,
-        IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_EXIT_PIP);
+        IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_EXIT_PIP,
+        theme_.primary_foreground_color_id);
   } else {
     picture_in_picture_button_->Update(
         static_cast<int>(MediaSessionAction::kEnterPictureInPicture),
         media_message_center::kMediaEnterPipIcon,
-        IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_ENTER_PIP);
+        IDS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_ACTION_ENTER_PIP,
+        theme_.primary_foreground_color_id);
   }
 
   UpdateActionButtonsVisibility();
@@ -442,9 +455,7 @@
   auto button = std::make_unique<MediaButton>(
       views::Button::PressedCallback(), button_id, vector_icon, tooltip_text_id,
       theme_.primary_foreground_color_id, theme_.secondary_foreground_color_id,
-      button_id == static_cast<int>(MediaSessionAction::kPlay)
-          ? theme_.primary_container_color_id
-          : theme_.focus_ring_color_id);
+      theme_.focus_ring_color_id);
   auto* button_ptr = parent->AddChildView(std::move(button));
 
   if (button_id != kNotMediaActionButtonId) {
diff --git a/components/media_message_center/media_squiggly_progress_view.cc b/components/media_message_center/media_squiggly_progress_view.cc
index 46bba8abd..9b5ae88 100644
--- a/components/media_message_center/media_squiggly_progress_view.cc
+++ b/components/media_message_center/media_squiggly_progress_view.cc
@@ -71,13 +71,17 @@
 }  // namespace
 
 MediaSquigglyProgressView::MediaSquigglyProgressView(
-    ui::ColorId foreground_color_id,
-    ui::ColorId background_color_id,
+    ui::ColorId playing_foreground_color_id,
+    ui::ColorId playing_background_color_id,
+    ui::ColorId paused_foreground_color_id,
+    ui::ColorId paused_background_color_id,
     ui::ColorId focus_ring_color_id,
     base::RepeatingCallback<void(bool)> dragging_callback,
     base::RepeatingCallback<void(double)> seek_callback)
-    : foreground_color_id_(foreground_color_id),
-      background_color_id_(background_color_id),
+    : playing_foreground_color_id_(playing_foreground_color_id),
+      playing_background_color_id_(playing_background_color_id),
+      paused_foreground_color_id_(paused_foreground_color_id),
+      paused_background_color_id_(paused_background_color_id),
       focus_ring_color_id_(focus_ring_color_id),
       dragging_callback_(std::move(dragging_callback)),
       seek_callback_(std::move(seek_callback)),
@@ -157,7 +161,8 @@
   flags.setStyle(cc::PaintFlags::kStroke_Style);
   flags.setStrokeWidth(kStrokeWidth);
   flags.setAntiAlias(true);
-  flags.setColor(color_provider->GetColor(foreground_color_id_));
+  flags.setColor(color_provider->GetColor(
+      is_paused_ ? paused_foreground_color_id_ : playing_foreground_color_id_));
 
   // Translate the canvas to avoid painting anything in the width inset.
   canvas->Save();
@@ -200,7 +205,9 @@
   // Paint the background straight line.
   if (progress_width + kProgressIndicatorSize.width() / 2 < view_width) {
     flags.setStyle(cc::PaintFlags::kStroke_Style);
-    flags.setColor(color_provider->GetColor(background_color_id_));
+    flags.setColor(
+        color_provider->GetColor(is_paused_ ? paused_background_color_id_
+                                            : playing_background_color_id_));
     canvas->DrawLine(
         gfx::PointF(progress_width + kProgressIndicatorSize.width() / 2,
                     view_height / 2),
diff --git a/components/media_message_center/media_squiggly_progress_view.h b/components/media_message_center/media_squiggly_progress_view.h
index dc9caa43..b6a7f8c 100644
--- a/components/media_message_center/media_squiggly_progress_view.h
+++ b/components/media_message_center/media_squiggly_progress_view.h
@@ -25,8 +25,10 @@
  public:
   METADATA_HEADER(MediaSquigglyProgressView);
   explicit MediaSquigglyProgressView(
-      ui::ColorId foreground_color_id,
-      ui::ColorId background_color_id,
+      ui::ColorId playing_foreground_color_id,
+      ui::ColorId playing_background_color_id,
+      ui::ColorId paused_foreground_color_id,
+      ui::ColorId paused_background_color_id,
       ui::ColorId focus_ring_color_id,
       base::RepeatingCallback<void(bool)> dragging_callback,
       base::RepeatingCallback<void(double)> seek_callback);
@@ -75,8 +77,10 @@
   bool IsValidSeekPosition(int x, int y);
 
   // Init parameters.
-  ui::ColorId foreground_color_id_;
-  ui::ColorId background_color_id_;
+  ui::ColorId playing_foreground_color_id_;
+  ui::ColorId playing_background_color_id_;
+  ui::ColorId paused_foreground_color_id_;
+  ui::ColorId paused_background_color_id_;
   ui::ColorId focus_ring_color_id_;
   const base::RepeatingCallback<void(bool)> dragging_callback_;
   const base::RepeatingCallback<void(double)> seek_callback_;
diff --git a/components/media_message_center/media_squiggly_progress_view_unittest.cc b/components/media_message_center/media_squiggly_progress_view_unittest.cc
index 7f1a908..9ccdfa3 100644
--- a/components/media_message_center/media_squiggly_progress_view_unittest.cc
+++ b/components/media_message_center/media_squiggly_progress_view_unittest.cc
@@ -28,7 +28,7 @@
     ui::ColorId id;
     view_ =
         widget_->SetContentsView(std::make_unique<MediaSquigglyProgressView>(
-            id, id, id,
+            id, id, id, id, id,
             base::BindRepeating(
                 &MediaSquigglyProgressViewTest::OnProgressDragging,
                 base::Unretained(this)),
diff --git a/components/media_message_center/notification_theme.h b/components/media_message_center/notification_theme.h
index 5235bed..8e925b91 100644
--- a/components/media_message_center/notification_theme.h
+++ b/components/media_message_center/notification_theme.h
@@ -27,20 +27,35 @@
   MediaColorTheme& operator=(const MediaColorTheme&);
   ~MediaColorTheme();
 
-  // Color ID for texts and icons.
+  // Color ID for primary texts and most media action button icons.
   ui::ColorId primary_foreground_color_id = 0;
 
   // Color ID for secondary texts.
   ui::ColorId secondary_foreground_color_id = 0;
 
-  // Color ID for time scrubber foreground.
-  ui::ColorId primary_container_color_id = 0;
+  // Color ID for the play button icon.
+  ui::ColorId play_button_foreground_color_id = 0;
 
-  // Color ID for time scrubber background.
-  ui::ColorId secondary_container_color_id = 0;
+  // Color ID for the play button container.
+  ui::ColorId play_button_container_color_id = 0;
 
-  // Color ID for the play/pause button background.
-  ui::ColorId system_container_color_id = 0;
+  // Color ID for the pause button icon.
+  ui::ColorId pause_button_foreground_color_id = 0;
+
+  // Color ID for the pause button container.
+  ui::ColorId pause_button_container_color_id = 0;
+
+  // Color ID for media squiggly progress foreground when the media is playing.
+  ui::ColorId playing_progress_foreground_color_id = 0;
+
+  // Color ID for media squiggly progress background when the media is playing.
+  ui::ColorId playing_progress_background_color_id = 0;
+
+  // Color ID for media squiggly progress foreground when the media is paused.
+  ui::ColorId paused_progress_foreground_color_id = 0;
+
+  // Color ID for media squiggly progress background when the media is paused.
+  ui::ColorId paused_progress_background_color_id = 0;
 
   // Color ID for media view background.
   ui::ColorId background_color_id = 0;
diff --git a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
index 96aa2066..3d4f8c6 100644
--- a/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
+++ b/components/omnibox/browser/android/java/src/org/chromium/components/omnibox/AutocompleteResult.java
@@ -30,8 +30,10 @@
     @IntDef({VerificationPoint.INVALID, VerificationPoint.SELECT_MATCH,
             VerificationPoint.UPDATE_MATCH, VerificationPoint.DELETE_MATCH,
             VerificationPoint.GROUP_BY_SEARCH_VS_URL_BEFORE,
-            VerificationPoint.GROUP_BY_SEARCH_VS_URL_AFTER, VerificationPoint.ON_TOUCH_MATCH})
+            VerificationPoint.GROUP_BY_SEARCH_VS_URL_AFTER, VerificationPoint.ON_TOUCH_MATCH,
+            VerificationPoint.GET_MATCHING_TAB})
     @Retention(RetentionPolicy.SOURCE)
+    // When updating this enum, please update corresponding enum in autocomplete_result_android.cc.
     public @interface VerificationPoint {
         int INVALID = 0;
         int SELECT_MATCH = 1;
@@ -40,6 +42,7 @@
         int GROUP_BY_SEARCH_VS_URL_BEFORE = 4;
         int GROUP_BY_SEARCH_VS_URL_AFTER = 5;
         int ON_TOUCH_MATCH = 6;
+        int GET_MATCHING_TAB = 7;
     }
 
     /** An empty, initialized AutocompleteResult object. */
diff --git a/components/omnibox/browser/autocomplete_result_android.cc b/components/omnibox/browser/autocomplete_result_android.cc
index 8d92fee..a55ef21 100644
--- a/components/omnibox/browser/autocomplete_result_android.cc
+++ b/components/omnibox/browser/autocomplete_result_android.cc
@@ -52,6 +52,7 @@
   GROUP_BY_SEARCH_VS_URL_BEFORE = 4,
   GROUP_BY_SEARCH_VS_URL_AFTER = 5,
   ON_TOUCH_MATCH = 6,
+  GET_MATCHING_TAB = 7,
 };
 
 const char* MatchVerificationPointToString(int verification_point) {
@@ -68,9 +69,12 @@
       return "Group/After";
     case MatchVerificationPoint::ON_TOUCH_MATCH:
       return "OnTouch";
+    case MatchVerificationPoint::GET_MATCHING_TAB:
+      return "GetMatchingTab";
     case MatchVerificationPoint::INVALID:
       return "Invalid";
   }
+  NOTREACHED();
 }
 
 bool sInvalidMatchMetricsUploaded = false;
diff --git a/components/performance_manager/graph/page_node_impl.cc b/components/performance_manager/graph/page_node_impl.cc
index 3020bcae..5d20f23 100644
--- a/components/performance_manager/graph/page_node_impl.cc
+++ b/components/performance_manager/graph/page_node_impl.cc
@@ -52,21 +52,20 @@
 PageNodeImpl::PageNodeImpl(const WebContentsProxy& contents_proxy,
                            const std::string& browser_context_id,
                            const GURL& visible_url,
-                           bool is_visible,
-                           bool is_audible,
+                           PagePropertyFlags initial_properties,
                            base::TimeTicks visibility_change_time,
                            PageState page_state)
     : contents_proxy_(contents_proxy),
       visibility_change_time_(visibility_change_time),
       main_frame_url_(visible_url),
       browser_context_id_(browser_context_id),
-      is_visible_(is_visible),
-      is_audible_(is_audible),
+      is_visible_(initial_properties.Has(PagePropertyFlag::kIsVisible)),
+      is_audible_(initial_properties.Has(PagePropertyFlag::kIsAudible)),
       page_state_(page_state) {
   DCHECK(IsValidInitialPageState(page_state));
   weak_this_ = weak_factory_.GetWeakPtr();
 
-  if (is_audible) {
+  if (is_audible_.value()) {
     audible_change_time_ = base::TimeTicks::Now();
   }
 
diff --git a/components/performance_manager/graph/page_node_impl.h b/components/performance_manager/graph/page_node_impl.h
index 8512e86..5ab9d20 100644
--- a/components/performance_manager/graph/page_node_impl.h
+++ b/components/performance_manager/graph/page_node_impl.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 
+#include "base/containers/enum_set.h"
 #include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -29,6 +30,16 @@
 class PageLoadTrackerAccess;
 class SiteDataAccess;
 
+// The starting state of various boolean properties of the PageNode.
+enum class PagePropertyFlag {
+  kIsVisible,  // initializes PageNode::IsVisible()
+  kMin = kIsVisible,
+  kIsAudible,  // initializes PageNode::IsAudible()
+  kMax = kIsAudible,
+};
+using PagePropertyFlags = base::
+    EnumSet<PagePropertyFlag, PagePropertyFlag::kMin, PagePropertyFlag::kMax>;
+
 class PageNodeImpl
     : public PublicNodeImpl<PageNodeImpl, PageNode>,
       public TypedNodeBase<PageNodeImpl, PageNode, PageNodeObserver> {
@@ -44,8 +55,7 @@
   PageNodeImpl(const WebContentsProxy& contents_proxy,
                const std::string& browser_context_id,
                const GURL& visible_url,
-               bool is_visible,
-               bool is_audible,
+               PagePropertyFlags initial_properties,
                base::TimeTicks visibility_change_time,
                PageState page_state);
 
diff --git a/components/performance_manager/graph/page_node_impl_unittest.cc b/components/performance_manager/graph/page_node_impl_unittest.cc
index 881690a..e3c6bda 100644
--- a/components/performance_manager/graph/page_node_impl_unittest.cc
+++ b/components/performance_manager/graph/page_node_impl_unittest.cc
@@ -126,8 +126,7 @@
   // Test a page that's audible at creation.
   auto audible_page = CreateNode<PageNodeImpl>(
       WebContentsProxy(), /*browser_context_id=*/std::string(), GURL(),
-      /*is_visible=*/false,
-      /*is_audible=*/true);
+      PagePropertyFlags{PagePropertyFlag::kIsAudible});
   AdvanceClock(base::Seconds(56));
   EXPECT_EQ(base::Seconds(56), audible_page->TimeSinceLastAudibleChange());
   EXPECT_TRUE(audible_page->is_audible());
@@ -480,8 +479,7 @@
       WebContentsProxy(),                   // wc_proxy
       std::string(),                        // browser_context_id
       GURL(),                               // url
-      false,                                // is_visible
-      false,                                // is_audible
+      PagePropertyFlags{},                  // initial_property_flags
       base::TimeTicks::Now(),               // visibility_change_time
       PageNode::PageState::kPrerendering);  // page_state
   EXPECT_EQ(PageNode::PageState::kPrerendering, page->page_state());
diff --git a/components/performance_manager/performance_manager_impl.cc b/components/performance_manager/performance_manager_impl.cc
index 54440b9..ddd3562 100644
--- a/components/performance_manager/performance_manager_impl.cc
+++ b/components/performance_manager/performance_manager_impl.cc
@@ -141,13 +141,12 @@
     const WebContentsProxy& contents_proxy,
     const std::string& browser_context_id,
     const GURL& visible_url,
-    bool is_visible,
-    bool is_audible,
+    PagePropertyFlags initial_property_flags,
     base::TimeTicks visibility_change_time,
     PageNode::PageState page_state) {
   return CreateNodeImpl<PageNodeImpl>(base::OnceCallback<void(PageNodeImpl*)>(),
                                       contents_proxy, browser_context_id,
-                                      visible_url, is_visible, is_audible,
+                                      visible_url, initial_property_flags,
                                       visibility_change_time, page_state);
 }
 
diff --git a/components/performance_manager/performance_manager_impl.h b/components/performance_manager/performance_manager_impl.h
index daa8e2d..883e31d 100644
--- a/components/performance_manager/performance_manager_impl.h
+++ b/components/performance_manager/performance_manager_impl.h
@@ -16,6 +16,7 @@
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "components/performance_manager/graph/graph_impl.h"
+#include "components/performance_manager/graph/page_node_impl.h"
 #include "components/performance_manager/public/browser_child_process_host_proxy.h"
 #include "components/performance_manager/public/graph/frame_node.h"
 #include "components/performance_manager/public/graph/page_node.h"
@@ -32,7 +33,6 @@
 
 namespace performance_manager {
 
-class PageNodeImpl;
 struct BrowserProcessNodeTag;
 
 // The performance manager is a rendezvous point for binding to performance
@@ -108,8 +108,7 @@
       const WebContentsProxy& contents_proxy,
       const std::string& browser_context_id,
       const GURL& visible_url,
-      bool is_visible,
-      bool is_audible,
+      PagePropertyFlags initial_properties,
       base::TimeTicks visibility_change_time,
       PageNode::PageState page_state);
   static std::unique_ptr<ProcessNodeImpl> CreateProcessNode(
diff --git a/components/performance_manager/performance_manager_impl_unittest.cc b/components/performance_manager/performance_manager_impl_unittest.cc
index 787f15c..15ed42b 100644
--- a/components/performance_manager/performance_manager_impl_unittest.cc
+++ b/components/performance_manager/performance_manager_impl_unittest.cc
@@ -59,7 +59,7 @@
   EXPECT_NE(nullptr, process_node.get());
   std::unique_ptr<PageNodeImpl> page_node =
       PerformanceManagerImpl::CreatePageNode(
-          WebContentsProxy(), std::string(), GURL(), false, false,
+          WebContentsProxy(), std::string(), GURL(), PagePropertyFlags{},
           base::TimeTicks::Now(), PageNode::PageState::kActive);
   EXPECT_NE(nullptr, page_node.get());
 
@@ -83,7 +83,7 @@
       PerformanceManagerImpl::CreateProcessNode(RenderProcessHostProxy());
   std::unique_ptr<PageNodeImpl> page_node =
       PerformanceManagerImpl::CreatePageNode(
-          WebContentsProxy(), std::string(), GURL(), false, false,
+          WebContentsProxy(), std::string(), GURL(), PagePropertyFlags{},
           base::TimeTicks::Now(), PageNode::PageState::kActive);
 
   std::unique_ptr<FrameNodeImpl> parent1_frame =
@@ -134,7 +134,7 @@
   // Create a page node for something to target.
   std::unique_ptr<PageNodeImpl> page_node =
       PerformanceManagerImpl::CreatePageNode(
-          WebContentsProxy(), std::string(), GURL(), false, false,
+          WebContentsProxy(), std::string(), GURL(), PagePropertyFlags{},
           base::TimeTicks::Now(), PageNode::PageState::kActive);
   base::RunLoop run_loop;
   base::OnceClosure quit_closure = run_loop.QuitClosure();
@@ -156,7 +156,7 @@
   // Create a page node for something to target.
   std::unique_ptr<PageNodeImpl> page_node =
       PerformanceManagerImpl::CreatePageNode(
-          WebContentsProxy(), std::string(), GURL(), false, false,
+          WebContentsProxy(), std::string(), GURL(), PagePropertyFlags{},
           base::TimeTicks::Now(), PageNode::PageState::kActive);
   base::RunLoop run_loop;
 
diff --git a/components/performance_manager/performance_manager_tab_helper.cc b/components/performance_manager/performance_manager_tab_helper.cc
index 7f619b33..5f56cfe7 100644
--- a/components/performance_manager/performance_manager_tab_helper.cc
+++ b/components/performance_manager/performance_manager_tab_helper.cc
@@ -95,14 +95,21 @@
   DCHECK_EQ(1u, frame_count);
 #endif
 
+  PagePropertyFlags initial_property_flags;
+  if (web_contents->GetVisibility() == content::Visibility::VISIBLE) {
+    initial_property_flags.Put(PagePropertyFlag::kIsVisible);
+  }
+  if (web_contents->IsCurrentlyAudible()) {
+    initial_property_flags.Put(PagePropertyFlag::kIsAudible);
+  }
+
   // Create the page node.
   std::unique_ptr<PageData> page = std::make_unique<PageData>();
   page->page_node = PerformanceManagerImpl::CreatePageNode(
       WebContentsProxy(weak_factory_.GetWeakPtr()),
       web_contents->GetBrowserContext()->UniqueId(),
-      web_contents->GetVisibleURL(),
-      web_contents->GetVisibility() == content::Visibility::VISIBLE,
-      web_contents->IsCurrentlyAudible(), web_contents->GetLastActiveTime(),
+      web_contents->GetVisibleURL(), initial_property_flags,
+      web_contents->GetLastActiveTime(),
       // TODO(crbug.com/1211368): Support MPArch fully!
       PageNode::PageState::kActive);
   content::RenderFrameHost* main_rfh = web_contents->GetPrimaryMainFrame();
diff --git a/components/performance_manager/test_support/graph_test_harness.h b/components/performance_manager/test_support/graph_test_harness.h
index 94b7ecd2..ad30df7 100644
--- a/components/performance_manager/test_support/graph_test_harness.h
+++ b/components/performance_manager/test_support/graph_test_harness.h
@@ -126,12 +126,11 @@
       const WebContentsProxy& wc_proxy = WebContentsProxy(),
       const std::string& browser_context_id = std::string(),
       const GURL& url = GURL(),
-      bool is_visible = false,
-      bool is_audible = false,
+      PagePropertyFlags initial_property_flags = {},
       base::TimeTicks visibility_change_time = base::TimeTicks::Now(),
       PageNode::PageState page_state = PageNode::PageState::kActive) {
     return std::make_unique<PageNodeImpl>(wc_proxy, browser_context_id, url,
-                                          is_visible, is_audible,
+                                          initial_property_flags,
                                           visibility_change_time, page_state);
   }
 };
diff --git a/components/performance_manager/worker_watcher_unittest.cc b/components/performance_manager/worker_watcher_unittest.cc
index bb84434..d6edade4 100644
--- a/components/performance_manager/worker_watcher_unittest.cc
+++ b/components/performance_manager/worker_watcher_unittest.cc
@@ -600,8 +600,7 @@
           WebContentsProxy(),
           "page_node_context_id",
           GURL(),
-          false,
-          false,
+          PagePropertyFlags{},
           base::TimeTicks::Now(),
           PageNode::PageState::kActive)) {}
 
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
index 766b8f4..ed2e2ae9 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
@@ -26,6 +26,7 @@
 constexpr char kPriority[] = "priority";
 constexpr char kAttachConfigurationFileKey[] = "attachConfigurationFile";
 constexpr char kAttachEncryptionSettingsKey[] = "attachEncryptionSettings";
+constexpr char kClientAutomatedTestKey[] = "clientAutomatedTest";
 constexpr char kDeviceKey[] = "device";
 constexpr char kBrowserKey[] = "browser";
 constexpr char kRequestId[] = "requestId";
@@ -325,6 +326,7 @@
       kAttachConfigurationFileKey,
       kAttachEncryptionSettingsKey,
       kBrowserKey,
+      kClientAutomatedTestKey,
       kDeviceKey,
       kEncryptedRecordListKey,
       kRequestId};
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
index dc043e37..913ad0ac 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
@@ -58,6 +58,7 @@
 //   ],
 //   "attachEncryptionSettings": true,  // optional field
 //   "attachConfigurationFile": true, // optional field
+//   "clientAutomatedTest": true, // optional field - used only by tast tests
 //   "requestId": "SomeString",
 //   "device": {
 //     "client_id": "abcdef1234",
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc b/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
index cddd8733..9cda8f3 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
@@ -66,6 +66,9 @@
 // Configuration file request key
 constexpr char kAttachConfigurationFileKey[] = "attachConfigurationFile";
 
+// Client automated test key
+constexpr char kClientAutomatedTestKey[] = "attachConfigurationFile";
+
 // Keys for EncryptedRecord
 constexpr char kEncryptedWrappedRecordKey[] = "encryptedWrappedRecord";
 constexpr char kSequenceInformationKey[] = "sequenceInformation";
@@ -100,13 +103,17 @@
 class RequestPayloadBuilder {
  public:
   explicit RequestPayloadBuilder(bool attach_encryption_settings = false,
-                                 bool attach_configuration_file = false) {
+                                 bool attach_configuration_file = false,
+                                 bool client_automated_test = false) {
     if (attach_encryption_settings) {
       payload_.Set(kAttachEncryptionSettingsKey, true);
     }
     if (attach_configuration_file) {
       payload_.Set(kAttachConfigurationFileKey, true);
     }
+    if (client_automated_test) {
+      payload_.Set(kClientAutomatedTestKey, true);
+    }
     payload_.Set(kEncryptedRecordListKey, base::Value::List());
   }
 
@@ -295,6 +302,14 @@
            attach_encryption_settings.value();
   }
 
+  bool GetClientAutomatedTest(
+      EncryptedReportingJobConfiguration* configuration) {
+    base::Value* const payload = GetPayload(configuration);
+    const auto client_automated_test =
+        payload->GetDict().FindBool(kClientAutomatedTestKey);
+    return client_automated_test.has_value() && client_automated_test.value();
+  }
+
   base::Value* GetPayload(EncryptedReportingJobConfiguration* configuration) {
     absl::optional<base::Value> payload_result =
         base::JSONReader::Read(configuration->GetPayload());
@@ -601,6 +616,84 @@
   EXPECT_TRUE(GetAttachConfigurationFile(&configuration));
 }
 
+TEST_F(EncryptedReportingJobConfigurationTest, AllowsClientAutomatedTestAlone) {
+  RequestPayloadBuilder builder{/*attach_encryption_settings=*/false,
+                                /*attach_configuration_file=*/false,
+                                /*client_automated_test=*/true};
+  StrictMock<MockCompleteCb> completion_cb;
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
+  EncryptedReportingJobConfiguration configuration(
+      shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
+      kServerUrl, builder.Build(), &client_,
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
+
+  base::Value::List* record_list = nullptr;
+  GetRecordList(&configuration, &record_list);
+
+  EXPECT_TRUE(record_list->empty());
+
+  EXPECT_TRUE(GetClientAutomatedTest(&configuration));
+}
+
+TEST_F(
+    EncryptedReportingJobConfigurationTest,
+    AllowsAttachConfigurationFileEncryptionSettingsAndClientAutomatedTestWithoutRecords) {
+  RequestPayloadBuilder builder{/*attach_encryption_settings=*/true,
+                                /*attach_configuration_file=*/true,
+                                /*client_automated_test=*/true};
+  StrictMock<MockCompleteCb> completion_cb;
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
+  EncryptedReportingJobConfiguration configuration(
+      shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
+      kServerUrl, builder.Build(), &client_,
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
+
+  base::Value::List* record_list = nullptr;
+  GetRecordList(&configuration, &record_list);
+
+  EXPECT_TRUE(record_list->empty());
+
+  EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
+  EXPECT_TRUE(GetAttachConfigurationFile(&configuration));
+  EXPECT_TRUE(GetClientAutomatedTest(&configuration));
+}
+
+TEST_F(
+    EncryptedReportingJobConfigurationTest,
+    CorrectlyAddsMultipleRecordsWithAttachConfigurationFileAttachEncryptionKeyAndClientAutomatedTest) {
+  const std::vector<std::string> kEncryptedWrappedRecords{
+      "T", "E", "S", "T", "_", "I", "N", "F", "O"};
+  base::Value::List records;
+  RequestPayloadBuilder builder{/*attach_encryption_settings=*/true,
+                                /*attach_configuration_file=*/true,
+                                /*client_automated_test=*/true};
+  for (auto value : kEncryptedWrappedRecords) {
+    records.Append(GenerateSingleRecord(value));
+    builder.AddRecord(records.back());
+  }
+
+  StrictMock<MockCompleteCb> completion_cb;
+  EXPECT_CALL(completion_cb, Call(_, _, _, _)).Times(1);
+  EncryptedReportingJobConfiguration configuration(
+      shared_url_loader_factory_, DMAuth::FromDMToken(client_.dm_token()),
+      kServerUrl, builder.Build(), &client_,
+      base::BindOnce(&MockCompleteCb::Call, base::Unretained(&completion_cb)));
+
+  base::Value::List* record_list = nullptr;
+  GetRecordList(&configuration, &record_list);
+
+  EXPECT_EQ(record_list->size(), records.size());
+
+  size_t counter = 0;
+  for (const auto& record : records) {
+    EXPECT_EQ((*record_list)[counter++], record);
+  }
+
+  EXPECT_TRUE(GetAttachEncryptionSettings(&configuration));
+  EXPECT_TRUE(GetAttachConfigurationFile(&configuration));
+  EXPECT_TRUE(GetClientAutomatedTest(&configuration));
+}
+
 // Ensures that the context can be updated.
 TEST_F(EncryptedReportingJobConfigurationTest, CorrectlyAddsAndUpdatesContext) {
   StrictMock<MockCompleteCb> completion_cb;
@@ -868,6 +961,7 @@
   request.Set(kEncryptedRecordListKey, base::Value::List());
   request.Set(kAttachConfigurationFileKey, true);
   request.Set(kAttachEncryptionSettingsKey, true);
+  request.Set(kClientAutomatedTestKey, true);
   request.Set(kDeviceKey, base::Value::Dict());
   request.Set(kBrowserKey, base::Value::Dict());
   request.Set(kInvalidKey, base::Value::Dict());
@@ -885,6 +979,7 @@
   EXPECT_TRUE(payload->GetDict().FindList(kEncryptedRecordListKey));
   EXPECT_TRUE(payload->GetDict().FindBool(kAttachEncryptionSettingsKey));
   EXPECT_TRUE(payload->GetDict().FindBool(kAttachConfigurationFileKey));
+  EXPECT_TRUE(payload->GetDict().FindBool(kClientAutomatedTestKey));
   EXPECT_TRUE(payload->GetDict().FindDict(kDeviceKey));
   EXPECT_TRUE(payload->GetDict().FindDict(kBrowserKey));
   EXPECT_FALSE(payload->GetDict().FindDict(kInvalidKey));
diff --git a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
index d52272f..5173337 100644
--- a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
+++ b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
@@ -564,7 +564,7 @@
 }
 
 - (void)miniaturize:(id)sender {
-  static const BOOL isMacOS13OrHigher = base::mac::IsAtLeastOS13();
+  static const BOOL isMacOS13OrHigher = base::mac::MacOSMajorVersion() >= 13;
   // On macOS 13, the miniaturize operation appears to no longer be "atomic"
   // because of non-blocking roundtrip IPC with the Dock. We want to note here
   // that miniaturization is in progress. The process completes when we
@@ -591,7 +591,7 @@
   // _miniaturizationInProgress is NO, the miniaturization process was
   // cancelled by a call to -makeKeyAndOrderFront:. In that case, we don't want
   // to proceed with miniaturization.
-  static const BOOL isMacOS13OrHigher = base::mac::IsAtLeastOS13();
+  static const BOOL isMacOS13OrHigher = base::mac::MacOSMajorVersion() >= 13;
   if (isMacOS13OrHigher && !_miniaturizationInProgress) {
     return;
   }
@@ -685,7 +685,7 @@
   // https://sector7.computest.nl/post/2022-08-process-injection-breaking-all-macos-security-layers-with-a-single-vulnerability/
   // for more details.
   NSKeyedArchiver* encoder = [[NSKeyedArchiver alloc]
-      initRequiringSecureCoding:base::mac::IsAtLeastOS12()];
+      initRequiringSecureCoding:base::mac::MacOSMajorVersion() >= 12];
   encoder.delegate = self;
   [self encodeRestorableStateWithCoder:encoder];
   [encoder finishEncoding];
diff --git a/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm b/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm
index dfb2bf80a..8a81c61 100644
--- a/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm
+++ b/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm
@@ -69,8 +69,9 @@
                                        IDS_SAVE_PAGE_FILE_FORMAT_PROMPT_MAC)];
   label.translatesAutoresizingMaskIntoConstraints = NO;
   label.textColor = NSColor.secondaryLabelColor;
-  if (base::mac::IsAtLeastOS11())
+  if (base::mac::MacOSMajorVersion() >= 11) {
     label.font = [NSFont systemFontOfSize:[NSFont smallSystemFontSize]];
+  }
 
   // The popup.
   NSPopUpButton* popup = [[NSPopUpButton alloc] initWithFrame:NSZeroRect
@@ -115,10 +116,11 @@
 
   // Horizontal and vertical baseline between the label and popup.
   CGFloat labelPopupPadding;
-  if (base::mac::IsAtLeastOS11())
+  if (base::mac::MacOSMajorVersion() >= 11) {
     labelPopupPadding = 8;
-  else
+  } else {
     labelPopupPadding = 5;
+  }
   [constraints addObject:[popup.leadingAnchor
                              constraintEqualToAnchor:label.trailingAnchor
                                             constant:labelPopupPadding]];
diff --git a/components/test/data/viz/invert_backdrop_filter_1x.png b/components/test/data/viz/invert_backdrop_filter_1x.png
index 49c3d24..0028f4c 100644
--- a/components/test/data/viz/invert_backdrop_filter_1x.png
+++ b/components/test/data/viz/invert_backdrop_filter_1x.png
Binary files differ
diff --git a/components/test/data/viz/invert_backdrop_filter_1x_graphite.png b/components/test/data/viz/invert_backdrop_filter_1x_graphite.png
new file mode 100644
index 0000000..ad3918d
--- /dev/null
+++ b/components/test/data/viz/invert_backdrop_filter_1x_graphite.png
Binary files differ
diff --git a/components/test/data/viz/invert_backdrop_filter_1x_sw.png b/components/test/data/viz/invert_backdrop_filter_1x_sw.png
index ac2e7ce..2c2de9d 100644
--- a/components/test/data/viz/invert_backdrop_filter_1x_sw.png
+++ b/components/test/data/viz/invert_backdrop_filter_1x_sw.png
Binary files differ
diff --git a/components/test/data/viz/invert_backdrop_filter_2x.png b/components/test/data/viz/invert_backdrop_filter_2x.png
index d867eb2..bd10810a 100644
--- a/components/test/data/viz/invert_backdrop_filter_2x.png
+++ b/components/test/data/viz/invert_backdrop_filter_2x.png
Binary files differ
diff --git a/components/test/data/viz/invert_backdrop_filter_2x_graphite.png b/components/test/data/viz/invert_backdrop_filter_2x_graphite.png
new file mode 100644
index 0000000..862d8736
--- /dev/null
+++ b/components/test/data/viz/invert_backdrop_filter_2x_graphite.png
Binary files differ
diff --git a/components/test/data/viz/invert_backdrop_filter_2x_sw.png b/components/test/data/viz/invert_backdrop_filter_2x_sw.png
index 1f5056d..3343cf9b 100644
--- a/components/test/data/viz/invert_backdrop_filter_2x_sw.png
+++ b/components/test/data/viz/invert_backdrop_filter_2x_sw.png
Binary files differ
diff --git a/components/test/data/viz/multi_linear_gradient_render_pass.png b/components/test/data/viz/multi_linear_gradient_render_pass.png
index 2526d02..fb5990e3 100644
--- a/components/test/data/viz/multi_linear_gradient_render_pass.png
+++ b/components/test/data/viz/multi_linear_gradient_render_pass.png
Binary files differ
diff --git a/components/test/data/viz/rounded_corner_render_pass_skia.png b/components/test/data/viz/rounded_corner_render_pass_skia.png
index 707c82cd..9a5fe6e 100644
--- a/components/test/data/viz/rounded_corner_render_pass_skia.png
+++ b/components/test/data/viz/rounded_corner_render_pass_skia.png
Binary files differ
diff --git a/components/test/data/viz/rounded_corner_render_pass_software.png b/components/test/data/viz/rounded_corner_render_pass_software.png
index 7e93d98ed..ee8bea0 100644
--- a/components/test/data/viz/rounded_corner_render_pass_software.png
+++ b/components/test/data/viz/rounded_corner_render_pass_software.png
Binary files differ
diff --git a/components/test/data/viz/unit_tests_bundle_data.filelist b/components/test/data/viz/unit_tests_bundle_data.filelist
index 55aab976..7e9e2f9 100644
--- a/components/test/data/viz/unit_tests_bundle_data.filelist
+++ b/components/test/data/viz/unit_tests_bundle_data.filelist
@@ -92,8 +92,10 @@
 //components/test/data/viz/intersecting_light_dark_squares_video_angle_metal.png
 //components/test/data/viz/intersecting_non_flipped_blue_green_half_size_rectangles.png
 //components/test/data/viz/invert_backdrop_filter_1x.png
+//components/test/data/viz/invert_backdrop_filter_1x_graphite.png
 //components/test/data/viz/invert_backdrop_filter_1x_sw.png
 //components/test/data/viz/invert_backdrop_filter_2x.png
+//components/test/data/viz/invert_backdrop_filter_2x_graphite.png
 //components/test/data/viz/invert_backdrop_filter_2x_sw.png
 //components/test/data/viz/layer_sort_cubes.json
 //components/test/data/viz/layer_sort_rubik.json
diff --git a/components/vector_icons/BUILD.gn b/components/vector_icons/BUILD.gn
index 676ed2e..cc8766b5 100644
--- a/components/vector_icons/BUILD.gn
+++ b/components/vector_icons/BUILD.gn
@@ -24,6 +24,7 @@
     "back_arrow.icon",
     "back_arrow_chrome_refresh.icon",
     "back_to_tab.icon",
+    "back_to_tab_chrome_refresh.icon",
     "blocked_badge.icon",
     "bluetooth.icon",
     "bluetooth_chrome_refresh.icon",
diff --git a/components/vector_icons/back_to_tab_chrome_refresh.icon b/components/vector_icons/back_to_tab_chrome_refresh.icon
new file mode 100644
index 0000000..949010f4
--- /dev/null
+++ b/components/vector_icons/back_to_tab_chrome_refresh.icon
@@ -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.
+
+CANVAS_DIMENSIONS, 16,
+MOVE_TO, 5.67f, 7.71f,
+LINE_TO, 2.33f, 4.38f,
+LINE_TO, 2.33f, 7.33f,
+LINE_TO, 1.33f, 7.33f,
+LINE_TO, 1.33f, 2.67f,
+LINE_TO, 6, 2.67f,
+LINE_TO, 6, 3.67f,
+LINE_TO, 3.04f, 3.67f,
+LINE_TO, 6.38f, 7,
+CLOSE,
+MOVE_TO, 2.33f, 13.33f,
+CUBIC_TO, 2.07f, 13.33f, 1.83f, 13.23f, 1.63f, 13.04f,
+CUBIC_TO, 1.43f, 12.83f, 1.33f, 12.6f, 1.33f, 12.33f,
+LINE_TO, 1.33f, 8.5f,
+LINE_TO, 2.33f, 8.5f,
+LINE_TO, 2.33f, 12.33f,
+LINE_TO, 8, 12.33f,
+LINE_TO, 8, 13.33f,
+CLOSE,
+MOVE_TO, 13.67f, 8.67f,
+LINE_TO, 13.67f, 3.67f,
+LINE_TO, 7.17f, 3.67f,
+LINE_TO, 7.17f, 2.67f,
+LINE_TO, 13.67f, 2.67f,
+CUBIC_TO, 13.93f, 2.67f, 14.17f, 2.77f, 14.37f, 2.96f,
+CUBIC_TO, 14.57f, 3.17f, 14.67f, 3.4f, 14.67f, 3.67f,
+LINE_TO, 14.67f, 8.67f,
+CLOSE,
+MOVE_TO, 14.67f, 9.67f,
+LINE_TO, 14.67f, 13.33f,
+LINE_TO, 9, 13.33f,
+LINE_TO, 9, 9.67f,
+CLOSE
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index 66032a70..085c63a 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -206,13 +206,17 @@
                                   AggregatedRenderPass* render_pass) {
   auto* quad =
       render_pass->CreateAndAppendDrawQuad<AggregatedRenderPassDrawQuad>();
+  // The full `rect` is drawn (visible_rect == rect) but texture coords
+  // are relative to the underlying image.
+  gfx::RectF tex_coords{static_cast<float>(rect.width()),
+                        static_cast<float>(rect.height())};
   quad->SetNew(shared_state, rect, rect, pass_id,
                kInvalidResourceId,    // mask_resource_id
                gfx::RectF(),          // mask_uv_rect
                gfx::Size(),           // mask_texture_size
                gfx::Vector2dF(1, 1),  // filters scale
                gfx::PointF(),         // filter origin
-               gfx::RectF(rect),      // tex_coord_rect
+               tex_coords,            // tex_coord_rect
                false,                 // force_anti_aliasing_off
                1.0f);                 // backdrop_filter_quality
 }
diff --git a/components/webauthn/json/value_conversions_unittest.cc b/components/webauthn/json/value_conversions_unittest.cc
index c1b2b1d..0ab3802 100644
--- a/components/webauthn/json/value_conversions_unittest.cc
+++ b/components/webauthn/json/value_conversions_unittest.cc
@@ -9,10 +9,12 @@
 #include <vector>
 
 #include "base/json/json_string_value_serializer.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "device/fido/authenticator_selection_criteria.h"
 #include "device/fido/cable/cable_discovery_data.h"
+#include "device/fido/features.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/fido_transport_protocol.h"
 #include "device/fido/fido_types.h"
@@ -281,6 +283,11 @@
 
 TEST(WebAuthenticationJSONConversionTest,
      AuthenticatorAttestationResponseOptionalFields) {
+  // TODO(https://crbug.com/1454841): Remove this.
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      device::kWebAuthnRequireUpToDateJSONForRemoteDesktop);
+
   // Test both that `null` is an error, and that omitting the field works, for
   // the sole optional field, `authenticatorAttachment`.
   constexpr char kJsonWithNull[] = R"({
@@ -337,6 +344,10 @@
 
 TEST(WebAuthenticationJSONConversionTest,
      AuthenticatorAttestationResponseEasyAccessorFields) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndDisableFeature(
+      device::kWebAuthnRequireUpToDateJSONForRemoteDesktop);
+
   constexpr char kJson[] = R"({
     "rawId":"Lnc6JGTv2WBS05AsZB6xdg",
     "authenticatorAttachment":"platform",
@@ -563,6 +574,11 @@
   }
 
   {
+    // TODO(https://crbug.com/1454841): Remove this.
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitAndDisableFeature(
+        device::kWebAuthnRequireUpToDateJSONForRemoteDesktop);
+
     // ... but not for remote-desktop
     auto [response, error] =
         GetAssertionResponseFromValue(*value, JSONUser::kRemoteDesktop);
diff --git a/components/webxr/android/java/res/menu/settings_menu.xml b/components/webxr/android/java/res/menu/settings_menu.xml
index fdfb7aa..5a8ad605 100644
--- a/components/webxr/android/java/res/menu/settings_menu.xml
+++ b/components/webxr/android/java/res/menu/settings_menu.xml
@@ -7,8 +7,8 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <item android:id="@+id/cardboard_menu_option_about_vr"
-        android:title="@string/about_vr" />
+    <item android:id="@+id/cardboard_menu_option_product_safety"
+        android:title="@string/cardboard_product_safety" />
     <item android:id="@+id/cardboard_menu_option_use_another_device"
         android:title="@string/use_another_device" />
-</menu>
\ No newline at end of file
+</menu>
diff --git a/components/webxr/android/java/src/org/chromium/components/webxr/CardboardOverlayDelegate.java b/components/webxr/android/java/src/org/chromium/components/webxr/CardboardOverlayDelegate.java
index 74d4a65..37f9bc0 100644
--- a/components/webxr/android/java/src/org/chromium/components/webxr/CardboardOverlayDelegate.java
+++ b/components/webxr/android/java/src/org/chromium/components/webxr/CardboardOverlayDelegate.java
@@ -28,7 +28,7 @@
 public class CardboardOverlayDelegate
         implements XrImmersiveOverlay.Delegate, PopupMenu.OnMenuItemClickListener {
     private static final String TAG = "CardboardOverlay";
-    private static final String ABOUT_VR_URL = "google.com/cardboard";
+    private static final String PRODUCT_SAFETY_URL = "google.com/get/cardboard/product-safety";
     private static final boolean DEBUG_LOGS = false;
     static final int VR_SYSTEM_UI_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
             | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -90,8 +90,8 @@
             XrSessionCoordinator.onActiveXrSessionButtonTouched();
             XrSessionCoordinator.endActiveSession();
             return true;
-        } else if (item.getItemId() == R.id.cardboard_menu_option_about_vr) {
-            LoadUrlParams url = new LoadUrlParams(ABOUT_VR_URL);
+        } else if (item.getItemId() == R.id.cardboard_menu_option_product_safety) {
+            LoadUrlParams url = new LoadUrlParams(PRODUCT_SAFETY_URL);
             // Storing this value in a new variable as the ending the active
             // session  could clear it otherwise.
             VrCompositorDelegate delegate = mCompositorDelegate;
diff --git a/components/webxr_strings.grdp b/components/webxr_strings.grdp
index d0609a1..05f44f83 100644
--- a/components/webxr_strings.grdp
+++ b/components/webxr_strings.grdp
@@ -14,8 +14,8 @@
     </message>
   </if>
   <if expr="enable_vr">
-    <message name="IDS_ABOUT_VR" desc="Short text shown on the settings menu to let the user open the About VR page.[CHAR_LIMIT=30]" formatter_data="android_java">
-      About VR
+    <message name="IDS_CARDBOARD_PRODUCT_SAFETY" desc="Short text shown on the settings menu to let the user open the Cardboard product safety page.[CHAR_LIMIT=30]" formatter_data="android_java">
+      Product safety
     </message>
     <message name="IDS_USE_ANOTHER_DEVICE" desc="Short text shown on the settings menu to let the user switch viewer.[CHAR_LIMIT=30]" formatter_data="android_java">
       Use another device
diff --git a/components/webxr_strings_grdp/IDS_ABOUT_VR.png.sha1 b/components/webxr_strings_grdp/IDS_ABOUT_VR.png.sha1
deleted file mode 100644
index f77aa71..0000000
--- a/components/webxr_strings_grdp/IDS_ABOUT_VR.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-7a907b01ad76c5a8853d22ed42efdd38081f7a79
\ No newline at end of file
diff --git a/components/webxr_strings_grdp/IDS_CARDBOARD_PRODUCT_SAFETY.png.sha1 b/components/webxr_strings_grdp/IDS_CARDBOARD_PRODUCT_SAFETY.png.sha1
new file mode 100644
index 0000000..8a4afc2
--- /dev/null
+++ b/components/webxr_strings_grdp/IDS_CARDBOARD_PRODUCT_SAFETY.png.sha1
@@ -0,0 +1 @@
+9ead66109e58680d650073f8d785ffa36f9bb6bd
\ No newline at end of file
diff --git a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
index e2bb90c1..afcc3bc4 100644
--- a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
+++ b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.h
@@ -33,8 +33,7 @@
 
 // Returns YES if the specified version is less than 13.0 or more than 13.2.
 // Manual occlusion detection is not supported on macOS 13.0-13.2.
-+ (BOOL)manualOcclusionDetectionSupportedForVersion:(int32_t)major
-                                                   :(int32_t)minor;
++ (BOOL)manualOcclusionDetectionSupportedForPackedVersion:(int)version;
 
 // Returns YES if manual occlusion detection is supported for the current macOS.
 + (BOOL)manualOcclusionDetectionSupportedForCurrentMacOSVersion;
diff --git a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
index 2eb84e4..378da34 100644
--- a/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
+++ b/content/app_shim_remote_cocoa/web_contents_occlusion_checker_mac.mm
@@ -12,9 +12,9 @@
 #include "base/debug/crash_logging.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
+#include "base/mac/mac_util.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/no_destructor.h"
-#include "base/system/sys_info.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/common/content_client.h"
 #include "content/public/common/content_features.h"
@@ -76,23 +76,17 @@
   return *sharedInstance;
 }
 
-+ (BOOL)manualOcclusionDetectionSupportedForVersion:(int32_t)major
-                                                   :(int32_t)minor {
-  if (major != 13) {
-    return YES;
++ (BOOL)manualOcclusionDetectionSupportedForPackedVersion:(int)version {
+  if (version >= 13'00'00 && version < 13'03'00) {
+    return NO;
   }
 
-  return minor >= 3;
+  return YES;
 }
 
 + (BOOL)manualOcclusionDetectionSupportedForCurrentMacOSVersion {
-  int32_t major_version;
-  int32_t minor_version;
-  int32_t bugfix_version;
-  base::SysInfo::OperatingSystemVersionNumbers(&major_version, &minor_version,
-                                               &bugfix_version);
-  return [self manualOcclusionDetectionSupportedForVersion:
-                                             major_version:minor_version];
+  return [self manualOcclusionDetectionSupportedForPackedVersion:
+                   base::mac::MacOSVersion()];
 }
 
 + (void)resetSharedInstanceForTesting {
diff --git a/content/app_shim_remote_cocoa/window_occlusion_browsertest_mac.mm b/content/app_shim_remote_cocoa/window_occlusion_browsertest_mac.mm
index e53da807b..2ce0bf28 100644
--- a/content/app_shim_remote_cocoa/window_occlusion_browsertest_mac.mm
+++ b/content/app_shim_remote_cocoa/window_occlusion_browsertest_mac.mm
@@ -30,8 +30,7 @@
 };
 
 struct Version {
-  int32_t major;
-  int32_t minor;
+  int packed_version;
   bool supported;
 };
 
@@ -605,12 +604,13 @@
   Class WebContentsOcclusionCheckerMac =
       NSClassFromString(@"WebContentsOcclusionCheckerMac");
   std::vector<Version> versions = {
-      {11, 0, true},  {12, 0, true},  {12, 9, true}, {13, 0, false},
-      {13, 1, false}, {13, 2, false}, {13, 3, true}, {14, 0, true}};
+      {11'00'00, true},  {12'00'00, true},  {12'09'00, true}, {13'00'00, false},
+      {13'01'00, false}, {13'02'00, false}, {13'03'00, true}, {14'00'00, true}};
 
   for (const auto& version : versions) {
-    bool supported = [WebContentsOcclusionCheckerMac manualOcclusionDetectionSupportedForVersion:version.major
-                                                                                                :version.minor];
+    bool supported = [WebContentsOcclusionCheckerMac
+        manualOcclusionDetectionSupportedForPackedVersion:version
+                                                              .packed_version];
     EXPECT_EQ(supported, version.supported);
   }
 }
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.mm b/content/browser/accessibility/browser_accessibility_manager_mac.mm
index 45aeb68..5a67064 100644
--- a/content/browser/accessibility/browser_accessibility_manager_mac.mm
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.mm
@@ -598,34 +598,39 @@
 
 bool BrowserAccessibilityManagerMac::ShouldFireLoadCompleteNotification() {
   // If it's not the top-level document, we shouldn't fire AXLoadComplete.
-  if (!IsRootFrameManager())
+  if (!IsRootFrameManager()) {
     return false;
+  }
 
   // On MacOS 10.15, firing AXLoadComplete causes focus to move to the
   // webpage and read content, despite the "Automatically speak the webpage"
   // checkbox in Voiceover utility being unchecked. The checkbox is
   // unchecked by default in 10.15 so we don't fire AXLoadComplete events to
   // support the default behavior.
-  if (base::mac::IsOS10_15())
+  if (base::mac::MacOSMajorVersion() < 11) {
     return false;
+  }
 
   // Voiceover moves focus to the web content when it receives an
   // AXLoadComplete event. On Chrome's new tab page, focus should stay
   // in the omnibox, so we purposefully do not fire the AXLoadComplete
   // event in this case.
-  if (IsChromeNewTabPage())
+  if (IsChromeNewTabPage()) {
     return false;
+  }
 
   // We also check that the window is focused because VoiceOver responds
   // to this notification by changing focus and possibly reading the entire
   // page contents, sometimes even when the window is minimized or another
   // Chrome window is active/focused.
   id window = GetWindow();
-  if (!window)
+  if (!window) {
     return false;
+  }
 
-  if ([NSApp isActive])
+  if ([NSApp isActive]) {
     return window == [NSApp accessibilityFocusedWindow];
+  }
 
   // TODO(accessibility): We need a solution to the problem described below.
   // If the window is NSAccessibilityRemoteUIElement, there are some challenges:
@@ -652,8 +657,9 @@
   // This may be due to the issues described above, or the fact that one
   // cannot ascend the accessibility tree all the way to the parent window
   // from within the app shim content.
-  if ([window isKindOfClass:[NSAccessibilityRemoteUIElement class]])
+  if ([window isKindOfClass:[NSAccessibilityRemoteUIElement class]]) {
     return true;
+  }
 
   return false;
 }
diff --git a/content/browser/accessibility/browser_accessibility_state_impl_android.cc b/content/browser/accessibility/browser_accessibility_state_impl_android.cc
index 5f90567..66d847f 100644
--- a/content/browser/accessibility/browser_accessibility_state_impl_android.cc
+++ b/content/browser/accessibility/browser_accessibility_state_impl_android.cc
@@ -417,18 +417,6 @@
   native_theme->NotifyOnNativeThemeUpdated();
 }
 
-void BrowserAccessibilityStateImplAndroid::OnContrastLevelChanged(
-    bool highContrastEnabled) {
-  // We need to call into GetInstanceForWeb on the UI thread,
-  // so ensure that we setup the notification on the correct thread.
-  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  ui::NativeTheme* native_theme = ui::NativeTheme::GetInstanceForWeb();
-  native_theme->SetPreferredContrast(
-      highContrastEnabled ? ui::NativeTheme::PreferredContrast::kMore
-                          : ui::NativeTheme::PreferredContrast::kNoPreference);
-  native_theme->NotifyOnNativeThemeUpdated();
-}
-
 void BrowserAccessibilityStateImplAndroid::UpdateHistogramsOnOtherThread() {
   BrowserAccessibilityStateImpl::UpdateHistogramsOnOtherThread();
 
diff --git a/content/browser/accessibility/browser_accessibility_state_impl_android.h b/content/browser/accessibility/browser_accessibility_state_impl_android.h
index 4e1c9e8..d15c31f 100644
--- a/content/browser/accessibility/browser_accessibility_state_impl_android.h
+++ b/content/browser/accessibility/browser_accessibility_state_impl_android.h
@@ -22,7 +22,6 @@
   // ui::AccessibilityState::AccessibilityStateDelegate overrides
   void OnAnimatorDurationScaleChanged() override;
   void OnDisplayInversionEnabledChanged(bool enabled) override;
-  void OnContrastLevelChanged(bool highContrastEnabled) override;
   void RecordAccessibilityServiceInfoHistograms() override;
 
  protected:
diff --git a/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc b/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
index ac5b34e..29e2987 100644
--- a/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
@@ -479,10 +479,11 @@
 // Before macOS 11 aria-description must be exposed in AXHelp, and since macOS
 // 11, it should only be exposed in AXCustomContent.
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityScriptTest, AriaDescription) {
-  if (base::mac::IsAtLeastOS11())
+  if (base::mac::MacOSMajorVersion() >= 11) {
     RunTypedTest<kMacDescription>("aria-description-in-axcustomcontent.html");
-  else
+  } else {
     RunTypedTest<kMacDescription>("aria-description-in-axhelp.html");
+  }
 }
 
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityScriptTest, SelectAllTextarea) {
diff --git a/content/browser/gpu/browser_child_process_backgrounded_bridge_browsertest.mm b/content/browser/gpu/browser_child_process_backgrounded_bridge_browsertest.mm
index 438ff56..0bed502 100644
--- a/content/browser/gpu/browser_child_process_backgrounded_bridge_browsertest.mm
+++ b/content/browser/gpu/browser_child_process_backgrounded_bridge_browsertest.mm
@@ -96,7 +96,7 @@
 
 IN_PROC_BROWSER_TEST_F(BrowserChildProcessBackgroundedBridgeTest,
                        InitiallyForegrounded) {
-  if (base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() >= 13) {
     GTEST_SKIP() << "Flaking on macOS 13: https://crbug.com/1444130";
   }
   // Set the browser process as foregrounded.
diff --git a/content/browser/interest_group/additional_bids_util.cc b/content/browser/interest_group/additional_bids_util.cc
index 71372693..6e08e50 100644
--- a/content/browser/interest_group/additional_bids_util.cc
+++ b/content/browser/interest_group/additional_bids_util.cc
@@ -4,12 +4,18 @@
 
 #include "content/browser/interest_group/additional_bids_util.h"
 
+#include <stdint.h>
+
+#include <array>
 #include <memory>
 #include <string>
+#include <string_view>
 
+#include "base/base64.h"
 #include "base/json/json_writer.h"
 #include "base/strings/strcat.h"
 #include "base/time/time.h"
+#include "base/types/expected.h"
 #include "base/types/optional_ref.h"
 #include "base/uuid.h"
 #include "base/values.h"
@@ -17,10 +23,32 @@
 #include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/interest_group/ad_display_size.h"
+#include "third_party/boringssl/src/include/openssl/curve25519.h"
 #include "url/origin.h"
 
 namespace content {
 
+namespace {
+
+// Returns error string on failure.
+template <size_t N>
+absl::optional<std::string> DecodeBase64Fixed(std::string_view field,
+                                              const std::string& in,
+                                              std::array<uint8_t, N>& out) {
+  std::string decoded;
+  if (!base::Base64Decode(in, &decoded)) {
+    return base::StrCat({"Field '", field, "' is not valid base64."});
+  }
+  if (decoded.size() != N) {
+    return base::StrCat({"Field '", field, "' has unexpected length."});
+  }
+  std::copy(decoded.begin(), decoded.end(), out.data());
+
+  return absl::nullopt;
+}
+
+}  // namespace
+
 AdditionalBidDecodeResult::AdditionalBidDecodeResult() = default;
 AdditionalBidDecodeResult::AdditionalBidDecodeResult(
     AdditionalBidDecodeResult&& other) = default;
@@ -319,4 +347,85 @@
   return result;
 }
 
+SignedAdditionalBid::SignedAdditionalBid() = default;
+SignedAdditionalBid::SignedAdditionalBid(SignedAdditionalBid&& other) = default;
+SignedAdditionalBid::~SignedAdditionalBid() = default;
+
+SignedAdditionalBid& SignedAdditionalBid::operator=(SignedAdditionalBid&&) =
+    default;
+
+std::vector<size_t> SignedAdditionalBid::VerifySignatures() {
+  std::vector<size_t> verified;
+  for (size_t i = 0; i < signatures.size(); ++i) {
+    if (ED25519_verify(
+            reinterpret_cast<const uint8_t*>(additional_bid_json.data()),
+            additional_bid_json.size(), signatures[i].signature.data(),
+            signatures[i].key.data())) {
+      verified.push_back(i);
+    }
+  }
+  return verified;
+}
+
+base::expected<SignedAdditionalBid, std::string> DecodeSignedAdditionalBid(
+    base::Value signed_additional_bid_in) {
+  base::Value::Dict* in_dict = signed_additional_bid_in.GetIfDict();
+  if (!in_dict) {
+    return base::unexpected("Signed additional bid not a dictionary.");
+  }
+
+  SignedAdditionalBid result;
+
+  std::string* bid_json = in_dict->FindString("bid");
+  if (!bid_json) {
+    return base::unexpected(
+        "Signed additional bid missing string 'bid' field.");
+  }
+  result.additional_bid_json = std::move(*bid_json);
+
+  const base::Value::List* signature_list = in_dict->FindList("signatures");
+  if (!signature_list) {
+    return base::unexpected(
+        "Signed additional bid missing list 'signatures' field.");
+  }
+
+  for (const base::Value& sig_entry : *signature_list) {
+    SignedAdditionalBid::Signature decoded_signature;
+
+    const base::Value::Dict* sig_entry_dict = sig_entry.GetIfDict();
+    if (!sig_entry_dict) {
+      return base::unexpected(
+          "Signed additional bid 'signatures' list entry not a dictionary.");
+    }
+    const std::string* key = sig_entry_dict->FindString("key");
+    if (!key) {
+      return base::unexpected(
+          "Signed additional bid 'signatures' list entry missing 'key' "
+          "string.");
+    }
+
+    absl::optional<std::string> maybe_key_error =
+        DecodeBase64Fixed("key", *key, decoded_signature.key);
+    if (maybe_key_error.has_value()) {
+      return base::unexpected(maybe_key_error.value());
+    }
+
+    const std::string* signature = sig_entry_dict->FindString("signature");
+    if (!signature) {
+      return base::unexpected(
+          "Signed additional bid 'signatures' list entry missing 'signature' "
+          "string.");
+    }
+
+    absl::optional<std::string> maybe_signature_error =
+        DecodeBase64Fixed("signature", *signature, decoded_signature.signature);
+    if (maybe_signature_error.has_value()) {
+      return base::unexpected(maybe_signature_error.value());
+    }
+    result.signatures.push_back(std::move(decoded_signature));
+  }
+
+  return result;
+}
+
 }  // namespace content
diff --git a/content/browser/interest_group/additional_bids_util.h b/content/browser/interest_group/additional_bids_util.h
index b747e809..93077bf 100644
--- a/content/browser/interest_group/additional_bids_util.h
+++ b/content/browser/interest_group/additional_bids_util.h
@@ -5,6 +5,9 @@
 #ifndef CONTENT_BROWSER_INTEREST_GROUP_ADDITIONAL_BIDS_UTIL_H_
 #define CONTENT_BROWSER_INTEREST_GROUP_ADDITIONAL_BIDS_UTIL_H_
 
+#include <stdint.h>
+
+#include <array>
 #include <string>
 #include <vector>
 
@@ -63,6 +66,32 @@
                     const url::Origin& seller,
                     base::optional_ref<const url::Origin> top_level_seller);
 
+struct CONTENT_EXPORT SignedAdditionalBid {
+  struct Signature {
+    std::array<uint8_t, 32> key;
+    std::array<uint8_t, 64> signature;
+  };
+
+  SignedAdditionalBid();
+  SignedAdditionalBid(const SignedAdditionalBid& other) = delete;
+  SignedAdditionalBid(SignedAdditionalBid&& other);
+  ~SignedAdditionalBid();
+
+  SignedAdditionalBid& operator=(const SignedAdditionalBid&) = delete;
+  SignedAdditionalBid& operator=(SignedAdditionalBid&&);
+
+  std::string additional_bid_json;
+  std::vector<Signature> signatures;
+
+  // Returns a vector of indices of signatures that succeed in verifying.
+  std::vector<size_t> VerifySignatures();
+};
+
+// Tries to decode a signed additional bid JSON represented as
+// `signed_additional_bid_in`.
+CONTENT_EXPORT base::expected<SignedAdditionalBid, std::string>
+DecodeSignedAdditionalBid(base::Value signed_additional_bid_in);
+
 }  // namespace content
 
 #endif  // CONTENT_BROWSER_INTEREST_GROUP_ADDITIONAL_BIDS_UTIL_H_
diff --git a/content/browser/interest_group/additional_bids_util_unittest.cc b/content/browser/interest_group/additional_bids_util_unittest.cc
index c7e14d1..4bd8ef3b 100644
--- a/content/browser/interest_group/additional_bids_util_unittest.cc
+++ b/content/browser/interest_group/additional_bids_util_unittest.cc
@@ -4,20 +4,62 @@
 
 #include "content/browser/interest_group/additional_bids_util.h"
 
+#include <stdint.h>
+
+#include <array>
 #include <limits>
 #include <string>
 
+#include "base/types/expected.h"
 #include "base/types/optional_ref.h"
 #include "base/uuid.h"
+#include "base/values.h"
+#include "crypto/openssl_util.h"
+#include "testing/gmock/include/gmock/gmock-matchers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/public/common/interest_group/ad_display_size.h"
+#include "third_party/boringssl/src/include/openssl/curve25519.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
+using testing::UnorderedElementsAre;
+
 namespace content {
 namespace {
 
+// Some test data for key/signature fields. These are just random sequences
+// of bytes of the right length.
+const uint8_t kKey1[] =
+    "\xF5\x30\x88\xE9\x9B\xC7\xB0\x2A\x8C\xBE\x11\x8D\xD3\xEC\xEF\xEB\xB5\x71"
+    "\xDF\xF9\x7D\x67\xEF\xFF\x9A\xAD\xE1\x63\x86\xAD\x57\x5E";
+const char kKey1Base64[] = "9TCI6ZvHsCqMvhGN0+zv67Vx3/l9Z+//mq3hY4atV14=";
+
+const uint8_t kKey2[] =
+    "\x79\x34\x0E\x99\xF6\x02\x98\xB2\xF6\x82\xAA\xDA\x3C\x95\xFA\x62\x3A\xF2"
+    "\x53\xA8\x56\xEB\x21\xC4\xC2\x67\x6C\x5D\xE3\x4B\xDA\xA0";
+const char kKey2Base64[] = "eTQOmfYCmLL2gqraPJX6YjryU6hW6yHEwmdsXeNL2qA=";
+
+const uint8_t kSig1[] =
+    "\x49\xD1\x27\x01\x29\x9E\xC8\x34\xE3\x12\x46\xA0\xFA\x17\x33\x1E\xD2\x7B"
+    "\xC0\x63\x7D\x7F\x63\xF6\x12\x49\x39\x40\x80\x2F\x31\x93\x99\xD7\x93\x16"
+    "\x58\x4D\x3B\xEC\x0F\x46\x07\x29\xE4\xE6\x13\x0D\xD7\xEA\x6D\x35\x60\xB8"
+    "\x27\x9E\x86\xC7\xE0\x10\x63\xEA\x44\xE6";
+const char kSig1Base64[] =
+    "SdEnASmeyDTjEkag+hczHtJ7wGN9f2P2Ekk5QIAvMZOZ15MWWE077A9GBynk5hMN1+"
+    "ptNWC4J56Gx+AQY+pE5g==";
+
+const uint8_t kSig2[] =
+    "\x91\x2C\xF4\x82\x8F\x62\x6B\x1F\x4A\x34\x1B\x8C\x4C\xB8\xD6\xA1\x41\xD0"
+    "\xBD\xCC\x67\xBA\xCF\x08\xE4\x32\x09\x5D\x97\x06\x09\x41\xFA\xEA\x12\x8E"
+    "\x49\x05\x73\xE2\xA4\x57\x7B\xA5\x3B\x00\xAE\x23\xAF\x61\xE9\x5F\xA4\x39"
+    "\xBD\x07\x9B\xB7\x49\x31\x52\xDD\x69\xDD";
+const char kSig2Base64[] =
+    "kSz0go9iax9KNBuMTLjWoUHQvcxnus8I5DIJXZcGCUH66hKOSQVz4qRXe6U7AK4jr2HpX6Q5vQ"
+    "ebt0kxUt1p3Q==";
+
+const char kPretendBid[] = "Hi, I am a base64-encoded JSON bid.";
+
 class AdditionalBidsUtilTest : public testing::Test {
  protected:
   base::Value::Dict MakeMinimalValid() {
@@ -53,6 +95,25 @@
     return additional_bid_dict;
   }
 
+  static base::Value::Dict MakeValidSignedBid() {
+    base::Value::Dict signed_dict;
+    signed_dict.Set("bid", kPretendBid);
+
+    base::Value::List sigs_list;
+    base::Value::Dict sig1;
+    sig1.Set("key", kKey1Base64);
+    sig1.Set("signature", kSig1Base64);
+    sigs_list.Append(std::move(sig1));
+
+    base::Value::Dict sig2;
+    sig2.Set("key", kKey2Base64);
+    sig2.Set("signature", kSig2Base64);
+    sigs_list.Append(std::move(sig2));
+
+    signed_dict.Set("signatures", std::move(sigs_list));
+    return signed_dict;
+  }
+
   const base::Uuid kAuctionNonce{base::Uuid::GenerateRandomV4()};
   const url::Origin kSeller = url::Origin::Create(GURL("https://seller.test"));
   const url::Origin kTopSeller =
@@ -823,5 +884,170 @@
       result.error());
 }
 
+TEST_F(AdditionalBidsUtilTest, DecodeBasicSignedBid) {
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_TRUE(result.has_value()) << result.error();
+  EXPECT_EQ(kPretendBid, result->additional_bid_json);
+  ASSERT_EQ(2u, result->signatures.size());
+  ASSERT_EQ(sizeof(kKey1) - 1, result->signatures[0].key.size());
+  ASSERT_EQ(sizeof(kSig1) - 1, result->signatures[0].signature.size());
+  EXPECT_EQ(0,
+            memcmp(kKey1, result->signatures[0].key.data(), sizeof(kKey1) - 1));
+  EXPECT_EQ(0, memcmp(kSig1, result->signatures[0].signature.data(),
+                      sizeof(kSig1) - 1));
+
+  ASSERT_EQ(sizeof(kKey2) - 1, result->signatures[1].key.size());
+  ASSERT_EQ(sizeof(kSig2) - 1, result->signatures[1].signature.size());
+  EXPECT_EQ(0,
+            memcmp(kKey2, result->signatures[1].key.data(), sizeof(kKey2) - 1));
+  EXPECT_EQ(0, memcmp(kSig2, result->signatures[1].signature.data(),
+                      sizeof(kSig2) - 1));
+}
+
+TEST_F(AdditionalBidsUtilTest, SignedNotDict) {
+  auto result = DecodeSignedAdditionalBid(base::Value(10));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ("Signed additional bid not a dictionary.", result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedMissingBid) {
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  signed_bid_dict.Remove("bid");
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ("Signed additional bid missing string 'bid' field.",
+            result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedMissingSignatures) {
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  signed_bid_dict.Remove("signatures");
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ("Signed additional bid missing list 'signatures' field.",
+            result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedInvalidSignatures) {
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  signed_bid_dict.FindList("signatures")->Append(40);
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ("Signed additional bid 'signatures' list entry not a dictionary.",
+            result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedMissingSignatureKey) {
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  (*signed_bid_dict.FindList("signatures"))[0].GetDict().Remove("key");
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ(
+      "Signed additional bid 'signatures' list entry missing 'key' string.",
+      result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedInvalidSignatureKey) {
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  (*signed_bid_dict.FindList("signatures"))[0].GetDict().Set("key", "$$$");
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ("Field 'key' is not valid base64.", result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedInvalidSignatureKeyLength) {
+  const char kLength31[] = "r7J39NbxqA5AvGD57ENOYdOvxzHPwA6KoehNIFCjDw==";
+
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  (*signed_bid_dict.FindList("signatures"))[0].GetDict().Set("key", kLength31);
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ("Field 'key' has unexpected length.", result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedMissingSignatureSig) {
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  (*signed_bid_dict.FindList("signatures"))[0].GetDict().Remove("signature");
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ(
+      "Signed additional bid 'signatures' list entry missing 'signature' "
+      "string.",
+      result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedInvalidSignatureSig) {
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  (*signed_bid_dict.FindList("signatures"))[0].GetDict().Set("signature",
+                                                             "$$$");
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ("Field 'signature' is not valid base64.", result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, DecodeSignedInvalidSignatureSigLength) {
+  const char kLength65[] =
+      "rq9Nm5seElZB7vH9u8o6Cjt4v72LkPKGVKVl6k4uOlmV8Y7n023fmOk47R2bPRNYx/"
+      "EzpBSXdJainpItZwK5DTI=";
+
+  base::Value::Dict signed_bid_dict = MakeValidSignedBid();
+  (*signed_bid_dict.FindList("signatures"))[0].GetDict().Set("signature",
+                                                             kLength65);
+  auto result =
+      DecodeSignedAdditionalBid(base::Value(std::move(signed_bid_dict)));
+  ASSERT_FALSE(result.has_value());
+  EXPECT_EQ("Field 'signature' has unexpected length.", result.error());
+}
+
+TEST_F(AdditionalBidsUtilTest, VerifySignature) {
+  const int kKeys = 4;
+
+  crypto::EnsureOpenSSLInit();
+
+  struct {
+    uint8_t public_key[32];
+    uint8_t private_key[64];
+  } key_pairs[kKeys];
+
+  SignedAdditionalBid data;
+  data.additional_bid_json = "Greetings. I am a base-64 encoded JSON!";
+  for (int i = 0; i < kKeys; ++i) {
+    ED25519_keypair(key_pairs[i].public_key, key_pairs[i].private_key);
+
+    data.signatures.emplace_back();
+    memcpy(data.signatures[i].key.data(), key_pairs[i].public_key, 32);
+
+    bool ok = ED25519_sign(
+        data.signatures[i].signature.data(),
+        reinterpret_cast<const uint8_t*>(data.additional_bid_json.data()),
+        data.additional_bid_json.size(), key_pairs[i].private_key);
+    CHECK(ok);
+  }
+
+  EXPECT_THAT(data.VerifySignatures(), UnorderedElementsAre(0, 1, 2, 3));
+
+  // Flip a bit in the [1] signature.
+  data.signatures[1].signature[3] ^= 0x02;
+  EXPECT_THAT(data.VerifySignatures(), UnorderedElementsAre(0, 2, 3));
+
+  // Flip a couple of bits in the [2] key.
+  data.signatures[2].key[7] ^= 0x41;
+  EXPECT_THAT(data.VerifySignatures(), UnorderedElementsAre(0, 3));
+
+  // Change the payload.
+  data.additional_bid_json += "Boo. Bad unverified data appended!";
+  EXPECT_THAT(data.VerifySignatures(), UnorderedElementsAre());
+}
+
 }  // namespace
 }  // namespace content
diff --git a/content/browser/interest_group/interest_group_browsertest.cc b/content/browser/interest_group/interest_group_browsertest.cc
index e8c2b747..a140f9d 100644
--- a/content/browser/interest_group/interest_group_browsertest.cc
+++ b/content/browser/interest_group/interest_group_browsertest.cc
@@ -4478,7 +4478,7 @@
       RunAuctionAndWait(JsReplace(R"({
       seller: $1,
       decisionLogicURL: $2,
-      trustedScoringSignalsUrl: 'https://invalid^&'
+      trustedScoringSignalsURL: 'https://invalid^&'
   })",
                                   origin, url)));
   WaitForAccessObserved({});
@@ -4542,7 +4542,7 @@
       RunAuctionAndWait(R"({
     seller: "https://a.test/",
     decisionLogicURL: "https://a.test/foo",
-    trustedScoringSignalsUrl: "https://b.test/foo",
+    trustedScoringSignalsURL: "https://b.test/foo",
     interestGroupBuyers: ["https://c.test/"],
                         })"));
   WaitForAccessObserved({});
@@ -8540,7 +8540,7 @@
 {
   seller: $1,
   decisionLogicURL: $2,
-  trustedScoringSignalsUrl: $3,
+  trustedScoringSignalsURL: $3,
   interestGroupBuyers: [$4],
 }
                   )",
@@ -10349,7 +10349,7 @@
   return await navigator.runAdAuction({
     seller: $1,
     decisionLogicURL: $2,
-    trustedScoringSignalsUrl: $3,
+    trustedScoringSignalsURL: $3,
     interestGroupBuyers: [$4, $5],
     auctionSignals: {so: 'I', hear: ['you', 'like', 'json']},
     sellerSignals: {signals: 'from', the: ['seller']},
@@ -10499,7 +10499,7 @@
   return await navigator.runAdAuction({
     seller: $1,
     decisionLogicURL: $2,
-    trustedScoringSignalsUrl: $3,
+    trustedScoringSignalsURL: $3,
     interestGroupBuyers: [$4, $5],
     auctionSignals: {so: 'I', hear: ['you', 'like', 'json']},
     sellerSignals: {signals: 'from', the: ['seller']},
@@ -12506,7 +12506,7 @@
   const char kAuctionConfigTemplate[] = R"({
     seller: $1,
     decisionLogicURL: $2,
-    trustedScoringSignalsUrl: $3,
+    trustedScoringSignalsURL: $3,
     interestGroupBuyers: [$4],
     sellerExperimentGroupId: 8349,
     perBuyerExperimentGroupIds: {'*': 3498},
diff --git a/content/browser/preloading/prefetch/prefetch_container.cc b/content/browser/preloading/prefetch/prefetch_container.cc
index 727f99ac..b071485 100644
--- a/content/browser/preloading/prefetch/prefetch_container.cc
+++ b/content/browser/preloading/prefetch/prefetch_container.cc
@@ -850,15 +850,13 @@
 }
 
 bool PrefetchContainer::ShouldBlockUntilHeadReceived() const {
-  // Can only block until head if the request has been started using a streaming
-  // URL loader...
-  if (streaming_loaders_.empty() || GetLastStreamingURLLoader()->Failed()) {
+  // Can only block until head if the request has been started using a
+  // streaming URL loader and head/failure/redirect hasn't been received yet.
+  if (streaming_loaders_.empty() || redirect_chain_.empty() ||
+      !redirect_chain_.back()->response_reader_->IsWaitingForResponse()) {
     return false;
   }
-  // ... and head hasn't been received yet.
-  if (GetNonRedirectResponseReader()) {
-    return false;
-  }
+
   return PrefetchShouldBlockUntilHead(prefetch_type_.GetEagerness());
 }
 
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index 1318340..32e3642 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -762,8 +762,7 @@
   if (!eligible && !prefetch_container->IsDecoy()) {
     active_prefetches_.erase(prefetch_container->GetPrefetchContainerKey());
     prefetch_container->GetLastStreamingURLLoader()->HandleRedirect(
-        PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect, redirect_info,
-        std::move(redirect_head));
+        PrefetchRedirectStatus::kFail, redirect_info, std::move(redirect_head));
     prefetch_container->ResetAllStreamingURLLoaders();
 
     Prefetch();
@@ -779,9 +778,8 @@
       prefetch_container
           ->IsIsolatedNetworkContextRequiredForPreviousRedirectHop()) {
     prefetch_container->GetLastStreamingURLLoader()->HandleRedirect(
-        PrefetchStreamingURLLoaderStatus::
-            kStopSwitchInNetworkContextForRedirect,
-        redirect_info, std::move(redirect_head));
+        PrefetchRedirectStatus::kSwitchNetworkContext, redirect_info,
+        std::move(redirect_head));
     // The new ResponseReader is associated with the new streaming URL loader at
     // the PrefetchStreamingURLLoader constructor.
     MakePrefetchRequest(prefetch_container, redirect_info.new_url);
@@ -791,8 +789,7 @@
 
   // Otherwise, follow the redirect in the same streaming URL loader.
   prefetch_container->GetLastStreamingURLLoader()->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFollowRedirect, redirect_info,
-      std::move(redirect_head));
+      PrefetchRedirectStatus::kFollow, redirect_info, std::move(redirect_head));
   // Associate the new ResponseReader with the current streaming URL loader.
   prefetch_container->GetLastStreamingURLLoader()->SetResponseReader(
       prefetch_container->GetResponseReaderForCurrentPrefetch());
@@ -1211,8 +1208,7 @@
     prefetch_container->SetPrefetchStatus(
         PrefetchStatus::kPrefetchFailedInvalidRedirect);
     prefetch_container->GetLastStreamingURLLoader()->HandleRedirect(
-        PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect, redirect_info,
-        std::move(redirect_head));
+        PrefetchRedirectStatus::kFail, redirect_info, std::move(redirect_head));
     prefetch_container->ResetAllStreamingURLLoaders();
 
     Prefetch();
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
index fb3b218..b64a667 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.cc
@@ -55,10 +55,7 @@
   }
 }
 
-PrefetchStreamingURLLoader::~PrefetchStreamingURLLoader() {
-  base::UmaHistogramEnumeration(
-      "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus", status_);
-}
+PrefetchStreamingURLLoader::~PrefetchStreamingURLLoader() = default;
 
 void PrefetchStreamingURLLoader::SetResponseReader(
     base::WeakPtr<PrefetchResponseReader> response_reader) {
@@ -92,33 +89,17 @@
                           response_complete_time_.value() + cacheable_duration);
 }
 
-bool PrefetchStreamingURLLoader::Failed() const {
-  switch (status_) {
-    case PrefetchStreamingURLLoaderStatus::kWaitingOnHead:
-    case PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion:
-    case PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy:
-    case PrefetchStreamingURLLoaderStatus::kFollowRedirect:
-    case PrefetchStreamingURLLoaderStatus::
-        kStopSwitchInNetworkContextForRedirect:
-    case PrefetchStreamingURLLoaderStatus::
-        kServedSwitchInNetworkContextForRedirect:
+bool PrefetchResponseReader::IsWaitingForResponse() const {
+  switch (load_state_) {
+    case LoadState::kStarted:
+      return true;
+
+    case LoadState::kResponseReceived:
+    case LoadState::kRedirectHandled:
+    case LoadState::kCompleted:
+    case LoadState::kFailedResponseReceived:
+    case LoadState::kFailed:
       return false;
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHead:
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHeaders:
-    case PrefetchStreamingURLLoaderStatus::kFailedNon2XX:
-    case PrefetchStreamingURLLoaderStatus::kFailedMIMENotSupported:
-    case PrefetchStreamingURLLoaderStatus::kFailedNetError:
-    case PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed:
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect:
-      return true;
-    case PrefetchStreamingURLLoaderStatus::kRedirected_DEPRECATED:
-    case PrefetchStreamingURLLoaderStatus::
-        kPauseRedirectForEligibilityCheck_DEPRECATED:
-      NOTREACHED();
-      return true;
   }
 }
 
@@ -169,52 +150,14 @@
 
   // Checks head to determine if the prefetch can be served.
   DCHECK(on_prefetch_response_started_callback_);
-  status_ = std::move(on_prefetch_response_started_callback_).Run(head.get());
-
-  bool servable = false;
-  switch (status_) {
-    case PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody:
-      servable = true;
-      break;
-    case PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy:
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHead:
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHeaders:
-    case PrefetchStreamingURLLoaderStatus::kFailedNon2XX:
-    case PrefetchStreamingURLLoaderStatus::kFailedMIMENotSupported:
-      break;
-    case PrefetchStreamingURLLoaderStatus::kWaitingOnHead:
-    case PrefetchStreamingURLLoaderStatus::kRedirected_DEPRECATED:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion:
-    case PrefetchStreamingURLLoaderStatus::kFailedNetError:
-    case PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed:
-    case PrefetchStreamingURLLoaderStatus::kFollowRedirect:
-    case PrefetchStreamingURLLoaderStatus::
-        kPauseRedirectForEligibilityCheck_DEPRECATED:
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect:
-    case PrefetchStreamingURLLoaderStatus::
-        kStopSwitchInNetworkContextForRedirect:
-    case PrefetchStreamingURLLoaderStatus::
-        kServedSwitchInNetworkContextForRedirect:
-      NOTREACHED();
-      break;
-  }
-
-  if (servable) {
-    head->navigation_delivery_type =
-        network::mojom::NavigationDeliveryType::kNavigationalPrefetch;
-  } else {
-    // Discard `body` for non-servable cases, to keep the existing behavior and
-    // also because `body` is not used.
-    body.reset();
-  }
+  PrefetchStreamingURLLoaderStatus status =
+      std::move(on_prefetch_response_started_callback_).Run(head.get());
 
   // `head` and `body` are discarded if `response_reader_` is `nullptr`, because
   // it means the `PrefetchResponseReader` is deleted and thus we no longer
   // serve the prefetched result.
   if (response_reader_) {
-    response_reader_->OnReceiveResponse(servable, std::move(head),
+    response_reader_->OnReceiveResponse(status, std::move(head),
                                         std::move(body));
   }
 
@@ -231,68 +174,42 @@
 }
 
 void PrefetchStreamingURLLoader::HandleRedirect(
-    PrefetchStreamingURLLoaderStatus new_status,
+    PrefetchRedirectStatus redirect_status,
     const net::RedirectInfo& redirect_info,
     network::mojom::URLResponseHeadPtr redirect_head) {
   DCHECK(redirect_head);
 
   // If the prefetch_url_loader_ is no longer connected, mark this as failed.
   if (!prefetch_url_loader_) {
-    new_status = PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect;
+    redirect_status = PrefetchRedirectStatus::kFail;
   }
 
-  status_ = new_status;
-  switch (status_) {
-    case PrefetchStreamingURLLoaderStatus::kFollowRedirect:
+  switch (redirect_status) {
+    case PrefetchRedirectStatus::kFollow:
       DCHECK(prefetch_url_loader_);
       prefetch_url_loader_->FollowRedirect(
           /*removed_headers=*/std::vector<std::string>(),
           /*modified_headers=*/net::HttpRequestHeaders(),
           /*modified_cors_exempt_headers=*/net::HttpRequestHeaders(),
           /*new_url=*/absl::nullopt);
-
-      if (response_reader_) {
-        response_reader_->HandleRedirect(redirect_info,
-                                         std::move(redirect_head));
-      }
       break;
-    case PrefetchStreamingURLLoaderStatus::
-        kStopSwitchInNetworkContextForRedirect:
+    case PrefetchRedirectStatus::kSwitchNetworkContext:
       // The redirect requires a switch in network context, so the redirect will
       // be followed using a separate PrefetchStreamingURLLoader, and this url
       // loader will stop its request.
       DisconnectPrefetchURLLoaderMojo();
       timeout_timer_.AbandonAndStop();
-
-      if (response_reader_) {
-        response_reader_->HandleRedirect(redirect_info,
-                                         std::move(redirect_head));
-      }
       break;
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect:
+    case PrefetchRedirectStatus::kFail:
       if (on_received_head_callback_) {
         std::move(on_received_head_callback_).Run();
       }
       break;
-    case PrefetchStreamingURLLoaderStatus::kWaitingOnHead:
-    case PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody:
-    case PrefetchStreamingURLLoaderStatus::kRedirected_DEPRECATED:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion:
-    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion:
-    case PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy:
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHead:
-    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHeaders:
-    case PrefetchStreamingURLLoaderStatus::kFailedNon2XX:
-    case PrefetchStreamingURLLoaderStatus::kFailedMIMENotSupported:
-    case PrefetchStreamingURLLoaderStatus::kFailedNetError:
-    case PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed:
-    case PrefetchStreamingURLLoaderStatus::
-        kPauseRedirectForEligibilityCheck_DEPRECATED:
-    case PrefetchStreamingURLLoaderStatus::
-        kServedSwitchInNetworkContextForRedirect:
-      NOTREACHED();
-      break;
+  }
+
+  if (response_reader_) {
+    response_reader_->HandleRedirect(redirect_status, redirect_info,
+                                     std::move(redirect_head));
   }
 }
 
@@ -316,17 +233,6 @@
   DisconnectPrefetchURLLoaderMojo();
   timeout_timer_.AbandonAndStop();
 
-  if (status_ == PrefetchStreamingURLLoaderStatus::kWaitingOnHead ||
-      status_ == PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody) {
-    status_ = completion_status.error_code == net::OK
-                  ? PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed
-                  : PrefetchStreamingURLLoaderStatus::kFailedNetError;
-  } else if (status_ == PrefetchStreamingURLLoaderStatus::
-                            kSuccessfulServedBeforeCompletion &&
-             completion_status.error_code != net::OK) {
-    status_ = PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed;
-  }
-
   if (response_reader_) {
     response_reader_->OnComplete(completion_status);
   }
@@ -345,19 +251,6 @@
 void PrefetchStreamingURLLoader::OnStartServing() {
   // Once the prefetch is served, stop the timeout timer.
   timeout_timer_.AbandonAndStop();
-
-  if (status_ == PrefetchStreamingURLLoaderStatus::
-                     kStopSwitchInNetworkContextForRedirect) {
-    status_ = PrefetchStreamingURLLoaderStatus::
-        kServedSwitchInNetworkContextForRedirect;
-  } else if (response_reader_ &&
-             response_reader_->GetCompletionStatus().has_value()) {
-    status_ =
-        PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion;
-  } else {
-    status_ =
-        PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion;
-  }
 }
 
 void PrefetchStreamingURLLoader::SetPriority(net::RequestPriority priority,
@@ -381,7 +274,13 @@
 
 PrefetchResponseReader::PrefetchResponseReader() = default;
 
-PrefetchResponseReader::~PrefetchResponseReader() = default;
+PrefetchResponseReader::~PrefetchResponseReader() {
+  if (should_record_metrics_) {
+    base::UmaHistogramEnumeration(
+        "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
+        GetStatusForRecording());
+  }
+}
 
 void PrefetchResponseReader::SetStreamingURLLoader(
     base::WeakPtr<PrefetchStreamingURLLoader> streaming_url_loader) {
@@ -427,6 +326,12 @@
   DCHECK(!self_pointer_);
   self_pointer_ = base::WrapRefCounted(this);
 
+  if (load_state_ == LoadState::kCompleted) {
+    served_after_completion_ = true;
+  } else {
+    served_before_completion_ = true;
+  }
+
   serving_url_loader_receiver_.Bind(std::move(receiver));
   serving_url_loader_receiver_.set_disconnect_handler(
       base::BindOnce(&PrefetchResponseReader::OnServingURLLoaderMojoDisconnect,
@@ -529,10 +434,30 @@
 }
 
 void PrefetchResponseReader::HandleRedirect(
+    PrefetchRedirectStatus redirect_status,
     const net::RedirectInfo& redirect_info,
     network::mojom::URLResponseHeadPtr redirect_head) {
   CHECK_EQ(load_state_, LoadState::kStarted);
-  load_state_ = LoadState::kRedirectHandled;
+
+  switch (redirect_status) {
+    case PrefetchRedirectStatus::kFollow:
+      load_state_ = LoadState::kRedirectHandled;
+      // To record only one UMA per `PrefetchStreamingURLLoader`, skip UMA
+      // recording if `this` is not the last `PrefetchResponseReader` of a
+      // `PrefetchStreamingURLLoader`. This is to keep the existing behavior.
+      should_record_metrics_ = false;
+      break;
+    case PrefetchRedirectStatus::kSwitchNetworkContext:
+      load_state_ = LoadState::kRedirectHandled;
+      break;
+
+    case PrefetchRedirectStatus::kFail:
+      load_state_ = LoadState::kFailed;
+      failure_reason_ =
+          PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect;
+      // Do not add to the event queue on failure.
+      return;
+  }
 
   DCHECK(event_queue_status_ == EventQueueStatus::kNotStarted);
   AddEventToQueue(base::BindOnce(&PrefetchResponseReader::ForwardRedirect,
@@ -541,19 +466,51 @@
 }
 
 void PrefetchResponseReader::OnReceiveResponse(
-    bool servable,
+    PrefetchStreamingURLLoaderStatus status,
     network::mojom::URLResponseHeadPtr head,
     mojo::ScopedDataPipeConsumerHandle body) {
   CHECK_EQ(load_state_, LoadState::kStarted);
-  if (servable) {
-    load_state_ = LoadState::kResponseReceived;
-  } else {
-    load_state_ = LoadState::kFailedResponseReceived;
-  }
+  CHECK_EQ(event_queue_status_, EventQueueStatus::kNotStarted);
+  CHECK(!head_);
+  CHECK(head);
 
-  DCHECK(event_queue_status_ == EventQueueStatus::kNotStarted);
-  DCHECK(!head_);
-  DCHECK(head);
+  switch (status) {
+    case PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody:
+      load_state_ = LoadState::kResponseReceived;
+      head->navigation_delivery_type =
+          network::mojom::NavigationDeliveryType::kNavigationalPrefetch;
+      break;
+
+    case PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy:
+    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHead:
+    case PrefetchStreamingURLLoaderStatus::kFailedInvalidHeaders:
+    case PrefetchStreamingURLLoaderStatus::kFailedNon2XX:
+    case PrefetchStreamingURLLoaderStatus::kFailedMIMENotSupported:
+      load_state_ = LoadState::kFailedResponseReceived;
+      failure_reason_ = status;
+      // Discard `body` for non-servable cases, to keep the existing behavior
+      // and also because `body` is not used.
+      body.reset();
+      break;
+
+    case PrefetchStreamingURLLoaderStatus::kWaitingOnHead:
+    case PrefetchStreamingURLLoaderStatus::kRedirected_DEPRECATED:
+    case PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed:
+    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedAfterCompletion:
+    case PrefetchStreamingURLLoaderStatus::kSuccessfulServedBeforeCompletion:
+    case PrefetchStreamingURLLoaderStatus::kFailedNetError:
+    case PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed:
+    case PrefetchStreamingURLLoaderStatus::kFollowRedirect_DEPRECATED:
+    case PrefetchStreamingURLLoaderStatus::
+        kPauseRedirectForEligibilityCheck_DEPRECATED:
+    case PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect:
+    case PrefetchStreamingURLLoaderStatus::
+        kStopSwitchInNetworkContextForRedirect:
+    case PrefetchStreamingURLLoaderStatus::
+        kServedSwitchInNetworkContextForRedirect:
+      NOTREACHED();
+      break;
+  }
 
   head_ = std::move(head);
   AddEventToQueue(base::BindOnce(&PrefetchResponseReader::ForwardResponse,
@@ -628,4 +585,58 @@
   }
 }
 
+PrefetchStreamingURLLoaderStatus PrefetchResponseReader::GetStatusForRecording()
+    const {
+  switch (load_state_) {
+    case LoadState::kStarted:
+      return PrefetchStreamingURLLoaderStatus::kWaitingOnHead;
+
+    case LoadState::kRedirectHandled:
+      if (served_before_completion_) {
+        return PrefetchStreamingURLLoaderStatus::
+            kServedSwitchInNetworkContextForRedirect;
+      } else {
+        return PrefetchStreamingURLLoaderStatus::
+            kStopSwitchInNetworkContextForRedirect;
+      }
+
+    case LoadState::kResponseReceived:
+      return PrefetchStreamingURLLoaderStatus::kHeadReceivedWaitingOnBody;
+
+    case LoadState::kCompleted:
+      if (served_before_completion_) {
+        return PrefetchStreamingURLLoaderStatus::
+            kSuccessfulServedBeforeCompletion;
+      } else if (served_after_completion_) {
+        return PrefetchStreamingURLLoaderStatus::
+            kSuccessfulServedAfterCompletion;
+      } else {
+        return PrefetchStreamingURLLoaderStatus::kSuccessfulNotServed;
+      }
+
+    case LoadState::kFailedResponseReceived:
+    case LoadState::kFailed:
+      if (failure_reason_) {
+        // Only certain enum values can be set here.
+        switch (*failure_reason_) {
+          case PrefetchStreamingURLLoaderStatus::kPrefetchWasDecoy:
+          case PrefetchStreamingURLLoaderStatus::kFailedInvalidHead:
+          case PrefetchStreamingURLLoaderStatus::kFailedInvalidHeaders:
+          case PrefetchStreamingURLLoaderStatus::kFailedNon2XX:
+          case PrefetchStreamingURLLoaderStatus::kFailedMIMENotSupported:
+          case PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect:
+            break;
+          default:
+            NOTREACHED();
+            break;
+        }
+        return *failure_reason_;
+      } else if (served_before_completion_) {
+        return PrefetchStreamingURLLoaderStatus::kFailedNetErrorButServed;
+      } else {
+        return PrefetchStreamingURLLoaderStatus::kFailedNetError;
+      }
+  }
+}
+
 }  // namespace content
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
index 02160d9..d6e8263 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader.h
@@ -52,10 +52,11 @@
   // via `AddEventToQueue()`) from the methods with the same names in
   // `PrefetchStreamingURLLoader`.
   void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints);
-  void OnReceiveResponse(bool servable,
+  void OnReceiveResponse(PrefetchStreamingURLLoaderStatus status,
                          network::mojom::URLResponseHeadPtr head,
                          mojo::ScopedDataPipeConsumerHandle body);
-  void HandleRedirect(const net::RedirectInfo& redirect_info,
+  void HandleRedirect(PrefetchRedirectStatus redirect_status,
+                      const net::RedirectInfo& redirect_info,
                       network::mojom::URLResponseHeadPtr redirect_head);
   void OnTransferSizeUpdated(int32_t transfer_size_diff);
   void OnComplete(network::URLLoaderCompletionStatus completion_status);
@@ -69,6 +70,7 @@
   RequestHandler CreateRequestHandler();
 
   bool Servable(base::TimeDelta cacheable_duration) const;
+  bool IsWaitingForResponse() const;
   absl::optional<network::URLLoaderCompletionStatus> GetCompletionStatus()
       const {
     return completion_status_;
@@ -117,6 +119,8 @@
 
   void OnServingURLLoaderMojoDisconnect();
 
+  PrefetchStreamingURLLoaderStatus GetStatusForRecording() const;
+
   // The URL Loader events that occur before serving the prefetch are queued up
   // until the prefetch is served.
   std::vector<base::OnceClosure> event_queue_;
@@ -165,6 +169,12 @@
 
   LoadState load_state_{LoadState::kStarted};
 
+  // Used for UMA recording.
+  absl::optional<PrefetchStreamingURLLoaderStatus> failure_reason_;
+  bool served_before_completion_{false};
+  bool served_after_completion_{false};
+  bool should_record_metrics_{true};
+
   // The prefetched data and metadata. Not set for a redirect response.
   network::mojom::URLResponseHeadPtr head_;
   absl::optional<network::URLLoaderCompletionStatus> completion_status_;
@@ -239,12 +249,10 @@
   //   network context.
   // - |kFailedInvalidRedirect|, if the redirect should not be followed by
   //   |this|.
-  void HandleRedirect(PrefetchStreamingURLLoaderStatus new_status,
+  void HandleRedirect(PrefetchRedirectStatus redirect_status,
                       const net::RedirectInfo& redirect_info,
                       network::mojom::URLResponseHeadPtr redirect_head);
 
-  bool Failed() const;
-
   void MakeSelfOwned(std::unique_ptr<PrefetchStreamingURLLoader> self);
   void PostTaskToDeleteSelf();
   void PostTaskToDeleteSelfIfDisconnected();
@@ -281,11 +289,6 @@
   // Set when this manages its own lifetime.
   std::unique_ptr<PrefetchStreamingURLLoader> self_pointer_;
 
-  // Status of the URL loader. This recorded to UMA when the URL loader is
-  // deleted.
-  PrefetchStreamingURLLoaderStatus status_{
-      PrefetchStreamingURLLoaderStatus::kWaitingOnHead};
-
   // The timer that triggers a timeout when a request takes too long.
   base::OneShotTimer timeout_timer_;
 
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.h b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.h
index 6e68e4f..53971ea 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.h
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_status.h
@@ -33,7 +33,7 @@
   kFailedNetErrorButServed = 12,
 
   // Statuses related to redirects.
-  kFollowRedirect = 13,
+  kFollowRedirect_DEPRECATED = 13,
   kPauseRedirectForEligibilityCheck_DEPRECATED = 14,
   kFailedInvalidRedirect = 15,
   kStopSwitchInNetworkContextForRedirect = 16,
@@ -45,4 +45,6 @@
   kMaxValue = kServedSwitchInNetworkContextForRedirect,
 };
 
+enum class PrefetchRedirectStatus { kFollow, kFail, kSwitchNetworkContext };
+
 #endif  // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_STREAMING_URL_LOADER_STATUS_H_
diff --git a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
index 662658c..4c8b119 100644
--- a/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
+++ b/content/browser/preloading/prefetch/prefetch_streaming_url_loader_unittest.cc
@@ -486,6 +486,7 @@
   on_response_complete_loop.Run();
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
@@ -542,6 +543,7 @@
   EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max()));
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
@@ -608,6 +610,7 @@
   EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max()));
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
@@ -662,6 +665,7 @@
   EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max()));
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
@@ -845,9 +849,8 @@
                                               net::HTTP_PERMANENT_REDIRECT);
   on_receive_redirect_loop.Run();
 
-  streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFollowRedirect, redirect_info,
-      std::move(redirect_head));
+  streaming_loader->HandleRedirect(PrefetchRedirectStatus::kFollow,
+                                   redirect_info, std::move(redirect_head));
   on_follow_redirect_loop.Run();
 
   // Switch to a new ResponseReader.
@@ -997,9 +1000,8 @@
                                               net::HTTP_PERMANENT_REDIRECT);
   on_receive_redirect_loop.Run();
 
-  streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFailedInvalidRedirect, redirect_info,
-      std::move(redirect_head));
+  streaming_loader->HandleRedirect(PrefetchRedirectStatus::kFail, redirect_info,
+                                   std::move(redirect_head));
   if (GetParam()) {
     on_head_received_loop.Run();
   }
@@ -1007,6 +1009,7 @@
   EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max()));
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
@@ -1061,8 +1064,8 @@
   // context. When this happens the streaming_loader will stop the fetch, and a
   // new streaming URL loader would start to fetch the redirect URL.
   streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kStopSwitchInNetworkContextForRedirect,
-      redirect_info, std::move(redirect_head));
+      PrefetchRedirectStatus::kSwitchNetworkContext, redirect_info,
+      std::move(redirect_head));
 
   task_environment()->RunUntilIdle();
   EXPECT_FALSE(test_url_loader_factory()->IsURLLoaderClientConnected());
@@ -1162,9 +1165,8 @@
   test_url_loader_factory()->DisconnectMojoPipes();
   task_environment()->RunUntilIdle();
 
-  streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFollowRedirect, redirect_info,
-      std::move(redirect_head));
+  streaming_loader->HandleRedirect(PrefetchRedirectStatus::kFollow,
+                                   redirect_info, std::move(redirect_head));
   if (GetParam()) {
     on_head_received_loop.Run();
   }
@@ -1174,6 +1176,7 @@
   EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max()));
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
@@ -1237,6 +1240,7 @@
   on_response_complete_loop.Run();
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
@@ -1291,6 +1295,7 @@
   EXPECT_FALSE(response_reader->Servable(base::TimeDelta::Max()));
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
@@ -1470,6 +1475,7 @@
   EXPECT_TRUE(response_reader->Servable(base::Seconds(5)));
 
   streaming_loader.reset();
+  response_reader.reset();
 
   histogram_tester.ExpectUniqueSample(
       "PrefetchProxy.Prefetch.StreamingURLLoaderFinalStatus",
diff --git a/content/browser/preloading/prefetch/prefetch_test_utils.cc b/content/browser/preloading/prefetch/prefetch_test_utils.cc
index 248bcfd..82b9480 100644
--- a/content/browser/preloading/prefetch/prefetch_test_utils.cc
+++ b/content/browser/preloading/prefetch/prefetch_test_utils.cc
@@ -193,8 +193,7 @@
 
   DCHECK(weak_streaming_loader);
   weak_streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kFollowRedirect, redirect_info,
-      std::move(redirect_head));
+      PrefetchRedirectStatus::kFollow, redirect_info, std::move(redirect_head));
 
   // GetResponseReaderForCurrentPrefetch() now points to a new ResponseReader
   // after `AddRedirectHop()` above.
@@ -270,8 +269,8 @@
 
   DCHECK(weak_first_streaming_loader);
   weak_first_streaming_loader->HandleRedirect(
-      PrefetchStreamingURLLoaderStatus::kStopSwitchInNetworkContextForRedirect,
-      redirect_info, std::move(redirect_head));
+      PrefetchRedirectStatus::kSwitchNetworkContext, redirect_info,
+      std::move(redirect_head));
 
   std::unique_ptr<network::ResourceRequest> redirect_request =
       std::make_unique<network::ResourceRequest>();
diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc
index 534ec05..eeab0fc1 100644
--- a/content/services/auction_worklet/seller_worklet.cc
+++ b/content/services/auction_worklet/seller_worklet.cc
@@ -278,7 +278,7 @@
 // {
 //  'seller': 'https://www.example-ssp.com/',
 //  'decisionLogicURL': 'https://www.example-ssp.com/seller.js',
-//  'trustedScoringSignalsUrl': ...,
+//  'trustedScoringSignalsURL': ...,
 //  'interestGroupBuyers': ['https://www.example-dsp.com', 'https://buyer2.com',
 //  ...], 'auctionSignals': {...}, 'sellerSignals': {...}, 'sellerTimeout': 100,
 //  'perBuyerSignals': {'https://www.example-dsp.com': {...},
diff --git a/device/fido/features.cc b/device/fido/features.cc
index 15405a16..265a40dc 100644
--- a/device/fido/features.cc
+++ b/device/fido/features.cc
@@ -90,10 +90,10 @@
              "WebAuthenticationRequireEasyAccessorFieldsInJSON",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// Not yet enabled by default.
+// Enabled in M118. Remove in or after M121.
 BASE_FEATURE(kWebAuthnRequireUpToDateJSONForRemoteDesktop,
              "WebAuthenticationRequireUpToDateJSONForRemoteDesktop",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enabled in M118. Remove in or after M121.
 BASE_FEATURE(kWebAuthnICloudKeychain,
diff --git a/device/udev_linux/udev_loader.cc b/device/udev_linux/udev_loader.cc
index 3c48ef5..bb37ecaa 100644
--- a/device/udev_linux/udev_loader.cc
+++ b/device/udev_linux/udev_loader.cc
@@ -7,6 +7,8 @@
 #include <memory>
 
 #include "base/check.h"
+#include "base/no_destructor.h"
+#include "base/synchronization/lock.h"
 #include "device/udev_linux/udev0_loader.h"
 #include "device/udev_linux/udev1_loader.h"
 
@@ -14,12 +16,21 @@
 
 namespace {
 
-UdevLoader* g_udev_loader = NULL;
+UdevLoader* g_udev_loader = nullptr;
+
+// Provides a lock to synchronize initializing and accessing `g_udev_loader`
+// across threads.
+base::Lock& GetLock() {
+  static base::NoDestructor<base::Lock> lock;
+  return *lock;
+}
 
 }  // namespace
 
 // static
 UdevLoader* UdevLoader::Get() {
+  base::AutoLock guard(GetLock());
+
   if (g_udev_loader)
     return g_udev_loader;
 
@@ -40,6 +51,8 @@
 
 // static
 void UdevLoader::SetForTesting(UdevLoader* loader, bool delete_previous) {
+  base::AutoLock guard(GetLock());
+
   if (g_udev_loader && delete_previous)
     delete g_udev_loader;
 
diff --git a/extensions/browser/api/web_request/web_request_api.cc b/extensions/browser/api/web_request/web_request_api.cc
index 9ed1cc0f..dbc8f581 100644
--- a/extensions/browser/api/web_request/web_request_api.cc
+++ b/extensions/browser/api/web_request/web_request_api.cc
@@ -719,7 +719,15 @@
   std::string extension_name =
       extension ? extension->name() : extension_id_safe();
 
-  if (!web_view_instance_id) {
+  if (web_view_instance_id) {
+    // If a web view ID has been supplied and the call is from an extension
+    // (i.e. not from WebUI), we require the extension to have the webview
+    // permission.
+    if (extension && !extension->permissions_data()->HasAPIPermission(
+                         mojom::APIPermissionID::kWebView)) {
+      return RespondNow(Error("Missing webview permission."));
+    }
+  } else {
     auto has_blocking_permission = [&extension, &event_name]() {
       if (extension->permissions_data()->HasAPIPermission(
               APIPermissionID::kWebRequestBlocking)) {
diff --git a/extensions/browser/event_router.cc b/extensions/browser/event_router.cc
index 8930256..d0e4520 100644
--- a/extensions/browser/event_router.cc
+++ b/extensions/browser/event_router.cc
@@ -9,6 +9,7 @@
 #include <utility>
 
 #include "base/atomic_sequence_num.h"
+#include "base/containers/contains.h"
 #include "base/functional/bind.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
@@ -587,7 +588,7 @@
 void EventRouter::RegisterObserver(Observer* observer,
                                    const std::string& event_name) {
   // Observing sub-event names like "foo.onBar/123" is not allowed.
-  DCHECK(event_name.find('/') == std::string::npos);
+  DCHECK(!base::Contains(event_name, '/'));
   auto& observers = observer_map_[event_name];
   if (!observers) {
     observers = std::make_unique<Observers>();
diff --git a/extensions/browser/extension_action.cc b/extensions/browser/extension_action.cc
index 9c2edf79..780dcfb 100644
--- a/extensions/browser/extension_action.cc
+++ b/extensions/browser/extension_action.cc
@@ -10,6 +10,7 @@
 
 #include "base/base64.h"
 #include "base/check_op.h"
+#include "base/containers/contains.h"
 #include "base/strings/string_number_conversions.h"
 #include "extensions/browser/extension_icon_image.h"
 #include "extensions/browser/extension_icon_placeholder.h"
@@ -66,7 +67,7 @@
 
 template <class T>
 bool HasValue(const std::map<int, T>& map, int tab_id) {
-  return map.find(tab_id) != map.end();
+  return base::Contains(map, tab_id);
 }
 
 }  // namespace
diff --git a/extensions/browser/extension_creator_unittest.cc b/extensions/browser/extension_creator_unittest.cc
index 40da41b5..c458585 100644
--- a/extensions/browser/extension_creator_unittest.cc
+++ b/extensions/browser/extension_creator_unittest.cc
@@ -15,6 +15,7 @@
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_paths.h"
 #include "extensions/strings/grit/extensions_strings.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -172,8 +173,8 @@
   ASSERT_TRUE(base::WriteFile(de_messages_file, de_data));
 
   EXPECT_FALSE(ValidateExtension(src_path, 0));
-  EXPECT_NE(std::string::npos, extension_creator()->error_message().find(
-                                   "Variable $VAR$ used but not defined."));
+  EXPECT_THAT(extension_creator()->error_message(),
+              testing::HasSubstr("Variable $VAR$ used but not defined."));
 }
 
 }  // namespace extensions
diff --git a/extensions/browser/extension_event_histogram_value_unittest.cc b/extensions/browser/extension_event_histogram_value_unittest.cc
index 9fdf16c..b9fadc54 100644
--- a/extensions/browser/extension_event_histogram_value_unittest.cc
+++ b/extensions/browser/extension_event_histogram_value_unittest.cc
@@ -8,6 +8,7 @@
 #include <set>
 #include <string>
 
+#include "base/containers/contains.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
@@ -44,7 +45,7 @@
     // expecting to find the string "ENTRY = <value>" somewhere in the file.
     std::string expected_string =
         base::StringPrintf("%s = %d,", entry.second.c_str(), entry.first);
-    EXPECT_NE(std::string::npos, file_contents.find(expected_string))
+    EXPECT_TRUE(base::Contains(file_contents, expected_string))
         << "Failed to find entry " << entry.second << " with value "
         << entry.first << ". Make sure events::HistogramValue and the "
         << "ExtensionEvents enum in enums.xml agree with each other.";
diff --git a/extensions/browser/extension_function_histogram_value_unittest.cc b/extensions/browser/extension_function_histogram_value_unittest.cc
index c1dcceb8..0b053460 100644
--- a/extensions/browser/extension_function_histogram_value_unittest.cc
+++ b/extensions/browser/extension_function_histogram_value_unittest.cc
@@ -8,6 +8,7 @@
 #include <set>
 #include <string>
 
+#include "base/containers/contains.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/path_service.h"
@@ -48,7 +49,7 @@
     //   not (yet) worth making it smart enough to deal with that.
     std::string expected_string =
         base::StringPrintf(" %s = %d", entry.second.c_str(), entry.first);
-    EXPECT_NE(std::string::npos, file_contents.find(expected_string))
+    EXPECT_TRUE(base::Contains(file_contents, expected_string))
         << "Failed to find entry " << entry.second << " with value "
         << entry.first;
   }
diff --git a/extensions/browser/extension_pref_value_map.cc b/extensions/browser/extension_pref_value_map.cc
index 06aae99..7d457a2 100644
--- a/extensions/browser/extension_pref_value_map.cc
+++ b/extensions/browser/extension_pref_value_map.cc
@@ -120,15 +120,14 @@
                                               const base::Time& install_time,
                                               bool is_enabled,
                                               bool is_incognito_enabled) {
-  if (entries_.find(ext_id) == entries_.end()) {
-    entries_[ext_id] = base::WrapUnique(new ExtensionEntry);
-
-    // Only update the install time if the extension is newly installed.
-    entries_[ext_id]->install_time = install_time;
+  auto& entry = entries_[ext_id];
+  if (!entry) {
+    entry = std::make_unique<ExtensionEntry>();
+    entry->install_time = install_time;
   }
 
-  entries_[ext_id]->enabled = is_enabled;
-  entries_[ext_id]->incognito_enabled = is_incognito_enabled;
+  entry->enabled = is_enabled;
+  entry->incognito_enabled = is_incognito_enabled;
 }
 
 void ExtensionPrefValueMap::UnregisterExtension(const std::string& ext_id) {
diff --git a/extensions/browser/mock_external_provider.cc b/extensions/browser/mock_external_provider.cc
index b5abbd29..066c935 100644
--- a/extensions/browser/mock_external_provider.cc
+++ b/extensions/browser/mock_external_provider.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/containers/contains.h"
 #include "base/version.h"
 #include "extensions/browser/external_install_info.h"
 #include "extensions/common/extension.h"
@@ -30,14 +31,14 @@
 void MockExternalProvider::UpdateOrAddExtension(
     std::unique_ptr<ExternalInstallInfoFile> info) {
   const std::string& id = info->extension_id;
-  CHECK(url_extension_map_.find(id) == url_extension_map_.end());
+  CHECK(!base::Contains(url_extension_map_, id));
   file_extension_map_[id] = std::move(info);
 }
 
 void MockExternalProvider::UpdateOrAddExtension(
     std::unique_ptr<ExternalInstallInfoUpdateUrl> info) {
   const std::string& id = info->extension_id;
-  CHECK(file_extension_map_.find(id) == file_extension_map_.end());
+  CHECK(!base::Contains(file_extension_map_, id));
   url_extension_map_[id] = std::move(info);
 }
 
@@ -65,8 +66,8 @@
 }
 
 bool MockExternalProvider::HasExtension(const std::string& id) const {
-  return file_extension_map_.find(id) != file_extension_map_.end() ||
-         url_extension_map_.find(id) != url_extension_map_.end();
+  return base::Contains(file_extension_map_, id) ||
+         base::Contains(url_extension_map_, id);
 }
 
 bool MockExternalProvider::GetExtensionDetails(
diff --git a/extensions/browser/permissions_manager.cc b/extensions/browser/permissions_manager.cc
index 3dec0c1..10e2fdf 100644
--- a/extensions/browser/permissions_manager.cc
+++ b/extensions/browser/permissions_manager.cc
@@ -336,12 +336,10 @@
 
 PermissionsManager::UserSiteSetting PermissionsManager::GetUserSiteSetting(
     const url::Origin& origin) const {
-  if (user_permissions_.permitted_sites.find(origin) !=
-      user_permissions_.permitted_sites.end()) {
+  if (base::Contains(user_permissions_.permitted_sites, origin)) {
     return UserSiteSetting::kGrantAllExtensions;
   }
-  if (user_permissions_.restricted_sites.find(origin) !=
-      user_permissions_.restricted_sites.end()) {
+  if (base::Contains(user_permissions_.restricted_sites, origin)) {
     return UserSiteSetting::kBlockAllExtensions;
   }
   return UserSiteSetting::kCustomizeByExtension;
diff --git a/extensions/browser/process_manager.cc b/extensions/browser/process_manager.cc
index b031e74..580df6d 100644
--- a/extensions/browser/process_manager.cc
+++ b/extensions/browser/process_manager.cc
@@ -8,6 +8,7 @@
 #include <unordered_set>
 #include <vector>
 
+#include "base/containers/contains.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
@@ -346,8 +347,7 @@
 
 bool ProcessManager::IsRenderFrameHostRegistered(
     content::RenderFrameHost* render_frame_host) {
-  return all_extension_frames_.find(render_frame_host) !=
-         all_extension_frames_.end();
+  return base::Contains(all_extension_frames_, render_frame_host);
 }
 
 void ProcessManager::AddObserver(ProcessManagerObserver* observer) {
@@ -596,8 +596,9 @@
   ExtensionHost* host = result->second;
   pending_network_requests_.erase(result);
 
-  if (background_hosts_.find(host) == background_hosts_.end())
+  if (!base::Contains(background_hosts_, host)) {
     return;
+  }
 
   DCHECK(IsFrameInExtensionHost(host, render_frame_host));
 
@@ -709,7 +710,7 @@
         mojom::ViewType::kExtensionBackgroundPage);
   delete host;
   // |host| should deregister itself from our structures.
-  CHECK(background_hosts_.find(host) == background_hosts_.end());
+  CHECK(!base::Contains(background_hosts_, host));
 
   for (auto& observer : observer_list_)
     observer.OnBackgroundHostClose(extension_id);
@@ -1057,7 +1058,7 @@
   TRACE_EVENT0("browser,startup", "ProcessManager::OnExtensionHostDestroyed");
   host->RemoveObserver(this);
 
-  DCHECK(background_hosts_.find(host) != background_hosts_.end());
+  DCHECK(base::Contains(background_hosts_, host));
   background_hosts_.erase(host);
   // Note: |host->extension()| may be null at this point.
   ClearBackgroundPageData(host->extension_id());
diff --git a/extensions/browser/quota_service_unittest.cc b/extensions/browser/quota_service_unittest.cc
index 20c62d02..4f6d44d 100644
--- a/extensions/browser/quota_service_unittest.cc
+++ b/extensions/browser/quota_service_unittest.cc
@@ -46,9 +46,11 @@
     for (const auto& val : args) {
       absl::optional<int> id = val.GetIfInt();
       ASSERT_TRUE(id.has_value());
-      if (buckets_.find(*id) == buckets_.end())
-        buckets_[*id] = std::make_unique<Bucket>();
-      buckets->push_back(buckets_[*id].get());
+      auto& entry = buckets_[*id];
+      if (!entry) {
+        entry = std::make_unique<Bucket>();
+      }
+      buckets->push_back(entry.get());
     }
   }
 
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index 201c178..ddf66ad 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -364,7 +364,8 @@
     "matches": [
       "chrome-untrusted://help-app/*",
       "chrome-untrusted://media-app/*",
-      "chrome-untrusted://projector/*"
+      "chrome-untrusted://projector/*",
+      "chrome-untrusted://read-anything-side-panel.top-chrome/*"
     ]
   }],
   "metricsPrivateIndividualApis": {
diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc
index d309e14..908c657 100644
--- a/gpu/command_buffer/service/feature_info.cc
+++ b/gpu/command_buffer/service/feature_info.cc
@@ -208,7 +208,8 @@
       gpu_feature_info.supported_buffer_formats_for_allocation_and_texturing,
       gfx::BufferFormat::P010);
 #elif BUILDFLAG(IS_MAC)
-  feature_flags_.chromium_image_ycbcr_p010 = base::mac::IsAtLeastOS11();
+  feature_flags_.chromium_image_ycbcr_p010 =
+      base::mac::MacOSMajorVersion() >= 11;
 #endif
 }
 
@@ -1291,7 +1292,7 @@
   }
 
 #if BUILDFLAG(IS_MAC)
-  if (base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() >= 11) {
     feature_flags_.gpu_memory_buffer_formats.Put(
         gfx::BufferFormat::YUVA_420_TRIPLANAR);
   }
diff --git a/gpu/config/webgpu_blocklist.cc b/gpu/config/webgpu_blocklist.cc
index 677d40ef..7011cfb 100644
--- a/gpu/config/webgpu_blocklist.cc
+++ b/gpu/config/webgpu_blocklist.cc
@@ -44,8 +44,9 @@
 #if BUILDFLAG(USE_DAWN)
 #if BUILDFLAG(IS_MAC)
   constexpr uint32_t kAMDVendorID = 0x1002;
-  // Blocklisted due to crbug.com/tint/1094
-  if (!base::mac::IsAtLeastOS13() && properties.vendorID == kAMDVendorID &&
+  // Blocklisted due to https://crbug.com/tint/1094
+  if (base::mac::MacOSMajorVersion() < 13 &&
+      properties.vendorID == kAMDVendorID &&
       properties.backendType == WGPUBackendType_Metal) {
     return true;
   }
diff --git a/gpu/ipc/service/gpu_watchdog_thread_unittest.cc b/gpu/ipc/service/gpu_watchdog_thread_unittest.cc
index 13bea03..7ac2c45 100644
--- a/gpu/ipc/service/gpu_watchdog_thread_unittest.cc
+++ b/gpu/ipc/service/gpu_watchdog_thread_unittest.cc
@@ -122,11 +122,16 @@
   TimeOutType timeout_type = kNormal;
 
 #if BUILDFLAG(IS_MAC)
-  // Use slow timeout for Mac versions < 11.00 and for MacBookPro model <
-  // MacBookPro14,1
-  int os_version = base::mac::internal::MacOSVersion();
-
-  if (os_version <= 1100) {
+  // Use a slow timeout for Mac versions <= 11.00 and for MacBookPro model <
+  // MacBookPro14,1.
+  //
+  // As per EveryMac, laptops older than MacBookPro14,1 max out at macOS 12
+  // Monterey. When macOS 13 is the minimum required version for Chromium, this
+  // check can be removed.
+  //
+  // Reference:
+  //   https://everymac.com/systems/by_capability/mac-specs-by-machine-model-machine-id.html
+  if (base::mac::MacOSMajorVersion() <= 11) {
     // Check MacOS version.
     timeout_type = kSlow;
   } else {
@@ -140,7 +145,7 @@
       // model_ver_str = "MacBookProXX,X", model_ver_str = "XX,X"
       std::string model_ver_str = model_str.substr(model_version_pos);
       int major_model_ver = std::atoi(model_ver_str.c_str());
-      // For model version < 14,1
+      // For model version < 14,1.
       if (major_model_ver < 14) {
         timeout_type = kSlow;
       }
diff --git a/infra/config/generated/luci/project.cfg b/infra/config/generated/luci/project.cfg
index 1b5df33..e729ec7d7 100644
--- a/infra/config/generated/luci/project.cfg
+++ b/infra/config/generated/luci/project.cfg
@@ -7,7 +7,7 @@
 name: "chromium"
 access: "group:all"
 lucicfg {
-  version: "1.39.14"
+  version: "1.39.15"
   package_dir: "../.."
   config_dir: "generated/luci"
   entry_point: "main.star"
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 500396e3..c796ed13 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -32,16 +32,16 @@
   },
   'LACROS_VERSION_SKEW_DEV': {
     'identifier': 'Lacros version skew testing ash dev',
-    'description': 'Run with ash-chrome version 117.0.5938.13',
+    'description': 'Run with ash-chrome version 118.0.5966.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v117.0.5938.13',
-          'revision': 'version:117.0.5938.13',
+          'location': 'lacros_version_skew_tests_v118.0.5966.0',
+          'revision': 'version:118.0.5966.0',
         },
       ],
     },
@@ -534,7 +534,7 @@
     'identifier': 'TROGDOR_PUBLIC_LKGM',
     'skylab': {
       'cros_board': 'trogdor',
-      'cros_img': 'trogdor-public/R118-15585.0.0',
+      'cros_img': 'trogdor-public/R118-15594.0.0',
       'bucket': 'chromiumos-image-archive',
     },
   },
diff --git a/infra/config/targets/cros-skylab-variants.json b/infra/config/targets/cros-skylab-variants.json
index 78ac425..ddfcdf0 100644
--- a/infra/config/targets/cros-skylab-variants.json
+++ b/infra/config/targets/cros-skylab-variants.json
@@ -204,8 +204,8 @@
   "CROS_TROGDOR_PUBLIC_LKGM": {
     "skylab": {
       "cros_board": "trogdor",
-      "cros_chrome_version": "118.0.5956.0",
-      "cros_img": "trogdor-public/R118-15585.0.0",
+      "cros_chrome_version": "118.0.5972.0",
+      "cros_img": "trogdor-public/R118-15594.0.0",
       "bucket": "chromiumos-image-archive"
     },
     "enabled": true,
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index 5b37ac9..c1847d3 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -17,16 +17,16 @@
   },
   "LACROS_VERSION_SKEW_DEV": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 117.0.5938.13",
+    "description": "Run with ash-chrome version 118.0.5966.0",
     "identifier": "Lacros version skew testing ash dev",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v117.0.5938.13",
-          "revision": "version:117.0.5938.13"
+          "location": "lacros_version_skew_tests_v118.0.5966.0",
+          "revision": "version:118.0.5966.0"
         }
       ]
     }
diff --git a/internal b/internal
index 97569bf..41d516d 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 97569bf6d194d12b46efbfc76b3f6b3e0d152252
+Subproject commit 41d516d494867e38abcdef24e629510b56ef7d49
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
index 3dea85d..186994c 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_mediator.mm
@@ -358,6 +358,7 @@
   _readingListModelBridge.reset();
   _authenticationService = nullptr;
   _authServiceObserverBridge.reset();
+  _syncObserverBridge.reset();
   _identityObserverBridge.reset();
   _syncedSessionsObserver.reset();
   if (_prefObserverBridge) {
diff --git a/ios/chrome/browser/ui/overlays/infobar_modal/translate/translate_infobar_modal_overlay_mediator_unittest.mm b/ios/chrome/browser/ui/overlays/infobar_modal/translate/translate_infobar_modal_overlay_mediator_unittest.mm
index d70b9861..eff877a5e 100644
--- a/ios/chrome/browser/ui/overlays/infobar_modal/translate/translate_infobar_modal_overlay_mediator_unittest.mm
+++ b/ios/chrome/browser/ui/overlays/infobar_modal/translate/translate_infobar_modal_overlay_mediator_unittest.mm
@@ -94,7 +94,8 @@
 // Tests that TranslateInfobarModalOverlayMediator calls RevertTranslation when
 // its showSourceLanguage API is called.
 TEST_F(TranslateInfobarModalOverlayMediatorTest, ShowSourceLanguage) {
-  OCMExpect(translate_delegate_->RevertWithoutClosingInfobar());
+  // TODO(crbug.com/1476697): Change translate_delegate_ to a mock
+  // object, and verify that RevertWithoutClosingInfobar() is called.
   OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
   [mediator_ showSourceLanguage];
 }
@@ -126,7 +127,8 @@
 
   EXPECT_EQ(kSourceLanguage, translate_delegate_->source_language_name());
   EXPECT_EQ(kTargetLanguage, translate_delegate_->target_language_name());
-  OCMExpect(translate_delegate_->Translate());
+  // TODO(crbug.com/1476697): Change translate_delegate_ to a mock
+  // object, and verify that Translate is called.
 
   OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
   [mediator_ translateWithNewLanguages];
@@ -137,8 +139,8 @@
 // is called.
 TEST_F(TranslateInfobarModalOverlayMediatorTest,
        AlwaysTranslateSourceLanguage) {
-  OCMExpect(translate_delegate_->ToggleAlwaysTranslate());
-  OCMExpect(translate_delegate_->Translate());
+  // TODO(crbug.com/1476697): Change translate_delegate_ to a mock
+  // object, and verify that ToggleAlwaysTranslate and Translate are called.
   OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
   [mediator_ alwaysTranslateSourceLanguage];
 }
@@ -147,7 +149,8 @@
 // ToggleNeverTranslateSourceLanguage when its neverTranslateSourceLanguage API
 // is called.
 TEST_F(TranslateInfobarModalOverlayMediatorTest, NeverTranslateSourceLanguage) {
-  OCMExpect(translate_delegate_->ToggleTranslatableLanguageByPrefs());
+  // TODO(crbug.com/1476697): Change translate_delegate_ to a mock
+  // object, and verify that ToggleTranslatableLanguageByPrefs is called.
   OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
   [mediator_ neverTranslateSourceLanguage];
 }
@@ -155,7 +158,8 @@
 // Tests that TranslateInfobarModalOverlayMediator calls ToggleNeverPromptSite
 // when its neverTranslateSite API is called.
 TEST_F(TranslateInfobarModalOverlayMediatorTest, NeverTranslateSite) {
-  OCMExpect(translate_delegate_->ToggleNeverPromptSite());
+  // TODO(crbug.com/1476697): Change translate_delegate_ to a mock
+  // object, and verify that ToggleNeverPromptSite is called.
   OCMExpect([delegate_ stopOverlayForMediator:mediator_]);
   [mediator_ neverTranslateSite];
 }
diff --git a/ios_internal b/ios_internal
index 1b35a59..425fdef 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 1b35a5961cccad1d65cd2be93e68f23cc9bab20a
+Subproject commit 425fdef2091ba80f95c72ab9f998633f7045d516
diff --git a/media/audio/mac/coreaudio_dispatch_override.cc b/media/audio/mac/coreaudio_dispatch_override.cc
index 3a27340e..11e698d 100644
--- a/media/audio/mac/coreaudio_dispatch_override.cc
+++ b/media/audio/mac/coreaudio_dispatch_override.cc
@@ -151,8 +151,9 @@
 
 bool InitializeCoreAudioDispatchOverride() {
   // Apple reports this issue is fixed in 11+.
-  if (base::mac::IsAtLeastOS11())
+  if (base::mac::MacOSMajorVersion() >= 11) {
     return true;
+  }
 
   if (g_dispatch_override_installed)
     return true;
diff --git a/media/capture/video/mac/uvc_control_mac.mm b/media/capture/video/mac/uvc_control_mac.mm
index b10f1b1e..b45250f 100644
--- a/media/capture/video/mac/uvc_control_mac.mm
+++ b/media/capture/video/mac/uvc_control_mac.mm
@@ -366,7 +366,8 @@
     VLOG(1) << "Unable to open control interface";
 
     // Temporary additional debug logging for crbug.com/1270335
-    VLOG_IF(1, base::mac::IsAtLeastOS12() && ret == kIOReturnExclusiveAccess)
+    VLOG_IF(1, base::mac::MacOSMajorVersion() >= 12 &&
+                   ret == kIOReturnExclusiveAccess)
         << "Camera USBInterfaceOpen failed with "
         << "kIOReturnExclusiveAccess";
     return ScopedIOUSBInterfaceInterface();
diff --git a/media/gpu/v4l2/BUILD.gn b/media/gpu/v4l2/BUILD.gn
index f939bd5..b535600 100644
--- a/media/gpu/v4l2/BUILD.gn
+++ b/media/gpu/v4l2/BUILD.gn
@@ -120,6 +120,7 @@
     ]
 
     deps += [
+      "//chromeos/components/cdm_factory_daemon:cdm_factory_daemon_gpu",
       "//components/chromeos_camera:jpeg_encode_accelerator",
       "//components/chromeos_camera:mjpeg_decode_accelerator",
       "//media/gpu:video_frame_mapper_common",
diff --git a/media/gpu/v4l2/DEPS b/media/gpu/v4l2/DEPS
new file mode 100644
index 0000000..ebf0abb
--- /dev/null
+++ b/media/gpu/v4l2/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+  "+chromeos/components/cdm_factory_daemon",
+]
diff --git a/media/gpu/v4l2/v4l2_video_decoder.cc b/media/gpu/v4l2/v4l2_video_decoder.cc
index 1b008518..7fb5d56 100644
--- a/media/gpu/v4l2/v4l2_video_decoder.cc
+++ b/media/gpu/v4l2/v4l2_video_decoder.cc
@@ -32,6 +32,13 @@
 #include "media/gpu/v4l2/v4l2_video_decoder_backend_stateful.h"
 #include "media/gpu/v4l2/v4l2_video_decoder_backend_stateless.h"
 
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+// gn check does not account for BUILDFLAG(), so including this header will
+// make gn check fail for builds other than ash-chrome. See gn help nogncheck
+// for more information.
+#include "chromeos/components/cdm_factory_daemon/chromeos_cdm_context.h"  // nogncheck
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 namespace media {
 
 namespace {
@@ -129,7 +136,12 @@
   if (configs.empty())
     return absl::nullopt;
 
-  return ConvertFromSupportedProfiles(configs, false);
+  return ConvertFromSupportedProfiles(configs,
+#if BUILDFLAG(USE_CHROMEOS_PROTECTED_MEDIA)
+                                      true /* allow_encrypted */);
+#else
+                                      false /* allow_encrypted */);
+#endif
 }
 
 V4L2VideoDecoder::V4L2VideoDecoder(
@@ -204,10 +216,30 @@
       return;
   }
 
-  if (cdm_context || config.is_encrypted()) {
-    VLOGF(1) << "V4L2 decoder does not support encrypted stream";
+  cdm_context_ref_ = nullptr;
+
+  if (config.is_encrypted()) {
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+    VLOGF(1) << "Encrypted content is not supported";
     std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
     return;
+#else
+    if (!cdm_context || !cdm_context->GetChromeOsCdmContext()) {
+      VLOGF(1) << "Cannot support encrypted stream w/out ChromeOsCdmContext";
+      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
+      return;
+    }
+    if (config.codec() != VideoCodec::kH264 &&
+        config.codec() != VideoCodec::kVP9 &&
+        config.codec() != VideoCodec::kAV1 &&
+        config.codec() != VideoCodec::kHEVC) {
+      VLOGF(1) << GetCodecName(config.codec())
+               << " is not supported for encrypted content";
+      std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
+      return;
+    }
+    cdm_context_ref_ = cdm_context->GetChromeOsCdmContext()->GetCdmContextRef();
+#endif
   }
 
   // Stop and reset the queues if we're currently decoding but want to
@@ -496,7 +528,7 @@
       client_->PickDecoderOutputFormat(
           candidates, visible_rect, aspect_ratio_.GetNaturalSize(visible_rect),
           /*output_size=*/absl::nullopt, num_codec_reference_frames,
-          /*use+protected=*/false, /*need_aux_frame_pool=*/false,
+          /*use_protected=*/!!cdm_context_ref_, /*need_aux_frame_pool=*/false,
           absl::nullopt);
   if (!status_or_output_format.has_value()) {
     VLOGF(1) << "Failed to pick an output format.";
@@ -779,6 +811,10 @@
   return VIDEO_MAX_FRAME;
 }
 
+bool V4L2VideoDecoder::NeedsTranscryption() {
+  return !!cdm_context_ref_;
+}
+
 CroStatus V4L2VideoDecoder::ContinueChangeResolution(
     const gfx::Size& pic_size,
     const gfx::Rect& visible_rect,
diff --git a/media/gpu/v4l2/v4l2_video_decoder.h b/media/gpu/v4l2/v4l2_video_decoder.h
index e89609e..f6a4b75 100644
--- a/media/gpu/v4l2/v4l2_video_decoder.h
+++ b/media/gpu/v4l2/v4l2_video_decoder.h
@@ -68,6 +68,7 @@
   // VideoDecoderMixin implementation, specific part.
   void ApplyResolutionChange() override;
   size_t GetMaxOutputFramePoolSize() const override;
+  bool NeedsTranscryption() override;
 
   // V4L2VideoDecoderBackend::Client implementation
   void OnBackendError() override;
@@ -194,6 +195,10 @@
   scoped_refptr<V4L2Queue> input_queue_;
   scoped_refptr<V4L2Queue> output_queue_;
 
+  // We need to use a CdmContextRef to ensure the lifetime of the CdmContext
+  // backing it while we are alive. This also indicates secure playback mode.
+  std::unique_ptr<CdmContextRef> cdm_context_ref_;
+
   SEQUENCE_CHECKER(decoder_sequence_checker_);
 
   // Whether or not our V4L2Queues should be requested with
diff --git a/net/socket/udp_socket_posix.cc b/net/socket/udp_socket_posix.cc
index 120ce58..21cd1c8 100644
--- a/net/socket/udp_socket_posix.cc
+++ b/net/socket/udp_socket_posix.cc
@@ -552,7 +552,7 @@
 
 // setsockopt(IP_DONTFRAG) is supported on macOS from Big Sur
 #elif BUILDFLAG(IS_MAC)
-  if (!base::mac::IsAtLeastOS11()) {
+  if (base::mac::MacOSMajorVersion() < 11) {
     return ERR_NOT_IMPLEMENTED;
   }
   int val = 1;
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc
index 6f36204..c0d4fd2 100644
--- a/net/socket/udp_socket_unittest.cc
+++ b/net/socket/udp_socket_unittest.cc
@@ -660,7 +660,7 @@
     // TODO(crbug.com/945590): IP_MTU_DISCOVER is not implemented on Fuchsia.
     EXPECT_THAT(rv, IsError(ERR_NOT_IMPLEMENTED));
 #elif BUILDFLAG(IS_MAC)
-    if (base::mac::IsAtLeastOS11()) {
+    if (base::mac::MacOSMajorVersion() >= 11) {
       EXPECT_THAT(rv, IsOk());
     } else {
       EXPECT_THAT(rv, IsError(ERR_NOT_IMPLEMENTED));
@@ -688,7 +688,7 @@
     // TODO(crbug.com/945590): IP_MTU_DISCOVER is not implemented on Fuchsia.
     EXPECT_THAT(rv, IsError(ERR_NOT_IMPLEMENTED));
 #elif BUILDFLAG(IS_MAC)
-    if (base::mac::IsAtLeastOS11()) {
+    if (base::mac::MacOSMajorVersion() >= 11) {
       EXPECT_THAT(rv, IsOk());
     } else {
       EXPECT_THAT(rv, IsError(ERR_NOT_IMPLEMENTED));
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index e125d22..c032813 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1486,7 +1486,7 @@
       {
         "bucket": "chromiumos-image-archive",
         "cros_board": "trogdor",
-        "cros_img": "trogdor-public/R118-15585.0.0",
+        "cros_img": "trogdor-public/R118-15594.0.0",
         "name": "lacros_all_tast_tests TROGDOR_PUBLIC_LKGM",
         "resultdb": {
           "enable": true,
@@ -4865,9 +4865,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -4877,8 +4877,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -5011,9 +5011,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5023,8 +5023,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -5144,9 +5144,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5156,8 +5156,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index ab1022e..d212e8a 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -25084,9 +25084,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25096,8 +25096,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -25230,9 +25230,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25242,8 +25242,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -25363,9 +25363,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -25375,8 +25375,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index d2a2bdc..2d5f588 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -40544,7 +40544,7 @@
       {
         "bucket": "chromiumos-image-archive",
         "cros_board": "trogdor",
-        "cros_img": "trogdor-public/R118-15585.0.0",
+        "cros_img": "trogdor-public/R118-15594.0.0",
         "name": "lacros_all_tast_tests TROGDOR_PUBLIC_LKGM",
         "resultdb": {
           "enable": true,
@@ -40604,7 +40604,7 @@
       {
         "bucket": "chromiumos-image-archive",
         "cros_board": "trogdor",
-        "cros_img": "trogdor-public/R118-15585.0.0",
+        "cros_img": "trogdor-public/R118-15594.0.0",
         "name": "lacros_all_tast_tests TROGDOR_PUBLIC_LKGM",
         "resultdb": {
           "enable": true,
@@ -42851,9 +42851,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42862,8 +42862,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -42997,9 +42997,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43008,8 +43008,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -43130,9 +43130,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43141,8 +43141,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -44376,9 +44376,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44387,8 +44387,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -44522,9 +44522,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44533,8 +44533,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -44655,9 +44655,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44666,8 +44666,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -45297,9 +45297,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -45308,8 +45308,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index e046222c..f324ea8 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -16150,12 +16150,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16165,8 +16165,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -16316,12 +16316,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16331,8 +16331,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
@@ -16464,12 +16464,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 117.0.5938.13",
+        "description": "Run with ash-chrome version 118.0.5966.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16479,8 +16479,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v117.0.5938.13",
-              "revision": "version:117.0.5938.13"
+              "location": "lacros_version_skew_tests_v118.0.5966.0",
+              "revision": "version:118.0.5966.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 500396e3..c796ed13 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -32,16 +32,16 @@
   },
   'LACROS_VERSION_SKEW_DEV': {
     'identifier': 'Lacros version skew testing ash dev',
-    'description': 'Run with ash-chrome version 117.0.5938.13',
+    'description': 'Run with ash-chrome version 118.0.5966.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v117.0.5938.13/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v118.0.5966.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v117.0.5938.13',
-          'revision': 'version:117.0.5938.13',
+          'location': 'lacros_version_skew_tests_v118.0.5966.0',
+          'revision': 'version:118.0.5966.0',
         },
       ],
     },
@@ -534,7 +534,7 @@
     'identifier': 'TROGDOR_PUBLIC_LKGM',
     'skylab': {
       'cros_board': 'trogdor',
-      'cros_img': 'trogdor-public/R118-15585.0.0',
+      'cros_img': 'trogdor-public/R118-15594.0.0',
       'bucket': 'chromiumos-image-archive',
     },
   },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 7d1ad1c..5912b78 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -4862,6 +4862,22 @@
             ]
         }
     ],
+    "DeskButton": [
+        {
+            "platforms": [
+                "chromeos",
+                "chromeos_lacros"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled_20230828",
+                    "enable_features": [
+                        "DeskButton"
+                    ]
+                }
+            ]
+        }
+    ],
     "DesktopNtpDriveCache": [
         {
             "platforms": [
@@ -6981,25 +6997,6 @@
             ]
         }
     ],
-    "GoogleLensHybridIOS": [
-        {
-            "platforms": [
-                "ios"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "EnableLensInHomeScreenWidget",
-                        "EnableLensInKeyboard",
-                        "EnableLensInNTP",
-                        "LensMobileWebDirectUpload",
-                        "kNewNTPOmniboxLayout"
-                    ]
-                }
-            ]
-        }
-    ],
     "GoogleLensTabletIntegration": [
         {
             "platforms": [
@@ -10334,24 +10331,7 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled_Custom_Missing_Value_071523",
-                    "enable_features": [
-                        "MlUrlScoring",
-                        "UrlScoringModel"
-                    ]
-                },
-                {
-                    "name": "Enabled_Custom_Missing_Value_CF_071523",
-                    "params": {
-                        "MlUrlScoringCounterfactual": "true"
-                    },
-                    "enable_features": [
-                        "MlUrlScoring",
-                        "UrlScoringModel"
-                    ]
-                },
-                {
-                    "name": "Enabled_Final_Matches_Only_Custom_Missing_Value_071523",
+                    "name": "Enabled_Final_Matches_Only_Custom_Missing_Value_082823",
                     "params": {
                         "MlUrlScoringRerankFinalMatchesOnly": "true"
                     },
@@ -10361,15 +10341,78 @@
                     ]
                 },
                 {
-                    "name": "Enabled_Final_Matches_Only_Preserve_Default_Custom_Missing_Value_071523",
+                    "name": "Enabled_Final_Matches_Only_Custom_Missing_Non_Default_082823",
                     "params": {
-                        "MlUrlScoringPreserveDefault": "true",
                         "MlUrlScoringRerankFinalMatchesOnly": "true"
                     },
                     "enable_features": [
                         "MlUrlScoring",
                         "UrlScoringModel"
                     ]
+                },
+                {
+                    "name": "Enabled_Sync_Custom_Missing_Value_082823",
+                    "params": {
+                        "MlSyncBatchUrlScoring": "true"
+                    },
+                    "enable_features": [
+                        "MlUrlScoring",
+                        "UrlScoringModel"
+                    ]
+                },
+                {
+                    "name": "Enabled_Sync_Custom_Missing_Non_Default_082823",
+                    "params": {
+                        "MlSyncBatchUrlScoring": "true"
+                    },
+                    "enable_features": [
+                        "MlUrlScoring",
+                        "UrlScoringModel"
+                    ]
+                },
+                {
+                    "name": "Enabled_Sync_Final_Matches_Only_Custom_Missing_Value_082823",
+                    "params": {
+                        "MlSyncBatchUrlScoring": "true",
+                        "MlUrlScoringRerankFinalMatchesOnly": "true"
+                    },
+                    "enable_features": [
+                        "MlUrlScoring",
+                        "UrlScoringModel"
+                    ]
+                },
+                {
+                    "name": "Enabled_Sync_Final_Matches_Only_Custom_Missing_Non_Default_082823",
+                    "params": {
+                        "MlSyncBatchUrlScoring": "true",
+                        "MlUrlScoringRerankFinalMatchesOnly": "true"
+                    },
+                    "enable_features": [
+                        "MlUrlScoring",
+                        "UrlScoringModel"
+                    ]
+                },
+                {
+                    "name": "Enabled_Sync_10_Matches_Provider_Limit_Custom_Missing_Value_082823",
+                    "params": {
+                        "MlSyncBatchUrlScoring": "true",
+                        "MlUrlScoringMaxMatchesByProvider": "1:10,4:10,8:10,64:10,65536:10"
+                    },
+                    "enable_features": [
+                        "MlUrlScoring",
+                        "UrlScoringModel"
+                    ]
+                },
+                {
+                    "name": "Enabled_Sync_10_Matches_Provider_Limit_Custom_Missing_Non_Default_082823",
+                    "params": {
+                        "MlSyncBatchUrlScoring": "true",
+                        "MlUrlScoringMaxMatchesByProvider": "1:10,4:10,8:10,64:10,65536:10"
+                    },
+                    "enable_features": [
+                        "MlUrlScoring",
+                        "UrlScoringModel"
+                    ]
                 }
             ]
         }
diff --git a/third_party/blink/renderer/core/css/style_property_serializer.cc b/third_party/blink/renderer/core/css/style_property_serializer.cc
index 63a920a..5836eb7 100644
--- a/third_party/blink/renderer/core/css/style_property_serializer.cc
+++ b/third_party/blink/renderer/core/css/style_property_serializer.cc
@@ -1889,62 +1889,113 @@
     const StylePropertyShorthand& shorthand) const {
   DCHECK_EQ(shorthand.length(), 6u);
 
-  const CSSValue* auto_flow_values =
+  const auto* template_row_values =
+      property_set_.GetPropertyCSSValue(*shorthand.properties()[0]);
+  const auto* template_column_values =
+      property_set_.GetPropertyCSSValue(*shorthand.properties()[1]);
+  const auto* template_area_value =
+      property_set_.GetPropertyCSSValue(*shorthand.properties()[2]);
+  const auto* auto_flow_values =
       property_set_.GetPropertyCSSValue(*shorthand.properties()[3]);
-  const CSSValue* auto_row_values =
+  const auto* auto_row_values =
       property_set_.GetPropertyCSSValue(*shorthand.properties()[4]);
-  const CSSValue* auto_column_values =
+  const auto* auto_column_values =
       property_set_.GetPropertyCSSValue(*shorthand.properties()[5]);
 
-  // 1- <'grid-template'>
-  if (IsA<CSSIdentifierValue>(auto_flow_values) &&
-      To<CSSIdentifierValue>(auto_flow_values)->GetValueID() ==
-          CSSValueID::kRow &&
-      IsA<CSSIdentifierValue>(auto_row_values) &&
-      To<CSSIdentifierValue>(auto_row_values)->GetValueID() ==
-          CSSValueID::kAuto &&
-      IsA<CSSIdentifierValue>(auto_column_values) &&
-      To<CSSIdentifierValue>(auto_column_values)->GetValueID() ==
-          CSSValueID::kAuto) {
-    return GetShorthandValueForGridTemplate(shorthand);
-  }
+  // `auto-flow`, `grid-auto-rows`, and `grid-auto-columns` are parsed as either
+  // an identifier with the default value, or a CSSValueList containing a single
+  // entry with the default value. Unlike `grid-template-rows` and
+  // `grid-template-columns`, we *can* determine if the author specified them by
+  // the presence of an associated CSSValueList.
+  auto HasInitialValueListValue = [](const CSSValueList* value_list,
+                                     auto* definition) -> bool {
+    return value_list && value_list->length() == 1 &&
+           value_list->First() == *(To<Longhand>(definition()).InitialValue());
+  };
+  auto HasInitialIdentifierValue = [](const CSSValue* value,
+                                      CSSValueID initial_value) -> bool {
+    return IsA<CSSIdentifierValue>(value) &&
+           To<CSSIdentifierValue>(value)->GetValueID() == initial_value;
+  };
 
-  // If we have grid-auto-{flow,row,column} along with named lines, we can't
-  // serialize as a "grid" shorthand, as a syntax that combines the two is not
-  // valid per the grammar.
-  const CSSValue* template_area_value =
-      property_set_.GetPropertyCSSValue(*shorthand.properties()[2]);
-  if (*template_area_value !=
-      *(To<Longhand>(GetCSSPropertyGridTemplateAreas()).InitialValue())) {
+  const auto* auto_row_value_list = DynamicTo<CSSValueList>(auto_row_values);
+  const bool is_auto_rows_initial_value =
+      HasInitialValueListValue(auto_row_value_list,
+                               GetCSSPropertyGridAutoRows) ||
+      HasInitialIdentifierValue(auto_row_values, CSSValueID::kAuto);
+  const bool specified_non_initial_auto_rows =
+      auto_row_value_list && !is_auto_rows_initial_value;
+
+  const auto* auto_column_value_list =
+      DynamicTo<CSSValueList>(auto_column_values);
+  const bool is_auto_columns_initial_value =
+      HasInitialValueListValue(auto_column_value_list,
+                               GetCSSPropertyGridAutoColumns) ||
+      HasInitialIdentifierValue(auto_column_values, CSSValueID::kAuto);
+  const bool specified_non_initial_auto_columns =
+      auto_column_value_list && !is_auto_columns_initial_value;
+
+  const auto* auto_flow_value_list = DynamicTo<CSSValueList>(auto_flow_values);
+  const bool is_auto_flow_initial_value =
+      HasInitialValueListValue(auto_flow_value_list,
+                               GetCSSPropertyGridAutoFlow) ||
+      HasInitialIdentifierValue(auto_flow_values, CSSValueID::kRow);
+
+  // `grid-auto-*` along with named lines is not valid per the grammar.
+  if ((auto_flow_value_list || auto_row_value_list || auto_column_value_list) &&
+      *template_area_value !=
+          *(To<Longhand>(GetCSSPropertyGridTemplateAreas()).InitialValue())) {
     return String();
   }
 
-  const CSSValue* template_row_values =
-      property_set_.GetPropertyCSSValue(*shorthand.properties()[0]);
-  const CSSValue* template_column_values =
-      property_set_.GetPropertyCSSValue(*shorthand.properties()[1]);
-  const CSSValueList* auto_flow_value_list =
-      DynamicTo<CSSValueList>(auto_flow_values);
-
-  // We cannot represent a grid shorthand if we have both template row and
-  // template column values along with auto-flow.
-  if (*template_row_values !=
-          *(To<Longhand>(GetCSSPropertyGridTemplateRows()).InitialValue()) &&
+  // `grid-template-rows` and `grid-template-columns` are shorthards within this
+  // shorthand. Based on how parsing works, we can't differentiate between an
+  // author specifying `none` and uninitialized.
+  const bool non_initial_template_rows =
+      (*template_row_values !=
+       *(To<Longhand>(GetCSSPropertyGridTemplateRows()).InitialValue()));
+  const bool non_initial_template_columns =
       *template_column_values !=
-          *(To<Longhand>(GetCSSPropertyGridTemplateColumns()).InitialValue())) {
+      *(To<Longhand>(GetCSSPropertyGridTemplateColumns()).InitialValue());
+
+  // `grid-template-*` and `grid-auto-*` are mutually exclusive per direction.
+  if ((non_initial_template_rows && specified_non_initial_auto_rows) ||
+      (non_initial_template_columns && specified_non_initial_auto_columns) ||
+      (specified_non_initial_auto_rows && specified_non_initial_auto_columns)) {
     return String();
   }
 
+  // 1- <'grid-template'>
+  // If the author didn't specify `auto-flow`, we should go down the
+  // `grid-template` path. This should also round-trip if the author specified
+  // the initial value for `auto-flow`, unless `auto-columns` or `auto-rows`
+  // were also set, causing it to match the shorthand syntax below.
+  if (!auto_flow_value_list ||
+      (is_auto_flow_initial_value && !(specified_non_initial_auto_columns ||
+                                       specified_non_initial_auto_rows))) {
+    return GetShorthandValueForGridTemplate(shorthand);
+  } else if (non_initial_template_rows && non_initial_template_columns) {
+    // Specifying both rows and columns is not valid per the grammar.
+    return String();
+  }
+
+  // At this point, the syntax matches:
+  // <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
+  // [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
+  // ...and thus will include `auto-flow` no matter what.
   StringBuilder auto_flow_text;
-  auto_flow_text.Append("auto-flow ");
+  auto_flow_text.Append("auto-flow");
   if (auto_flow_value_list &&
       auto_flow_value_list->HasValue(
           *CSSIdentifierValue::Create(CSSValueID::kDense))) {
-    auto_flow_text.Append("dense ");
+    auto_flow_text.Append(" dense");
   }
 
   // 2- <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>?
-  // | [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'>
+  // We can't distinguish between `grid-template-rows` being unspecified or
+  // being specified as `none` (see the comment near the definition of
+  // `non_initial_template_rows`), as both are initial values. So we must
+  // distinguish between the remaining two possible paths via `auto-flow`.
   StringBuilder result;
   if (auto_flow_value_list &&
       auto_flow_value_list->HasValue(
@@ -1952,10 +2003,21 @@
     result.Append(template_row_values->CssText());
     result.Append(" / ");
     result.Append(auto_flow_text);
-    result.Append(auto_column_values->CssText());
+
+    if (specified_non_initial_auto_columns) {
+      result.Append(" ");
+      result.Append(auto_column_values->CssText());
+    }
   } else {
+    // 3- [ auto-flow && dense? ] <'grid-auto-rows'>? /
+    // <'grid-template-columns'>
     result.Append(auto_flow_text);
-    result.Append(auto_row_values->CssText());
+
+    if (specified_non_initial_auto_rows) {
+      result.Append(" ");
+      result.Append(auto_row_values->CssText());
+    }
+
     result.Append(" / ");
     result.Append(template_column_values->CssText());
   }
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index 9599c05..9ae9d7bea 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -2273,9 +2273,6 @@
     SetFlag(kIsInShadowTreeFlag);
   if (auto* cache = GetDocument().ExistingAXObjectCache()) {
     cache->NodeIsConnected(this);
-    // TODO(accessibility) NodeIsAttached() calls ChildrenChanged(), so we may
-    // not need this.
-    cache->ChildrenChanged(&insertion_point);
   }
   return kInsertionDone;
 }
diff --git a/third_party/blink/renderer/core/paint/paint_invalidator.cc b/third_party/blink/renderer/core/paint/paint_invalidator.cc
index 5b3aa25..290317b 100644
--- a/third_party/blink/renderer/core/paint/paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/paint_invalidator.cc
@@ -246,38 +246,23 @@
   }
 
   if (pre_paint_info) {
-    FragmentData& fragment_data = *pre_paint_info->fragment_data;
-    context.fragment_data = &fragment_data;
-
-    if (tree_builder_context) {
-      DCHECK_EQ(tree_builder_context->fragments.size(), 1u);
-      const auto& fragment_tree_builder_context =
-          tree_builder_context->fragments[0];
-      UpdateFromTreeBuilderContext(fragment_tree_builder_context, context);
-      UpdateLayoutShiftTracking(object, fragment_tree_builder_context, context);
-    } else {
-      context.old_paint_offset = fragment_data.PaintOffset();
-    }
-
-    object.InvalidatePaint(context);
+    context.fragment_data = pre_paint_info->fragment_data;
+    CHECK(context.fragment_data);
   } else {
-    DCHECK(!object.IsFragmented());
-
-    auto* fragment_data = &object.GetMutableForPainting().FirstFragment();
-    context.fragment_data = fragment_data;
-
-    if (tree_builder_context) {
-      const auto& fragment_tree_builder_context =
-          tree_builder_context->fragments[0];
-      UpdateFromTreeBuilderContext(fragment_tree_builder_context, context);
-      UpdateLayoutShiftTracking(object, fragment_tree_builder_context, context);
-    } else {
-      context.old_paint_offset = fragment_data->PaintOffset();
-    }
-
-    object.InvalidatePaint(context);
+    context.fragment_data = &object.GetMutableForPainting().FirstFragment();
   }
 
+  if (tree_builder_context) {
+    const auto& fragment_tree_builder_context =
+        tree_builder_context->fragment_context;
+    UpdateFromTreeBuilderContext(fragment_tree_builder_context, context);
+    UpdateLayoutShiftTracking(object, fragment_tree_builder_context, context);
+  } else {
+    context.old_paint_offset = context.fragment_data->PaintOffset();
+  }
+
+  object.InvalidatePaint(context);
+
   auto reason = static_cast<const DisplayItemClient&>(object)
                     .GetPaintInvalidationReason();
   if (object.ShouldDelayFullPaintInvalidation() &&
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 8787a76..05992b9 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -102,6 +102,7 @@
 #include "third_party/blink/renderer/platform/graphics/compositor_filter_operations.h"
 #include "third_party/blink/renderer/platform/graphics/filters/filter.h"
 #include "third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h"
+#include "third_party/blink/renderer/platform/heap/collection_support/clear_collection_scope.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index b3ab5f7..e2faa71b 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -118,29 +118,12 @@
       composited_scrolling_preference(
           static_cast<unsigned>(CompositedScrollingPreference::kDefault)) {}
 
-PaintPropertyTreeBuilderContext::~PaintPropertyTreeBuilderContext() {
-  fragments.clear();
-}
-
-void PaintPropertyTreeBuilderFragmentContext::Trace(Visitor* visitor) const {
-  visitor->Trace(current);
-  visitor->Trace(absolute_position);
-  visitor->Trace(fixed_position);
-}
-
-void PaintPropertyTreeBuilderFragmentContext::ContainingBlockContext::Trace(
-    Visitor* visitor) const {
-  visitor->Trace(paint_offset_root);
-}
-
 void VisualViewportPaintPropertyTreeBuilder::Update(
     LocalFrameView& main_frame_view,
     VisualViewport& visual_viewport,
     PaintPropertyTreeBuilderContext& full_context) {
-  if (full_context.fragments.empty())
-    full_context.fragments.push_back(PaintPropertyTreeBuilderFragmentContext());
-
-  PaintPropertyTreeBuilderFragmentContext& context = full_context.fragments[0];
+  PaintPropertyTreeBuilderFragmentContext& context =
+      full_context.fragment_context;
 
   auto property_changed =
       visual_viewport.UpdatePaintPropertyNodesIfNeeded(context);
@@ -178,10 +161,8 @@
 void PaintPropertyTreeBuilder::SetupContextForFrame(
     LocalFrameView& frame_view,
     PaintPropertyTreeBuilderContext& full_context) {
-  if (full_context.fragments.empty())
-    full_context.fragments.push_back(PaintPropertyTreeBuilderFragmentContext());
-
-  PaintPropertyTreeBuilderFragmentContext& context = full_context.fragments[0];
+  PaintPropertyTreeBuilderFragmentContext& context =
+      full_context.fragment_context;
 
   // Block fragmentation doesn't cross frame boundaries.
   context.current.is_in_block_fragmentation = false;
@@ -206,12 +187,11 @@
       const LayoutObject& object,
       NGPrePaintInfo* pre_paint_info,
       PaintPropertyTreeBuilderContext& full_context,
-      PaintPropertyTreeBuilderFragmentContext& context,
       FragmentData& fragment_data)
       : object_(object),
         pre_paint_info_(pre_paint_info),
         full_context_(full_context),
-        context_(context),
+        context_(full_context.fragment_context),
         fragment_data_(fragment_data),
         properties_(fragment_data.PaintProperties()) {}
 
@@ -3173,102 +3153,9 @@
 
 }  // namespace
 
-void PaintPropertyTreeBuilder::InitFragmentPaintProperties(
-    FragmentData& fragment,
-    bool needs_paint_properties,
-    PaintPropertyTreeBuilderFragmentContext& context) {
-  if (const auto* properties = fragment.PaintProperties()) {
-    if (const auto* translation = properties->PaintOffsetTranslation()) {
-      // If there is a paint offset translation, it only causes a net change
-      // in additional_offset_to_layout_shift_root_delta by the amount the
-      // paint offset translation changed from the prior frame. To implement
-      // this, we record a negative offset here, and then re-add it in
-      // UpdatePaintOffsetTranslation. The net effect is that the value
-      // of additional_offset_to_layout_shift_root_delta is the difference
-      // between the old and new paint offset translation.
-      context.pending_additional_offset_to_layout_shift_root_delta =
-          -PhysicalOffset::FromVector2dFRound(translation->Get2dTranslation());
-    }
-    gfx::Vector2dF translation2d;
-    for (const TransformPaintPropertyNode* transform :
-         properties->AllCSSTransformPropertiesOutsideToInside()) {
-      if (transform) {
-        if (IsLayoutShiftRootTransform(*transform)) {
-          translation2d = gfx::Vector2dF();
-          break;
-        }
-        translation2d += transform->Get2dTranslation();
-      }
-    }
-    context.translation_2d_to_layout_shift_root_delta -= translation2d;
-  }
-
-  if (needs_paint_properties) {
-    fragment.EnsurePaintProperties();
-  } else if (auto* properties = fragment.PaintProperties()) {
-    if (properties->HasTransformNode()) {
-      properties_changed_.transform_changed =
-          PaintPropertyChangeType::kNodeAddedOrRemoved;
-    }
-    if (properties->HasClipNode()) {
-      properties_changed_.clip_changed =
-          PaintPropertyChangeType::kNodeAddedOrRemoved;
-    }
-    if (properties->HasEffectNode()) {
-      properties_changed_.effect_changed =
-          PaintPropertyChangeType::kNodeAddedOrRemoved;
-    }
-    if (properties->Scroll()) {
-      properties_changed_.scroll_changed =
-          PaintPropertyChangeType::kNodeAddedOrRemoved;
-    }
-    fragment.ClearPaintProperties();
-  }
-}
-
-void PaintPropertyTreeBuilder::InitFragmentPaintPropertiesForNG(
-    bool needs_paint_properties) {
-  if (context_.fragments.empty())
-    context_.fragments.push_back(PaintPropertyTreeBuilderFragmentContext());
-  else
-    context_.fragments.resize(1);
-  context_.fragments[0].current.fragmentainer_idx =
-      pre_paint_info_->fragmentainer_idx;
-  InitFragmentPaintProperties(*pre_paint_info_->fragment_data,
-                              needs_paint_properties, context_.fragments[0]);
-}
-
-void PaintPropertyTreeBuilder::InitSingleFragmentFromParent(
-    bool needs_paint_properties) {
-  FragmentData& first_fragment =
-      object_.GetMutableForPainting().FirstFragment();
-  first_fragment.ClearNextFragment();
-  if (context_.fragments.empty()) {
-    context_.fragments.push_back(PaintPropertyTreeBuilderFragmentContext());
-  } else {
-    context_.fragments.resize(1);
-  }
-
-  PaintPropertyTreeBuilderFragmentContext& context = context_.fragments[0];
-  InitFragmentPaintProperties(first_fragment, needs_paint_properties, context);
-  if (context.current.fragmentainer_idx == WTF::kNotFound) {
-    // We're not fragmented, but we may have been previously. Reset the
-    // fragmentainer index.
-    first_fragment.SetFragmentID(0);
-  } else {
-    // We're inside monolithic content, but further out there's a fragmentation
-    // context. Keep the fragmentainer index, so that the contents end up in the
-    // right one.
-    first_fragment.SetFragmentID(context.current.fragmentainer_idx);
-  }
-}
-
-void PaintPropertyTreeBuilder::UpdateFragments() {
+void PaintPropertyTreeBuilder::InitPaintProperties() {
   bool needs_paint_properties =
-#if !DCHECK_IS_ON()
-      // If DCHECK is not on, use fast path for text.
-      !object_.IsText() &&
-#endif
+      ObjectTypeMightNeedPaintProperties() &&
       (NeedsPaintOffsetTranslation(object_, context_.direct_compositing_reasons,
                                    context_.container_for_fixed_position,
                                    context_.painting_layer) ||
@@ -3294,24 +3181,66 @@
   // If the object is a text, none of the above function should return true.
   DCHECK(!needs_paint_properties || !object_.IsText());
 
-  // Need of fragmentation clip will be determined in CreateFragmentContexts().
+  FragmentData& fragment = GetFragmentData();
+  if (const auto* properties = fragment.PaintProperties()) {
+    if (const auto* translation = properties->PaintOffsetTranslation()) {
+      // If there is a paint offset translation, it only causes a net change
+      // in additional_offset_to_layout_shift_root_delta by the amount the
+      // paint offset translation changed from the prior frame. To implement
+      // this, we record a negative offset here, and then re-add it in
+      // UpdatePaintOffsetTranslation. The net effect is that the value
+      // of additional_offset_to_layout_shift_root_delta is the difference
+      // between the old and new paint offset translation.
+      context_.fragment_context
+          .pending_additional_offset_to_layout_shift_root_delta =
+          -PhysicalOffset::FromVector2dFRound(translation->Get2dTranslation());
+    }
+    gfx::Vector2dF translation2d;
+    for (const TransformPaintPropertyNode* transform :
+         properties->AllCSSTransformPropertiesOutsideToInside()) {
+      if (transform) {
+        if (IsLayoutShiftRootTransform(*transform)) {
+          translation2d = gfx::Vector2dF();
+          break;
+        }
+        translation2d += transform->Get2dTranslation();
+      }
+    }
+    context_.fragment_context.translation_2d_to_layout_shift_root_delta -=
+        translation2d;
+  }
 
-  if (IsInNGFragmentTraversal()) {
-    InitFragmentPaintPropertiesForNG(needs_paint_properties);
-  } else {
-    InitSingleFragmentFromParent(needs_paint_properties);
+  if (needs_paint_properties) {
+    fragment.EnsurePaintProperties();
+  } else if (auto* properties = fragment.PaintProperties()) {
+    if (properties->HasTransformNode()) {
+      properties_changed_.transform_changed =
+          PaintPropertyChangeType::kNodeAddedOrRemoved;
+    }
+    if (properties->HasClipNode()) {
+      properties_changed_.clip_changed =
+          PaintPropertyChangeType::kNodeAddedOrRemoved;
+    }
+    if (properties->HasEffectNode()) {
+      properties_changed_.effect_changed =
+          PaintPropertyChangeType::kNodeAddedOrRemoved;
+    }
+    if (properties->Scroll()) {
+      properties_changed_.scroll_changed =
+          PaintPropertyChangeType::kNodeAddedOrRemoved;
+    }
+    fragment.ClearPaintProperties();
   }
 
   if (object_.IsSVGHiddenContainer()) {
     // SVG resources are painted within one or more other locations in the
     // SVG during paint, and hence have their own independent paint property
     // trees, paint offset, etc.
-    context_.fragments.clear();
-    context_.fragments.Grow(1);
+    context_.fragment_context = PaintPropertyTreeBuilderFragmentContext();
     context_.has_svg_hidden_container_ancestor = true;
-    PaintPropertyTreeBuilderFragmentContext& fragment_context =
-        context_.fragments[0];
 
+    PaintPropertyTreeBuilderFragmentContext& fragment_context =
+        context_.fragment_context;
     fragment_context.current.paint_offset_root =
         fragment_context.absolute_position.paint_offset_root =
             fragment_context.fixed_position.paint_offset_root = &object_;
@@ -3325,6 +3254,36 @@
   }
 }
 
+FragmentData& PaintPropertyTreeBuilder::GetFragmentData() const {
+  if (pre_paint_info_) {
+    CHECK(pre_paint_info_->fragment_data);
+    return *pre_paint_info_->fragment_data;
+  }
+  return object_.GetMutableForPainting().FirstFragment();
+}
+
+void PaintPropertyTreeBuilder::UpdateFragmentData() {
+  FragmentData& fragment = GetFragmentData();
+  if (IsInNGFragmentTraversal()) {
+    context_.fragment_context.current.fragmentainer_idx =
+        pre_paint_info_->fragmentainer_idx;
+  } else {
+    fragment.ClearNextFragment();
+
+    if (context_.fragment_context.current.fragmentainer_idx == WTF::kNotFound) {
+      // We're not fragmented, but we may have been previously. Reset the
+      // fragmentainer index.
+      fragment.SetFragmentID(0);
+    } else {
+      // We're inside monolithic content, but further out there's a
+      // fragmentation context. Keep the fragmentainer index, so that the
+      // contents end up in the right one.
+      fragment.SetFragmentID(
+          context_.fragment_context.current.fragmentainer_idx);
+    }
+  }
+}
+
 bool PaintPropertyTreeBuilder::ObjectTypeMightNeedPaintProperties() const {
   return !object_.IsText() && (object_.IsBoxModelObject() || object_.IsSVG());
 }
@@ -3384,32 +3343,13 @@
   }
 
   UpdatePaintingLayer();
+  UpdateFragmentData();
+  InitPaintProperties();
 
-  if (ObjectTypeMightNeedPaintProperties()) {
-    UpdateFragments();
-  } else {
-    DCHECK_EQ(context_.direct_compositing_reasons, CompositingReason::kNone);
-    if (!IsInNGFragmentTraversal())
-      object_.GetMutableForPainting().FirstFragment().ClearNextFragment();
-  }
-
-  if (pre_paint_info_) {
-    DCHECK_EQ(context_.fragments.size(), 1u);
-    FragmentPaintPropertyTreeBuilder builder(object_, pre_paint_info_, context_,
-                                             context_.fragments[0],
-                                             *pre_paint_info_->fragment_data);
-    builder.UpdateForSelf();
-    properties_changed_.Merge(builder.PropertiesChanged());
-  } else {
-    DCHECK_EQ(context_.fragments.size(), 1u);
-    auto* fragment_data = &object_.GetMutableForPainting().FirstFragment();
-    auto& fragment_context = context_.fragments[0];
-    FragmentPaintPropertyTreeBuilder builder(
-        object_, /* pre_paint_info */ nullptr, context_, fragment_context,
-        *fragment_data);
-    builder.UpdateForSelf();
-    properties_changed_.Merge(builder.PropertiesChanged());
-  }
+  FragmentPaintPropertyTreeBuilder builder(object_, pre_paint_info_, context_,
+                                           GetFragmentData());
+  builder.UpdateForSelf();
+  properties_changed_.Merge(builder.PropertiesChanged());
 
   if (!PrePaintDisableSideEffectsScope::IsDisabled()) {
     object_.GetMutableForPainting()
@@ -3456,23 +3396,12 @@
   if (!ObjectTypeMightNeedPaintProperties())
     return;
 
-  FragmentData* fragment_data;
-  if (pre_paint_info_) {
-    DCHECK_EQ(context_.fragments.size(), 1u);
-    fragment_data = pre_paint_info_->fragment_data;
-    DCHECK(fragment_data);
-  } else {
-    fragment_data = &object_.GetMutableForPainting().FirstFragment();
-  }
-
   // For now, only consider single fragment elements as possible isolation
   // boundaries.
   // TODO(crbug.com/890932): See if this is needed.
   bool is_isolated = true;
-  DCHECK_EQ(context_.fragments.size(), 1u);
-  auto& fragment_context = context_.fragments[0];
   FragmentPaintPropertyTreeBuilder builder(object_, pre_paint_info_, context_,
-                                           fragment_context, *fragment_data);
+                                           GetFragmentData());
   // The element establishes an isolation boundary if it has isolation nodes
   // before and after updating the children. In other words, if it didn't have
   // isolation nodes previously then we still want to do a subtree walk. If it
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
index 327ec96..1ec0c04 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.h
@@ -6,16 +6,11 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_PROPERTY_TREE_BUILDER_H_
 
 #include "base/dcheck_is_on.h"
-#include "base/memory/scoped_refptr.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 #include "third_party/blink/renderer/platform/graphics/paint/clip_paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/effect_paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/scroll_paint_property_node.h"
 #include "third_party/blink/renderer/platform/graphics/paint/transform_paint_property_node.h"
-#include "third_party/blink/renderer/platform/heap/collection_support/clear_collection_scope.h"
-#include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
-#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
@@ -31,22 +26,18 @@
 // It's responsible for bookkeeping tree state in other order, for example, the
 // most recent position container seen.
 struct PaintPropertyTreeBuilderFragmentContext {
-  DISALLOW_NEW();
+  STACK_ALLOCATED();
 
  public:
   // Initializes all property tree nodes to the roots.
   PaintPropertyTreeBuilderFragmentContext();
 
-  void Trace(Visitor*) const;
-
   // State that propagates on the containing block chain (and so is adjusted
   // when an absolute or fixed position object is encountered).
   struct ContainingBlockContext {
-    DISALLOW_NEW();
+    STACK_ALLOCATED();
 
    public:
-    void Trace(Visitor*) const;
-
     // The combination of a transform and paint offset describes a linear space.
     // When a layout object recur to its children, the main context is expected
     // to refer the object's border box, then the callee will derive its own
@@ -85,7 +76,7 @@
     PhysicalOffset directly_composited_container_paint_offset_subpixel_delta;
 
     // The PaintLayer corresponding to the origin of |paint_offset|.
-    Member<const LayoutObject> paint_offset_root;
+    const LayoutObject* paint_offset_root;
 
     // True if any fixed-position children within this context are fixed to the
     // root of the FrameView (and hence above its scroll).
@@ -182,15 +173,12 @@
 
  public:
   PaintPropertyTreeBuilderContext();
-  PaintPropertyTreeBuilderContext(const PaintPropertyTreeBuilderContext&) =
-      default;
-  ~PaintPropertyTreeBuilderContext();
 
-  // TODO(paint-dev): With the removal of legacy block fragmentation support,
-  // there'll only ever be one entry in this vector.
-  // PaintPropertyTreeBuilderFragmentContext should be folded into
-  // PaintPropertyTreeBuilderContext.
-  HeapVector<PaintPropertyTreeBuilderFragmentContext, 1> fragments;
+  // TODO(paint-dev): We should fold PaintPropertyTreeBuilderFragmentContext
+  // into PaintPropertyTreeBuilderContext but we can't do it for now because
+  // SVG hidden containers need the default constructor of the former to
+  // initialize an independent paint property tree context.
+  PaintPropertyTreeBuilderFragmentContext fragment_context;
 
   const LayoutObject* container_for_absolute_position = nullptr;
   const LayoutObject* container_for_fixed_position = nullptr;
@@ -364,21 +352,17 @@
   static bool ScheduleDeferredOpacityNodeUpdate(LayoutObject& object);
 
  private:
-  ALWAYS_INLINE void InitFragmentPaintProperties(
-      FragmentData&,
-      bool needs_paint_properties,
-      PaintPropertyTreeBuilderFragmentContext&);
-  ALWAYS_INLINE void InitFragmentPaintPropertiesForNG(
-      bool needs_paint_properties);
-  ALWAYS_INLINE void InitSingleFragmentFromParent(bool needs_paint_properties);
+  ALWAYS_INLINE void InitPaintProperties();
   ALWAYS_INLINE bool ObjectTypeMightNeedPaintProperties() const;
-  ALWAYS_INLINE void UpdateFragments();
+  ALWAYS_INLINE FragmentData& GetFragmentData() const;
+  ALWAYS_INLINE void UpdateFragmentData();
   ALWAYS_INLINE void UpdatePaintingLayer();
   ALWAYS_INLINE bool IsAffectedByOuterViewportBoundsDelta() const;
 
   ALWAYS_INLINE void UpdateGlobalMainThreadScrollingReasons();
 
   bool IsInNGFragmentTraversal() const { return pre_paint_info_; }
+
   static bool CanDoDeferredTransformNodeUpdate(const LayoutObject& object);
   static bool CanDoDeferredOpacityNodeUpdate(const LayoutObject& object);
 
@@ -390,7 +374,4 @@
 
 }  // namespace blink
 
-WTF_ALLOW_CLEAR_UNUSED_SLOTS_WITH_MEM_FUNCTIONS(
-    blink::PaintPropertyTreeBuilderFragmentContext)
-
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_PAINT_PROPERTY_TREE_BUILDER_H_
diff --git a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
index c1b83a2b..bc1f4f8 100644
--- a/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
+++ b/third_party/blink/renderer/core/paint/pre_paint_tree_walk.cc
@@ -671,7 +671,7 @@
   PaintPropertyTreeBuilderContext& property_context =
       *context.tree_builder_context;
   PaintPropertyTreeBuilderFragmentContext& fragment_context =
-      property_context.fragments[0];
+      property_context.fragment_context;
   // Reset the relevant OOF context to this fragmentainer, since this is its
   // containing block, as far as the NG fragment structure is concerned.
   property_context.container_for_absolute_position = &object;
@@ -717,7 +717,7 @@
       if (descendant_context.tree_builder_context.has_value()) {
         PaintPropertyTreeBuilderContext* builder_context =
             &descendant_context.tree_builder_context.value();
-        builder_context->fragments[0].current.paint_offset +=
+        builder_context->fragment_context.current.paint_offset +=
             offset_to_block_start_edge;
       }
 
@@ -815,7 +815,7 @@
         containing_block_context = nullptr;
     if (LIKELY(fragmentainer_context.tree_builder_context)) {
       PaintPropertyTreeBuilderFragmentContext& fragment_context =
-          fragmentainer_context.tree_builder_context->fragments[0];
+          fragmentainer_context.tree_builder_context->fragment_context;
       containing_block_context = &fragment_context.current;
       containing_block_context->paint_offset += child.offset;
 
@@ -1253,7 +1253,8 @@
         if (!physical_fragment || physical_fragment->IsFirstForNode() ||
             CanPaintMultipleFragments(*physical_fragment)) {
           if (context.tree_builder_context) {
-            auto& current = context.tree_builder_context->fragments[0].current;
+            auto& current =
+                context.tree_builder_context->fragment_context.current;
             current.paint_offset = PhysicalOffset(ToRoundedPoint(
                 current.paint_offset +
                 layout_embedded_content->ReplacedContentRect().offset -
diff --git a/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc b/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
index 64925b3..1546013 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
+++ b/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
@@ -1424,7 +1424,21 @@
       // TODO(kenrb): Add some renderer-side validation here, such as
       // validating |provider|, and making sure the calling context is legal.
       // Some of this has not been spec'd yet.
+
+      if (!provider->hasConfigURL()) {
+        exception_state.ThrowTypeError("Missing the provider's configURL.");
+        resolver->Detach();
+        return ScriptPromise();
+      }
+
       KURL provider_url(provider->configURL());
+
+      if (!provider->hasClientId()) {
+        exception_state.ThrowTypeError("Missing the provider's clientId.");
+        resolver->Detach();
+        return ScriptPromise();
+      }
+
       String client_id = provider->clientId();
 
       ++provider_index;
diff --git a/third_party/blink/renderer/modules/credentialmanagement/identity_provider_config.idl b/third_party/blink/renderer/modules/credentialmanagement/identity_provider_config.idl
index 1eef730c..344d2be 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/identity_provider_config.idl
+++ b/third_party/blink/renderer/modules/credentialmanagement/identity_provider_config.idl
@@ -6,8 +6,8 @@
 
 dictionary IdentityProviderConfig {
   // URL for the Identity Provider Configuration.
-  required USVString configURL;
-  required USVString clientId;
+  USVString configURL;
+  USVString clientId;
   USVString nonce;
   DOMString loginHint;
   [RuntimeEnabled=FedCmHostedDomain] DOMString hostedDomain;
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
index 48f7630..ce33eb40 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
@@ -1906,8 +1906,9 @@
 #endif
 TEST_F(HarfBuzzShaperTest, MAYBE_EmojiPercentage) {
 #if BUILDFLAG(IS_MAC)
-  if (base::mac::IsAtLeastOS11())
+  if (base::mac::MacOSMajorVersion() >= 11) {
     GTEST_SKIP() << "Broken on macOS >= 11: https://crbug.com/1194323";
+  }
 #endif
 #if BUILDFLAG(IS_WIN)
   if (base::win::OSInfo::GetInstance()->version() >=
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index 263458b1..400ad66 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -732,8 +732,9 @@
 
             # Useful for platform-specific code.
             'base::apple::(CFToNSPtrCast|NSToCFPtrCast|CFToNSOwnershipCast|NSToCFOwnershipCast)',
-            'base::mac::Is(AtMost|AtLeast)?OS.+',
             'base::apple::ScopedCFTypeRef',
+            'base::mac::MacOSVersion',
+            'base::mac::MacOSMajorVersion',
         ],
         'disallowed': [
             ('base::Bind(|Once|Repeating)',
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 6699ff22..a9360a95 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -318,6 +318,9 @@
 crbug.com/1473117 external/wpt/html/canvas/element/text/2d.text.measure.baselines.html [ Failure ]
 crbug.com/1473117 external/wpt/html/canvas/offscreen/text/2d.text.measure.baselines.html [ Failure ]
 crbug.com/1473117 external/wpt/html/canvas/offscreen/text/2d.text.measure.baselines.worker.html [ Failure ]
+# Context lost.
+crbug.com/1476080 [ Linux ] virtual/no-alloc-direct-call/fast/canvas/offscreencanvas-oncontextlost.html [ Failure Pass ]
+crbug.com/1476080 [ Linux ] virtual/oopr-canvas2d/fast/canvas/offscreencanvas-lost-gpu-context.html [ Failure Pass ]
 
 # Off by one pixel error on ref test
 crbug.com/1259367 [ Mac11-arm64 Release ] printing/offscreencanvas-webgl-printing.html [ Failure ]
@@ -6622,8 +6625,6 @@
 # Flaky
 crbug.com/1459333 [ Mac11 ] virtual/disable-frequency-capping-for-overlay-popup-detection/http/tests/subresource_filter/overlay_popup_ad/overlay-popup-ad-viewport-resize.html [ Failure Pass ]
 # Slightly flaky
-crbug.com/1459304 [ Linux ] virtual/no-alloc-direct-call/fast/canvas/offscreencanvas-oncontextlost.html [ Failure Pass ]
-crbug.com/1459304 [ Linux ] virtual/oopr-canvas2d/fast/canvas/offscreencanvas-lost-gpu-context.html [ Failure Pass ]
 crbug.com/1459304 [ Linux ] virtual/partitioned-cookies/http/tests/inspector-protocol/network/request-will-be-sent-extra-info-connect-timing.js [ Failure Pass ]
 crbug.com/1459304 [ Linux ] fast/filesystem/workers/file-writer-events.html [ Failure Pass ]
 crbug.com/1459304 [ Linux ] virtual/compositor-threaded-percent-based-scrolling/fast/scrolling/scrolling-container-moves-offscreen.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization-expected.txt
deleted file mode 100644
index 87a8e7b0..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization-expected.txt
+++ /dev/null
@@ -1,51 +0,0 @@
-This is a testharness.js-based test.
-FAIL e.style.cssText = grid: auto-flow auto / 100px 100px should set grid assert_equals: grid serialization should be canonical expected "none / 100px 100px" but got "auto-flow auto / 100px 100px"
-PASS e.style.cssText = grid: auto-flow auto / 100px 100px should set grid-template-areas
-PASS e.style.cssText = grid: auto-flow auto / 100px 100px; grid-template-areas: "one two" "three four" should set grid
-PASS e.style.cssText = grid: auto-flow auto / 100px 100px; grid-template-areas: "one two" "three four" should set grid-auto-flow
-PASS e.style.cssText = grid: auto-flow auto / 100px 100px; grid-template-areas: "one two" "three four" should set grid-auto-rows
-PASS e.style.cssText = grid: auto-flow auto / 100px 100px; grid-template-areas: "one two" "three four" should set grid-template-areas
-PASS e.style.cssText = grid: 30px 40px / 50px 60px; grid-auto-flow: column should set grid
-PASS e.style.cssText = grid: 30px 40px / 50px 60px; grid-auto-flow: column should set grid-auto-flow
-PASS e.style.cssText = grid: 30px 40px / 50px 60px; grid-auto-flow: column should set grid-template
-PASS cssText ('grid-auto-columns: auto; grid-auto-rows: auto; grid-auto-flow: column; grid-template: 30px 40px / 50px 60px;') must contain 'grid-template: 30px 40px / 50px 60px;'
-PASS cssText ('grid-auto-columns: auto; grid-auto-rows: auto; grid-auto-flow: column; grid-template: 30px 40px / 50px 60px;') must contain 'grid-auto-rows: auto;'
-PASS cssText ('grid-auto-columns: auto; grid-auto-rows: auto; grid-auto-flow: column; grid-template: 30px 40px / 50px 60px;') must contain 'grid-auto-columns: auto;'
-PASS cssText ('grid-auto-columns: auto; grid-auto-rows: auto; grid-auto-flow: column; grid-template: 30px 40px / 50px 60px;') must contain 'grid-auto-flow: column;'
-FAIL e.style.cssText = grid: auto-flow / 10px; grid-template-rows: 20px should set grid assert_equals: grid serialization should be canonical expected "20px / 10px" but got ""
-PASS e.style.cssText = grid: auto-flow / 10px; grid-template-rows: 20px should set grid-template
-FAIL cssText ('grid-auto-flow: row; grid-auto-columns: auto; grid-auto-rows: auto; grid-template: 20px / 10px;') must contain 'grid: 20px / 10px;' assert_true: cssText ('grid-auto-flow: row; grid-auto-columns: auto; grid-auto-rows: auto; grid-template: 20px / 10px;') must contain 'grid: 20px / 10px;' expected true got false
-PASS e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-flow: inherit should set grid
-FAIL e.style.cssText = grid: 1px / 2px; grid-auto-flow: row should set grid assert_equals: grid serialization should be canonical expected "1px / 2px" but got ""
-FAIL e.style.cssText = grid: 1px / 2px; grid-auto-columns: auto should set grid assert_equals: grid serialization should be canonical expected "1px / 2px" but got ""
-FAIL e.style.cssText = grid: 1px / 2px; grid-auto-rows: auto should set grid assert_equals: grid serialization should be canonical expected "1px / 2px" but got ""
-PASS e.style.cssText = grid: 1px / auto-flow 2px; grid-auto-rows: auto should set grid
-FAIL e.style.cssText = grid: 1px / auto-flow; grid-auto-columns: auto should set grid assert_equals: grid serialization should be canonical expected "1px / auto-flow" but got "1px / auto-flow auto"
-PASS e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-columns: auto should set grid
-FAIL e.style.cssText = grid: auto-flow dense / 2px; grid-auto-rows: auto should set grid assert_equals: grid serialization should be canonical expected "auto-flow dense / 2px" but got "auto-flow dense auto / 2px"
-FAIL e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-columns: 3px should set grid assert_equals: grid serialization should be canonical expected "" but got "auto-flow 1px / 2px"
-PASS e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-columns: 3px should set grid-auto-columns
-PASS e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-columns: 3px should set grid-auto-flow
-PASS e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-columns: 3px should set grid-auto-rows
-PASS e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-columns: 3px should set grid-template-columns
-PASS e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-columns: 3px should set grid-template-rows
-FAIL e.style.cssText = grid: 1px / auto-flow 2px; grid-auto-rows: 3px should set grid assert_equals: grid serialization should be canonical expected "" but got "1px / auto-flow 2px"
-PASS e.style.cssText = grid: 1px / auto-flow 2px; grid-auto-rows: 3px should set grid-auto-columns
-PASS e.style.cssText = grid: 1px / auto-flow 2px; grid-auto-rows: 3px should set grid-auto-flow
-PASS e.style.cssText = grid: 1px / auto-flow 2px; grid-auto-rows: 3px should set grid-auto-rows
-PASS e.style.cssText = grid: 1px / auto-flow 2px; grid-auto-rows: 3px should set grid-template-columns
-PASS e.style.cssText = grid: 1px / auto-flow 2px; grid-auto-rows: 3px should set grid-template-rows
-FAIL e.style.cssText = grid: none / auto-flow 1px; grid-template-columns: 3px should set grid assert_equals: grid serialization should be canonical expected "" but got "none / auto-flow 1px"
-PASS e.style.cssText = grid: none / auto-flow 1px; grid-template-columns: 3px should set grid-auto-columns
-PASS e.style.cssText = grid: none / auto-flow 1px; grid-template-columns: 3px should set grid-auto-flow
-PASS e.style.cssText = grid: none / auto-flow 1px; grid-template-columns: 3px should set grid-auto-rows
-PASS e.style.cssText = grid: none / auto-flow 1px; grid-template-columns: 3px should set grid-template-columns
-PASS e.style.cssText = grid: none / auto-flow 1px; grid-template-columns: 3px should set grid-template-rows
-FAIL e.style.cssText = grid: auto-flow 1px / none; grid-template-rows: 3px should set grid assert_equals: grid serialization should be canonical expected "" but got "auto-flow 1px / none"
-PASS e.style.cssText = grid: auto-flow 1px / none; grid-template-rows: 3px should set grid-auto-columns
-PASS e.style.cssText = grid: auto-flow 1px / none; grid-template-rows: 3px should set grid-auto-flow
-PASS e.style.cssText = grid: auto-flow 1px / none; grid-template-rows: 3px should set grid-auto-rows
-PASS e.style.cssText = grid: auto-flow 1px / none; grid-template-rows: 3px should set grid-template-columns
-PASS e.style.cssText = grid: auto-flow 1px / none; grid-template-rows: 3px should set grid-template-rows
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization.html b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization.html
index 20ba330..7751fd8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization.html
@@ -78,11 +78,13 @@
 
   test_shorthand_roundtrip('grid: auto-flow 1px / 2px; grid-auto-flow: inherit', { 'grid': '' });
   test_shorthand_roundtrip('grid: 1px / 2px; grid-auto-flow: row', { 'grid': '1px / 2px' });
+  test_shorthand_roundtrip('grid: none / 2px; grid-auto-flow: row', { 'grid': 'none / 2px' });
   test_shorthand_roundtrip('grid: 1px / 2px; grid-auto-columns: auto', { 'grid': '1px / 2px' });
   test_shorthand_roundtrip('grid: 1px / 2px; grid-auto-rows: auto', { 'grid': '1px / 2px' });
   test_shorthand_roundtrip('grid: 1px / auto-flow 2px; grid-auto-rows: auto', { 'grid': '1px / auto-flow 2px' });
   test_shorthand_roundtrip('grid: 1px / auto-flow; grid-auto-columns: auto', { 'grid': '1px / auto-flow' });
   test_shorthand_roundtrip('grid: auto-flow 1px / 2px; grid-auto-columns: auto', { 'grid': 'auto-flow 1px / 2px' });
+  test_shorthand_roundtrip('grid: auto-flow 1px / none; grid-auto-columns: auto', { 'grid': 'auto-flow 1px / none' });
   test_shorthand_roundtrip('grid: auto-flow dense / 2px; grid-auto-rows: auto', { 'grid': 'auto-flow dense / 2px' });
 
   test_shorthand_roundtrip('grid: auto-flow 1px / 2px; grid-auto-columns: 3px',
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization.html.ini b/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization.html.ini
deleted file mode 100644
index 15220d5d..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/parsing/grid-shorthand-serialization.html.ini
+++ /dev/null
@@ -1,36 +0,0 @@
-[grid-shorthand-serialization.html]
-  [cssText ('grid-auto-flow: row; grid-auto-columns: auto; grid-auto-rows: auto; grid-template: 20px / 10px;') must contain 'grid: 20px / 10px;']
-    expected: FAIL
-
-  [e.style.cssText = grid: 1px / 2px; grid-auto-columns: auto should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: 1px / 2px; grid-auto-flow: row should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: 1px / 2px; grid-auto-rows: auto should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: 1px / auto-flow 2px; grid-auto-rows: 3px should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: 1px / auto-flow; grid-auto-columns: auto should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: auto-flow / 10px; grid-template-rows: 20px should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: auto-flow 1px / 2px; grid-auto-columns: 3px should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: auto-flow 1px / none; grid-template-rows: 3px should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: auto-flow auto / 100px 100px should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: auto-flow dense / 2px; grid-auto-rows: auto should set grid]
-    expected: FAIL
-
-  [e.style.cssText = grid: none / auto-flow 1px; grid-template-columns: 3px should set grid]
-    expected: FAIL
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/TODO b/third_party/blink/web_tests/external/wpt/fledge/tentative/TODO
index fc2c296..5090044 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/TODO
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/TODO
@@ -3,22 +3,34 @@
 * directFromSellerSignals.
 * All generateBid() and scoreAd() input parameters.
 * All interest group fields (passed to auction, have effect on auction).
-    Validation when joining/leaving interest group is already covered.
+    Very few fields covered.
+    Should be sure to cover buyerAndSellerReportingId and buyerReportingId for
+        component ads, as they should not be settable.
+    Already covered:
+        Validation when joining/leaving interest group.
+        renderURL and metadata for component ads (only integers covered, but
+            probably not worth covering all types, if we have coverage for the
+            main renderURL).
 * Filtering/prioritization (including bidding signals influencing priorities)
 * Size restrictions.
+* additionalBids.
+* adCost.
+* bidCurrency.
+* modellingSignals.
+* Ad and component ad sizes.
 * Updates (both after auction and triggered).
 * All auctionConfig parameters (including invalid auctionConfigs, and ones
     with no buyers).
 * Joining interest group with duration of 0 should just leave the IG (not
     currently guaranteed to work, due to potential time skew between processes).
 * Multiple buyers.
-* Multiple interest group with same owner.
+* Multiple interest groups with same owner.
 * Multiple origin auctions (buyer != publisher != seller).
 * Multiple frame tests (including join IG policy, run auction policy,
     loading URNs in fencedframes in other frames, loading component
     ad URNs in fenced frames of other frames, etc)
 * adAuctionConfig passed to reportResult().
-* Component ads (including scoring signals fetches).
+* Component ads scoring signals fetches.
 * Component auctions.
 * browserSignals fields in scoring/bidding methods.
 * In reporting methods, browserSignals fields: dataVersion, topLevelSeller,
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.sub.window.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.sub.window.js
index 989d761..d4c14862 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.sub.window.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.sub.window.js
@@ -116,15 +116,15 @@
 });
 
 makeTest({
-  name: 'trustedScoringSignalsUrl is invalid',
+  name: 'trustedScoringSignalsURL is invalid',
   expect: EXPECT_EXCEPTION(TypeError),
-  auctionConfigOverrides: { trustedScoringSignalsUrl: "https://foo:99999999999" },
+  auctionConfigOverrides: { trustedScoringSignalsURL: "https://foo:99999999999" },
 });
 
 makeTest({
-  name: 'trustedScoringSignalsUrl is cross-origin with seller',
+  name: 'trustedScoringSignalsURL is cross-origin with seller',
   expect: EXPECT_EXCEPTION(TypeError),
-  auctionConfigOverrides: { trustedScoringSignalsUrl: "https://example.com" },
+  auctionConfigOverrides: { trustedScoringSignalsURL: "https://example.com" },
 });
 
 makeTest({
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.sub.window.js.ini b/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.sub.window.js.ini
index bbe09211..f9da46e7 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.sub.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/auction-config.https.sub.window.js.ini
@@ -83,10 +83,10 @@
     expected:
       if product == "chrome": FAIL
 
-  [trustedScoringSignalsUrl is cross-origin with seller]
+  [trustedScoringSignalsURL is cross-origin with seller]
     expected:
       if product == "chrome": FAIL
 
-  [trustedScoringSignalsUrl is invalid]
+  [trustedScoringSignalsURL is invalid]
     expected:
       if product == "chrome": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/component-ads.https.sub.window.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/component-ads.https.sub.window.js
new file mode 100644
index 0000000..813ac361
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/component-ads.https.sub.window.js
@@ -0,0 +1,435 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.js
+// META: timeout=long
+
+"use strict";
+
+// Creates a tracker URL for a component ad. These are fetched from component ad URLs.
+function createComponentAdTrackerURL(uuid, id) {
+  return createTrackerUrl(window.location.origin, uuid, 'track_get',
+                          `component_ad_${id}`)
+}
+
+// Returns a component ad render URL that fetches the correspondinding component ad
+// tracker URL.
+function createComponentAdRenderURL(uuid, id) {
+  return createRenderUrl(
+      uuid,
+      `fetch("${createComponentAdTrackerURL(uuid, id)}");`);
+}
+
+// Runs a generic component ad loading test. It joins an interest group with a
+// "numComponentAdsInInterestGroup" component ads. The IG will make a bid that
+// potentially includes some of them. Then an auction will be run, component
+// ads potentially will be loaded in nested fenced frame within the main frame,
+// and the test will make sure that each component ad render URL that should have
+// been loaded in an iframe was indeed loaded.
+//
+// Joins an interest group that has "numComponentAdsInInterestGroup" component ads.
+//
+// "componentAdsInBid" is a list of 0-based indices of which of those ads will be
+// included in the bid. It may contain duplicate component ads. If it's null then the
+// bid will have no adComponents field, while if it is empty, the bid will have an empty
+// adComponents field.
+//
+// "componentAdsToLoad" is another list of 0-based ad components, but it's the index of
+// fenced frame configs in the top frame ad's getNestedConfigs(). It may also contain
+// duplicates to load a particular ad twice.
+//
+// If "adMetadata" is true, metadata is added to each component ad. Only integer metadata
+// is used, relying on renderURL tests to cover other types of renderURL metadata.
+async function runComponentAdLoadingTest(test, uuid, numComponentAdsInInterestGroup,
+                                         componentAdsInBid, componentAdsToLoad,
+                                         adMetadata = false) {
+  let interestGroupAdComponents = [];
+  for (let i = 0; i < numComponentAdsInInterestGroup; ++i) {
+    const componentRenderURL = createComponentAdRenderURL(uuid, i);
+    let adComponent = {renderURL: componentRenderURL};
+    if (adMetadata)
+      adComponent.metadata = i;
+    interestGroupAdComponents.push(adComponent);
+  }
+
+  const renderURL = createRenderUrl(
+      uuid,
+      `// "status" is passed to the beacon URL, to be verified by waitForObservedRequests().
+       let status = "ok";
+       const componentAds = window.fence.getNestedConfigs()
+       if (componentAds.length != 20)
+         status = "unexpected getNestedConfigs() length";
+       for (let i of ${JSON.stringify(componentAdsToLoad)}) {
+         let fencedFrame = document.createElement("fencedframe");
+         fencedFrame.mode = "opaque-ads";
+         fencedFrame.config = componentAds[i];
+         document.body.appendChild(fencedFrame);
+       }
+
+       window.fence.reportEvent({eventType: "beacon",
+                                 eventData: status,
+                                 destination: ["buyer"]});`
+      );
+
+  let bid = {bid:1, render: renderURL};
+  if (componentAdsInBid) {
+    bid.adComponents = [];
+    for (let index of componentAdsInBid) {
+      bid.adComponents.push(interestGroupAdComponents[index].renderURL);
+    }
+  }
+
+  // In these tests, the bidder should always request a beacon URL.
+  let expectedTrackerURLs = [`${createBidderBeaconUrl(uuid)}, body: ok`];
+  // Figure out which, if any, elements of "componentAdsToLoad" correspond to
+  // component ads listed in bid.adComponents, and for those ads, add a tracker URL
+  // to "expectedTrackerURLs".
+  if (componentAdsToLoad && bid.adComponents) {
+    for (let index of componentAdsToLoad) {
+      if (index < componentAdsInBid.length)
+        expectedTrackerURLs.push(createComponentAdTrackerURL(uuid, componentAdsInBid[index]));
+    }
+  }
+
+  await joinInterestGroup(
+      test, uuid,
+      { biddingLogicURL:
+          createBiddingScriptURL({
+              generateBid:
+                  `let expectedAdComponents = ${JSON.stringify(interestGroupAdComponents)};
+                   let adComponents = interestGroup.adComponents;
+                   if (adComponents.length !== expectedAdComponents.length)
+                     throw "Unexpected adComponents";
+                   for (let i = 0; i < adComponents.length; ++i) {
+                    if (adComponents[i].renderURL !== expectedAdComponents[i].renderURL ||
+                        adComponents[i].metadata !== expectedAdComponents[i].metadata) {
+                      throw "Unexpected adComponents";
+                    }
+                   }
+                   return ${JSON.stringify(bid)}`,
+              reportWin:
+                  `registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});` }),
+        ads: [{renderURL: renderURL}],
+        adComponents: interestGroupAdComponents});
+
+  if (!bid.adComponents || bid.adComponents.length === 0) {
+    await runBasicFledgeAuctionAndNavigate(
+      test, uuid,
+      {decisionLogicURL: createDecisionScriptURL(
+        uuid,
+        { scoreAd: `if (browserSignals.adComponents !== undefined)
+                      throw "adComponents should be undefined"`})});
+  } else {
+    await runBasicFledgeAuctionAndNavigate(
+      test, uuid,
+      {decisionLogicURL: createDecisionScriptURL(
+        uuid,
+        { scoreAd:
+              `if (JSON.stringify(browserSignals.adComponents) !==
+                       '${JSON.stringify(bid.adComponents)}') {
+                 throw "Unexpected adComponents: " + JSON.stringify(browserSignals.adComponents);
+               }`})});
+  }
+
+  await waitForObservedRequests(uuid, expectedTrackerURLs);
+}
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+
+  const renderURL = createRenderUrl(
+    uuid,
+    `let status = "ok";
+     const nestedConfigsLength = window.fence.getNestedConfigs().length
+     // "getNestedConfigs()" should return a list of 20 configs, to avoid leaking
+     // whether there were any component URLs to the page.
+     if (nestedConfigsLength != 20)
+       status = "unexpected getNestedConfigs() length: " + nestedConfigsLength;
+     window.fence.reportEvent({eventType: "beacon",
+                               eventData: status,
+                               destination: ["buyer"]});`);
+  await joinInterestGroup(
+      test, uuid,
+      { biddingLogicURL:
+          createBiddingScriptURL({
+              generateBid:
+                  'if (interestGroup.componentAds !== undefined) throw "unexpected componentAds"',
+              reportWin:
+                  `registerAdBeacon({beacon: "${createBidderBeaconUrl(uuid)}"});` }),
+        ads: [{renderUrl: renderURL}]});
+  await runBasicFledgeAuctionAndNavigate(
+      test, uuid,
+      {decisionLogicURL: createDecisionScriptURL(
+        uuid,
+        { scoreAd: `if (browserSignals.adComponents !== undefined)
+                      throw "adComponents should be undefined"`})});
+  await waitForObservedRequests(uuid, [`${createBidderBeaconUrl(uuid)}, body: ok`]);
+}, 'Group has no component ads, no adComponents in bid.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+
+  await runBasicFledgeTestExpectingNoWinner(
+      test,
+      {uuid: uuid,
+       interestGroupOverrides: {
+           biddingLogicURL:
+           createBiddingScriptURL({
+               generateBid:
+                   `return {bid: 1,
+                            render: interestGroup.ads[0].renderUrl,
+                            adComponents: []};`})}});
+}, 'Group has no component ads, adComponents in bid is empty array.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  await runComponentAdLoadingTest(
+      test, uuid, /*numComponentAdsInInterestGroup=*/2, /*componentAdsInBid=*/null,
+      // Try to load ad components, even though there are none. This should load
+      // about:blank in those frames, though that's not testible.
+      // The waitForObservedRequests() call may see extra requests, racily, if
+      // component ads not found in the bid are used.
+      /*componentAdsToLoad=*/[0, 1]);
+}, 'Group has component ads, but not used in bid (no adComponents field).');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  await runComponentAdLoadingTest(
+      test, uuid, /*numComponentAdsInInterestGroup=*/2, /*componentAdsInBid=*/[],
+      // Try to load ad components, even though there are none. This should load
+      // about:blank in those frames, though that's not testible.
+      // The waitForObservedRequests() call may see extra requests, racily, if
+      // component ads not found in the bid are used.
+      /*componentAdsToLoad=*/[0, 1]);
+}, 'Group has component ads, but not used in bid (adComponents field empty array).');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  await runComponentAdLoadingTest(
+      test, uuid, /*numComponentAdsInInterestGroup=*/2, /*componentAdsInBid=*/null,
+      // Try to load ad components, even though there are none. This should load
+      // about:blank in those frames, though that's not testible.
+      // The waitForObservedRequests() call may see extra requests, racily, if
+      // component ads not found in the bid are used.
+      /*componentAdsToLoad=*/[0, 1], /*adMetadata=*/true);
+}, 'Unused component ads with metadata.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+
+  await runBasicFledgeTestExpectingNoWinner(
+      test,
+      { uuid: uuid,
+        interestGroupOverrides: {
+            biddingLogicURL:
+                createBiddingScriptURL({
+                    generateBid:
+                        `return {bid: 1,
+                                 render: interestGroup.ads[0].renderUrl,
+                                 adComponents: ["https://random.url.test/"]};`}),
+            adComponents: [{renderURL: createComponentAdRenderURL(uuid, 0)}]}});
+}, 'Unknown component ad URL in bid.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+
+  await runBasicFledgeTestExpectingNoWinner(
+      test,
+      { uuid: uuid,
+        interestGroupOverrides: {
+            biddingLogicURL:
+                createBiddingScriptURL({
+                    generateBid:
+                        `return {bid: 1,
+                                 render: interestGroup.ads[0].renderUrl,
+                                 adComponents: [interestGroup.ads[0].renderUrl]};`}),
+            adComponents: [{renderURL: createComponentAdRenderURL(uuid, 0)}]}});
+}, 'Render URL used as component ad URL in bid.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+
+  await runBasicFledgeTestExpectingNoWinner(
+      test,
+      { uuid: uuid,
+        interestGroupOverrides: {
+            biddingLogicURL:
+                createBiddingScriptURL({
+                    generateBid:
+                        `return {bid: 1, render: interestGroup.adComponents[0].renderURL};`}),
+            adComponents: [{renderURL: createComponentAdRenderURL(uuid, 0)}]}});
+}, 'Component ad URL used as render URL.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/2,
+                                  /*componentAdsInBid=*/[0, 1], /*componentAdsToLoad=*/[0, 1]);
+}, '2 of 2 component ads in bid and then shown.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/2,
+                                  /*componentAdsInBid=*/[0, 1], /*componentAdsToLoad=*/[0, 1],
+                                  /*adMetadata=*/true);
+}, '2 of 2 component ads in bid and then shown, with metadata.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/20,
+                                  /*componentAdsInBid=*/[3, 10], /*componentAdsToLoad=*/[0, 1]);
+}, '2 of 20 component ads in bid and then shown.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  const intsUpTo19 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19];
+  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/20,
+                                  /*componentAdsInBid=*/intsUpTo19,
+                                  /*componentAdsToLoad=*/intsUpTo19);
+}, '20 of 20 component ads in bid and then shown.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/20,
+                                  /*componentAdsInBid=*/[1, 2, 3, 4, 5, 6],
+                                  /*componentAdsToLoad=*/[1, 3]);
+}, '6 of 20 component ads in bid, 2 shown.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  // It should be possible to load ads multiple times. Each loaded ad should request a new tracking
+  // URLs, as they're fetched via XHRs, rather than reporting.
+  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/4,
+                                  /*componentAdsInBid=*/[0, 1, 2, 3],
+                                  /*componentAdsToLoad=*/[0, 1, 1, 0, 3, 3, 2, 2, 1, 0]);
+}, '4 of 4 component ads shown multiple times.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/2,
+                                  /*componentAdsInBid=*/[0, 0, 0, 0],
+                                  /*componentAdsToLoad=*/[0, 1, 2, 3]);
+}, 'Same component ad used multiple times in bid.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  // The bid only has one component ad, but the renderURL tries to load 5 component ads.
+  // The others should all be about:blank. Can't test that, so just make sure there aren't
+  // more requests than expected, and there's no crash.
+  await runComponentAdLoadingTest(test, uuid, /*numComponentAdsInInterestGroup=*/2,
+                                  /*componentAdsInBid=*/[0],
+                                  /*componentAdsToLoad=*/[4, 3, 2, 1, 0]);
+}, 'Load component ads not in bid.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  const renderURL = createRenderUrl(uuid);
+
+  let adComponents = [];
+  let adComponentsList = [];
+  for (let i = 0; i < 21; ++i) {
+    let componentRenderURL = createComponentAdTrackerURL(uuid, i);
+    adComponents.push({renderURL: componentRenderURL});
+    adComponentsList.push(componentRenderURL);
+  }
+
+  await runBasicFledgeTestExpectingNoWinner(
+      test,
+      { uuid: uuid,
+        interestGroupOverrides: {
+            biddingLogicURL:
+                createBiddingScriptURL({
+                    generateBid:
+                        `return {bid: 1,
+                                 render: "${renderURL}",
+                                 adComponents: ${JSON.stringify(adComponentsList)}};`}),
+            ads: [{renderURL: renderURL}],
+            adComponents: adComponents}});
+}, '21 component ads not allowed in bid.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+  const renderURL = createRenderUrl(uuid);
+
+  let adComponents = [];
+  let adComponentsList = [];
+  for (let i = 0; i < 21; ++i) {
+    let componentRenderURL = createComponentAdTrackerURL(uuid, i);
+    adComponents.push({renderURL: componentRenderURL});
+    adComponentsList.push(adComponents[0].renderURL);
+  }
+
+  await runBasicFledgeTestExpectingNoWinner(
+      test,
+      { uuid: uuid,
+        interestGroupOverrides: {
+            biddingLogicURL:
+                createBiddingScriptURL({
+            generateBid:
+                `return {bid: 1,
+                         render: "${renderURL}",
+                         adComponents: ${JSON.stringify(adComponentsList)}};`}),
+            ads: [{renderURL: renderURL}],
+            adComponents: adComponents}});
+}, 'Same component ad not allowed 21 times in bid.');
+
+promise_test(async test => {
+  const uuid = generateUuid(test);
+
+  // The component ad's render URL will try to send buyer and seller reports,
+  // which should not be sent (but not throw an exception), and then request a
+  // a tracker URL via fetch, which should be requested from the server.
+  const componentRenderURL =
+      createRenderUrl(
+        uuid,
+        `window.fence.reportEvent({eventType: "beacon",
+                                   eventData: "Should not be sent",
+                                   destination: ["buyer", "seller"]});
+         fetch("${createComponentAdTrackerURL(uuid, 0)}");`);
+
+  const renderURL = createRenderUrl(
+      uuid,
+      `let fencedFrame = document.createElement("fencedframe");
+       fencedFrame.mode = "opaque-ads";
+       fencedFrame.config = window.fence.getNestedConfigs()[0];
+       document.body.appendChild(fencedFrame);
+
+       async function waitForRequestAndSendBeacons() {
+         // Wait for the nested fenced frame to request its tracker URL.
+         await waitForObservedRequests("${uuid}", ["${createComponentAdTrackerURL(uuid, 0)}"]);
+
+         // Now that the tracker URL has been received, the component ad has tried to
+         // send a beacon, so have the main renderURL send a beacon, which should succeed
+         // and should hopefully be sent after the component ad's beacon, if it was
+         // going to (incorrectly) send one.
+         window.fence.reportEvent({eventType: "beacon",
+                                   eventData: "top-ad",
+                                   destination: ["buyer", "seller"]});
+       }
+       waitForRequestAndSendBeacons();`);
+
+  await joinInterestGroup(
+      test, uuid,
+      { biddingLogicURL:
+          createBiddingScriptURL({
+              generateBid:
+                  `return {bid: 1,
+                           render: "${renderURL}",
+                           adComponents: ["${componentRenderURL}"]};`,
+              reportWin:
+                  `registerAdBeacon({beacon: '${createBidderBeaconUrl(uuid)}'});` }),
+        ads: [{renderURL: renderURL}],
+        adComponents: [{renderURL: componentRenderURL}]});
+
+  await runBasicFledgeAuctionAndNavigate(
+    test, uuid,
+    {decisionLogicURL: createDecisionScriptURL(
+        uuid,
+        { reportResult: `registerAdBeacon({beacon: '${createSellerBeaconUrl(uuid)}'});`})});
+
+  // Only the renderURL should have sent any beacons, though the component ad should have sent
+  // a tracker URL fetch request.
+  await waitForObservedRequests(uuid, [createComponentAdTrackerURL(uuid, 0),
+                                       `${createBidderBeaconUrl(uuid)}, body: top-ad`,
+                                       `${createSellerBeaconUrl(uuid)}, body: top-ad`]);
+
+
+}, 'Reports not sent from component ad.');
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fenced-frame.sub.py b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fenced-frame.sub.py
index c29bb6fec..a3cc488 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fenced-frame.sub.py
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fenced-frame.sub.py
@@ -8,6 +8,13 @@
     return """
         <!DOCTYPE html>
         <html>
+        <head>
+        <!--- Allow injected scripts to use functions in fledge-util.js --->
+        <base href="..">
+        <script src="/resources/testharness.js"></script>
+        <script src="/common/utils.js"></script>
+        <script src="resources/fledge-util.js"></script>
+        </head>
         <body>
         <script>
         {{GET[script]}}
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.js
index ffb93e9..b01ffe7 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.js
@@ -1,7 +1,6 @@
 "use strict;"
 
-const FULL_URL = window.location.href;
-const BASE_URL = FULL_URL.substring(0, FULL_URL.lastIndexOf('/') + 1);
+const BASE_URL = document.baseURI.substring(0, document.baseURI.lastIndexOf('/') + 1);
 const BASE_PATH = (new URL(BASE_URL)).pathname;
 
 const DEFAULT_INTEREST_GROUP_NAME = 'default name';
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.sub.window.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.sub.window.js
index 924fe92..fe8e905d 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.sub.window.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.sub.window.js
@@ -16,7 +16,7 @@
 // given for TRUSTED_SCORING_SIGNALS_URL.
 async function runTrustedScoringSignalsTest(test, uuid, renderURL, scoreAdCheck) {
   const auctionConfigOverrides = {
-      trustedScoringSignalsUrl: TRUSTED_SCORING_SIGNALS_URL,
+      trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL,
     decisionLogicURL:
       createDecisionScriptURL(uuid, {
               scoreAd: `if (!(${scoreAdCheck})) throw "error";` })};
@@ -58,7 +58,7 @@
                  sendReportTo('${createSellerReportUrl(uuid, '2-error')}')
                sendReportTo('${createSellerReportUrl(uuid, '2')}')`,
         }),
-        trustedScoringSignalsUrl: TRUSTED_SCORING_SIGNALS_URL
+        trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL
   }
   await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfigOverrides);
   await waitForObservedRequests(
@@ -87,7 +87,7 @@
       { uuid: uuid,
         auctionConfigOverrides: { decisionLogicURL: decisionLogicScriptUrl }
       });
-}, 'No trustedScoringSignalsUrl.');
+}, 'No trustedScoringSignalsURL.');
 
 promise_test(async test => {
   const uuid = generateUuid(test);
@@ -265,7 +265,7 @@
   const renderURL2 = createRenderUrl(uuid, /*script=*/null, /*signalsParam=*/'string-value');
   await joinInterestGroup(test, uuid, {ads: [{renderUrl: renderURL1}], name: '1'});
   await joinInterestGroup(test, uuid, {ads: [{renderUrl: renderURL2}], name: '2'});
-  let auctionConfigOverrides = { trustedScoringSignalsUrl: TRUSTED_SCORING_SIGNALS_URL };
+  let auctionConfigOverrides = { trustedScoringSignalsURL: TRUSTED_SCORING_SIGNALS_URL };
 
   // scoreAd() only accepts the first IG's bid, validating its trustedScoringSignals.
   auctionConfigOverrides.decisionLogicURL =
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.sub.window.js.ini b/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.sub.window.js.ini
index 92561fa..bd5b3d7 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.sub.window.js.ini
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/trusted-scoring-signals.https.sub.window.js.ini
@@ -1,5 +1,5 @@
 [trusted-scoring-signals.https.sub.window.html]
-  [No trustedScoringSignalsUrl.]
+  [No trustedScoringSignalsURL.]
     expected:
       if product == "chrome": FAIL
 
diff --git a/third_party/catapult b/third_party/catapult
index 48e812d..599ca89 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 48e812dfb95279ba02d1213898a3b60dab1cdd01
+Subproject commit 599ca89cf441075ca3373a6fb46c2ec7da62bbff
diff --git a/third_party/dawn b/third_party/dawn
index 9dffe91..6a90b51 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 9dffe91f6a4f715007a2e949dfca94eb02077479
+Subproject commit 6a90b51aab40b6703a7cec3108f9008322fdf31c
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 7688e78..e6f40ea 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 7688e784503525b598a18991e190038381c333cf
+Subproject commit e6f40ea0341552c3e78fb107fe9f37b87d95f321
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 1a1e2f4..9f07965 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 1a1e2f467cae76c971250f0d30d86783e622c447
+Subproject commit 9f07965c141f3b35e84a10efcef6095e5c14380d
diff --git a/third_party/freetype/README.chromium b/third_party/freetype/README.chromium
index 667b1ef..4de1eb1 100644
--- a/third_party/freetype/README.chromium
+++ b/third_party/freetype/README.chromium
@@ -1,7 +1,7 @@
 Name: FreeType
 URL: http://www.freetype.org/
-Version: VER-2-13-2-2-g4a0c5639f
-Revision: 4a0c5639f7d6ab8b36e0f3989c76d09b2604e5b6
+Version: VER-2-13-2-3-g2d9fce53d
+Revision: 2d9fce53d4ce89f36075168282fcdd7289e082f9
 CPEPrefix: cpe:/a:freetype:freetype:2.13.2
 License: Custom license "inspired by the BSD, Artistic, and IJG (Independent
          JPEG Group) licenses"
diff --git a/third_party/freetype/src b/third_party/freetype/src
index 4a0c563..2d9fce5 160000
--- a/third_party/freetype/src
+++ b/third_party/freetype/src
@@ -1 +1 @@
-Subproject commit 4a0c5639f7d6ab8b36e0f3989c76d09b2604e5b6
+Subproject commit 2d9fce53d4ce89f36075168282fcdd7289e082f9
diff --git a/third_party/perfetto b/third_party/perfetto
index 08377bc..9102e6d 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 08377bc86ca266241b49296fcfc4086b9b4038de
+Subproject commit 9102e6d39287a1e0c912ac0b5e088e3c886f2688
diff --git a/third_party/skia b/third_party/skia
index 335f748..66e367b 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 335f748463db07bd9b238a2d3271528e89569316
+Subproject commit 66e367b12e96cce50722a541f73048a83fdc6a7e
diff --git a/third_party/speedometer/v3.0 b/third_party/speedometer/v3.0
new file mode 160000
index 0000000..5107c73
--- /dev/null
+++ b/third_party/speedometer/v3.0
@@ -0,0 +1 @@
+Subproject commit 5107c739c1b2a008e7293e3b489c4f80a8fb2e01
diff --git a/third_party/webrtc b/third_party/webrtc
index a041a97..e17ac9f 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit a041a97f630555469a9eac49eb2b51eece8ddf4f
+Subproject commit e17ac9fb05426dd2a8c7306e9a6bbb414682b43d
diff --git a/third_party/wpt_tools/README.chromium b/third_party/wpt_tools/README.chromium
index b97444c..7797786 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: d1202c3cafa1fa7b17742cdd479175e0deda3cb9
+Version: c042d5272215336b6c6c5fd42047081ad280c714
 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/wpt/tools/wptrunner/wptrunner/manifestupdate.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/manifestupdate.py
index 26d1d68..6a12cf1 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/manifestupdate.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/manifestupdate.py
@@ -667,7 +667,14 @@
                 else:
                     errors.append(error)
 
-            for child in node.children:
+            try:
+                # Attempt to stably order the next group of conditions by their
+                # values, which are typically string/numeric types that have an
+                # order defined.
+                children = sorted(node.children, key=lambda child: child.value)
+            except TypeError:
+                children = node.children
+            for child in children:
                 queue.append((child, parents_and_self))
 
         conditions = conditions[::-1]
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testloader.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testloader.py
index cc41dfa..583fad9 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testloader.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testloader.py
@@ -6,6 +6,7 @@
 import itertools
 import json
 import os
+import queue
 from urllib.parse import urlsplit
 from abc import ABCMeta, abstractmethod
 from queue import Empty
@@ -15,7 +16,6 @@
 from . import manifestinclude
 from . import manifestexpected
 from . import manifestupdate
-from . import mpcontext
 from . import wpttest
 from mozlog import structured
 
@@ -502,8 +502,7 @@
 
     @classmethod
     def make_queue(cls, tests_by_type, **kwargs):
-        mp = mpcontext.get_context()
-        test_queue = mp.Queue()
+        test_queue = queue.SimpleQueue()
         groups = cls.make_groups(tests_by_type, **kwargs)
         processes = cls.process_count(kwargs["processes"], len(groups))
         if processes > 1:
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 e8630d6..07e6502 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
@@ -46,9 +46,9 @@
     that is passed in.
 
     :param logger: Structured logger
-    :param command_queue: subprocess.Queue used to send commands to the
+    :param command_queue: multiprocessing.Queue used to send commands to the
                           process
-    :param result_queue: subprocess.Queue used to send results to the
+    :param result_queue: multiprocessing.Queue used to send results to the
                          parent TestRunnerManager process
     :param executor: TestExecutor object that will actually run a test.
     """
@@ -617,6 +617,7 @@
             wait_timeout = (self.state.test.timeout * self.executor_kwargs['timeout_multiplier'] +
                             3 * self.executor_cls.extra_timeout)
             self.timer = threading.Timer(wait_timeout, self._timeout)
+            self.timer.name = f"{self.name}-timeout"
 
         self.send_message("run_test", self.state.test)
         if self.timer:
@@ -773,6 +774,7 @@
                 self.logger.info("Restarting browser for new test group")
                 restart = True
         else:
+            subsuite = self.state.subsuite
             test_type = self.state.test_type
             test_group = self.state.test_group
             group_metadata = self.state.group_metadata
@@ -868,6 +870,8 @@
         self.logger.debug("TestRunnerManager cleanup")
         if self.browser:
             self.browser.cleanup()
+        if self.timer:
+            self.timer.cancel()
         while True:
             try:
                 cmd, data = self.command_queue.get_nowait()
@@ -891,17 +895,6 @@
                 break
 
 
-def make_test_queue(tests, test_source):
-    queue, num_of_workers = test_source.cls.make_queue(tests, **test_source.kwargs)
-
-    # There is a race condition that means sometimes we continue
-    # before the tests have been written to the underlying pipe.
-    # Polling the pipe for data here avoids that
-    queue._reader.poll(10)
-    assert not queue.empty()
-    return queue, num_of_workers
-
-
 class ManagerGroup:
     """Main thread object that owns all the TestRunnerManager threads."""
     def __init__(self, suite_name, test_source, test_implementations,
@@ -944,8 +937,8 @@
 
     def run(self, tests):
         """Start all managers in the group"""
-
-        test_queue, size = make_test_queue(tests, self.test_source)
+        test_queue, size = self.test_source.cls.make_queue(
+            tests, **self.test_source.kwargs)
         self.logger.info("Using %i child processes" % size)
 
         for idx in range(size):
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 4be9f91..0bcbe7d8 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -64297,7 +64297,6 @@
   <int value="-96820962"
       label="ExperimentalAccessibilitySelectToSpeakVoiceSwitching:enabled"/>
   <int value="-95160434" label="AdvancedPeripheralsSupport:disabled"/>
-  <int value="-95018838" label="BuiltInHlsPlayer:enabled"/>
   <int value="-94535722" label="TimeOfDayWallpaper:enabled"/>
   <int value="-94335249" label="UseAAudioDriver:enabled"/>
   <int value="-93619449" label="video-tutorials-instant-fetch"/>
@@ -64362,7 +64361,6 @@
   <int value="-69427025" label="OfflinePagesPrefetchingUI:enabled"/>
   <int value="-69224661" label="FeedAblation:enabled"/>
   <int value="-68877684" label="BackgroundVideoTrackOptimization:enabled"/>
-  <int value="-68797870" label="BuiltInHlsPlayer:disabled"/>
   <int value="-68619312" label="PassiveDocumentWheelEventListeners:disabled"/>
   <int value="-68613812" label="DarkLightMode:disabled"/>
   <int value="-68225452" label="enable-translate-new-ux"/>
@@ -66811,7 +66809,6 @@
   <int value="1176183341" label="SearchHistoryLink:enabled"/>
   <int value="1177120582" label="InstallableInkDrop:disabled"/>
   <int value="1177174300" label="LayoutNGTable:disabled"/>
-  <int value="1177255376" label="enable-builtin-hls:enabled"/>
   <int value="1177628103" label="GaiaActionButtons:disabled"/>
   <int value="1178640449" label="ProductivityLauncher:disabled"/>
   <int value="1179013979"
@@ -68364,7 +68361,6 @@
   <int value="1927259098" label="TranslateLanguageByULP:enabled"/>
   <int value="1927374573" label="EnterpriseRealtimeExtensionRequest:enabled"/>
   <int value="1927572319" label="OmniboxShortcutExpanding:enabled"/>
-  <int value="1928641476" label="enable-builtin-hls:disabled"/>
   <int value="1928944156" label="ReadLaterNewBadgePromo:disabled"/>
   <int value="1929174284" label="ChromeManagementPageAndroid:disabled"/>
   <int value="1929535031" label="LanguageSettingsUpdate2:enabled"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index 29e3d92..28c9670 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -2198,20 +2198,16 @@
 </histogram>
 
 <histogram name="Android.Omnibox.InvalidMatch" enum="MatchResult"
-    expires_after="2024-01-14">
+    expires_after="2024-08-14">
   <owner>ender@chromium.org</owner>
-  <owner>tedchoc@chromium.org</owner>
-  <owner>mpearson@chromium.org</owner>
-  <owner>jdonnelly@chromium.org</owner>
+  <owner>chrome-mobile-search@google.com</owner>
   <summary>
     Recorded every time AutocompleteController.java interacts with
     autocomplete_controller_android.cc. Indicates how often matches referenced
     by Java are different from the matches referenced by C++ at any position.
 
-    Note: Chrome M91 and before only increased this histogram for specific Match
-    objects that the user interacted with. As of M92, this histogram is
-    increased for every case where at least one Match referenced by Java was
-    different from its corresponding C++ match at the same exact position.
+    Note: starting Chrome 118, this is recorded every time we detect a
+    non-cached match, when Native and Java AutocompleteResult objects differ.
   </summary>
 </histogram>
 
@@ -2645,10 +2641,9 @@
 </histogram>
 
 <histogram name="Android.Omnibox.UsedSuggestionFromCache" enum="Boolean"
-    expires_after="2024-01-14">
+    expires_after="2024-08-14">
   <owner>ender@chromium.org</owner>
-  <owner>jdonnelly@chromium.org</owner>
-  <owner>mpearson@chromium.org</owner>
+  <owner>chrome-mobile-search@google.com</owner>
   <summary>
     Records whether users interacted with suggestion originating from Java
     cache.
@@ -2662,6 +2657,9 @@
 
     Recorded every time the User initiates navigation using the Omnibox or the
     Suggestions list, regardless of context.
+
+    Note: starting Chrome 118 this is recorded for every Autocomplete action
+    requiring native AutocompleteMatch instance.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index a3f76e7f..379a26f 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -274,7 +274,7 @@
   </summary>
 </histogram>
 
-<histogram name="Arc.AndroidBootTime" units="ms" expires_after="2024-01-07">
+<histogram name="Arc.AndroidBootTime" units="ms" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>The time elapsed for booting up the ARC instance.</summary>
@@ -344,7 +344,7 @@
   </summary>
 </histogram>
 
-<histogram name="Arc.Anr.{AnrPeriod}" units="ANRs" expires_after="2023-09-01">
+<histogram name="Arc.Anr.{AnrPeriod}" units="ANRs" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <owner>arc-performance@google.com</owner>
@@ -355,7 +355,7 @@
 </histogram>
 
 <histogram name="Arc.Anr.{AnrPeriod}.{ArcBootType}" units="ANRs"
-    expires_after="2023-09-01">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -366,7 +366,7 @@
   <token key="ArcBootType" variants="ArcBootTypes"/>
 </histogram>
 
-<histogram name="Arc.Anr.{AnrSource}" enum="ArcAnr" expires_after="2023-09-01">
+<histogram name="Arc.Anr.{AnrSource}" enum="ArcAnr" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <owner>arc-performance@google.com</owner>
@@ -1002,7 +1002,7 @@
 </histogram>
 
 <histogram name="Arc.CpuRestrictionDisabled{ArcThrottleObservers}" units="ms"
-    expires_after="2023-09-01">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <owner>arc-performance@google.com</owner>
@@ -1029,7 +1029,7 @@
 </histogram>
 
 <histogram name="Arc.CpuRestrictionVmResult" enum="ArcCpuRestrictionVmResult"
-    expires_after="2023-09-01">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <owner>arc-performance@google.com</owner>
@@ -1229,7 +1229,7 @@
 </histogram>
 
 <histogram name="Arc.FirstAppLaunchDelay.TimeDelta" units="ms"
-    expires_after="2024-01-14">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -1243,7 +1243,7 @@
 </histogram>
 
 <histogram name="Arc.FirstAppLaunchDelay.TimeDeltaUntilAppLaunch" units="ms"
-    expires_after="2024-01-14">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -1256,7 +1256,7 @@
 </histogram>
 
 <histogram name="Arc.FirstAppLaunchRequest.TimeDelta" units="ms"
-    expires_after="2023-09-01">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -1839,7 +1839,7 @@
 </histogram>
 
 <histogram name="Arc.PlayStoreLaunch.TimeDelta" units="ms"
-    expires_after="2023-09-01">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -1924,7 +1924,7 @@
 </histogram>
 
 <histogram name="Arc.PlayStoreShown.TimeDelta{ArcUserTypes}" units="ms"
-    expires_after="2023-09-01">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -2090,7 +2090,7 @@
 
 <histogram
     name="Arc.Runtime.Performance.CommitDeviation2{ArcPerformanceAppCategories}"
-    units="microseconds" expires_after="2023-09-01">
+    units="microseconds" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2105,7 +2105,7 @@
 </histogram>
 
 <histogram name="Arc.Runtime.Performance.FPS2{ArcPerformanceAppCategories}"
-    units="fps" expires_after="2023-09-01">
+    units="fps" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2119,7 +2119,7 @@
 </histogram>
 
 <histogram name="Arc.Runtime.Performance.Generic.FirstFrameRendered" units="ms"
-    expires_after="2024-01-14">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2130,7 +2130,7 @@
 </histogram>
 
 <histogram name="Arc.Runtime.Performance.Generic.FrameTime" units="ms"
-    expires_after="2023-09-01">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2140,7 +2140,7 @@
 </histogram>
 
 <histogram name="Arc.Runtime.Performance.Generic.Jankiness" units="%"
-    expires_after="2024-01-07">
+    expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2151,7 +2151,7 @@
 
 <histogram
     name="Arc.Runtime.Performance.RenderQuality2{ArcPerformanceAppCategories}"
-    units="%" expires_after="2023-09-01">
+    units="%" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>alanding@google.com</owner>
   <summary>
@@ -2312,7 +2312,7 @@
 </histogram>
 
 <histogram name="Arc.UiAvailable.AlreadyProvisioned.TimeDelta{ArcUserTypes}"
-    units="ms" expires_after="2023-09-01">
+    units="ms" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -2325,7 +2325,7 @@
 </histogram>
 
 <histogram name="Arc.UiAvailable.InSessionProvisioning.TimeDelta{ArcUserTypes}"
-    units="ms" expires_after="2023-09-01">
+    units="ms" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
@@ -2338,7 +2338,7 @@
 </histogram>
 
 <histogram name="Arc.UiAvailable.OobeProvisioning.TimeDelta{ArcUserTypes}"
-    units="ms" expires_after="2023-09-01">
+    units="ms" expires_after="2024-03-01">
   <owner>khmel@google.com</owner>
   <owner>arc-performance@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index f7a9b51e..9410fd20 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -498,7 +498,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Actions.FinalState.Number{Event}"
-    units="count" expires_after="2023-10-01">
+    units="count" expires_after="2024-01-31">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -547,7 +547,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Actions.LinksOpened" units="links opened"
-    expires_after="2023-10-01">
+    expires_after="2024-01-31">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -673,7 +673,7 @@
 
 <histogram
     name="History.Clusters.Backend.ClusterSimilarityHeuristicsProcessor.ClusterSearchTermOverridden"
-    enum="Boolean" expires_after="2023-10-01">
+    enum="Boolean" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -713,7 +713,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.ComputeClusters.ThreadTime"
-    units="ms" expires_after="2023-10-08">
+    units="ms" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -728,7 +728,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.ComputeClustersForUI.ThreadTime"
-    units="ms" expires_after="2023-10-08">
+    units="ms" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -766,7 +766,7 @@
 
 <histogram
     name="History.Clusters.Backend.ComputeClusterTriggerability2.ThreadTime"
-    units="ms" expires_after="2023-10-08">
+    units="ms" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -893,7 +893,7 @@
 
 <histogram
     name="History.Clusters.Backend.GetMostRecentClusters.{Segment}{Source}"
-    units="ms" expires_after="2023-10-01">
+    units="ms" expires_after="2024-01-31">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -930,7 +930,7 @@
 
 <histogram
     name="History.Clusters.Backend.GetMostRecentClustersForUI.GetMostRecentPersistedClustersTimeHorizon{Source}"
-    units="hours" expires_after="2023-10-01">
+    units="hours" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -964,7 +964,7 @@
 
 <histogram
     name="History.Clusters.Backend.GetMostRecentClustersForUI.{Segment}{Source}"
-    units="ms" expires_after="2023-10-01">
+    units="ms" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1096,7 +1096,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.NumClustersReturned"
-    units="number clusters returned" expires_after="2023-10-08">
+    units="number clusters returned" expires_after="2024-01-31">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1186,7 +1186,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.NumVisitsToCluster"
-    units="number visits" expires_after="2023-10-08">
+    units="number visits" expires_after="2024-01-31">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1246,7 +1246,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.UpdateClusters.Counts.{Segment}"
-    units="count" expires_after="2023-10-01">
+    units="count" expires_after="2024-01-31">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1285,7 +1285,7 @@
 </histogram>
 
 <histogram name="History.Clusters.Backend.UpdateClusters.{Segment}" units="ms"
-    expires_after="2023-10-01">
+    expires_after="2024-01-31">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1324,7 +1324,7 @@
 
 <histogram
     name="History.Clusters.Backend.UpdateClusterTriggerability.DidUpdateClusterTriggerability{Segment}"
-    enum="Boolean" expires_after="2023-10-01">
+    enum="Boolean" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1398,7 +1398,7 @@
 
 <histogram
     name="History.Clusters.Backend.UpdateClusterTriggerability.{Segment}"
-    units="ms" expires_after="2023-10-01">
+    units="ms" expires_after="2024-01-31">
   <owner>manukh@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1456,7 +1456,7 @@
 
 <histogram
     name="History.Clusters.Backend.WasClusterFiltered.{ClusterFilterReason}"
-    enum="BooleanFiltered" expires_after="2023-10-01">
+    enum="BooleanFiltered" expires_after="2024-01-31">
   <owner>mcrouse@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1488,7 +1488,7 @@
 </histogram>
 
 <histogram name="History.Clusters.ContextClusterer.DbLatency.{Segment}"
-    units="ms" expires_after="2023-10-01">
+    units="ms" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1515,7 +1515,7 @@
 </histogram>
 
 <histogram name="History.Clusters.ContextClusterer.NumClusters.AtCleanUp"
-    units="num clusters" expires_after="2023-12-04">
+    units="num clusters" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1526,7 +1526,7 @@
 </histogram>
 
 <histogram name="History.Clusters.ContextClusterer.NumClusters.CleanedUp"
-    units="num clusters" expires_after="2023-10-01">
+    units="num clusters" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1537,7 +1537,7 @@
 </histogram>
 
 <histogram name="History.Clusters.ContextClusterer.NumClusters.PostCleanUp"
-    units="num clusters" expires_after="2023-10-01">
+    units="num clusters" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1571,7 +1571,7 @@
 
 <histogram
     name="History.Clusters.ContextClusterer.VisitProcessingLatency.{Segment}"
-    units="ms" expires_after="2023-10-01">
+    units="ms" expires_after="2024-01-31">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1791,7 +1791,7 @@
 </histogram>
 
 <histogram name="History.Clusters.UIActions.Cluster.{ClusterAction}"
-    units="index" expires_after="2023-10-01">
+    units="index" expires_after="2024-01-31">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1814,7 +1814,7 @@
 
 <histogram
     name="History.Clusters.UIActions.RelatedSearch.{RelatedSearchAction}"
-    units="index" expires_after="2023-10-01">
+    units="index" expires_after="2024-01-31">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1830,7 +1830,7 @@
 </histogram>
 
 <histogram name="History.Clusters.UIActions.ToggledVisibility"
-    enum="BooleanVisible" expires_after="2023-10-01">
+    enum="BooleanVisible" expires_after="2024-01-31">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
@@ -1842,7 +1842,7 @@
 </histogram>
 
 <histogram name="History.Clusters.UIActions.{VisitType}Visit.{VisitAction}"
-    units="index" expires_after="2023-10-01">
+    units="index" expires_after="2024-01-31">
   <owner>mahmadi@chromium.org</owner>
   <owner>chrome-journeys@google.com</owner>
   <component>UI&gt;Browser&gt;Journeys</component>
diff --git a/tools/metrics/histograms/metadata/language/histograms.xml b/tools/metrics/histograms/metadata/language/histograms.xml
index 149936b5..f065d2b 100644
--- a/tools/metrics/histograms/metadata/language/histograms.xml
+++ b/tools/metrics/histograms/metadata/language/histograms.xml
@@ -214,7 +214,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.AcceptLanguageAndContentLanguageUsage"
-    enum="AcceptLanguageAndContentLanguageUsage" expires_after="2024-01-14">
+    enum="AcceptLanguageAndContentLanguageUsage" expires_after="2024-02-11">
   <owner>victortan@chromium.org</owner>
   <owner>potassium-katabolism@google.com</owner>
   <summary>
@@ -225,7 +225,7 @@
 </histogram>
 
 <histogram name="LanguageUsage.AcceptLanguageAndXmlHtmlLangUsage"
-    enum="AcceptLanguageAndXmlHtmlLangUsage" expires_after="2023-09-03">
+    enum="AcceptLanguageAndXmlHtmlLangUsage" expires_after="2024-02-11">
   <owner>victortan@chromium.org</owner>
   <owner>potassium-katabolism@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index 781b537..fcba4c1 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -141,9 +141,9 @@
 </histogram>
 
 <histogram name="NewTabPage.BackgroundService.Images.RequestLatency" units="ms"
-    expires_after="2023-09-30">
+    expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
+  <owner>pauladedeji@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     The time it took until a request from the New Tab Page for Backdrop Images
@@ -483,9 +483,9 @@
 </histogram>
 
 <histogram name="NewTabPage.CustomizeChromeBackgroundAction"
-    enum="NTPCustomizeChromeBackgroundAction" expires_after="2023-09-30">
+    enum="NTPCustomizeChromeBackgroundAction" expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
+  <owner>pauladedeji@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Captures the actions performed when configuring a 'Chrome background' image
@@ -531,9 +531,9 @@
 </histogram>
 
 <histogram name="NewTabPage.CustomizeLocalImageBackgroundAction"
-    enum="NTPCustomizeLocalImageBackgroundAction" expires_after="2023-09-30">
+    enum="NTPCustomizeLocalImageBackgroundAction" expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
+  <owner>pauladedeji@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Captures the actions performed when uploading a local image as the
@@ -611,9 +611,9 @@
 </histogram>
 
 <histogram name="NewTabPage.HasCredentials" enum="BooleanYesNo"
-    expires_after="2023-09-03">
+    expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
+  <owner>romanarora@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Logs whether Chrome has valid user credentials for the profile the NTP is
@@ -917,9 +917,9 @@
 </histogram>
 
 <histogram name="NewTabPage.LogoDownloadTime" units="ms"
-    expires_after="2023-09-30">
+    expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
+  <owner>romanarora@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     The amount of time it took to download the static logo. This includes
@@ -1037,8 +1037,8 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.Disabled{Interaction}" enum="NtpModules"
-    expires_after="2023-09-01">
-  <owner>danpeng@google.com</owner>
+    expires_after="2024-01-31">
+  <owner>romanarora@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
@@ -1090,9 +1090,9 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.Enabled{Interaction}" enum="NtpModules"
-    expires_after="2023-09-30">
+    expires_after="2024-01-31">
+  <owner>romanarora@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Logged when a module was enabled by {Interaction}. Only logged on the 1P
@@ -1285,8 +1285,8 @@
 </histogram>
 
 <histogram name="NewTabPage.Modules.LoadedWith.{NtpModule}" enum="NtpModules"
-    expires_after="2023-09-01">
-  <owner>danpeng@google.com</owner>
+    expires_after="2024-01-31">
+  <owner>romanarora@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
@@ -1396,9 +1396,9 @@
 </histogram>
 
 <histogram name="NewTabPage.NumberOfTiles" units="units"
-    expires_after="2023-09-30">
+    expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>yyushkina@chromium.org</owner>
+  <owner>romanarora@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     The number of tiles that are on the NTP, no matter if they are thumbnails,
@@ -1413,9 +1413,9 @@
 </histogram>
 
 <histogram name="NewTabPage.OneGoogleBar.RequestLatency" units="ms"
-    expires_after="2023-09-30">
+    expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
+  <owner>romanarora@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     The time it took until a request from the New Tab page for the OneGoogleBar
@@ -1424,10 +1424,9 @@
 </histogram>
 
 <histogram name="NewTabPage.OneGoogleBar.ShownTime" units="ms"
-    expires_after="2024-01-14">
-  <owner>danpeng@google.com</owner>
+    expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>yyushkina@chromium.org</owner>
+  <owner>romanarora@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Histogram of the time, in milliseconds since navigation start, it took until
@@ -1590,7 +1589,6 @@
     expires_after="2024-02-20">
   <owner>danpeng@google.com</owner>
   <owner>tiborg@chromium.org</owner>
-  <owner>yyushkina@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Logged when a user clicked on a recipe in the recipe tasks module. The value
@@ -1599,9 +1597,9 @@
 </histogram>
 
 <histogram name="NewTabPage.RecipeTasks.RecipesDownloadCount" units="recipes"
-    expires_after="2023-09-03">
+    expires_after="2024-01-31">
+  <owner>romanarora@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     The number of recipe search links in the primary shopping task (top-ranked,
@@ -1612,9 +1610,9 @@
 </histogram>
 
 <histogram name="NewTabPage.RecipeTasks.RelatedSearchClick" units="index"
-    expires_after="2023-09-30">
+    expires_after="2024-01-31">
+  <owner>romanarora@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Logged when a user clicked on a related search pill in the recipe tasks
@@ -1623,10 +1621,9 @@
 </histogram>
 
 <histogram name="NewTabPage.RecipeTasks.RelatedSearchDownloadCount"
-    units="count" expires_after="2023-10-08">
-  <owner>danpeng@google.com</owner>
+    units="count" expires_after="2024-01-31">
+  <owner>romanarora@chromium.org</owner>
   <owner>tiborg@chromium.org</owner>
-  <owner>yyushkina@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     The number of related search links in the primary recipe task (top-ranked,
@@ -1935,9 +1932,9 @@
 </histogram>
 
 <histogram name="NewTabPage.URLState" enum="NewTabURLState"
-    expires_after="2023-09-30">
+    expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
+  <owner>romanarora@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Records the status of the New Tab page URL when an NTP is opened.
@@ -1970,9 +1967,9 @@
 </histogram>
 
 <histogram name="NewTabPage.VoiceErrors" enum="NewTabPageVoiceError"
-    expires_after="2023-09-03">
+    expires_after="2024-01-31">
   <owner>tiborg@chromium.org</owner>
-  <owner>danpeng@google.com</owner>
+  <owner>romanarora@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
   <summary>
     Errors that occur during Voice Search use on the local New Tab Page on
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index 726a31a3..95ac2bb 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -223,7 +223,7 @@
 
 <histogram
     name="OptimizationGuide.EntityAnnotatorNativeLibrary.InitiatedSuccessfully"
-    enum="BooleanSuccess" expires_after="2023-10-08">
+    enum="BooleanSuccess" expires_after="M121">
   <owner>sophiechang@chromium.org</owner>
   <owner>chrome-intelligence-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index d2c571e..32b8272 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -10221,7 +10221,7 @@
 </histogram>
 
 <histogram name="ReduceAcceptLanguage.ClearLatency" units="ms"
-    expires_after="M119">
+    expires_after="M122">
   <owner>awillia@chromium.org</owner>
   <owner>miketaylr@chromium.org</owner>
   <owner>victortan@chromium.org</owner>
@@ -10233,7 +10233,7 @@
 </histogram>
 
 <histogram name="ReduceAcceptLanguage.FetchLatencyUs" units="microseconds"
-    expires_after="M119">
+    expires_after="M122">
   <owner>awillia@chromium.org</owner>
   <owner>miketaylr@chromium.org</owner>
   <owner>victortan@chromium.org</owner>
@@ -10251,7 +10251,7 @@
 </histogram>
 
 <histogram name="ReduceAcceptLanguage.StoreLatency" units="ms"
-    expires_after="M119">
+    expires_after="M122">
   <owner>awillia@chromium.org</owner>
   <owner>miketaylr@chromium.org</owner>
   <owner>victortan@chromium.org</owner>
@@ -10263,7 +10263,7 @@
 </histogram>
 
 <histogram name="ReduceAcceptLanguage.UpdateSize" units="count"
-    expires_after="M119">
+    expires_after="M122">
   <owner>awillia@chromium.org</owner>
   <owner>miketaylr@chromium.org</owner>
   <owner>victortan@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/prefetch/histograms.xml b/tools/metrics/histograms/metadata/prefetch/histograms.xml
index bc749eaa..1a86a1ff 100644
--- a/tools/metrics/histograms/metadata/prefetch/histograms.xml
+++ b/tools/metrics/histograms/metadata/prefetch/histograms.xml
@@ -496,9 +496,9 @@
     the prefetch.
 
     This is recorded once for each prefetch that is requested using a streaming
-    URL loader. It is recorded when the streaming URL loader is deleted which
-    can be when: 1) the URL loader is canceled, 2) the URL loader completed
-    serving the prefetch, or 3) the prefetch was deleted and not used.
+    URL loader. It is recorded when the corresponding PrefetchResponseReader is
+    deleted which can be when: 1) the URL loader is canceled, 2) the URL loader
+    completed serving the prefetch, or 3) the prefetch was deleted and not used.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/renderer/histograms.xml b/tools/metrics/histograms/metadata/renderer/histograms.xml
index 1f68cd69..abb443df 100644
--- a/tools/metrics/histograms/metadata/renderer/histograms.xml
+++ b/tools/metrics/histograms/metadata/renderer/histograms.xml
@@ -383,7 +383,7 @@
 </histogram>
 
 <histogram name="Renderer.Images.HasOverfetchedPixels" enum="Boolean"
-    expires_after="2023-09-03">
+    expires_after="2024-09-03">
   <owner>yoavweiss@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
   <summary>
@@ -393,7 +393,7 @@
 </histogram>
 
 <histogram name="Renderer.Images.HasSizesAttributeMiss" enum="Boolean"
-    expires_after="2023-09-03">
+    expires_after="2024-09-03">
   <owner>yoavweiss@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
   <summary>
@@ -404,7 +404,7 @@
 </histogram>
 
 <histogram name="Renderer.Images.OverfetchedCappedPixels" units="px^2"
-    expires_after="2023-09-03">
+    expires_after="2024-09-03">
   <owner>yoavweiss@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
   <summary>
@@ -430,7 +430,7 @@
 </histogram>
 
 <histogram name="Renderer.Images.SizesAttributeMiss" units="px"
-    expires_after="2023-09-03">
+    expires_after="2024-09-03">
   <owner>yoavweiss@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
   <summary>
diff --git a/ui/accessibility/android/accessibility_state.cc b/ui/accessibility/android/accessibility_state.cc
index 73dfcc2b..3712aac9 100644
--- a/ui/accessibility/android/accessibility_state.cc
+++ b/ui/accessibility/android/accessibility_state.cc
@@ -38,13 +38,6 @@
 }
 
 // static
-void JNI_AccessibilityState_OnContrastLevelChanged(
-    JNIEnv* env,
-    jboolean highContrastEnabled) {
-  AccessibilityState::NotifyContrastLevelObservers((bool)highContrastEnabled);
-}
-
-// static
 void JNI_AccessibilityState_RecordAccessibilityServiceInfoHistograms(
     JNIEnv* env) {
   AccessibilityState::NotifyRecordAccessibilityServiceInfoHistogram();
@@ -78,14 +71,6 @@
 }
 
 // static
-void AccessibilityState::NotifyContrastLevelObservers(
-    bool highContrastEnabled) {
-  for (AccessibilityStateDelegate* delegate : GetDelegates()) {
-    delegate->OnContrastLevelChanged(highContrastEnabled);
-  }
-}
-
-// static
 void AccessibilityState::NotifyRecordAccessibilityServiceInfoHistogram() {
   for (AccessibilityStateDelegate* delegate : GetDelegates()) {
     delegate->RecordAccessibilityServiceInfoHistograms();
diff --git a/ui/accessibility/android/accessibility_state.h b/ui/accessibility/android/accessibility_state.h
index f9da5ef..d2343de 100644
--- a/ui/accessibility/android/accessibility_state.h
+++ b/ui/accessibility/android/accessibility_state.h
@@ -23,9 +23,6 @@
     // Called when the display inversion state changes.
     virtual void OnDisplayInversionEnabledChanged(bool enabled) = 0;
 
-    // Called when the contrast level changes.
-    virtual void OnContrastLevelChanged(bool highContrastEnabled) = 0;
-
     // Called during browser startup and any time enabled services change.
     virtual void RecordAccessibilityServiceInfoHistograms() = 0;
   };
@@ -42,9 +39,6 @@
   // Notifies all delegates of a display inversion state change.
   static void NotifyDisplayInversionEnabledObservers(bool enabled);
 
-  // Notifies all delegates of a contrast level change.
-  static void NotifyContrastLevelObservers(bool highContrastEnabled);
-
   // Notifies all delegates to record service info histograms.
   static void NotifyRecordAccessibilityServiceInfoHistogram();
 
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 a5f29f1..e81459f 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
@@ -187,7 +187,6 @@
 
     private static boolean sExtraStateInitialized;
     private static boolean sDisplayInversionEnabled;
-    private static boolean sHighContrastEnabled;
 
     // Observers for various System, Activity, and Settings states relevant to accessibility.
     private static final ApplicationStatus.ActivityStateListener sActivityStateListener =
@@ -197,7 +196,6 @@
     private static ServicesObserver sAccessibilityServicesObserver;
     private static ServicesObserver sAnimationDurationScaleObserver;
     private static ServicesObserver sDisplayInversionEnabledObserver;
-    private static ServicesObserver sContrastLevelObserver;
     private static AccessibilityManager sAccessibilityManager;
 
     /**
@@ -323,11 +321,6 @@
         return sDisplayInversionEnabled;
     }
 
-    public static boolean isHighContrastEnabled() {
-        if (!sExtraStateInitialized) updateExtraState();
-        return sHighContrastEnabled;
-    }
-
     @Deprecated
     public static boolean isAccessibilitySpeakPasswordEnabled() {
         if (!sInitialized) updateAccessibilityServices();
@@ -422,14 +415,6 @@
                 Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, 0);
         boolean isDisplayInversionEnabled = displayInversionEnabledSetting == 1;
         sDisplayInversionEnabled = isDisplayInversionEnabled;
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-            float contrastLevelSetting = Settings.Secure.getFloat(context.getContentResolver(),
-                    /*Settings.Secure.CONTRAST_LEVEL*/ "contrast_level", 0);
-            // The contrast level setting has 3 options, Standard (0), Medium (0.5) and High (1).
-            // If set to medium or high, then prefers-contrast should be more.
-            sHighContrastEnabled = contrastLevelSetting >= 0.5;
-        }
     }
 
     static void updateAccessibilityServices() {
@@ -720,10 +705,8 @@
                 () -> AccessibilityStateJni.get().onAnimatorDurationScaleChanged());
         sAccessibilityServicesObserver = new ServicesObserver(
                 ThreadUtils.getUiThreadHandler(), AccessibilityState::processServicesChange);
-        sDisplayInversionEnabledObserver = new ServicesObserver(
-                ThreadUtils.getUiThreadHandler(), AccessibilityState::processExtraStateChange);
-        sContrastLevelObserver = new ServicesObserver(
-                ThreadUtils.getUiThreadHandler(), AccessibilityState::processExtraStateChange);
+        sDisplayInversionEnabledObserver = new ServicesObserver(ThreadUtils.getUiThreadHandler(),
+                AccessibilityState::processDisplayInversionChange);
 
         // We want to be notified whenever the user has updated the animator duration scale.
         contentResolver.registerContentObserver(
@@ -750,13 +733,6 @@
         contentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
                 false, sDisplayInversionEnabledObserver);
-
-        // We want to be notified if the user changes their contrast settings.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-            contentResolver.registerContentObserver(
-                    Settings.Secure.getUriFor(/*Settings.Secure.CONTRAST_LEVEL*/ "contrast_level"),
-                    false, sContrastLevelObserver);
-        }
     }
 
     public static void initializeOnStartup() {
@@ -782,10 +758,7 @@
     private static void onActivityStateChange(Activity activity, int newState) {
         // If Chrome is sent to the background, we will unregister observers, and re-register the
         // observers and query state when Chrome is brought back to the foreground.
-        if (newState == ActivityState.RESUMED) {
-            processServicesChange();
-            processExtraStateChange();
-        }
+        if (newState == ActivityState.RESUMED) processServicesChange();
     }
 
     private static void onApplicationStateChange(int newState) {
@@ -804,14 +777,10 @@
         contentResolver.unregisterContentObserver(sAccessibilityServicesObserver);
         contentResolver.unregisterContentObserver(sAnimationDurationScaleObserver);
         contentResolver.unregisterContentObserver(sDisplayInversionEnabledObserver);
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
-            contentResolver.unregisterContentObserver(sContrastLevelObserver);
-        }
         sState = null;
         sInitialized = false;
         sExtraStateInitialized = false;
         sDisplayInversionEnabled = false;
-        sHighContrastEnabled = false;
         sAccessibilityManager = null;
     }
 
@@ -820,10 +789,9 @@
         AccessibilityStateJni.get().recordAccessibilityServiceInfoHistograms();
     }
 
-    private static void processExtraStateChange() {
+    private static void processDisplayInversionChange() {
         updateExtraState();
         AccessibilityStateJni.get().onDisplayInversionEnabledChanged(isDisplayInversionEnabled());
-        AccessibilityStateJni.get().onContrastLevelChanged(isHighContrastEnabled());
     }
 
     private static class ServicesObserver extends ContentObserver {
@@ -849,7 +817,6 @@
     interface Natives {
         void onAnimatorDurationScaleChanged();
         void onDisplayInversionEnabledChanged(boolean enabled);
-        void onContrastLevelChanged(boolean highContrastEnabled);
         void recordAccessibilityServiceInfoHistograms();
     }
 
diff --git a/ui/accessibility/platform/ax_platform_node_cocoa.mm b/ui/accessibility/platform/ax_platform_node_cocoa.mm
index fa03d48d..62291c7 100644
--- a/ui/accessibility/platform/ax_platform_node_cocoa.mm
+++ b/ui/accessibility/platform/ax_platform_node_cocoa.mm
@@ -1819,7 +1819,7 @@
   // AXCustomContent is only supported by VoiceOver since macOS 11. In
   // macOS 11 or later we expose the aria description in AXCustomContent,
   // before then we expose the description in AXHelp.
-  if (base::mac::IsAtLeastOS11() &&
+  if (base::mac::MacOSMajorVersion() >= 11 &&
       [[self descriptionIfFromAriaDescription] length]) {
     return nil;
   }
diff --git a/ui/accessibility/platform/inspect/ax_inspect_test_helper.cc b/ui/accessibility/platform/inspect/ax_inspect_test_helper.cc
index aa418ff..802d2f14 100644
--- a/ui/accessibility/platform/inspect/ax_inspect_test_helper.cc
+++ b/ui/accessibility/platform/inspect/ax_inspect_test_helper.cc
@@ -381,7 +381,7 @@
   // When running tests in a platform specific test directory (such as
   // content/test/data/accessibility/mac/) the expectation_type_ == content.
   if ((expectation_type_ == "mac" || expectation_type_ == "content") &&
-      !base::mac::IsAtLeastOS11()) {
+      base::mac::MacOSMajorVersion() < 11) {
     FilePath::StringType suffix;
     if (!expectations_qualifier.empty()) {
       suffix = FILE_PATH_LITERAL("-") + expectations_qualifier;
diff --git a/ui/base/cocoa/permissions_utils.mm b/ui/base/cocoa/permissions_utils.mm
index 19930f8..86e982ce 100644
--- a/ui/base/cocoa/permissions_utils.mm
+++ b/ui/base/cocoa/permissions_utils.mm
@@ -78,7 +78,7 @@
 }
 
 void WarmScreenCapture() {
-  if (base::mac::IsAtLeastOS14()) {
+  if (base::mac::MacOSMajorVersion() >= 14) {
     // Starting in macOS 14, a "your screen is being captured" chip shows in the
     // menu bar while an app is capturing the screen, and if it's a one-time
     // image capture, it shows for ten seconds. Doing the warmup below would
diff --git a/ui/events/ozone/evdev/device_event_dispatcher_evdev.h b/ui/events/ozone/evdev/device_event_dispatcher_evdev.h
index 85ff6a385..e550706 100644
--- a/ui/events/ozone/evdev/device_event_dispatcher_evdev.h
+++ b/ui/events/ozone/evdev/device_event_dispatcher_evdev.h
@@ -195,6 +195,7 @@
   virtual void DispatchTouchEvent(const TouchEventParams& params) = 0;
   virtual void DispatchGamepadEvent(const GamepadEvent& event) = 0;
   virtual void DispatchMicrophoneMuteSwitchValueChanged(bool muted) = 0;
+  virtual void DispatchAnyKeysPressedUpdated(bool any) = 0;
 
   // Device lifecycle events.
   virtual void DispatchKeyboardDevicesUpdated(
diff --git a/ui/events/ozone/evdev/event_converter_test_util.cc b/ui/events/ozone/evdev/event_converter_test_util.cc
index 5b771b15..31c944e 100644
--- a/ui/events/ozone/evdev/event_converter_test_util.cc
+++ b/ui/events/ozone/evdev/event_converter_test_util.cc
@@ -107,6 +107,9 @@
   void DispatchStylusStateChanged(StylusState stylus_state) override {
     event_factory_evdev_->DispatchStylusStateChanged(stylus_state);
   }
+  void DispatchAnyKeysPressedUpdated(bool any) override {
+    event_factory_evdev_->DispatchAnyKeysPressedUpdated(any);
+  }
 
   void DispatchGamepadEvent(const GamepadEvent& event) override {
     event_factory_evdev_->DispatchGamepadEvent(event);
diff --git a/ui/events/ozone/evdev/event_factory_evdev.cc b/ui/events/ozone/evdev/event_factory_evdev.cc
index 79c5983e..28a6ba9 100644
--- a/ui/events/ozone/evdev/event_factory_evdev.cc
+++ b/ui/events/ozone/evdev/event_factory_evdev.cc
@@ -179,6 +179,13 @@
                        event_factory_evdev_, devices));
   }
 
+  void DispatchAnyKeysPressedUpdated(bool any) override {
+    ui_thread_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&EventFactoryEvdev::DispatchAnyKeysPressedUpdated,
+                       event_factory_evdev_, any));
+  }
+
  private:
   scoped_refptr<base::SingleThreadTaskRunner> ui_thread_runner_;
   base::WeakPtr<EventFactoryEvdev> event_factory_evdev_;
@@ -217,10 +224,13 @@
                                      KeyboardLayoutEngine* keyboard_layout)
     : device_manager_(device_manager),
       gamepad_provider_(GamepadProviderOzone::GetInstance()),
-      keyboard_(&modifiers_,
-                keyboard_layout,
-                base::BindRepeating(&EventFactoryEvdev::DispatchUiEvent,
-                                    base::Unretained(this))),
+      keyboard_(
+          &modifiers_,
+          keyboard_layout,
+          base::BindRepeating(&EventFactoryEvdev::DispatchUiEvent,
+                              base::Unretained(this)),
+          base::BindRepeating(&EventFactoryEvdev::DispatchAnyKeysPressedUpdated,
+                              base::Unretained(this))),
       cursor_(cursor),
       input_controller_(&keyboard_,
                         &mouse_button_map_,
@@ -510,6 +520,11 @@
   gamepad_provider_->DispatchGamepadDevicesUpdated(devices);
 }
 
+void EventFactoryEvdev::DispatchAnyKeysPressedUpdated(bool any) {
+  TRACE_EVENT0("evdev", "EventFactoryEvdev::DispatchAnyKeysPressedUpdated");
+  input_controller_.set_any_keys_pressed(any);
+}
+
 void EventFactoryEvdev::OnDeviceEvent(const DeviceEvent& event) {
   if (event.device_type() != DeviceEvent::INPUT)
     return;
diff --git a/ui/events/ozone/evdev/event_factory_evdev.h b/ui/events/ozone/evdev/event_factory_evdev.h
index 8af2848..65c4af1 100644
--- a/ui/events/ozone/evdev/event_factory_evdev.h
+++ b/ui/events/ozone/evdev/event_factory_evdev.h
@@ -105,6 +105,7 @@
   void DispatchDeviceListsComplete();
   void DispatchStylusStateChanged(StylusState stylus_state);
   void DispatchMicrophoneMuteSwitchValueChanged(bool muted);
+  void DispatchAnyKeysPressedUpdated(bool any);
 
   // Gamepad event and gamepad device event. These events are dispatched to
   // GamepadObserver through GamepadProviderOzone.
diff --git a/ui/events/ozone/evdev/input_controller_evdev.cc b/ui/events/ozone/evdev/input_controller_evdev.cc
index ad2f007..5288bae8 100644
--- a/ui/events/ozone/evdev/input_controller_evdev.cc
+++ b/ui/events/ozone/evdev/input_controller_evdev.cc
@@ -72,6 +72,9 @@
 void InputControllerEvdev::set_has_mouse(bool has_mouse) {
   has_mouse_ = has_mouse;
 }
+void InputControllerEvdev::set_any_keys_pressed(bool any) {
+  any_keys_are_pressed_ = any;
+}
 
 void InputControllerEvdev::set_has_pointing_stick(bool has_pointing_stick) {
   has_pointing_stick_ = has_pointing_stick;
@@ -449,4 +452,8 @@
   ScheduleUpdateDeviceSettings();
 }
 
+bool InputControllerEvdev::AreAnyKeysPressed() {
+  return any_keys_are_pressed_;
+}
+
 }  // namespace ui
diff --git a/ui/events/ozone/evdev/input_controller_evdev.h b/ui/events/ozone/evdev/input_controller_evdev.h
index 6624110..a240a4b 100644
--- a/ui/events/ozone/evdev/input_controller_evdev.h
+++ b/ui/events/ozone/evdev/input_controller_evdev.h
@@ -45,6 +45,7 @@
   void set_has_pointing_stick(bool has_pointing_stick);
   void set_has_touchpad(bool has_touchpad);
   void set_has_haptic_touchpad(bool has_haptic_touchpad);
+  void set_any_keys_pressed(bool any);
 
   void SetInputDevicesEnabled(bool enabled);
 
@@ -128,7 +129,7 @@
   void SetHapticTouchpadEffectForNextButtonRelease(
       HapticTouchpadEffect effect,
       HapticTouchpadEffectStrength strength) override;
-
+  bool AreAnyKeysPressed() override;
   // Notifies the controller to delete any data for the given `device_id`.
   void OnInputDeviceRemoved(int device_id);
 
@@ -175,6 +176,8 @@
   // if has_haptic_touchpad_ is true, then has_touchpad_ is also true.
   bool has_haptic_touchpad_ = false;
 
+  bool any_keys_are_pressed_ = false;
+
   // LED state.
   bool caps_lock_led_state_ = false;
 
diff --git a/ui/events/ozone/evdev/input_device_factory_evdev_unittest.cc b/ui/events/ozone/evdev/input_device_factory_evdev_unittest.cc
index 5d4129d..9e2c088 100644
--- a/ui/events/ozone/evdev/input_device_factory_evdev_unittest.cc
+++ b/ui/events/ozone/evdev/input_device_factory_evdev_unittest.cc
@@ -158,6 +158,7 @@
       const std::vector<InputDevice>& devices) override {}
   void DispatchDeviceListsComplete() override {}
   void DispatchStylusStateChanged(StylusState stylus_state) override {}
+  void DispatchAnyKeysPressedUpdated(bool any) override {}
 
   void DispatchGamepadEvent(const GamepadEvent& event) override {}
 
diff --git a/ui/events/ozone/evdev/keyboard_evdev.cc b/ui/events/ozone/evdev/keyboard_evdev.cc
index b439c4f..45e68b9c 100644
--- a/ui/events/ozone/evdev/keyboard_evdev.cc
+++ b/ui/events/ozone/evdev/keyboard_evdev.cc
@@ -18,10 +18,13 @@
 
 namespace ui {
 
-KeyboardEvdev::KeyboardEvdev(EventModifiers* modifiers,
-                             KeyboardLayoutEngine* keyboard_layout_engine,
-                             const EventDispatchCallback& callback)
+KeyboardEvdev::KeyboardEvdev(
+    EventModifiers* modifiers,
+    KeyboardLayoutEngine* keyboard_layout_engine,
+    const EventDispatchCallback& callback,
+    base::RepeatingCallback<void(bool)> any_keys_are_pressed_callback)
     : callback_(callback),
+      any_keys_are_pressed_callback_(any_keys_are_pressed_callback),
       modifiers_(modifiers),
       keyboard_layout_engine_(keyboard_layout_engine),
       auto_repeat_handler_(this) {}
@@ -45,6 +48,7 @@
     return;  // Key already released.
 
   key_state_.set(key, down);
+  any_keys_are_pressed_callback_.Run(key_state_.any());
   auto_repeat_handler_.UpdateKeyRepeat(key, scan_code, down,
                                        suppress_auto_repeat, device_id);
   DispatchKey(key, scan_code, down, is_repeat, timestamp, device_id, flags);
diff --git a/ui/events/ozone/evdev/keyboard_evdev.h b/ui/events/ozone/evdev/keyboard_evdev.h
index bdc684c..1e22f929 100644
--- a/ui/events/ozone/evdev/keyboard_evdev.h
+++ b/ui/events/ozone/evdev/keyboard_evdev.h
@@ -10,6 +10,7 @@
 #include <bitset>
 
 #include "base/component_export.h"
+#include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
@@ -32,9 +33,11 @@
 class COMPONENT_EXPORT(EVDEV) KeyboardEvdev
     : public EventAutoRepeatHandler::Delegate {
  public:
-  KeyboardEvdev(EventModifiers* modifiers,
-                KeyboardLayoutEngine* keyboard_layout_engine,
-                const EventDispatchCallback& callback);
+  KeyboardEvdev(
+      EventModifiers* modifiers,
+      KeyboardLayoutEngine* keyboard_layout_engine,
+      const EventDispatchCallback& callback,
+      base::RepeatingCallback<void(bool)> any_keys_are_pressed_callback);
 
   KeyboardEvdev(const KeyboardEvdev&) = delete;
   KeyboardEvdev& operator=(const KeyboardEvdev&) = delete;
@@ -98,6 +101,8 @@
   // Callback for dispatching events.
   const EventDispatchCallback callback_;
 
+  const base::RepeatingCallback<void(bool)> any_keys_are_pressed_callback_;
+
   // Shared modifier state.
   const raw_ptr<EventModifiers> modifiers_;
 
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
index efb57fd..312b5fca 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
@@ -292,6 +292,7 @@
       const std::vector<InputDevice>& devices) override {}
   void DispatchDeviceListsComplete() override {}
   void DispatchStylusStateChanged(StylusState stylus_state) override {}
+  void DispatchAnyKeysPressedUpdated(bool any) override {}
 
   // Dispatch Gamepad Event.
   void DispatchGamepadEvent(const GamepadEvent& event) override {}
diff --git a/ui/file_manager/BUILD.gn b/ui/file_manager/BUILD.gn
index 59a4bde..e7d4980 100644
--- a/ui/file_manager/BUILD.gn
+++ b/ui/file_manager/BUILD.gn
@@ -54,6 +54,7 @@
     "image_loader:closure_compile",
     "integration_tests:closure_compile",
     "integration_tests/file_manager:closure_compile",
+    "integration_tests/file_manager/page_objects:closure_compile",
   ]
 }
 
diff --git a/ui/file_manager/file_manager/widgets/xf_tree_item.ts b/ui/file_manager/file_manager/widgets/xf_tree_item.ts
index dbf225a3a..1ab25270 100644
--- a/ui/file_manager/file_manager/widgets/xf_tree_item.ts
+++ b/ui/file_manager/file_manager/widgets/xf_tree_item.ts
@@ -281,7 +281,8 @@
 
   override updated(changedProperties: PropertyValues<this>) {
     super.updated(changedProperties);
-    this.setAttribute('has-children', this.hasChildren().toString());
+    // For browser test use only.
+    this.setAttribute('has-children', String(this.items_.length > 0));
     if (changedProperties.has('expanded')) {
       this.onExpandChanged_();
     }
diff --git a/ui/file_manager/integration_tests/file_manager/BUILD.gn b/ui/file_manager/integration_tests/file_manager/BUILD.gn
index f80fb9b..d67c20b 100644
--- a/ui/file_manager/integration_tests/file_manager/BUILD.gn
+++ b/ui/file_manager/integration_tests/file_manager/BUILD.gn
@@ -127,6 +127,7 @@
   testonly = true
   deps = [
     ":test_data",
+    "page_objects:directory_tree",
     "//ui/file_manager/integration_tests:testcase",
   ]
 }
diff --git a/ui/file_manager/integration_tests/file_manager/background.js b/ui/file_manager/integration_tests/file_manager/background.js
index ef38647..d8c188b 100644
--- a/ui/file_manager/integration_tests/file_manager/background.js
+++ b/ui/file_manager/integration_tests/file_manager/background.js
@@ -51,11 +51,12 @@
 import './zip_files.js';
 
 import {FilesAppState} from '../files_app_state.js';
-import {RemoteCall, RemoteCallFilesApp} from '../remote_call.js';
-import {addEntries, checkIfNoErrorsOccuredOnApp, ENTRIES, getCaller, getRootPathsResult, pending, repeatUntil, RootPath, sendBrowserTestCommand, sendTestMessage, TestEntryInfo, testPromiseAndApps} from '../test_util.js';
+import {RemoteCallFilesApp} from '../remote_call.js';
+import {addEntries, getCaller, getRootPathsResult, pending, repeatUntil, RootPath, sendBrowserTestCommand, sendTestMessage, TestEntryInfo} from '../test_util.js';
 import {testcase} from '../testcase.js';
 
 import {CHOOSE_ENTRY_PROPERTY} from './choose_entry_const.js';
+import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
 import {BASIC_CROSTINI_ENTRY_SET, BASIC_DRIVE_ENTRY_SET, BASIC_LOCAL_ENTRY_SET, FILE_MANAGER_EXTENSIONS_ID} from './test_data.js';
 
 /**
@@ -367,8 +368,8 @@
       'fakeMouseClick', appId,
       ['[command="#pin-folder"]:not([hidden]):not([disabled])']));
 
-  await remoteCall.waitForElement(
-      appId, `.tree-item[entry-label="${directoryName}"]`);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
+  await directoryTree.waitForItemByLabel(directoryName);
 }
 
 /**
@@ -379,20 +380,8 @@
  * @return {Promise} Promise fulfilled on success.
  */
 export async function expandTreeItem(appId, treeItem) {
-  // TODO(b/272125628): Use page object.
-  const useNewTree =
-      await sendTestMessage({name: 'isFilesExperimentalEnabled'}) === 'true';
-  const expandIcon = useNewTree ?
-      // expand-icon is inside shadow DOM.
-      [treeItem, '.tree-item > .tree-row-wrapper > .tree-row > .expand-icon'] :
-      (treeItem +
-       ' > .tree-row:is([has-children=true], [may-have-children]) .expand-icon');
-  await remoteCall.waitAndClickElement(appId, expandIcon);
-
-  const expandedSubtree = useNewTree ?
-      `${treeItem}[expanded]` :
-      (treeItem + '> .tree-children[expanded]');
-  await remoteCall.waitForElement(appId, expandedSubtree);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
+  await directoryTree.expandTreeItem(treeItem);
 }
 
 /**
@@ -401,36 +390,12 @@
  * @param {string} appId Files app windowId.
  * @param {string} breadcrumbsPath Path based in the entry labels like:
  *    /My files/Downloads/photos
- * @return {Promise<string>} Promise fulfilled on success with the selector
+ * @return {!Promise<string>} Promise fulfilled on success with the selector
  *    query of the last directory expanded.
  */
 export async function recursiveExpand(appId, breadcrumbsPath) {
-  const paths = breadcrumbsPath.split('/').filter(path => path);
-  // TODO(b/272125628): Use page object.
-  const useNewTree =
-      await sendTestMessage({name: 'isFilesExperimentalEnabled'}) === 'true';
-  const hasChildren = useNewTree ?
-      ' > xf-tree-item' :
-      ' > .tree-row:is([has-children=true], [may-have-children])';
-
-  // Expand each directory in the breadcrumb.
-  let query = '#directory-tree';
-  for (const parentLabel of paths) {
-    // Wait for parent element to be displayed.
-    query += useNewTree ? ` [label="${parentLabel}"]` :
-                          ` [entry-label="${parentLabel}"]`;
-    await remoteCall.waitForElement(appId, query);
-
-    // Only expand if element isn't expanded yet.
-    const elements = await remoteCall.callRemoteTestUtil(
-        'queryAllElements', appId, [query + '[expanded]']);
-    if (!elements.length) {
-      await remoteCall.waitForElement(appId, query + hasChildren);
-      await expandTreeItem(appId, query);
-    }
-  }
-
-  return query;
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
+  return directoryTree.recursiveExpand(breadcrumbsPath);
 }
 
 /**
@@ -446,33 +411,8 @@
  */
 export async function navigateWithDirectoryTree(
     appId, breadcrumbsPath, shortcutToPath) {
-  // Focus the directory tree.
-  chrome.test.assertTrue(
-      !!await remoteCall.callRemoteTestUtil(
-          'focus', appId, ['#directory-tree']),
-      'focus failed: #directory-tree');
-
-  const paths = breadcrumbsPath.split('/');
-  const leaf = paths.pop();
-
-  // Expand all parents of the leaf entry.
-  let query = await recursiveExpand(appId, paths.join('/'));
-
-  // Navigate to the final entry.
-  query += ` [entry-label="${leaf}"]`;
-  await remoteCall.waitAndClickElement(appId, query);
-
-  // Wait directory to finish scanning its content.
-  await remoteCall.waitForElement(appId, `[scan-completed="${leaf}"]`);
-
-  // If the search was not closed, wait for it to close.
-  await remoteCall.waitForElement(appId, '#search-wrapper[collapsed]');
-
-  // Wait to navigation to final entry to finish.
-  await remoteCall.waitUntilCurrentDirectoryIsChanged(
-      appId, (shortcutToPath || breadcrumbsPath));
-
-  return query;
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
+  return directoryTree.navigateToPath(breadcrumbsPath, shortcutToPath);
 }
 
 /**
@@ -483,18 +423,17 @@
  */
 export async function mountCrostini(
     appId, initialEntries = BASIC_CROSTINI_ENTRY_SET) {
-  const fakeLinuxFiles = '#directory-tree [root-type-icon="crostini"]';
-  const realLinxuFiles = '#directory-tree [volume-type-icon="crostini"]';
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Add entries to crostini volume, but do not mount.
   await addEntries(['crostini'], initialEntries);
 
   // Linux files fake root is shown.
-  await remoteCall.waitForElement(appId, fakeLinuxFiles);
+  await directoryTree.waitForPlaceholderItem('crostini');
 
   // Mount crostini, and ensure real root and files are shown.
-  remoteCall.callRemoteTestUtil('fakeMouseClick', appId, [fakeLinuxFiles]);
-  await remoteCall.waitForElement(appId, realLinxuFiles);
+  await directoryTree.selectPlaceholderItem('crostini');
+  await directoryTree.waitForItemByType('crostini');
   const files = TestEntryInfo.getExpectedRows(BASIC_CROSTINI_ENTRY_SET);
   await remoteCall.waitForFiles(appId, files);
 }
@@ -507,20 +446,19 @@
  *     load in the volume.
  */
 export async function mountGuestOs(appId, initialEntries) {
-  const id = await sendTestMessage({
+  await sendTestMessage({
     name: 'registerMountableGuest',
     displayName: 'Bluejohn',
     canMount: true,
     vmType: 'bruschetta',
   });
-  const placeholder = '#directory-tree [root-type-icon="bruschetta"]';
-  const real = '#directory-tree [volume-type-icon="bruschetta"]';
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Wait for the GuestOS fake root then click it.
-  remoteCall.waitAndClickElement(appId, placeholder);
+  await directoryTree.selectPlaceholderItem('bruschetta');
 
   // Wait for the volume to get mounted.
-  await remoteCall.waitForElement(appId, real);
+  await directoryTree.waitForItemByType('bruschetta');
 
   // Add entries to GuestOS volume
   await addEntries(['guest_os_0'], initialEntries);
diff --git a/ui/file_manager/integration_tests/file_manager/directory_tree.js b/ui/file_manager/integration_tests/file_manager/directory_tree.js
index d3429b5..d5e2830 100644
--- a/ui/file_manager/integration_tests/file_manager/directory_tree.js
+++ b/ui/file_manager/integration_tests/file_manager/directory_tree.js
@@ -5,140 +5,105 @@
 import {addEntries, ENTRIES, EntryType, RootPath, sendTestMessage, TestEntryInfo} from '../test_util.js';
 import {testcase} from '../testcase.js';
 
-import {navigateWithDirectoryTree, recursiveExpand, remoteCall, setupAndWaitUntilReady} from './background.js';
+import {remoteCall, setupAndWaitUntilReady} from './background.js';
+import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
 import {BASIC_LOCAL_ENTRY_SET} from './test_data.js';
 
 /**
- * Tests that when the current folder is changed, the 'active' attribute
- * appears in the active folder .tree-row and the "Current directory" aria
+ * Tests that when the current folder is changed, the 'selected' attribute
+ * appears in the selected folder .tree-row and the "Current directory" aria
  * description is present on the corresponding tree item.
  */
 testcase.directoryTreeActiveDirectory = async () => {
-  const activeRow = '.tree-row[selected][active]';
-  const selectedAriaCurrentDirectory =
-      '.tree-item[selected][aria-description="Current directory"]';
-
   // Open FilesApp on Downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Change to My files folder.
-  await navigateWithDirectoryTree(appId, '/My files');
+  await directoryTree.navigateToPath('/My files');
 
-  // Check: the My files folder should be the active tree row.
-  const myFiles = await remoteCall.waitForElement(appId, activeRow);
-  chrome.test.assertTrue(myFiles.text.includes('My files'));
-  // Check: the corresponding tree item should be selected and have the "Current
+  // Check: the My files folder should be the selected tree row.
+  await directoryTree.waitForSelectedItemByLabel('My files');
+  // Check: the corresponding tree item should be focused and have the "Current
   // directory" aria description.
-  let currentDirectoryTreeItem =
-      await remoteCall.waitForElement(appId, selectedAriaCurrentDirectory);
-  chrome.test.assertEq(
-      currentDirectoryTreeItem.attributes['entry-label'], 'My files');
+  await directoryTree.waitForFocusedItemByLabel('My files');
+  await directoryTree.waitForCurrentDirectoryItemByLabel('My files');
 
   // Change to Downloads folder.
-  await navigateWithDirectoryTree(appId, '/My files/Downloads');
+  await directoryTree.navigateToPath('/My files/Downloads');
 
-  // Check: the Downloads folder should be the active tree row.
-  const downloads = await remoteCall.waitForElement(appId, activeRow);
-  chrome.test.assertTrue(downloads.text.includes('Downloads'));
-  // Check: the corresponding tree item should be selected and have the "Current
+  // Check: the Downloads folder should be the selected tree row.
+  await directoryTree.waitForSelectedItemByLabel('Downloads');
+  // Check: the corresponding tree item should be focused and have the "Current
   // directory" aria description.
-  currentDirectoryTreeItem =
-      await remoteCall.waitForElement(appId, selectedAriaCurrentDirectory);
-  chrome.test.assertEq(
-      currentDirectoryTreeItem.attributes['entry-label'], 'Downloads');
+  await directoryTree.waitForFocusedItemByLabel('Downloads');
+  await directoryTree.waitForCurrentDirectoryItemByLabel('Downloads');
 
   // Change to Google Drive volume's My Drive folder.
-  await navigateWithDirectoryTree(appId, '/My Drive');
+  await directoryTree.navigateToPath('/My Drive');
 
-  // Check: the My Drive folder should be the active tree row.
-  const myDrive = await remoteCall.waitForElement(appId, activeRow);
-  chrome.test.assertTrue(myDrive.text.includes('My Drive'));
-  // Check: the corresponding tree item should be selected and have the "Current
+  // Check: the My Drive folder should be the selected tree row.
+  await directoryTree.waitForSelectedItemByLabel('My Drive');
+  // Check: the corresponding tree item should be focused and have the "Current
   // directory" aria description.
-  currentDirectoryTreeItem =
-      await remoteCall.waitForElement(appId, selectedAriaCurrentDirectory);
-  chrome.test.assertEq(
-      currentDirectoryTreeItem.attributes['entry-label'], 'My Drive');
+  await directoryTree.waitForFocusedItemByLabel('My Drive');
+  await directoryTree.waitForCurrentDirectoryItemByLabel('My Drive');
 
   // Change to Recent folder.
-  await navigateWithDirectoryTree(appId, '/Recent');
+  await directoryTree.navigateToPath('/Recent');
 
-  // Check: the Recent folder should be the active tree row.
-  const recent = await remoteCall.waitForElement(appId, activeRow);
-  chrome.test.assertTrue(recent.text.includes('Recent'));
-  // Check: the corresponding tree item should be selected and have the "Current
+  // Check: the Recent folder should be the selected tree row.
+  await directoryTree.waitForSelectedItemByLabel('Recent');
+  // Check: the corresponding tree item should be focused and have the "Current
   // directory" aria description.
-  currentDirectoryTreeItem =
-      await remoteCall.waitForElement(appId, selectedAriaCurrentDirectory);
-  chrome.test.assertEq(
-      currentDirectoryTreeItem.attributes['entry-label'], 'Recent');
+  await directoryTree.waitForFocusedItemByLabel('Recent');
+  await directoryTree.waitForCurrentDirectoryItemByLabel('Recent');
 };
 
 /**
- * Tests that when the selected folder in the directory tree changes, the active
- * folder and the tree item that has the "Current directory" aria description do
- * not change. Also tests that when the selected folder is activated (via the
- * Enter key for example), both the active folder and the tree item with a
- * "Current directory" aria description are updated.
+ * Tests that when the selected folder in the directory tree changes, the
+ * selected folder and the tree item that has the "Current directory" aria
+ * description do not change. Also tests that when the focused folder is
+ * activated (via the Enter key for example), both the selected folder and the
+ * tree item with a "Current directory" aria description are updated.
  */
 testcase.directoryTreeSelectedDirectory = async () => {
-  const selectedActiveRow = '.tree-row[selected][active]';
-  const selectedAriaCurrentDirectory =
-      '.tree-item[selected][aria-description="Current directory"]';
-
   // Open FilesApp on Downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Change to My files folder.
-  await navigateWithDirectoryTree(appId, '/My files');
+  await directoryTree.navigateToPath('/My files');
 
-  // Check: the My files folder should be [selected] and [active].
-  let treeRow = await remoteCall.waitForElement(appId, selectedActiveRow);
-  chrome.test.assertTrue(treeRow.text.includes('My files'));
-  // Check: the corresponding tree item should be selected and have the "Current
+  // Check: the My files folder should be selected.
+  await directoryTree.waitForSelectedItemByLabel('My files');
+  // Check: the corresponding tree item should be focused and have the "Current
   // directory" aria description.
-  let treeItem =
-      await remoteCall.waitForElement(appId, selectedAriaCurrentDirectory);
-  chrome.test.assertEq(treeItem.attributes['entry-label'], 'My files');
+  await directoryTree.waitForFocusedItemByLabel('My files');
+  await directoryTree.waitForCurrentDirectoryItemByLabel('My files');
 
-  // Send ArrowUp key to change the selected folder.
-  const arrowUp = ['#directory-tree', 'ArrowUp', false, false, false];
-  await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, arrowUp);
+  // Send ArrowUp key to change the focused folder.
+  await directoryTree.focusPreviousItem();
 
-  // Check: no folder should be [selected] and [active].
-  await remoteCall.waitForElementLost(appId, selectedActiveRow);
-  // Check: no tree item should be [selected] and have the "Current directory"
-  // aria description.
-  await remoteCall.waitForElementLost(appId, selectedAriaCurrentDirectory);
-
-  // Check: the My files folder should be [active].
-  treeRow = await remoteCall.waitForElement(appId, '.tree-row[active]');
-  chrome.test.assertTrue(treeRow.text.includes('My files'));
+  await directoryTree.waitForFocusedItemByLabel('Recent');
+  // Check: the My files folder should be selected.
+  await directoryTree.waitForSelectedItemByLabel('My files');
   // Check: the corresponding tree item should have the "Current directory" aria
   // description.
-  treeItem = await remoteCall.waitForElement(
-      appId, '.tree-item[aria-description="Current directory"]');
-  chrome.test.assertEq(treeItem.attributes['entry-label'], 'My files');
+  await directoryTree.waitForCurrentDirectoryItemByLabel('My files');
 
-  // Check: the Recent Media View folder should be [selected].
-  treeRow = await remoteCall.waitForElement(appId, '.tree-row[selected]');
-  chrome.test.assertTrue(treeRow.text.includes('Recent'));
-  // Check: the corresponding tree item should be [selected].
-  treeItem = await remoteCall.waitForElement(appId, '.tree-item[selected]');
-  chrome.test.assertEq(treeItem.attributes['entry-label'], 'Recent');
+  // Check: the Recent Media View folder should be focused.
+  await directoryTree.waitForFocusedItemByLabel('Recent');
 
-  // Send Enter key to activate the selected folder.
-  const enter = ['#directory-tree', 'Enter', false, false, false];
-  await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, enter);
+  // Send Enter key to activate the focused folder.
+  await directoryTree.selectFocusedItem();
 
-  // Check: the Recent folder should be [selected] and [active].
-  treeRow = await remoteCall.waitForElement(appId, selectedActiveRow);
-  chrome.test.assertTrue(treeRow.text.includes('Recent'));
-  // Check: the corresponding tree item should be selected and have the "Current
+  // Check: the Recent folder should be focused and selected.
+  await directoryTree.waitForSelectedItemByLabel('Recent');
+  // Check: the corresponding tree item should be focused and have the "Current
   // directory" aria description.
-  treeItem =
-      await remoteCall.waitForElement(appId, selectedAriaCurrentDirectory);
-  chrome.test.assertEq(treeItem.attributes['entry-label'], 'Recent');
+  await directoryTree.waitForFocusedItemByLabel('Recent');
+  await directoryTree.waitForCurrentDirectoryItemByLabel('Recent');
 };
 
 /**
@@ -161,21 +126,21 @@
 
   // Open FilesApp on Downloads and expand the tree view of Downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, folders, []);
-  await recursiveExpand(appId, '/My files/Downloads');
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
+  await directoryTree.recursiveExpand('/My files/Downloads');
 
   // Verify the directory tree is not vertically scrolled.
-  const directoryTree = '#directory-tree';
   const original = await remoteCall.waitForElementStyles(
-      appId, directoryTree, ['scrollTop']);
+      appId, directoryTree.rootSelector, ['scrollTop']);
   chrome.test.assertTrue(original.scrollTop === 0);
 
   // Scroll the directory tree down (vertical scroll).
   await remoteCall.callRemoteTestUtil(
-      'setScrollTop', appId, [directoryTree, 100]);
+      'setScrollTop', appId, [directoryTree.rootSelector, 100]);
 
   // Check: the directory tree should be vertically scrolled.
   const scrolled = await remoteCall.waitForElementStyles(
-      appId, directoryTree, ['scrollTop']);
+      appId, directoryTree.rootSelector, ['scrollTop']);
   const scrolledDown = scrolled.scrollTop === 100;
   chrome.test.assertTrue(scrolledDown, 'Tree should scroll down');
 };
@@ -187,63 +152,31 @@
   // Open FilesApp on Downloads and expand the tree view of Downloads.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
-  await recursiveExpand(appId, '/My files/Downloads');
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
+  await directoryTree.recursiveExpand('/My files/Downloads');
 
   // Verify the directory tree is not horizontally scrolled.
-  const directoryTree = '#directory-tree';
   const original = await remoteCall.waitForElementStyles(
-      appId, directoryTree, ['scrollLeft']);
+      appId, directoryTree.rootSelector, ['scrollLeft']);
   chrome.test.assertTrue(original.scrollLeft === 0);
 
   // Shrink the tree to 50px. TODO(files-ng): consider using 150px?
-  const navigationList = '.dialog-navigation-list';
   await remoteCall.callRemoteTestUtil(
-      'setElementStyles', appId, [navigationList, {width: '50px'}]);
+      'setElementStyles', appId,
+      [directoryTree.containerSelector, {width: '50px'}]);
 
   // Scroll the directory tree left (horizontal scroll).
   await remoteCall.callRemoteTestUtil(
-      'setScrollLeft', appId, [directoryTree, 100]);
+      'setScrollLeft', appId, [directoryTree.rootSelector, 100]);
 
   // Check: the directory tree should not be horizontally scrolled.
   const scrolled = await remoteCall.waitForElementStyles(
-      appId, directoryTree, ['scrollLeft']);
+      appId, directoryTree.rootSelector, ['scrollLeft']);
   const noScrollLeft = scrolled.scrollLeft === 0;
   chrome.test.assertTrue(noScrollLeft, 'Tree should not scroll left');
 };
 
 /**
- * Tests that clicking a directory tree recent subtype {audio,image,video}
- * tab does not vertically scroll the tree.
- */
-testcase.directoryTreeRecentsSubtypeScroll = async () => {
-  // Open FilesApp on Downloads.
-  const appId = await setupAndWaitUntilReady(
-      RootPath.DOWNLOADS, BASIC_LOCAL_ENTRY_SET, []);
-
-  // Set window height to 400px so the tree has a vertical scroll bar.
-  await remoteCall.callRemoteTestUtil('resizeWindow', appId, [680, 400]);
-  await remoteCall.waitForWindowGeometry(appId, 680, 400);
-
-  // Wait for the recent image tab and save its element properties.
-  const recentQuery =
-      '#directory-tree [root-type-icon="recent"][recent-file-type="image"]';
-  const savedElement =
-      await remoteCall.waitForElementStyles(appId, recentQuery, ['display']);
-  chrome.test.assertTrue(savedElement.renderedTop > 0);
-
-  // Click recent image tab and wait for its file-list content to appear.
-  await remoteCall.waitAndClickElement(appId, recentQuery);
-  const file = TestEntryInfo.getExpectedRows([ENTRIES.desktop]);
-  await remoteCall.waitForFiles(appId, file, {ignoreLastModifiedTime: true});
-
-  // Check: the recent image tab element.renderedTop should not change.
-  const resultElement =
-      await remoteCall.waitForElementStyles(appId, recentQuery, ['display']);
-  const notScrolled = savedElement.renderedTop === resultElement.renderedTop;
-  chrome.test.assertTrue(notScrolled, 'Tree should not vertically scroll');
-};
-
-/**
  * Tests that the directory tree does not horizontally scroll when expanding
  * nested folder items.
  */
@@ -275,26 +208,25 @@
   // Open FilesApp on Downloads containing the folder test entries.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, nestedFolderTestEntries, []);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Verify the directory tree is not horizontally scrolled.
-  const directoryTree = '#directory-tree';
   const original = await remoteCall.waitForElementStyles(
-      appId, directoryTree, ['scrollLeft']);
+      appId, directoryTree.rootSelector, ['scrollLeft']);
   chrome.test.assertTrue(original.scrollLeft === 0);
 
   // Shrink the tree to 150px, enough to hide the deep folder names.
-  const navigationList = '.dialog-navigation-list';
   await remoteCall.callRemoteTestUtil(
-      'setElementStyles', appId, [navigationList, {width: '150px'}]);
+      'setElementStyles', appId,
+      [directoryTree.containerSelector, {width: '150px'}]);
 
   // Expand the tree Downloads > nested-folder1 > nested-folder2 ...
   const lastFolderPath = nestedFolderTestEntries.pop().targetPath;
-  await navigateWithDirectoryTree(
-      appId, `/My files/Downloads/${lastFolderPath}`);
+  await directoryTree.navigateToPath(`/My files/Downloads/${lastFolderPath}`);
 
   // Check: the directory tree should be showing the last test entry.
-  await remoteCall.waitForElement(
-      appId, '.tree-item[entry-label="nested-folder5"]:not([hidden])');
+  const folder5Item = await directoryTree.waitForItemByLabel('nested-folder5');
+  chrome.test.assertFalse(!!folder5Item.attributes.hidden);
 
   // Ensure the directory tree scroll event handling is complete.
   chrome.test.assertTrue(
@@ -302,7 +234,7 @@
 
   // Check: the directory tree should not be horizontally scrolled.
   const scrolled = await remoteCall.waitForElementStyles(
-      appId, directoryTree, ['scrollLeft']);
+      appId, directoryTree.rootSelector, ['scrollLeft']);
   const notScrolled = scrolled.scrollLeft === 0;
   chrome.test.assertTrue(notScrolled, 'Tree should not scroll left');
 };
@@ -339,37 +271,36 @@
   // Open FilesApp on Downloads containing the folder test entries.
   const appId = await setupAndWaitUntilReady(
       RootPath.DOWNLOADS, nestedFolderTestEntries, []);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Redraw FilesApp with text direction RTL.
   chrome.test.assertTrue(await remoteCall.callRemoteTestUtil(
       'renderWindowTextDirectionRTL', appId, []));
 
   // Verify the directory tree is not horizontally scrolled.
-  const directoryTree = '#directory-tree';
   const original = await remoteCall.waitForElementStyles(
-      appId, directoryTree, ['scrollLeft']);
+      appId, directoryTree.rootSelector, ['scrollLeft']);
   chrome.test.assertTrue(original.scrollLeft === 0);
 
   // Shrink the tree to 150px, enough to hide the deep folder names.
-  const navigationList = '.dialog-navigation-list';
   await remoteCall.callRemoteTestUtil(
-      'setElementStyles', appId, [navigationList, {width: '150px'}]);
+      'setElementStyles', appId,
+      [directoryTree.containerSelector, {width: '150px'}]);
 
   // Expand the tree Downloads > nested-folder1 > nested-folder2 ...
   const lastFolderPath = nestedFolderTestEntries.pop().targetPath;
-  await navigateWithDirectoryTree(
-      appId, `/My files/Downloads/${lastFolderPath}`);
+  await directoryTree.navigateToPath(`/My files/Downloads/${lastFolderPath}`);
 
   // Check: the directory tree should be showing the last test entry.
-  await remoteCall.waitForElement(
-      appId, '.tree-item[entry-label="nested-folder5"]:not([hidden])');
+  const folder5Item = await directoryTree.waitForItemByLabel('nested-folder5');
+  chrome.test.assertFalse(!!folder5Item.attributes.hidden);
 
   // Ensure the directory tree scroll event handling is complete.
   chrome.test.assertTrue(
       await remoteCall.callRemoteTestUtil('requestAnimationFrame', appId, []));
 
   const scrolled = await remoteCall.waitForElementStyles(
-      appId, directoryTree, ['scrollLeft']);
+      appId, directoryTree.rootSelector, ['scrollLeft']);
 
   // Check: the directory tree should not be horizontally scrolled.
   const notScrolled = scrolled.scrollLeft === 0;
@@ -425,34 +356,24 @@
 
   // Open FilesApp on Downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, entries, []);
-  // TODO(b/272125628): Use page object.
-  const useNewTree =
-      await sendTestMessage({name: 'isFilesExperimentalEnabled'}) === 'true';
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   const start = Date.now();
 
   // Expand the large-folder-0.
-  await recursiveExpand(appId, '/My files/Downloads/large-folder-0');
+  await directoryTree.recursiveExpand('/My files/Downloads/large-folder-0');
 
   // Wait for all sub-folders to have the expand icon.
-  const querySubFolderExpandIcons = useNewTree ?
-      ['#directory-tree [label="large-folder-0"] > xf-tree-item'] :
-      ['#directory-tree [entry-label="large-folder-0"] > ' +
-       '.tree-children > .tree-item > .tree-row[has-children="true"]'];
-  await remoteCall.waitForElementsCount(
-      appId, querySubFolderExpandIcons, numberOfSubFolders);
+  await directoryTree.waitForChildItemsCountByLabel(
+      'large-folder-0', numberOfSubFolders, /* excludeEmptyChild= */ true);
 
   // Expand a sub-folder.
-  await recursiveExpand(
-      appId, '/My files/Downloads/large-folder-0/sub-folder-0');
+  await directoryTree.recursiveExpand(
+      '/My files/Downloads/large-folder-0/sub-folder-0');
 
   // Wait sub-folder to have its 1k sub-sub-folders.
-  const querySubSubFolderItems = useNewTree ?
-      ['#directory-tree [label="sub-folder-0"] > xf-tree-item'] :
-      ['#directory-tree [entry-label="sub-folder-0"] > ' +
-       '.tree-children > .tree-item'];
-  await remoteCall.waitForElementsCount(
-      appId, querySubSubFolderItems, numberOfSubSubFolders);
+  await directoryTree.waitForChildItemsCountByLabel(
+      'sub-folder-0', numberOfSubSubFolders);
 
   const testTime = Date.now() - start;
   console.log(`[measurement] Test time: ${testTime}ms`);
@@ -472,16 +393,14 @@
 
   // Opens FilesApp on downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, entries, []);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Expand all sub-directories in downloads.
-  await recursiveExpand(appId, '/My files/Downloads');
-
-  // Target the non-hidden folder.
-  const normalFolder = '#directory-tree [entry-label="normal-folder"]';
-  const response = await remoteCall.waitForElement(appId, normalFolder);
+  await directoryTree.recursiveExpand('/My files/Downloads');
 
   // Assert that the expand icon will not show up.
-  chrome.test.assertTrue(response.attributes['has-children'] === 'false');
+  await directoryTree.waitForItemToHaveChildren(
+      'normal-folder', /* hasChildren= */ false);
 };
 
 /**
@@ -498,6 +417,7 @@
 
   // Opens FilesApp on downloads.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, entries, []);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Enable show hidden files.
   await remoteCall.waitAndClickElement(appId, '#gear-button');
@@ -507,12 +427,11 @@
           ':not([checked]):not([hidden])');
 
   // Expand all sub-directories in Downloads.
-  await recursiveExpand(appId, '/My files/Downloads');
+  await directoryTree.recursiveExpand('/My files/Downloads');
 
   // Assert that the expand icon shows up.
-  const normalFolder =
-      '#directory-tree [entry-label="normal-folder"][has-children="true"]';
-  await remoteCall.waitForElement(appId, normalFolder);
+  await directoryTree.waitForItemToHaveChildren(
+      'normal-folder', /* hasChildren= */ true);
 };
 
 /**
@@ -533,22 +452,21 @@
 
   // Opens FilesApp on downloads with the folders above.
   const appId = await setupAndWaitUntilReady(RootPath.DOWNLOADS, entries, []);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Expand the parent folder, which should check if the child folder
   // itself has children.
-  await recursiveExpand(appId, '/My files/Downloads/parent-folder');
+  await directoryTree.recursiveExpand('/My files/Downloads/parent-folder');
 
   // Check that the empty child folder has been checked for children, and was
   // found to have none. This ensures the expand icon is hidden.
-  const emptyChildFolder =
-      '#directory-tree [entry-label="empty-child-folder"][has-children=false]';
-  await remoteCall.waitForElement(appId, emptyChildFolder);
+  await directoryTree.waitForItemToHaveChildren(
+      'empty-child-folder', /* hasChildren= */ false);
 
   // Check that the non-empty child folder has been checked for children, and
   // was found to have some. This ensures the expand icon is shown.
-  const nonEmptyChildFolder =
-      '#directory-tree [entry-label="non-empty-child-folder"][has-children=true]';
-  await remoteCall.waitForElement(appId, nonEmptyChildFolder);
+  await directoryTree.waitForItemToHaveChildren(
+      'non-empty-child-folder', /* hasChildren= */ true);
 };
 
 /**
@@ -572,10 +490,9 @@
         'grandparent-folder/middle-child-folder/grandchild-folder'),
   ];
 
-  const SMBFS_VOLUME_QUERY = '#directory-tree [volume-type-icon="smb"]';
-
   // Open Files app.
   const appId = await setupAndWaitUntilReady(null, [], []);
+  const directoryTree = await DirectoryTreePageObject.create(appId, remoteCall);
 
   // Populate Smbfs with the directories.
   await addEntries(['smbfs'], entries);
@@ -583,53 +500,40 @@
   // Mount Smbfs volume.
   await sendTestMessage({name: 'mountSmbfs'});
 
-  // Wait for the Smbfs volume to mount.
-  await remoteCall.waitForElement(appId, SMBFS_VOLUME_QUERY);
-
   // Click to open the Smbfs volume.
-  await remoteCall.waitAndClickElement(appId, [SMBFS_VOLUME_QUERY]);
+  await directoryTree.selectItemByType('smb');
 
   // Expand the parent folder.
-  await recursiveExpand(appId, '/SMB Share/parent-folder');
+  await directoryTree.recursiveExpand('/SMB Share/parent-folder');
 
   // The child folder should have the 'may-have-children' attribute and
   // should not have the 'has-children' attribute. In this state, it
   // will display an expansion icon (until it's expanded and Files app
   // *knows* it has no children.
-  const childFolder = '#directory-tree [entry-label="child-folder"]';
-  await remoteCall.waitForElement(
-      appId, childFolder + '[may-have-children]:not([has-children])');
+  await directoryTree.waitForItemToMayHaveChildren('child-folder');
 
   // Expand the child folder, which will discover it has no children and
   // remove its expansion icon.
-  const childFolderExpandIcon =
-      childFolder + '> .tree-row[may-have-children] .expand-icon';
-  await remoteCall.waitAndClickElement(appId, childFolderExpandIcon);
+  await directoryTree.expandTreeItemByLabel(
+      'child-folder', /* allowEmpty= */ true);
 
   // The child folder should now have the 'has-children' attribute and
   // it should be false.
-  const childFolderExpandedSubtree =
-      childFolder + '> .tree-row[has-children=false]';
-  await remoteCall.waitForElement(appId, childFolderExpandedSubtree);
+  await directoryTree.waitForItemToHaveChildren(
+      'child-folder', /* hasChildren= */ false);
 
   // Expand the grandparent that has a middle child with a child.
-  await recursiveExpand(appId, '/SMB Share/grandparent-folder');
+  await directoryTree.recursiveExpand('/SMB Share/grandparent-folder');
 
   // The middle child should have an (eager) expand icon.
-  const middleChildFolder =
-      '#directory-tree [entry-label="middle-child-folder"]';
-  await remoteCall.waitForElement(
-      appId, middleChildFolder + '[may-have-children]:not([has-children])');
+  await directoryTree.waitForItemToMayHaveChildren('middle-child-folder');
 
   // Expand the middle child, which will discover it does actually have
   // children.
-  const middleChildFolderExpandIcon =
-      middleChildFolder + '> .tree-row[may-have-children] .expand-icon';
-  await remoteCall.waitAndClickElement(appId, middleChildFolderExpandIcon);
+  await directoryTree.expandTreeItemByLabel('middle-child-folder');
 
   // The middle child folder should now have the 'has-children' attribute
   // and it should be true (eg. it will retain its expand icon).
-  const middleChildFolderExpandedSubtree =
-      middleChildFolder + '> .tree-row[has-children=true]';
-  await remoteCall.waitForElement(appId, middleChildFolderExpandedSubtree);
+  await directoryTree.waitForItemToHaveChildren(
+      'middle-child-folder', /* hasChildren= */ true);
 };
diff --git a/ui/file_manager/integration_tests/file_manager/folder_shortcuts.js b/ui/file_manager/integration_tests/file_manager/folder_shortcuts.js
index 51c3464..dd39bf6 100644
--- a/ui/file_manager/integration_tests/file_manager/folder_shortcuts.js
+++ b/ui/file_manager/integration_tests/file_manager/folder_shortcuts.js
@@ -11,12 +11,12 @@
 /**
  * Directory tree selector constants.
  */
-const TREEITEM_A = TREEITEM_DRIVE + ' [entry-label="A"] ';
-const TREEITEM_B = TREEITEM_A + '[entry-label="B"] ';
-const TREEITEM_C = TREEITEM_B + '[entry-label="C"] ';
+const TREEITEM_A = TREEITEM_DRIVE + ' [entry-label="A"]';
+const TREEITEM_B = TREEITEM_A + ' [entry-label="B"]';
+const TREEITEM_C = TREEITEM_B + ' [entry-label="C"]';
 
-const TREEITEM_D = TREEITEM_DRIVE + ' [entry-label="D"] ';
-const TREEITEM_E = TREEITEM_D + '[entry-label="E"] ';
+const TREEITEM_D = TREEITEM_DRIVE + ' [entry-label="D"]';
+const TREEITEM_E = TREEITEM_D + ' [entry-label="E"]';
 
 /**
  * Entry set used for the folder shortcut tests.
diff --git a/ui/file_manager/integration_tests/file_manager/page_objects/BUILD.gn b/ui/file_manager/integration_tests/file_manager/page_objects/BUILD.gn
new file mode 100644
index 0000000..816dc20
--- /dev/null
+++ b/ui/file_manager/integration_tests/file_manager/page_objects/BUILD.gn
@@ -0,0 +1,27 @@
+# Copyright 2023 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/closure_compiler/compile_js.gni")
+
+js_type_check("closure_compile") {
+  testonly = true
+  deps = [ ":directory_tree" ]
+  closure_flags =
+      strict_error_checking_closure_args + [
+        "language_in=ECMASCRIPT_2020",
+        "js_module_root=./gen/ui",
+        "js_module_root=" + rebase_path("//ui", root_build_dir),
+        "browser_resolver_prefix_replacements=\"chrome://webui-test/=./\"",
+        "hide_warnings_for=third_party/",
+      ]
+}
+
+js_library("directory_tree") {
+  testonly = true
+  deps = [
+    "//ui/file_manager/integration_tests:element_object",
+    "//ui/file_manager/integration_tests:remote_call",
+    "//ui/file_manager/integration_tests:test_util",
+  ]
+}
diff --git a/ui/file_manager/integration_tests/file_manager/page_objects/directory_tree.js b/ui/file_manager/integration_tests/file_manager/page_objects/directory_tree.js
new file mode 100644
index 0000000..c3b60fc
--- /dev/null
+++ b/ui/file_manager/integration_tests/file_manager/page_objects/directory_tree.js
@@ -0,0 +1,599 @@
+// 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 {ElementObject} from '../../element_object.js';
+import {RemoteCallFilesApp} from '../../remote_call.js';
+import {sendTestMessage} from '../../test_util.js';
+
+
+const FAKE_ENTRY_PATH_PREFIX = 'fake-entry:';
+const REAL_ENTRY_PATH_PREFIX = 'filesystem:chrome://file-manager/external';
+
+/**
+ * This serves as the additional selector of the tree item.
+ *
+ * @typedef {{
+ *   expanded: (?boolean|undefined),
+ *   selected: (?boolean|undefined),
+ *   focused: (?boolean|undefined),
+ *   renaming: (?boolean|undefined),
+ *   acceptDrop: (?boolean|undefined),
+ *   denyDrop: (?boolean|undefined),
+ *   hasChildren: (?boolean|undefined),
+ *   mayHaveChildren: (?boolean|undefined),
+ *   currentDirectory: (?boolean|undefined),
+ * }}
+ */
+let ModifierOptions;
+
+/**
+ * Page object for Directory Tree, this class abstracts all the selectors
+ * related to directory tree and its tree items.
+ */
+export class DirectoryTreePageObject {
+  /**
+   * Return a singleton instance of DirectoryTreePageObject. This will make sure
+   * the directory tree DOM element is ready.
+   *
+   * @param {string} appId
+   * @param {!RemoteCallFilesApp} remoteCall
+   * @return {!Promise<DirectoryTreePageObject>}
+   */
+  static async create(appId, remoteCall) {
+    const useNewTree =
+        await sendTestMessage({name: 'isFilesExperimentalEnabled'}) === 'true';
+    const directoryTree =
+        new DirectoryTreePageObject(appId, remoteCall, useNewTree);
+    remoteCall.waitForElement(appId, directoryTree.rootSelector);
+    return directoryTree;
+  }
+
+  /**
+   * Note: do not use constructor directly, use `create` static method
+   * instead, which will fetch the `useNewTree_` value and make sure the tree
+   * DOM element is ready.
+   *
+   * @param {string} appId
+   * @param {!RemoteCallFilesApp} remoteCall
+   * @param {boolean} useNewTree
+   */
+  constructor(appId, remoteCall, useNewTree) {
+    /** @private {string} */
+    this.appId_ = appId;
+    /** @private {!RemoteCallFilesApp} */
+    this.remoteCall_ = remoteCall;
+    /** @private {boolean} */
+    this.useNewTree_ = useNewTree;
+    /** @private {!DirectoryTreeSelectors_} */
+    this.selectors_ = new DirectoryTreeSelectors_(useNewTree);
+  }
+
+  /**
+   * Returns the selector for the tree root.
+   * @return {string}
+   */
+  get rootSelector() {
+    return this.selectors_.root;
+  }
+
+  /**
+   * Returns the selector for the tree container.
+   * @return {string}
+   */
+  get containerSelector() {
+    return this.selectors_.container;
+  }
+
+  /**
+   * Wait for the selected(aka "active" in the old tree implementation) tree
+   * item with the label.
+   *
+   * @param {string} label Label of the tree item
+   * @return {!Promise<!ElementObject>}
+   */
+  async waitForSelectedItemByLabel(label) {
+    return this.remoteCall_.waitForElement(
+        this.appId_, this.selectors_.itemByLabel(label, {selected: true}));
+  }
+
+  /**
+   * Wait for the tree item with the label to have focused(aka "selected" in the
+   * old tree implementation) state.
+   *
+   * @param {string} label Label of the tree item
+   * @return {!Promise<!ElementObject>}
+   */
+  async waitForFocusedItemByLabel(label) {
+    return this.remoteCall_.waitForElement(
+        this.appId_, this.selectors_.itemByLabel(label, {focused: true}));
+  }
+
+  /**
+   * Wait for the tree item with the label to have the the current directory
+   * aria-description attribute.
+   *
+   * @param {string} label Label of the tree item
+   * @return {!Promise<!ElementObject>}
+   */
+  async waitForCurrentDirectoryItemByLabel(label) {
+    return this.remoteCall_.waitForElement(
+        this.appId_,
+        this.selectors_.itemByLabel(label, {currentDirectory: true}));
+  }
+
+  /**
+   * Wait for the child items of the specific item
+   *
+   * @param {string} label Label of the tree item
+   * @param {number} count Expected number of the child items
+   * @param {boolean=} excludeEmptyChild child items without any children
+   * @return {Promise}
+   */
+  async waitForChildItemsCountByLabel(label, count, excludeEmptyChild) {
+    const itemSelector = this.selectors_.itemByLabel(label);
+    const childItemsSelector = excludeEmptyChild ?
+        this.selectors_.childItemsWithNestedChildren(itemSelector) :
+        this.selectors_.childItems(itemSelector);
+    return this.remoteCall_.waitForElementsCount(
+        this.appId_, [childItemsSelector], count);
+  }
+
+  /**
+   * Get the label of the tree item.
+   * @param {?ElementObject} item the tree item.
+   * @returns {string}
+   */
+  getItemLabel(item) {
+    if (!item) {
+      chrome.test.fail('Item is not a valid tree item.');
+      return '';
+    }
+    return this.useNewTree_ ? item.attributes['label'] :
+                              item.attributes['entry-label'];
+  }
+
+  /**
+   * Wait for the item with the label to get the `has-children` attribute with
+   * the specified value.
+   *
+   * @param {string} label the label of the tree item.
+   * @param {boolean} hasChildren should hte tree item has children or not.
+   * @return {!Promise<!ElementObject>}
+   */
+  async waitForItemToHaveChildren(label, hasChildren) {
+    return this.remoteCall_.waitForElement(
+        this.appId_,
+        this.selectors_.itemByLabel(label, {hasChildren: hasChildren}));
+  }
+
+  /**
+   * Wait for the item with the label to get the `may-have-children` attribute.
+   *
+   * @param {string} label the tree item.
+   * @return {!Promise<!ElementObject>}
+   */
+  async waitForItemToMayHaveChildren(label) {
+    return this.remoteCall_.waitForElement(
+        this.appId_,
+        this.selectors_.itemByLabel(label, {mayHaveChildren: true}));
+  }
+
+  /**
+   * Expands a single tree item with the specified label by clicking on its
+   * expand icon.
+   * @param {string} label Label of the tree item we want to expand on.
+   * @param {boolean=} allowEmpty Allow expanding tree item without
+   *     any children.
+   * @return {!Promise<void>}
+   */
+  async expandTreeItemByLabel(label, allowEmpty) {
+    this.expandTreeItem(this.selectors_.itemByLabel(label), allowEmpty);
+  }
+
+  /**
+   * Expands each directory in the breadcrumbs path.
+   *
+   * @param {string} breadcrumbsPath Path based in the entry labels like:
+   *     /My files/Downloads/photos
+   * @return {!Promise<string>} Promise fulfilled on success with the selector
+   *    query of the last directory expanded.
+   */
+  async recursiveExpand(breadcrumbsPath) {
+    const paths = breadcrumbsPath.split('/').filter(path => path);
+
+    // Expand each directory in the breadcrumb.
+    let query = this.selectors_.root;
+    for (const parentLabel of paths) {
+      // Wait for parent element to be displayed.
+      query += ` ${this.selectors_.itemItselfByLabel(parentLabel)}`;
+      await this.remoteCall_.waitForElement(this.appId_, query);
+
+      // Only expand if element isn't expanded yet.
+      const elements = await this.remoteCall_.callRemoteTestUtil(
+          'queryAllElements', this.appId_,
+          [this.selectors_.attachModifier(query, {expanded: true})]);
+      if (elements.length === 0) {
+        await this.expandTreeItem(query);
+      }
+    }
+
+    return Promise.resolve(query);
+  }
+
+  /**
+   * Focus the directory tree and navigates using mouse clicks.
+   *
+   * @param {string} breadcrumbsPath Path based on the entry labels like:
+   *     /My files/Downloads/photos to item that should navigate to.
+   * @param {string=} shortcutToPath For shortcuts it navigates to a different
+   *   breadcrumbs path, like /My Drive/ShortcutName.
+   * @return {!Promise<string>} the final selector used to click on the
+   * desired tree item.
+   */
+  async navigateToPath(breadcrumbsPath, shortcutToPath) {
+    // Focus the directory tree.
+    await this.remoteCall_.callRemoteTestUtil(
+        'focus', this.appId_, [this.selectors_.root]);
+
+    const paths = breadcrumbsPath.split('/');
+    // For "/My Drive", expand the "Google Drive" first.
+    if (this.useNewTree_ && paths[1] === 'My Drive') {
+      paths.unshift('', 'Google Drive');
+    }
+    const leaf = paths.pop();
+
+    // Expand all parents of the leaf entry.
+    let query = await this.recursiveExpand(paths.join('/'));
+
+    // Navigate to the final entry.
+    query += ` ${this.selectors_.itemItselfByLabel(leaf)}`;
+    await this.remoteCall_.waitAndClickElement(this.appId_, query);
+
+    // Wait directory to finish scanning its content.
+    await this.remoteCall_.waitForElement(
+        this.appId_, `[scan-completed="${leaf}"]`);
+
+    // If the search was not closed, wait for it to close.
+    await this.remoteCall_.waitForElement(
+        this.appId_, '#search-wrapper[collapsed]');
+
+    // Wait to navigation to final entry to finish.
+    await this.remoteCall_.waitUntilCurrentDirectoryIsChanged(
+        this.appId_, (shortcutToPath || breadcrumbsPath));
+
+    // Focus the directory tree.
+    await this.remoteCall_.callRemoteTestUtil(
+        'focus', this.appId_, [this.selectors_.root]);
+
+    return query;
+  }
+
+  /**
+   * Trigger a keydown event with ArrowUp key to move the focus to the previous
+   * tree item.
+   *
+   * @return {!Promise<void>}
+   */
+  async focusPreviousItem() {
+    // Focus the tree first before keyboard event.
+    await this.remoteCall_.callRemoteTestUtil(
+        'focus', this.appId_, [this.selectors_.root]);
+
+    const arrowUp =
+        [this.selectors_.keyboardRecipient, 'ArrowUp', false, false, false];
+    await this.remoteCall_.callRemoteTestUtil(
+        'fakeKeyDown', this.appId_, arrowUp);
+  }
+
+  /**
+   * Trigger a keydown event with Enter key to select currently focused item.
+   *
+   * @return {!Promise<void>}
+   */
+  async selectFocusedItem() {
+    // Focus the tree first before keyboard event.
+    await this.remoteCall_.callRemoteTestUtil(
+        'focus', this.appId_, [this.selectors_.root]);
+
+    const enter =
+        [this.selectors_.keyboardRecipient, 'Enter', false, false, false];
+    await this.remoteCall_.callRemoteTestUtil(
+        'fakeKeyDown', this.appId_, enter);
+  }
+
+  /**
+   * Wait for the tree item by its label.
+   *
+   * @param {string} label Label of the tree item.
+   * @return {!Promise<!ElementObject>}
+   */
+  async waitForItemByLabel(label) {
+    return this.remoteCall_.waitForElement(
+        this.appId_, this.selectors_.itemByLabel(label));
+  }
+
+  /**
+   * Wait for the tree item by its type.
+   *
+   * @param {string} type Type of the tree item.
+   * @return {!Promise<!ElementObject>}
+   */
+  async waitForItemByType(type) {
+    return this.remoteCall_.waitForElement(
+        this.appId_,
+        this.selectors_.itemByType(type, /* isPlaceholder= */ false));
+  }
+
+  /**
+   * Wait for the placeholder tree item by its type.
+   *
+   * @param {string} type Type of the tree item.
+   * @return {!Promise<!ElementObject>}
+   */
+  async waitForPlaceholderItem(type) {
+    return this.remoteCall_.waitForElement(
+        this.appId_,
+        this.selectors_.itemByType(type, /* isPlaceholder= */ true));
+  }
+
+  /**
+   * Select the tree item by its label.
+   *
+   * @param {string} label Label of the tree item.
+   * @return {!Promise<!ElementObject>}
+   */
+  async selectItemByLabel(label) {
+    return this.remoteCall_.waitAndClickElement(
+        this.appId_, this.selectors_.itemByLabel(label));
+  }
+
+  /**
+   * Select the tree item by its type.
+   *
+   * @param {string} type Type of the tree item.
+   * @return {!Promise<void>}
+   */
+  async selectItemByType(type) {
+    this.selectItem_(
+        this.selectors_.itemByType(type, /* isPlaceholder= */ false));
+  }
+
+  /**
+   * Select the placeholder tree item by its type.
+   *
+   * @param {string} type Type of the placeholder tree item.
+   * @return {!Promise<void>}
+   */
+  async selectPlaceholderItem(type) {
+    this.selectItem_(
+        this.selectors_.itemByType(type, /* isPlaceholder= */ true));
+  }
+
+  /**
+   * Select the tree item by clicking it.
+   *
+   * @private
+   * @param {string} itemSelector
+   * @return {!Promise<void>}
+   */
+  async selectItem_(itemSelector) {
+    await this.remoteCall_.waitAndClickElement(this.appId_, [itemSelector]);
+  }
+
+  /**
+   * Expands a single tree item by clicking on its expand icon.
+   * TODO: this "selector" version should be private in future.
+   *
+   * @param {string} itemSelector Selector to the tree item that should be
+   *     expanded.
+   * @param {boolean=} allowEmpty Allow expanding tree item without
+   *     any children.
+   * @return {!Promise<void>}
+   */
+  async expandTreeItem(itemSelector, allowEmpty) {
+    await this.remoteCall_.waitForElement(this.appId_, itemSelector);
+    const elements = await this.remoteCall_.callRemoteTestUtil(
+        'queryAllElements', this.appId_,
+        [this.selectors_.attachModifier(itemSelector, {expanded: true})]);
+    // If it's already expanded just set the focus on directory tree.
+    if (elements.length > 0) {
+      if (!this.useNewTree_) {
+        return this.remoteCall_.callRemoteTestUtil(
+            'focus', this.appId_, [this.selectors_.root]);
+      }
+      return;
+    }
+
+    let expandIcon;
+    let expandedSubtree;
+    if (this.useNewTree_) {
+      // Use array here because they are inside shadow DOM.
+      expandIcon = [
+        this.selectors_.attachModifier(itemSelector, {expanded: false}),
+        '.tree-item > .tree-row-wrapper > .tree-row > .expand-icon',
+      ];
+      expandedSubtree = [
+        this.selectors_.attachModifier(itemSelector, {expanded: true}),
+        '.tree-item[aria-expanded="true"]',
+      ];
+    } else {
+      expandIcon = `${this.selectors_.attachModifier(itemSelector, {
+        expanded: false,
+      })} > .tree-row:is([has-children=true], [may-have-children]) .expand-icon`;
+      expandedSubtree = this.selectors_.attachModifier(
+          `${itemSelector} > .tree-children`, {expanded: true});
+    }
+
+    await this.remoteCall_.waitAndClickElement(this.appId_, expandIcon);
+    if (!allowEmpty) {
+      // Wait for the expansion to finish.
+      await this.remoteCall_.waitForElement(this.appId_, expandedSubtree);
+    }
+    if (!this.useNewTree_) {
+      // Force the focus on directory tree.
+      await this.remoteCall_.callRemoteTestUtil(
+          'focus', this.appId_, [this.selectors_.root]);
+    }
+  }
+}
+
+/**
+ * Selectors of DirectoryTree, all the method provided by this class return
+ * the selector string.
+ */
+class DirectoryTreeSelectors_ {
+  /**
+   * @param {boolean} useNewTree
+   */
+  constructor(useNewTree) {
+    /** @type {boolean} */
+    this.useNewTree = useNewTree;
+  }
+
+  /**
+   * The root selector of the directory tree.
+   *
+   * @return {string}
+   */
+  get root() {
+    return '#directory-tree';
+  }
+
+  /**
+   * The container selector of the directory tree.
+   *
+   * @return {string}
+   */
+  get container() {
+    return '.dialog-navigation-list';
+  }
+
+  /**
+   * Get tree item by the label of the item.
+   *
+   * @param {string} label
+   * @param {ModifierOptions=} modifiers
+   * @return {string}
+   */
+  itemByLabel(label, modifiers) {
+    const itemSelector = `${this.root} ${this.itemItselfByLabel(label)}`;
+    return this.attachModifier(itemSelector, modifiers);
+  }
+
+  /**
+   * Get tree item by the type of the item.
+   *
+   * @param {string} type
+   * @param {boolean=} isPlaceholder
+   * @param {ModifierOptions=} modifiers
+   * @return {string}
+   */
+  itemByType(type, isPlaceholder, modifiers) {
+    const itemSelector =
+        `${this.root} ${this.itemItselfByType(type, !!isPlaceholder)}`;
+    return this.attachModifier(itemSelector, modifiers);
+  }
+
+  /**
+   * Get all the child items of the specific item.
+   *
+   * @param {string} itemSelector
+   * @return {string}
+   */
+  childItems(itemSelector) {
+    return `${itemSelector} > ${
+        this.useNewTree ? 'xf-tree-item' : '.tree-children > .tree-item'}`;
+  }
+
+  /**
+   * Get all the child items of the specific item, which have the nested
+   * children.
+   *
+   * @param {string} itemSelector
+   * @return {string}
+   */
+  childItemsWithNestedChildren(itemSelector) {
+    const nestedItemSelector = this.useNewTree ?
+        'xf-tree-item:has(xf-tree-item)' :
+        '.tree-children > .tree-item > .tree-row';
+    return this.attachModifier(
+        `${itemSelector} > ${nestedItemSelector}`, {hasChildren: true});
+  }
+
+  /**
+   *
+   * @param {string} type
+   * @param {boolean} isPlaceholder Is the tree item a placeholder or not.
+   * @return {string}
+   */
+  itemItselfByType(type, isPlaceholder) {
+    if (this.useNewTree) {
+      return isPlaceholder ? `xf-tree-item[data-navigation-key^="${
+                                 FAKE_ENTRY_PATH_PREFIX}"][icon="${type}"]` :
+                             `xf-tree-item[data-navigation-key^="${
+                                 REAL_ENTRY_PATH_PREFIX}"][icon="${type}"]`;
+    }
+    return isPlaceholder ? `[root-type-icon="${type}"]` :
+                           `[volume-type-icon="${type}"]`;
+  }
+
+  /**
+   *
+   * @param {string} label The label of the tree item.
+   * @return {string}
+   */
+  itemItselfByLabel(label) {
+    return this.useNewTree ? `xf-tree-item[label="${label}"]` :
+                             `.tree-item[entry-label="${label}"]`;
+  }
+
+
+  /**
+   * Return the recipient element of the keyboard event.
+   */
+  get keyboardRecipient() {
+    return this.useNewTree ? [this.root, 'ul'] : this.root;
+  }
+
+  /**
+   * Append the modifier selector to the item selector.
+   *
+   * @param {string} itemSelector
+   * @param {ModifierOptions=} modifiers
+   */
+  attachModifier(itemSelector, modifiers = {}) {
+    const appendedSelectors = [];
+    if (typeof modifiers.expanded !== 'undefined') {
+      appendedSelectors.push(
+          modifiers.expanded ? '[expanded]' : ':not([expanded])');
+    }
+    if (modifiers.selected) {
+      appendedSelectors.push(
+          this.useNewTree ? '[selected]' : ':has(.tree-row[active])');
+    }
+    if (modifiers.renaming) {
+      appendedSelectors.push('[renaming]');
+    }
+    if (modifiers.acceptDrop) {
+      appendedSelectors.push('.accepts');
+    }
+    if (modifiers.denyDrop) {
+      appendedSelectors.push('.denies');
+    }
+    if (typeof modifiers.hasChildren != 'undefined') {
+      appendedSelectors.push(`[has-children=${String(modifiers.hasChildren)}`);
+    }
+    if (modifiers.mayHaveChildren) {
+      appendedSelectors.push('[may-have-children]');
+    }
+    if (modifiers.currentDirectory) {
+      appendedSelectors.push('[aria-description="Current directory"]');
+    }
+    // ":focus" is a pseudo-class selector, should be put at the end.
+    if (modifiers.focused) {
+      appendedSelectors.push(this.useNewTree ? ':focus' : '[selected]');
+    }
+    return `${itemSelector}${appendedSelectors.join('')}`;
+  }
+}
diff --git a/ui/file_manager/integration_tests/remote_call.js b/ui/file_manager/integration_tests/remote_call.js
index 5c65ad0..946249c 100644
--- a/ui/file_manager/integration_tests/remote_call.js
+++ b/ui/file_manager/integration_tests/remote_call.js
@@ -181,10 +181,10 @@
    *     If query is an array, |query[0]| specifies the first
    *     element(s), |query[1]| specifies elements inside the shadow DOM of
    *     the first element, and so on.
-   * @return {Promise<ElementObject>} Promise to be fulfilled when the element
+   * @return {!Promise<!ElementObject>} Promise to be fulfilled when the element
    *     appears.
    */
-  waitForElement(appId, query) {
+  async waitForElement(appId, query) {
     return this.waitForElementStyles(appId, query, []);
   }
 
@@ -197,10 +197,10 @@
    *     the first element, and so on.
    * @param {!Array<string>} styleNames List of CSS property name to be
    *     obtained. NOTE: Causes element style re-calculation.
-   * @return {Promise<ElementObject>} Promise to be fulfilled when the element
+   * @return {!Promise<!ElementObject>} Promise to be fulfilled when the element
    *     appears.
    */
-  waitForElementStyles(appId, query, styleNames) {
+  async waitForElementStyles(appId, query, styleNames) {
     const caller = getCaller();
     return repeatUntil(async () => {
       const elements = await this.callRemoteTestUtil(
@@ -276,7 +276,7 @@
    * @param {number} count The expected element match count.
    * @return {Promise} Promise to be fulfilled on success.
    */
-  waitForElementsCount(appId, query, count) {
+  async waitForElementsCount(appId, query, count) {
     const caller = getCaller();
     return repeatUntil(async () => {
       const expect = `Waiting for [${query}] to match ${count} elements`;
@@ -359,7 +359,8 @@
    *     element(s), |query[1]| specifies elements inside the shadow DOM of
    *     the first element, and so on.
    * @param {KeyModifiers=} opt_keyModifiers Object
-   * @return {Promise} Promise to be fulfilled with the clicked element.
+   * @return {!Promise<ElementObject>} Promise to be fulfilled with the clicked
+   *     element.
    */
   async waitAndClickElement(appId, query, opt_keyModifiers) {
     const element = await this.waitForElement(appId, query);
diff --git a/ui/gl/direct_composition_support.cc b/ui/gl/direct_composition_support.cc
index 33de11c..fa36f01 100644
--- a/ui/gl/direct_composition_support.cc
+++ b/ui/gl/direct_composition_support.cc
@@ -136,7 +136,7 @@
 bool g_enable_bgra8_overlays_with_yuv_overlay_support = false;
 
 // Force enabling DXGI_FORMAT_R10G10B10A2_UNORM format for overlay. Intel
-// celake and Tigerlake fail to report the cap of this HDR overlay format.
+// Icelake and Tigerlake fail to report the cap of this HDR overlay format.
 // TODO(magchen@): Remove this workaround when this cap is fixed in the Intel
 // drivers.
 bool g_force_rgb10a2_overlay_support = false;
diff --git a/ui/ozone/platform/wayland/host/wayland_input_controller.cc b/ui/ozone/platform/wayland/host/wayland_input_controller.cc
index 84f54ad..0679da70 100644
--- a/ui/ozone/platform/wayland/host/wayland_input_controller.cc
+++ b/ui/ozone/platform/wayland/host/wayland_input_controller.cc
@@ -128,6 +128,7 @@
     // TODO(b:205702807) Implement after adding to wayland protocol
     NOTIMPLEMENTED_LOG_ONCE();
   }
+  bool AreAnyKeysPressed() override { return false; }
 
  private:
   const raw_ptr<WaylandConnection> connection_;
diff --git a/ui/ozone/public/input_controller.cc b/ui/ozone/public/input_controller.cc
index 86781f5..03a6f90 100644
--- a/ui/ozone/public/input_controller.cc
+++ b/ui/ozone/public/input_controller.cc
@@ -119,6 +119,7 @@
   void SetHapticTouchpadEffectForNextButtonRelease(
       HapticTouchpadEffect effect_type,
       HapticTouchpadEffectStrength strength) override {}
+  bool AreAnyKeysPressed() override { return false; }
 };
 
 }  // namespace
diff --git a/ui/ozone/public/input_controller.h b/ui/ozone/public/input_controller.h
index 1d8d0d4..4350ff1 100644
--- a/ui/ozone/public/input_controller.h
+++ b/ui/ozone/public/input_controller.h
@@ -183,6 +183,8 @@
   virtual void GetGesturePropertiesService(
       mojo::PendingReceiver<ui::ozone::mojom::GesturePropertiesService>
           receiver) = 0;
+
+  virtual bool AreAnyKeysPressed() = 0;
 };
 
 // Create an input controller that does nothing.
diff --git a/ui/touch_selection/touch_selection_magnifier_aura_unittest.cc b/ui/touch_selection/touch_selection_magnifier_aura_unittest.cc
index 00345c4..4b7aa33 100644
--- a/ui/touch_selection/touch_selection_magnifier_aura_unittest.cc
+++ b/ui/touch_selection/touch_selection_magnifier_aura_unittest.cc
@@ -4,8 +4,6 @@
 
 #include "ui/touch_selection/touch_selection_magnifier_aura.h"
 
-#include <memory>
-
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
@@ -30,111 +28,139 @@
 
   ~TouchSelectionMagnifierAuraTest() override = default;
 
-  void SetUp() override {
-    magnifier_ = std::make_unique<TouchSelectionMagnifierAura>();
-  }
-
-  // Returns the bounds of the magnified area in coordinates of the magnifier's
-  // parent layer.
-  gfx::Rect GetMagnifiedAreaBounds() const {
-    return magnifier_->GetMagnifiedAreaBoundsForTesting();
-  }
-
-  const Layer* GetMagnifierParent() const {
-    return magnifier_->GetMagnifierParentForTesting();
-  }
-
-  void ShowMagnifier(Layer* parent,
-                     const gfx::Point& caret_top,
-                     const gfx::Point& caret_bottom) {
-    magnifier_->ShowFocusBound(parent, caret_top, caret_bottom);
-  }
-
  private:
-  std::unique_ptr<TouchSelectionMagnifierAura> magnifier_;
   ui::ScopedAnimationDurationScaleMode disable_animations_;
 };
 
 // Tests that the magnifier is horizontally centered above a vertical caret.
 TEST_F(TouchSelectionMagnifierAuraTest, BoundsForVerticalCaret) {
-  auto magnifier_parent = std::make_unique<Layer>();
-  magnifier_parent->SetBounds(gfx::Rect(500, 400));
+  TouchSelectionMagnifierAura magnifier;
+  Layer magnifier_parent;
+  magnifier_parent.SetBounds(gfx::Rect(500, 400));
 
-  gfx::Point caret_top(300, 200);
-  gfx::Point caret_bottom(300, 210);
-  ShowMagnifier(magnifier_parent.get(), caret_top, caret_bottom);
-  gfx::Rect magnified_area_bounds = GetMagnifiedAreaBounds();
-  EXPECT_EQ(magnified_area_bounds.CenterPoint().x(), caret_top.x());
-  EXPECT_LT(magnified_area_bounds.bottom(), caret_top.y());
+  // Show the magnifier at a vertical caret.
+  constexpr gfx::Point kCaretTop(300, 200);
+  constexpr gfx::Point kCaretBottom(300, 210);
+  magnifier.ShowFocusBound(&magnifier_parent, kCaretTop, kCaretBottom);
 
-  // Move the caret.
-  caret_top.Offset(10, -5);
-  caret_bottom.Offset(10, -5);
-  ShowMagnifier(magnifier_parent.get(), caret_top, caret_bottom);
-  magnified_area_bounds = GetMagnifiedAreaBounds();
-  EXPECT_EQ(magnified_area_bounds.CenterPoint().x(), caret_top.x());
-  EXPECT_LT(magnified_area_bounds.bottom(), caret_top.y());
-
-  // Show a differently sized caret.
-  caret_bottom.Offset(0, 5);
-  ShowMagnifier(magnifier_parent.get(), caret_top, caret_bottom);
-  magnified_area_bounds = GetMagnifiedAreaBounds();
-  EXPECT_EQ(magnified_area_bounds.CenterPoint().x(), caret_top.x());
-  EXPECT_LT(magnified_area_bounds.bottom(), caret_top.y());
+  // Magnifier should be horizontally centered above the caret.
+  const gfx::Rect magnified_area_bounds =
+      magnifier.GetMagnifiedAreaBoundsForTesting();
+  EXPECT_EQ(magnified_area_bounds.CenterPoint().x(), kCaretTop.x());
+  EXPECT_LT(magnified_area_bounds.bottom(), kCaretBottom.y());
 }
 
-// Tests that the magnifier stays inside the parent layer when showing a caret
-// close to the edge of the parent layer.
-TEST_F(TouchSelectionMagnifierAuraTest, StaysInsideParentLayer) {
-  auto magnifier_parent = std::make_unique<Layer>();
+// Tests that the magnifier bounds are updated as a caret moves.
+TEST_F(TouchSelectionMagnifierAuraTest, BoundsUpdate) {
+  TouchSelectionMagnifierAura magnifier;
+  Layer magnifier_parent;
+  magnifier_parent.SetBounds(gfx::Rect(500, 400));
+
+  // Show the magnifier at a caret.
+  constexpr gfx::Point kCaretTop(300, 200);
+  constexpr gfx::Point kCaretBottom(300, 210);
+  magnifier.ShowFocusBound(&magnifier_parent, kCaretTop, kCaretBottom);
+  // Move and resize the caret.
+  constexpr gfx::Point kUpdatedCaretTop(310, 190);
+  constexpr gfx::Point kUpdatedCaretBottom(310, 220);
+  magnifier.ShowFocusBound(&magnifier_parent, kUpdatedCaretTop,
+                           kUpdatedCaretBottom);
+
+  // Magnifier should be horizontally centered above the updated caret.
+  const gfx::Rect magnified_area_bounds =
+      magnifier.GetMagnifiedAreaBoundsForTesting();
+  EXPECT_EQ(magnified_area_bounds.CenterPoint().x(), kUpdatedCaretTop.x());
+  EXPECT_LT(magnified_area_bounds.bottom(), kUpdatedCaretBottom.y());
+}
+
+// Tests that the magnifier is adjusted to stay inside the parent layer when
+// showing a caret close to the left edge of the parent.
+TEST_F(TouchSelectionMagnifierAuraTest, StaysInsideParentLeftEdge) {
+  TouchSelectionMagnifierAura magnifier;
+  Layer magnifier_parent;
   constexpr gfx::Rect kParentBounds(500, 400);
-  magnifier_parent->SetBounds(kParentBounds);
+  magnifier_parent.SetBounds(kParentBounds);
 
-  // Left edge.
-  ShowMagnifier(magnifier_parent.get(), gfx::Point(10, 200),
-                gfx::Point(10, 210));
-  EXPECT_TRUE(kParentBounds.Contains(GetMagnifiedAreaBounds()));
+  // Show the magnifier at a caret near the left edge of the parent.
+  magnifier.ShowFocusBound(&magnifier_parent, gfx::Point(10, 200),
+                           gfx::Point(10, 210));
 
-  // Top edge.
-  ShowMagnifier(magnifier_parent.get(), gfx::Point(200, 2),
-                gfx::Point(200, 12));
-  EXPECT_TRUE(kParentBounds.Contains(GetMagnifiedAreaBounds()));
+  // Magnifier should be contained in the parent bounds.
+  EXPECT_TRUE(
+      kParentBounds.Contains(magnifier.GetMagnifiedAreaBoundsForTesting()));
+}
 
-  // Right edge.
-  ShowMagnifier(magnifier_parent.get(), gfx::Point(495, 200),
-                gfx::Point(495, 210));
-  EXPECT_TRUE(kParentBounds.Contains(GetMagnifiedAreaBounds()));
+// Tests that the magnifier is adjusted to stay inside the parent layer when
+// showing a caret close to the right edge of the parent.
+TEST_F(TouchSelectionMagnifierAuraTest, StaysInsideParentRightEdge) {
+  TouchSelectionMagnifierAura magnifier;
+  Layer magnifier_parent;
+  constexpr gfx::Rect kParentBounds(500, 400);
+  magnifier_parent.SetBounds(kParentBounds);
+
+  // Show the magnifier at a caret near the right edge of the parent.
+  magnifier.ShowFocusBound(&magnifier_parent, gfx::Point(495, 200),
+                           gfx::Point(495, 210));
+
+  // Magnifier should be contained in the parent bounds.
+  EXPECT_TRUE(
+      kParentBounds.Contains(magnifier.GetMagnifiedAreaBoundsForTesting()));
+}
+
+// Tests that the magnifier is adjusted to stay inside the parent layer when
+// showing a caret close to the top edge of the parent.
+TEST_F(TouchSelectionMagnifierAuraTest, StaysInsideParentTopEdge) {
+  TouchSelectionMagnifierAura magnifier;
+  Layer magnifier_parent;
+  constexpr gfx::Rect kParentBounds(500, 400);
+  magnifier_parent.SetBounds(kParentBounds);
+
+  // Show the magnifier at a caret near the top edge of the parent.
+  magnifier.ShowFocusBound(&magnifier_parent, gfx::Point(200, 2),
+                           gfx::Point(200, 12));
+
+  // Magnifier should be contained in the parent bounds.
+  EXPECT_TRUE(
+      kParentBounds.Contains(magnifier.GetMagnifiedAreaBoundsForTesting()));
 }
 
 // Tests that the magnifier remains the same size even at the edge of the
 // parent layer.
 TEST_F(TouchSelectionMagnifierAuraTest, Size) {
-  auto magnifier_parent = std::make_unique<Layer>();
-  magnifier_parent->SetBounds(gfx::Rect(500, 400));
+  TouchSelectionMagnifierAura magnifier;
+  Layer magnifier_parent;
+  magnifier_parent.SetBounds(gfx::Rect(500, 400));
 
-  ShowMagnifier(magnifier_parent.get(), gfx::Point(300, 200),
-                gfx::Point(300, 210));
-  const gfx::Size magnifier_layer_size = GetMagnifiedAreaBounds().size();
-
+  // Show magnifier.
+  magnifier.ShowFocusBound(&magnifier_parent, gfx::Point(300, 200),
+                           gfx::Point(300, 210));
+  const gfx::Size magnifier_size =
+      magnifier.GetMagnifiedAreaBoundsForTesting().size();
   // Move the caret near the edge of the parent container.
-  ShowMagnifier(magnifier_parent.get(), gfx::Point(10, 3), gfx::Point(10, 13));
-  EXPECT_EQ(GetMagnifiedAreaBounds().size(), magnifier_layer_size);
+  magnifier.ShowFocusBound(&magnifier_parent, gfx::Point(10, 3),
+                           gfx::Point(10, 13));
+
+  // Magnifier should remain the same size.
+  EXPECT_EQ(magnifier.GetMagnifiedAreaBoundsForTesting().size(),
+            magnifier_size);
 }
 
 // Tests that the magnifier can be reparented to a different layer if needed.
 TEST_F(TouchSelectionMagnifierAuraTest, SwitchesParentLayer) {
-  auto magnifier_parent = std::make_unique<Layer>();
-  magnifier_parent->SetBounds(gfx::Rect(500, 400));
+  TouchSelectionMagnifierAura magnifier;
 
-  // Check that the magnifier is correctly parented.
-  ShowMagnifier(magnifier_parent.get(), gfx::Point(10, 20), gfx::Point(10, 30));
-  EXPECT_EQ(GetMagnifierParent(), magnifier_parent.get());
+  Layer magnifier_parent;
+  magnifier_parent.SetBounds(gfx::Rect(500, 400));
+  magnifier.ShowFocusBound(&magnifier_parent, gfx::Point(10, 20),
+                           gfx::Point(10, 30));
+  // Reparent the magnifier.
+  Layer new_parent;
+  new_parent.SetBounds(gfx::Rect(600, 400));
+  magnifier.ShowFocusBound(&new_parent, gfx::Point(200, 20),
+                           gfx::Point(200, 30));
 
-  // Check that the magnifier is correctly reparented.
-  auto new_parent = std::make_unique<Layer>();
-  new_parent->SetBounds(gfx::Rect(600, 400));
-  ShowMagnifier(new_parent.get(), gfx::Point(200, 20), gfx::Point(200, 30));
-  EXPECT_EQ(GetMagnifierParent(), new_parent.get());
+  // Magnifier should have the updated parent.
+  EXPECT_EQ(magnifier.GetMagnifierParentForTesting(), &new_parent);
 }
 
 }  // namespace
diff --git a/ui/views/controls/button/toggle_button.cc b/ui/views/controls/button/toggle_button.cc
index d206a2d..307f7ab 100644
--- a/ui/views/controls/button/toggle_button.cc
+++ b/ui/views/controls/button/toggle_button.cc
@@ -55,6 +55,7 @@
 constexpr int kRefreshThumbInsetSelected = -2;
 constexpr int kRefreshThumbPressedOutset = 1;
 constexpr int kRefreshHoverDiameter = 20;
+constexpr float kBorderStrokeWidth = 1.0f;
 
 const gfx::Size GetTrackSize() {
   return features::IsChromeRefresh2023() ? kRefreshTrackSize : kTrackSize;
@@ -573,9 +574,10 @@
       GetTrackColor(true), GetTrackColor(false), color_ratio));
   canvas->DrawRoundRect(track_rect, radius, track_flags);
   if (!GetIsOn() && features::IsChromeRefresh2023()) {
+    track_rect.Inset(kBorderStrokeWidth * dsf / 2.0f);
     track_flags.setColor(
         GetColorProvider()->GetColor(ui::kColorToggleButtonShadow));
-    track_flags.setStrokeWidth(0.5f * dsf);
+    track_flags.setStrokeWidth(kBorderStrokeWidth * dsf);
     track_flags.setStyle(cc::PaintFlags::kStroke_Style);
     canvas->DrawRoundRect(track_rect, radius, track_flags);
   }
diff --git a/ui/views/controls/native/native_view_host_mac_unittest.mm b/ui/views/controls/native/native_view_host_mac_unittest.mm
index c9c75c0..1344284e 100644
--- a/ui/views/controls/native/native_view_host_mac_unittest.mm
+++ b/ui/views/controls/native/native_view_host_mac_unittest.mm
@@ -165,7 +165,7 @@
 
   // On Ventura, the attach rips Widget A's contentView from its window.
   // NativeViewHostMac::AttachNativeView() should have stored a reference.
-  if (base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() >= 13) {
     EXPECT_EQ([native_window contentView], nullptr);
     EXPECT_EQ(GetMovedContentViewForWidget(second_widget), view);
   } else {
@@ -184,7 +184,7 @@
 // On macOS13, if Widget A has been attached to Widget B, ensure Widget A's
 // reference to its native view disappears when the native view is freed.
 TEST_F(NativeViewHostMacTest, CheckNoNativeViewReferenceOnDestruct) {
-  if (!base::mac::IsAtLeastOS13()) {
+  if (base::mac::MacOSMajorVersion() < 13) {
     return;
   }
 
@@ -243,7 +243,7 @@
   // The new visual style on macOS 11 (and presumably later) has slightly taller
   // titlebars, which means the window rect has to leave a bit of extra space
   // for the titlebar.
-  int titlebar_extra = base::mac::IsAtLeastOS11() ? 6 : 0;
+  int titlebar_extra = base::mac::MacOSMajorVersion() >= 11 ? 6 : 0;
 
   native_host()->ShowWidget(5, 10, 100, 100, 200, 200);
   EXPECT_NSEQ(NSMakeRect(5, -32 - titlebar_extra, 100, 100),
diff --git a/ui/views/controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm b/ui/views/controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm
index 8685cdcd..1f8719b 100644
--- a/ui/views/controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm
+++ b/ui/views/controls/tabbed_pane/tabbed_pane_accessibility_mac_unittest.mm
@@ -118,7 +118,7 @@
   // versions of Cocoa by exposing the role description of "tab" even in older
   // versions of macOS. Doing so causes a mismatch between native Cocoa and our
   // tabs.
-  if (base::mac::IsAtLeastOS12()) {
+  if (base::mac::MacOSMajorVersion() >= 12) {
     EXPECT_NSEQ(
         GetLegacyA11yAttributeValue(cocoa_tabs[0],
                                     NSAccessibilityRoleDescriptionAttribute),
diff --git a/ui/views/view.cc b/ui/views/view.cc
index 4283e7a..e7f0248 100644
--- a/ui/views/view.cc
+++ b/ui/views/view.cc
@@ -1067,7 +1067,7 @@
 // static
 gfx::Rect View::ConvertRectToTarget(const View* source,
                                     const View* target,
-                                    gfx::Rect& rect) {
+                                    const gfx::Rect& rect) {
   constexpr float kDefaultAllowedConversionError = 0.00001f;
   return gfx::ToEnclosedRectIgnoringError(
       ConvertRectToTarget(source, target, gfx::RectF(rect)),
diff --git a/ui/views/view.h b/ui/views/view.h
index 6891cfa1..80a2b76 100644
--- a/ui/views/view.h
+++ b/ui/views/view.h
@@ -916,7 +916,7 @@
   // (0.00001f).
   static gfx::Rect ConvertRectToTarget(const View* source,
                                        const View* target,
-                                       gfx::Rect& rect);
+                                       const gfx::Rect& rect);
 
   // Converts a point from a View's coordinate system to that of its Widget.
   static void ConvertPointToWidget(const View* src, gfx::Point* point);
diff --git a/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.html b/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.html
index bed81be..9ea072e 100644
--- a/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.html
+++ b/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.html
@@ -1,11 +1,8 @@
 <style include="cr-icons">
-  cr-action-menu {
-    --cr-menu-background-sheen: transparent;
-  }
-
-  cr-action-menu::part(dialog) {
+  dialog {
     background: var(--color-theme-color-picker-hue-slider-dialog-background,
         var(--cr-fallback-color-surface));
+    border: 0;
     border-radius: 12px;
     box-shadow: var(--cr-elevation-3);
     box-sizing: border-box;
@@ -14,8 +11,11 @@
     height: 137px;
     isolation: isolate;
     overflow: hidden;
-    padding: 12px 20px;
+    margin: 0;
+    padding: 20px;
+    position: absolute;
     width: 296px;
+    z-index: 999;
   }
 
   #header {
@@ -29,7 +29,6 @@
     flex: 1;
     font-size: 16px;
     font-weight: 500;
-    line-height: 24px;
     margin: 0;
   }
 
@@ -67,7 +66,7 @@
   }
 </style>
 
-<cr-action-menu id="crActionMenu">
+<dialog id="dialog">
   <div id="header">
     <h2 id="title">[[i18n('hueSliderTitle')]]</h2>
     <cr-icon-button id="close" class="icon-clear"
@@ -84,4 +83,4 @@
       aria-labelledby="title"
       noink>
   </cr-slider>
-</cr-action-menu>
+</dialog>
diff --git a/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.ts b/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.ts
index 31ac52d..27d21f0 100644
--- a/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.ts
+++ b/ui/webui/resources/cr_components/theme_color_picker/theme_hue_slider_dialog.ts
@@ -2,12 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.js';
 import 'chrome://resources/cr_elements/cr_icons.css.js';
 import 'chrome://resources/cr_elements/cr_slider/cr_slider.js';
 
-import {AnchorAlignment, CrActionMenuElement} from 'chrome://resources/cr_elements/cr_action_menu/cr_action_menu.js';
 import {CrSliderElement} from 'chrome://resources/cr_elements/cr_slider/cr_slider.js';
 import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
@@ -31,7 +29,7 @@
 
 export interface ThemeHueSliderDialogElement {
   $: {
-    crActionMenu: CrActionMenuElement,
+    dialog: HTMLDialogElement,
     slider: CrSliderElement,
   };
 }
@@ -92,22 +90,24 @@
   }
 
   showAt(anchor: HTMLElement) {
-    // By default, align the dialog below the anchor. If the window is too
-    // small (140px is about the dialog's height with some margin), show it
-    // above the anchor.
-    let anchorAlignmentY = AnchorAlignment.AFTER_END;
-    if (this.getBoundingClientRect().top + 140 >= window.innerHeight) {
-      anchorAlignmentY = AnchorAlignment.BEFORE_START;
-    }
+    this.$.dialog.show();
 
-    this.$.crActionMenu.showAt(anchor, {
-      anchorAlignmentY,
-      noOffset: true,
-    });
+    this.$.dialog.style.left = `${
+        anchor.offsetLeft + anchor.offsetWidth - this.$.dialog.offsetWidth}px`;
+
+    // By default, align the dialog below the anchor. If the window is too
+    // small, show it above the anchor.
+    const anchorBottom = anchor.offsetTop + anchor.offsetHeight;
+    if (anchorBottom + this.$.dialog.offsetHeight >= window.innerHeight) {
+      this.$.dialog.style.top =
+          `${anchor.offsetTop - this.$.dialog.offsetHeight}px`;
+    } else {
+      this.$.dialog.style.top = `${anchorBottom}px`;
+    }
   }
 
   hide() {
-    this.$.crActionMenu.close();
+    this.$.dialog.close();
   }
 
   private updateSelectedHueValue_() {
diff --git a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html
index ded97bb..7f38c166 100644
--- a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html
+++ b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.html
@@ -8,7 +8,6 @@
         min-width: 128px;
         outline: none;
         padding: 0;
-        position: absolute;
       }
 
       @media (forced-colors: active) {