diff --git a/DEPS b/DEPS
index f77c49c..8617c31 100644
--- a/DEPS
+++ b/DEPS
@@ -299,11 +299,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': '2f4e35e9c81b8c737b0a03b3d28d374831d66aff',
+  'src_internal_revision': 'db449982a6f6dda8b8aa3d44acaf100805040349',
   # 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': '276075b975675ba92d907dd64247937b7b73245e',
+  'skia_revision': '63bed826008ee7e0170ce9224c6aaab1b2f720f7',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -374,7 +374,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': '61af87d894922b368c4733137916ecfaaa607fea',
+  'catapult_revision': '23118cc3c34d629778abac83f10b39810ea1fdfb',
   # 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.
@@ -818,7 +818,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'a2fed0cd29a5673211c8327dfd847830ebcbcb24',
+    '81557d351b8541438b80a494040df59f3eec95fd',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -980,7 +980,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'sqQJ-wARDWk_E32q9UOmDuZsVYv8ZVY37AD5jc996aYC',
+          'version': 'l9XWlRoYArKQAmTUYR0DpOfDJnfUxN3XZ3s0NiKbu3EC',
       },
     ],
     'condition': 'checkout_android',
@@ -1846,7 +1846,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'eb1892c3e4ab734a135e8752f5442e270a4738d1',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '2b311ea96a10b87cec66e2626ecadfa08dd868b1',
+    Var('webrtc_git') + '/src.git' + '@' + 'acdc89d65328911b4e98afe0757028eca162cbb3',
 
   # 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.
@@ -4003,7 +4003,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '5efd774964ac708dfd7625fc954f8c70391c79e6',
+        'bd7110a6a048f80efeaa1f23eeaabca81ff20271',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index fea52d2..11c923c 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -41,6 +41,8 @@
     "accelerators/accelerator_ids.h",
     "accelerators/accelerator_launcher_state_machine.cc",
     "accelerators/accelerator_launcher_state_machine.h",
+    "accelerators/accelerator_lookup.cc",
+    "accelerators/accelerator_lookup.h",
     "accelerators/accelerator_notifications.cc",
     "accelerators/accelerator_notifications.h",
     "accelerators/accelerator_prefs.cc",
@@ -3143,6 +3145,7 @@
     "accelerators/accelerator_filter_unittest.cc",
     "accelerators/accelerator_history_unittest.cc",
     "accelerators/accelerator_launcher_state_machine_unittest.cc",
+    "accelerators/accelerator_lookup_unittest.cc",
     "accelerators/accelerator_prefs_unittest.cc",
     "accelerators/accelerator_shift_disable_capslock_state_machine_unittest.cc",
     "accelerators/accelerator_table_unittest.cc",
diff --git a/ash/accelerators/accelerator_lookup.cc b/ash/accelerators/accelerator_lookup.cc
new file mode 100644
index 0000000..dbc3269
--- /dev/null
+++ b/ash/accelerators/accelerator_lookup.cc
@@ -0,0 +1,48 @@
+// 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_lookup.h"
+
+#include <memory>
+#include <utility>
+
+#include "ash/public/cpp/accelerators_util.h"
+#include "base/types/optional_ref.h"
+
+namespace ash {
+
+namespace {
+
+using AcceleratorDetails = AcceleratorLookup::AcceleratorDetails;
+
+using OptionalAccelerators =
+    base::optional_ref<const std::vector<ui::Accelerator>>;
+
+}  // namespace
+
+AcceleratorLookup::AcceleratorLookup(
+    raw_ptr<AcceleratorConfiguration> ash_accelerators)
+    : ash_accelerator_configuration_(ash_accelerators) {}
+
+AcceleratorLookup::~AcceleratorLookup() = default;
+
+std::vector<AcceleratorDetails> AcceleratorLookup::GetAcceleratorsForAction(
+    uint32_t action) const {
+  CHECK(ash_accelerator_configuration_);
+
+  std::vector<AcceleratorDetails> details;
+  OptionalAccelerators accelerators =
+      ash_accelerator_configuration_->GetAcceleratorsForAction(action);
+  if (!accelerators.has_value()) {
+    return details;
+  }
+
+  for (const auto& accelerator : *accelerators) {
+    details.push_back({accelerator, GetKeyDisplay(accelerator.key_code())});
+  }
+
+  return details;
+}
+
+}  // namespace ash
diff --git a/ash/accelerators/accelerator_lookup.h b/ash/accelerators/accelerator_lookup.h
new file mode 100644
index 0000000..04b5e000
--- /dev/null
+++ b/ash/accelerators/accelerator_lookup.h
@@ -0,0 +1,44 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_ACCELERATORS_ACCELERATOR_LOOKUP_H_
+#define ASH_ACCELERATORS_ACCELERATOR_LOOKUP_H_
+
+#include <string>
+#include <vector>
+
+#include "ash/ash_export.h"
+#include "ash/public/cpp/accelerator_configuration.h"
+#include "ui/base/accelerators/accelerator.h"
+
+namespace ash {
+
+// AceleratorLookup is a slim singleton class that is used to lookup
+// accelerators for a particular action.
+class ASH_EXPORT AcceleratorLookup {
+ public:
+  explicit AcceleratorLookup(
+      raw_ptr<AcceleratorConfiguration> ash_accelerators);
+  ~AcceleratorLookup();
+  AcceleratorLookup(const AcceleratorLookup&) = delete;
+  AcceleratorLookup& operator=(const AcceleratorLookup&) = delete;
+
+  struct AcceleratorDetails {
+    // The base accelerator.
+    ui::Accelerator accelerator;
+    // The regionalized string representation of the activation key.
+    std::u16string key_display;
+  };
+
+  // Returns a list of accelerator details for `action`.
+  std::vector<AcceleratorDetails> GetAcceleratorsForAction(
+      uint32_t action) const;
+
+ private:
+  raw_ptr<AcceleratorConfiguration> ash_accelerator_configuration_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_ACCELERATORS_ACCELERATOR_LOOKUP_H_
diff --git a/ash/accelerators/accelerator_lookup_unittest.cc b/ash/accelerators/accelerator_lookup_unittest.cc
new file mode 100644
index 0000000..44101d6
--- /dev/null
+++ b/ash/accelerators/accelerator_lookup_unittest.cc
@@ -0,0 +1,156 @@
+// 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_lookup.h"
+
+#include <memory>
+#include <vector>
+
+#include "ash/accelerators/ash_accelerator_configuration.h"
+#include "ash/public/cpp/accelerators.h"
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "base/memory/raw_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/test/scoped_feature_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ui_base_features.h"
+#include "ui/events/keycodes/dom/dom_codes_array.h"
+#include "ui/events/keycodes/dom/dom_key.h"
+#include "ui/events/keycodes/keyboard_codes_posix.h"
+
+namespace ash {
+
+namespace {
+
+using AcceleratorDetails = AcceleratorLookup::AcceleratorDetails;
+
+bool CompareAccelerators(const std::vector<AcceleratorDetails>& expected,
+                         const std::vector<AcceleratorDetails>& actual) {
+  if (expected.size() != actual.size()) {
+    return false;
+  }
+
+  for (size_t i = 0; i < expected.size(); ++i) {
+    const bool accelerators_equal =
+        expected[i].accelerator == actual[i].accelerator;
+    const bool key_display_equal =
+        expected[i].key_display == actual[i].key_display;
+    if (!accelerators_equal || !key_display_equal) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+}  // namespace
+
+class AcceleratorLookupTest : public AshTestBase {
+ public:
+  AcceleratorLookupTest() = default;
+  ~AcceleratorLookupTest() override = default;
+
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        ::features::kShortcutCustomization);
+    AshTestBase::SetUp();
+    config_ = Shell::Get()->ash_accelerator_configuration();
+    accelerator_lookup_ = Shell::Get()->accelerator_lookup();
+  }
+
+  void TearDown() override {
+    config_ = nullptr;
+    accelerator_lookup_ = nullptr;
+    AshTestBase::TearDown();
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  raw_ptr<AshAcceleratorConfiguration> config_;
+  raw_ptr<AcceleratorLookup> accelerator_lookup_;
+};
+
+TEST_F(AcceleratorLookupTest, NoAccelerators) {
+  config_->Initialize({});
+
+  std::vector<AcceleratorDetails> accelerators =
+      accelerator_lookup_->GetAcceleratorsForAction(
+          AcceleratorAction::kSwitchToLastUsedIme);
+
+  EXPECT_TRUE(accelerators.empty());
+}
+
+TEST_F(AcceleratorLookupTest, LoadAndFetchAccelerator) {
+  const AcceleratorData test_data[] = {
+      {/*trigger_on_press=*/true, ui::VKEY_A, ui::EF_CONTROL_DOWN,
+       AcceleratorAction::kSwitchToLastUsedIme},
+  };
+
+  config_->Initialize(test_data);
+
+  const std::vector<ui::Accelerator> expected_accelerators = {
+      {ui::VKEY_A, ui::EF_CONTROL_DOWN},
+  };
+
+  std::vector<AcceleratorDetails> actual =
+      accelerator_lookup_->GetAcceleratorsForAction(
+          AcceleratorAction::kSwitchToLastUsedIme);
+
+  std::vector<AcceleratorDetails> expected = {
+      {{ui::VKEY_A, ui::EF_CONTROL_DOWN}, std::u16string(u"a")},
+  };
+
+  EXPECT_TRUE(CompareAccelerators(expected, actual));
+}
+
+TEST_F(AcceleratorLookupTest, ModifiedAccelerator) {
+  const AcceleratorData test_data[] = {
+      {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
+       AcceleratorAction::kSwitchToLastUsedIme},
+  };
+
+  config_->Initialize(test_data);
+
+  std::vector<AcceleratorDetails> expected = {
+      {{ui::VKEY_SPACE, ui::EF_CONTROL_DOWN}, std::u16string(u"space")},
+  };
+
+  std::vector<AcceleratorDetails> actual =
+      accelerator_lookup_->GetAcceleratorsForAction(
+          AcceleratorAction::kSwitchToLastUsedIme);
+
+  EXPECT_TRUE(CompareAccelerators(expected, actual));
+
+  config_->AddUserAccelerator(AcceleratorAction::kSwitchToLastUsedIme,
+                              {ui::VKEY_A, ui::EF_COMMAND_DOWN});
+
+  expected = {
+      {{ui::VKEY_SPACE, ui::EF_CONTROL_DOWN}, std::u16string(u"space")},
+      {{ui::VKEY_A, ui::EF_COMMAND_DOWN}, std::u16string(u"a")},
+  };
+
+  actual = accelerator_lookup_->GetAcceleratorsForAction(
+      AcceleratorAction::kSwitchToLastUsedIme);
+  EXPECT_TRUE(CompareAccelerators(expected, actual));
+}
+
+TEST_F(AcceleratorLookupTest, RemovedAccelerator) {
+  const AcceleratorData test_data[] = {
+      {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
+       AcceleratorAction::kSwitchToLastUsedIme},
+  };
+
+  config_->Initialize(test_data);
+  config_->RemoveAccelerator(AcceleratorAction::kSwitchToLastUsedIme,
+                             {ui::VKEY_SPACE, ui::EF_CONTROL_DOWN});
+
+  std::vector<AcceleratorDetails> accelerators =
+      accelerator_lookup_->GetAcceleratorsForAction(
+          AcceleratorAction::kBrightnessDown);
+
+  EXPECT_TRUE(accelerators.empty());
+}
+
+}  // namespace ash
diff --git a/ash/accelerators/ash_accelerator_configuration.cc b/ash/accelerators/ash_accelerator_configuration.cc
index 6d6abce9..66d0504 100644
--- a/ash/accelerators/ash_accelerator_configuration.cc
+++ b/ash/accelerators/ash_accelerator_configuration.cc
@@ -223,11 +223,13 @@
   }
 }
 
-const std::vector<ui::Accelerator>&
+base::optional_ref<const std::vector<ui::Accelerator>>
 AshAcceleratorConfiguration::GetAcceleratorsForAction(
     AcceleratorActionId action_id) {
   const auto accelerator_iter = id_to_accelerators_.find(action_id);
-  CHECK(accelerator_iter != id_to_accelerators_.end());
+  if (accelerator_iter == id_to_accelerators_.end()) {
+    return std::nullopt;
+  }
 
   return accelerator_iter->second;
 }
diff --git a/ash/accelerators/ash_accelerator_configuration.h b/ash/accelerators/ash_accelerator_configuration.h
index fdc4577..d81be54 100644
--- a/ash/accelerators/ash_accelerator_configuration.h
+++ b/ash/accelerators/ash_accelerator_configuration.h
@@ -20,6 +20,7 @@
 #include "base/containers/span.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
+#include "base/types/optional_ref.h"
 #include "base/values.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "mojo/public/cpp/bindings/clone_traits.h"
@@ -69,9 +70,6 @@
 
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  // AcceleratorConfiguration::
-  const std::vector<ui::Accelerator>& GetAcceleratorsForAction(
-      AcceleratorActionId action_id) override;
   // Whether the source is mutable and shortcuts can be changed. If this returns
   // false then any of the Add/Remove/Replace class will DCHECK. The two Restore
   // methods will be no-ops.
@@ -136,9 +134,15 @@
   bool IsValid(uint32_t id) const;
 
  private:
+  friend class AshAcceleratorConfigurationTest;
+
   // A map for looking up actions from accelerators.
   using AcceleratorActionMap = ui::AcceleratorMap<AcceleratorAction>;
 
+  // AcceleratorConfiguration::
+  base::optional_ref<const std::vector<ui::Accelerator>>
+  GetAcceleratorsForAction(AcceleratorActionId action_id) override;
+
   void InitializeDeprecatedAccelerators();
 
   void AddAccelerators(base::span<const AcceleratorData> accelerators);
diff --git a/ash/accelerators/ash_accelerator_configuration_unittest.cc b/ash/accelerators/ash_accelerator_configuration_unittest.cc
index e9851aac..d11c62e 100644
--- a/ash/accelerators/ash_accelerator_configuration_unittest.cc
+++ b/ash/accelerators/ash_accelerator_configuration_unittest.cc
@@ -20,6 +20,7 @@
 #include "base/ranges/algorithm.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/types/optional_ref.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/ui_base_features.h"
 #include "ui/events/event_constants.h"
@@ -173,6 +174,11 @@
   }
 
  protected:
+  base::optional_ref<const std::vector<ui::Accelerator>>
+  GetAcceleratorsForAction(AcceleratorActionId action_id) {
+    return config_->GetAcceleratorsForAction(action_id);
+  }
+
   base::test::ScopedFeatureList scoped_feature_list_;
   UpdatedAcceleratorsObserver observer_;
   std::unique_ptr<AshAcceleratorConfiguration> config_;
@@ -496,9 +502,10 @@
   for (const auto& data : test_data) {
     std::vector<AcceleratorData> expected =
         id_to_accelerator_data.at(data.action);
-    std::vector<ui::Accelerator> actual =
-        config_->GetAcceleratorsForAction(data.action);
-    ExpectAllAcceleratorsEqual(expected, actual);
+    base::optional_ref<const std::vector<ui::Accelerator>> actual =
+        GetAcceleratorsForAction(data.action);
+    ASSERT_TRUE(actual.has_value());
+    ExpectAllAcceleratorsEqual(expected, *actual);
   }
 }
 
@@ -986,9 +993,10 @@
   EXPECT_EQ(AcceleratorAction::kSwitchToLastUsedIme, *found_action);
 
   // Confirm that conflicting accelerator was removed.
-  const std::vector<ui::Accelerator>& backward_mru_accelerators =
-      config_->GetAcceleratorsForAction(AcceleratorAction::kCycleBackwardMru);
-  EXPECT_TRUE(backward_mru_accelerators.empty());
+  base::optional_ref<const std::vector<ui::Accelerator>>
+      backward_mru_accelerators =
+          GetAcceleratorsForAction(AcceleratorAction::kCycleBackwardMru);
+  ASSERT_TRUE(backward_mru_accelerators->empty());
 }
 
 // Add accelerator that conflicts with a deprecated accelerator.
@@ -1266,9 +1274,12 @@
   EXPECT_EQ(AcceleratorAction::kSwitchToLastUsedIme, *found_action);
 
   // Confirm that conflicting accelerator was removed.
-  const std::vector<ui::Accelerator>& forward_mru_accelerators =
-      config_->GetAcceleratorsForAction(AcceleratorAction::kCycleForwardMru);
-  EXPECT_EQ(1u, forward_mru_accelerators.size());
+  base::optional_ref<const std::vector<ui::Accelerator>>
+      forward_mru_accelerators =
+          GetAcceleratorsForAction(AcceleratorAction::kCycleForwardMru);
+  ASSERT_TRUE(forward_mru_accelerators.has_value());
+
+  EXPECT_EQ(1u, forward_mru_accelerators->size());
 
   // Now restore the default of `kCycleForwardMru`, this will effectively be a
   // no-opt since one of its default is a used by `kSwitchToLastUsedIme`.
diff --git a/ash/constants/ash_switches.cc b/ash/constants/ash_switches.cc
index b05972b..d138cf7 100644
--- a/ash/constants/ash_switches.cc
+++ b/ash/constants/ash_switches.cc
@@ -28,6 +28,9 @@
     "\x1a\x93\x5f\x64\x0d\x7f\x0c\x2f\x88\xe8\x80\x9a\x5f\x16\xbb\xd8\x74\x06"
     "\x8a\xb1";
 
+// Whether checking the birch secret key is ignored.
+bool g_ignore_birch_secret_key = false;
+
 }  // namespace
 
 // Please keep the order of these switches synchronized with the header file
@@ -1324,6 +1327,10 @@
 }
 
 bool IsBirchSecretKeyMatched() {
+  if (g_ignore_birch_secret_key) {
+    return true;
+  }
+
   // Commandline looks like:
   //  out/Default/chrome --user-data-dir=/tmp/tmp123
   //  --birch-feature-key="INSERT KEY HERE" --enable-features=BirchFeature
@@ -1339,5 +1346,9 @@
   return birch_key_matched;
 }
 
+void SetIgnoreBirchSecretKeyForTest(bool ignore) {
+  g_ignore_birch_secret_key = ignore;
+}
+
 }  // namespace switches
 }  // namespace ash
diff --git a/ash/constants/ash_switches.h b/ash/constants/ash_switches.h
index 2369f85a..d5f9d28 100644
--- a/ash/constants/ash_switches.h
+++ b/ash/constants/ash_switches.h
@@ -500,6 +500,9 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsBirchSecretKeyMatched();
 
+COMPONENT_EXPORT(ASH_CONSTANTS)
+void SetIgnoreBirchSecretKeyForTest(bool ignore);
+
 }  // namespace ash::switches
 
 #endif  // ASH_CONSTANTS_ASH_SWITCHES_H_
diff --git a/ash/login/ui/kiosk_app_default_message.cc b/ash/login/ui/kiosk_app_default_message.cc
index db342a9..54b30fb 100644
--- a/ash/login/ui/kiosk_app_default_message.cc
+++ b/ash/login/ui/kiosk_app_default_message.cc
@@ -11,7 +11,6 @@
 #include "ash/style/typography.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tray_utils.h"
-#include "chromeos/constants/chromeos_features.h"
 #include "components/vector_icons/vector_icons.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -70,13 +69,8 @@
   title_->SetLineHeight(kTitleLineHeight);
   title_->SetMultiLine(true);
   title_->SetEnabledColorId(kColorAshTextColorPrimary);
-  if (chromeos::features::IsJellyEnabled()) {
-    title_->SetAutoColorReadabilityEnabled(false);
-    TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosBody2, *title_);
-  } else {
-    TrayPopupUtils::SetLabelFontList(title_,
-                                     TrayPopupUtils::FontStyle::kSmallTitle);
-  }
+  title_->SetAutoColorReadabilityEnabled(false);
+  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosBody2, *title_);
 }
 
 KioskAppDefaultMessage::~KioskAppDefaultMessage() = default;
diff --git a/ash/public/cpp/accelerator_configuration.h b/ash/public/cpp/accelerator_configuration.h
index 2138847..50a3e29 100644
--- a/ash/public/cpp/accelerator_configuration.h
+++ b/ash/public/cpp/accelerator_configuration.h
@@ -12,6 +12,7 @@
 #include "ash/public/mojom/accelerator_configuration.mojom.h"
 #include "ash/public/mojom/accelerator_info.mojom.h"
 #include "base/functional/callback.h"
+#include "base/types/optional_ref.h"
 #include "ui/base/accelerators/accelerator.h"
 
 namespace ash {
@@ -38,8 +39,8 @@
   void RemoveAcceleratorsUpdatedCallback(AcceleratorsUpdatedCallback callback);
 
   // Get the accelerators for a single action.
-  virtual const std::vector<ui::Accelerator>& GetAcceleratorsForAction(
-      AcceleratorActionId action_id) = 0;
+  virtual base::optional_ref<const std::vector<ui::Accelerator>>
+  GetAcceleratorsForAction(AcceleratorActionId action_id) = 0;
 
   // Whether this source of shortcuts can be modified. If this returns false
   // then any of the Add/Remove/Replace class will DCHECK. The two Restore
diff --git a/ash/shelf/kiosk_app_instruction_bubble.cc b/ash/shelf/kiosk_app_instruction_bubble.cc
index 0b962f43..6170e1fe 100644
--- a/ash/shelf/kiosk_app_instruction_bubble.cc
+++ b/ash/shelf/kiosk_app_instruction_bubble.cc
@@ -4,7 +4,6 @@
 
 #include "ash/shelf/kiosk_app_instruction_bubble.h"
 
-#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -13,7 +12,6 @@
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tray_utils.h"
 #include "base/functional/callback_helpers.h"
-#include "chromeos/constants/chromeos_features.h"
 #include "components/strings/grit/components_strings.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -63,13 +61,8 @@
 
   // Set up the title view.
   title_ = AddChildView(std::make_unique<views::Label>());
-  if (chromeos::features::IsJellyEnabled()) {
-    title_->SetAutoColorReadabilityEnabled(false);
-    TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosBody2, *title_);
-  } else {
-    TrayPopupUtils::SetLabelFontList(title_,
-                                     TrayPopupUtils::FontStyle::kSmallTitle);
-  }
+  title_->SetAutoColorReadabilityEnabled(false);
+  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosBody2, *title_);
   title_->SetText(l10n_util::GetStringUTF16(IDS_SHELF_KIOSK_APP_INSTRUCTION));
   title_->SetMultiLine(true);
   title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
diff --git a/ash/shelf/shelf_shutdown_confirmation_bubble.cc b/ash/shelf/shelf_shutdown_confirmation_bubble.cc
index e3716bb..e27ddfed 100644
--- a/ash/shelf/shelf_shutdown_confirmation_bubble.cc
+++ b/ash/shelf/shelf_shutdown_confirmation_bubble.cc
@@ -4,7 +4,6 @@
 
 #include "ash/shelf/shelf_shutdown_confirmation_bubble.h"
 
-#include "ash/constants/ash_features.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
 #include "ash/style/pill_button.h"
@@ -15,7 +14,6 @@
 #include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
 #include "base/metrics/histogram_functions.h"
-#include "chromeos/constants/chromeos_features.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/accessibility/ax_node_data.h"
@@ -97,14 +95,9 @@
   title_ = AddChildView(std::make_unique<views::Label>());
   title_->SetMultiLine(true);
   title_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
-  if (chromeos::features::IsJellyEnabled()) {
-    title_->SetAutoColorReadabilityEnabled(false);
-    TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosHeadline1,
-                                          *title_);
-  } else {
-    TrayPopupUtils::SetLabelFontList(title_,
-                                     TrayPopupUtils::FontStyle::kSubHeader);
-  }
+  title_->SetAutoColorReadabilityEnabled(false);
+  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosHeadline1,
+                                        *title_);
   title_->SetText(
       l10n_util::GetStringUTF16(IDS_ASH_SHUTDOWN_CONFIRMATION_TITLE));
   title_->SetProperty(
diff --git a/ash/shell.cc b/ash/shell.cc
index cfeabef..da788ce1 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "ash/accelerators/accelerator_controller_impl.h"
+#include "ash/accelerators/accelerator_lookup.h"
 #include "ash/accelerators/accelerator_prefs.h"
 #include "ash/accelerators/accelerator_tracker.h"
 #include "ash/accelerators/ash_accelerator_configuration.h"
@@ -1434,6 +1435,8 @@
   ash_accelerator_configuration_ =
       std::make_unique<AshAcceleratorConfiguration>();
   ash_accelerator_configuration_->Initialize();
+  accelerator_lookup_ =
+      std::make_unique<AcceleratorLookup>(ash_accelerator_configuration_.get());
   accelerator_controller_ = std::make_unique<AcceleratorControllerImpl>(
       ash_accelerator_configuration_.get());
 
diff --git a/ash/shell.h b/ash/shell.h
index dc4b81df..c54b4ea 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -94,6 +94,7 @@
 
 class AcceleratorControllerImpl;
 class AcceleratorKeycodeLookupCache;
+class AcceleratorLookup;
 class AcceleratorPrefs;
 class AcceleratorTracker;
 class AccessibilityController;
@@ -430,6 +431,7 @@
   AshAcceleratorConfiguration* ash_accelerator_configuration() {
     return ash_accelerator_configuration_.get();
   }
+  AcceleratorLookup* accelerator_lookup() { return accelerator_lookup_.get(); }
   AssistantControllerImpl* assistant_controller() {
     return assistant_controller_.get();
   }
@@ -968,6 +970,7 @@
 
   std::unique_ptr<AcceleratorPrefs> accelerator_prefs_;
   std::unique_ptr<AshAcceleratorConfiguration> ash_accelerator_configuration_;
+  std::unique_ptr<AcceleratorLookup> accelerator_lookup_;
   std::unique_ptr<AcceleratorControllerImpl> accelerator_controller_;
   std::unique_ptr<AcceleratorKeycodeLookupCache>
       accelerator_keycode_lookup_cache_;
diff --git a/ash/system/network/network_list_header_view.cc b/ash/system/network/network_list_header_view.cc
index 8ce01b78..c0d8bfd3 100644
--- a/ash/system/network/network_list_header_view.cc
+++ b/ash/system/network/network_list_header_view.cc
@@ -16,7 +16,7 @@
 namespace ash {
 
 NetworkListHeaderView::NetworkListHeaderView() {
-  TrayPopupUtils::ConfigureAsStickyHeader(this);
+  TrayPopupUtils::ConfigureHeader(this);
   SetUseDefaultFillLayout(true);
   entry_row_ =
       AddChildView(std::make_unique<HoverHighlightView>(/*listener=*/this));
diff --git a/ash/system/network/network_list_view_controller_impl.cc b/ash/system/network/network_list_view_controller_impl.cc
index 595fef3..66d998f 100644
--- a/ash/system/network/network_list_view_controller_impl.cc
+++ b/ash/system/network/network_list_view_controller_impl.cc
@@ -962,7 +962,7 @@
   // Set up layout and apply sticky row property.
   std::unique_ptr<TriView> connection_warning(
       TrayPopupUtils::CreateDefaultRowView(/*use_wide_layout=*/false));
-  TrayPopupUtils::ConfigureAsStickyHeader(connection_warning.get());
+  TrayPopupUtils::ConfigureHeader(connection_warning.get());
 
   SetConnectionWarningIcon(connection_warning.get(),
                            /*use_managed_icon=*/show_managed_icon);
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index ab35b38..28b4dfa8 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -116,14 +116,8 @@
     label_ = AddChildView(std::make_unique<views::Label>(
         l10n_util::GetStringUTF16(IDS_ASH_STYLUS_BATTERY_LOW_LABEL)));
     label_->SetEnabledColor(stylus_battery_delegate_.GetColorForBatteryLevel());
-    if (chromeos::features::IsJellyEnabled()) {
-      label_->SetAutoColorReadabilityEnabled(false);
-      TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosBody2,
-                                            *label_);
-    } else {
-      TrayPopupUtils::SetLabelFontList(label_,
-                                       TrayPopupUtils::FontStyle::kSmallTitle);
-    }
+    label_->SetAutoColorReadabilityEnabled(false);
+    TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosBody2, *label_);
   }
 
   // views::View:
@@ -182,14 +176,9 @@
         l10n_util::GetStringUTF16(IDS_ASH_STYLUS_TOOLS_TITLE)));
     title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
     title_label->SetEnabledColorId(kColorAshTextColorPrimary);
-    if (chromeos::features::IsJellyEnabled()) {
-      title_label->SetAutoColorReadabilityEnabled(false);
-      TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosTitle1,
-                                            *title_label);
-    } else {
-      TrayPopupUtils::SetLabelFontList(
-          title_label, TrayPopupUtils::FontStyle::kPodMenuHeader);
-    }
+    title_label->SetAutoColorReadabilityEnabled(false);
+    TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosTitle1,
+                                          *title_label);
     layout_ptr->SetFlexForView(title_label, 1);
 
     if (ash::features::IsStylusBatteryStatusEnabled()) {
diff --git a/ash/system/phonehub/phone_hub_interstitial_view.cc b/ash/system/phonehub/phone_hub_interstitial_view.cc
index 859994d0..d2eaec48 100644
--- a/ash/system/phonehub/phone_hub_interstitial_view.cc
+++ b/ash/system/phonehub/phone_hub_interstitial_view.cc
@@ -15,7 +15,6 @@
 #include "ash/system/phonehub/ui_constants.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
-#include "chromeos/constants/chromeos_features.h"
 #include "skia/ext/image_operations.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -89,14 +88,8 @@
   auto label_color = color_provider->GetContentLayerColor(
       AshColorProvider::ContentLayerType::kTextColorPrimary);
   title_->SetEnabledColor(label_color);
-
-  if (chromeos::features::IsJellyrollEnabled()) {
-    TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosButton1,
-                                          *title_);
-  } else {
-    TrayPopupUtils::SetLabelFontList(title_,
-                                     TrayPopupUtils::FontStyle::kSubHeader);
-  }
+  TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosButton1,
+                                        *title_);
 
   // Overriding because the typography line height set does not match Phone
   // Hub specs.
@@ -114,16 +107,10 @@
   description_->SetEnabledColor(label_color);
   description_->SetMultiLine(true);
   description_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT);
-
-  if (chromeos::features::IsJellyrollEnabled()) {
-    // TODO(b/281844561): Migrate the |description_| to usea slightly lighter
-    // text color when tokens have been finalized.
-    TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosBody2,
-                                          *description_);
-  } else {
-    TrayPopupUtils::SetLabelFontList(
-        description_, TrayPopupUtils::FontStyle::kDetailedViewLabel);
-  }
+  // TODO(b/281844561): Migrate the `description_` to use a slightly lighter
+  // text color when tokens have been finalized.
+  TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosBody2,
+                                        *description_);
   description_->SetLineHeight(20);
 
   // Set up button container view, which should be right-aligned.
diff --git a/ash/system/phonehub/phone_status_view.cc b/ash/system/phonehub/phone_status_view.cc
index da3f7e1..0bc93678 100644
--- a/ash/system/phonehub/phone_status_view.cc
+++ b/ash/system/phonehub/phone_status_view.cc
@@ -112,14 +112,8 @@
   phone_name_label_->SetEnabledColor(
       AshColorProvider::Get()->GetContentLayerColor(
           AshColorProvider::ContentLayerType::kTextColorPrimary));
-
-  if (chromeos::features::IsJellyrollEnabled()) {
-    TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosHeadline1,
-                                          *phone_name_label_);
-  } else {
-    TrayPopupUtils::SetLabelFontList(phone_name_label_,
-                                     TrayPopupUtils::FontStyle::kSubHeader);
-  }
+  TypographyProvider::Get()->StyleLabel(ash::TypographyToken::kCrosHeadline1,
+                                        *phone_name_label_);
 
   phone_name_label_->SetElideBehavior(gfx::ElideBehavior::ELIDE_TAIL);
   AddView(TriView::Container::START, phone_name_label_);
diff --git a/ash/system/tray/tray_info_label.cc b/ash/system/tray/tray_info_label.cc
index f019f80..7b1836f 100644
--- a/ash/system/tray/tray_info_label.cc
+++ b/ash/system/tray/tray_info_label.cc
@@ -7,7 +7,6 @@
 #include "ash/style/ash_color_id.h"
 #include "ash/style/typography.h"
 #include "ash/system/tray/tray_popup_utils.h"
-#include "chromeos/constants/chromeos_features.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/views/layout/fill_layout.h"
@@ -36,13 +35,8 @@
 
 void TrayInfoLabel::Update(int message_id) {
   label_->SetEnabledColorId(kColorAshTextColorPrimary);
-  if (chromeos::features::IsJellyEnabled()) {
-    label_->SetAutoColorReadabilityEnabled(false);
-    TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosBody2, *label_);
-  } else {
-    TrayPopupUtils::SetLabelFontList(label_,
-                                     TrayPopupUtils::FontStyle::kSystemInfo);
-  }
+  label_->SetAutoColorReadabilityEnabled(false);
+  TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosBody2, *label_);
   label_->SetText(l10n_util::GetStringUTF16(message_id));
 }
 
diff --git a/ash/system/tray/tray_popup_utils.cc b/ash/system/tray/tray_popup_utils.cc
index 1b0765b2..58445db1 100644
--- a/ash/system/tray/tray_popup_utils.cc
+++ b/ash/system/tray/tray_popup_utils.cc
@@ -242,8 +242,7 @@
       kFocusBorderThickness, gfx::InsetsF());
 }
 
-// TODO(b/309681875): Rename this method.
-void TrayPopupUtils::ConfigureAsStickyHeader(views::View* view) {
+void TrayPopupUtils::ConfigureHeader(views::View* view) {
   view->SetBorder(views::CreateEmptyBorder(
       gfx::Insets::VH(kMenuSeparatorVerticalPadding, 0)));
   view->SetPaintToLayer();
@@ -353,9 +352,4 @@
       GetDefaultSizeOfVectorIcon(kCheckCircleIcon));
 }
 
-// static
-void TrayPopupUtils::SetLabelFontList(views::Label* label, FontStyle style) {
-  // TODO(b/309681875): Remove this method.
-}
-
 }  // namespace ash
diff --git a/ash/system/tray/tray_popup_utils.h b/ash/system/tray/tray_popup_utils.h
index fc01fe6d..850cc62 100644
--- a/ash/system/tray/tray_popup_utils.h
+++ b/ash/system/tray/tray_popup_utils.h
@@ -132,8 +132,8 @@
   // Creates a default focus painter used for most things in tray popups.
   static std::unique_ptr<views::Painter> CreateFocusPainter();
 
-  // Sets up `view` to be a sticky header in a tray detail scroll view.
-  static void ConfigureAsStickyHeader(views::View* view);
+  // Sets up `view` with the tray detail scroll view header specs.
+  static void ConfigureHeader(views::View* view);
 
   // Sets up `ink_drop` according to jelly ux requirements for row buttons.
   static void ConfigureRowButtonInkdrop(views::InkDropHost* ink_drop);
@@ -188,11 +188,6 @@
 
   // Creates the check mark.
   static ui::ImageModel CreateCheckMark(ui::ColorId color_id);
-
-  // Sets the font list for |label| based on |style|.
-  // DEPRECATED: Use `TypographyProvider` in new code. If you need legacy fonts,
-  // use TypographyToken::kLegacy*.
-  static void SetLabelFontList(views::Label* label, FontStyle style);
 };
 
 }  // namespace ash
diff --git a/ash/user_education/welcome_tour/welcome_tour_controller_unittest.cc b/ash/user_education/welcome_tour/welcome_tour_controller_unittest.cc
index c161635..b66c8ee 100644
--- a/ash/user_education/welcome_tour/welcome_tour_controller_unittest.cc
+++ b/ash/user_education/welcome_tour/welcome_tour_controller_unittest.cc
@@ -10,7 +10,7 @@
 #include <utility>
 #include <vector>
 
-#include "ash/accelerators/ash_accelerator_configuration.h"
+#include "ash/accelerators/accelerator_lookup.h"
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/ash_element_identifiers.h"
 #include "ash/constants/ash_features.h"
@@ -89,6 +89,7 @@
 using ::user_education::TutorialDescription;
 using ::views::test::WidgetDestroyedWaiter;
 
+using AcceleratorDetails = AcceleratorLookup::AcceleratorDetails;
 using ContextMode = TutorialDescription::ContextMode;
 using ElementSpecifier = TutorialDescription::ElementSpecifier;
 
@@ -1167,14 +1168,14 @@
   // received as expected.
   void PerformActionAndCheckKeyEvents(AcceleratorAction action, bool received) {
     // Get the accelerators corresponding to `action`.
-    const std::vector<ui::Accelerator>& accelerators =
-        Shell::Get()->ash_accelerator_configuration()->GetAcceleratorsForAction(
-            action);
-    ASSERT_FALSE(accelerators.empty());
+    const std::vector<AcceleratorDetails>& accelerators_details =
+        Shell::Get()->accelerator_lookup()->GetAcceleratorsForAction(action);
+    ASSERT_FALSE(accelerators_details.empty());
 
-    for (const ui::Accelerator& accelerator : accelerators) {
+    for (const AcceleratorDetails& accelerator_details : accelerators_details) {
       // If `received` is true, then `accelerator` should be received;
       // otherwise, `accelerator` should NOT be received.
+      const ui::Accelerator accelerator = accelerator_details.accelerator;
       EXPECT_CALL(
           *mock_pretarget_event_handler_,
           OnKeyEvent(AllOf(
diff --git a/ash/webui/common/resources/fake_observables.d.ts b/ash/webui/common/resources/fake_observables.d.ts
index 261f6849..c3bd999b 100644
--- a/ash/webui/common/resources/fake_observables.d.ts
+++ b/ash/webui/common/resources/fake_observables.d.ts
@@ -5,7 +5,7 @@
 export class FakeObservables<T = any> {
   register(methodName: string): void;
   registerObservableWithArg(methodName: string): void;
-  observe(methodName: string, callback: (arg0: T) => void): void;
+  observe(methodName: string, callback: (...args: T[]) => void): void;
   observeWithArg(methodName: string, arg: string, callback: (arg0: T) => void):
       void;
   setObservableData(methodName: string, observations: T[]): void;
diff --git a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
index 6abdb1aa..fa44ec6 100644
--- a/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
+++ b/ash/webui/common/resources/shortcut_input_ui/shortcut_input.html
@@ -25,7 +25,8 @@
   <template is="dom-if" if="[[shouldShowConfirmView(isCapturing)]]">
     <div id="confirmContainer">
       <template is="dom-repeat" items="[[modifiers]]">
-        <shortcut-input-key key="[[item]]" key-state="modifier-selected">
+        <shortcut-input-key key="[[item]]" key-state="modifier-selected"
+            has-launcher-button="[[hasLauncherButton]]">
         </shortcut-input-key>
       </template>
       <template is="dom-if"
@@ -39,7 +40,8 @@
             key="[[getConfirmKey(pendingKeyEvent.*,
                                  pendingPrerewrittenKeyEvent.*)]]"
             key-state="[[getConfirmKeyState(pendingKeyEvent.*,
-                                            pendingPrerewrittenKeyEvent.*)]]">
+                                            pendingPrerewrittenKeyEvent.*)]]"
+            has-launcher-button="[[hasLauncherButton]]">
         </shortcut-input-key>
       </template>
     </div>
diff --git a/ash/webui/shimless_rma/resources/BUILD.gn b/ash/webui/shimless_rma/resources/BUILD.gn
index 8f9ce8e..f2a6f925 100644
--- a/ash/webui/shimless_rma/resources/BUILD.gn
+++ b/ash/webui/shimless_rma/resources/BUILD.gn
@@ -37,7 +37,7 @@
   web_component_files = [
     "base_page.js",
     "calibration_component_chip.ts",
-    "critical_error_page.js",
+    "critical_error_page.ts",
     "hardware_error_page.js",
     "onboarding_choose_destination_page.js",
     "onboarding_choose_wp_disable_method_page.js",
@@ -70,7 +70,7 @@
     "data.js",
     "events.ts",
     "fake_data.js",
-    "fake_shimless_rma_service.js",
+    "fake_shimless_rma_service.ts",
     "mojo_interface_provider.js",
     "shimless_rma_util.ts",
   ]
diff --git a/ash/webui/shimless_rma/resources/critical_error_page.js b/ash/webui/shimless_rma/resources/critical_error_page.ts
similarity index 60%
rename from ash/webui/shimless_rma/resources/critical_error_page.js
rename to ash/webui/shimless_rma/resources/critical_error_page.ts
index cf83f16..72a26bc 100644
--- a/ash/webui/shimless_rma/resources/critical_error_page.js
+++ b/ash/webui/shimless_rma/resources/critical_error_page.ts
@@ -7,9 +7,8 @@
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.js';
 import 'chrome://resources/cr_elements/cr_button/cr_button.js';
 
-import {I18nBehavior, I18nBehaviorInterface} from 'chrome://resources/ash/common/i18n_behavior.js';
-import {mixinBehaviors, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {getTemplate} from './critical_error_page.html.js';
 import {getShimlessRmaService} from './mojo_interface_provider.js';
 import {ShimlessRmaServiceInterface} from './shimless_rma.mojom-webui.js';
@@ -21,56 +20,49 @@
  * continuing.
  */
 
-/**
- * @constructor
- * @extends {PolymerElement}
- * @implements {I18nBehaviorInterface}
- */
-const CriticalErrorPageBase = mixinBehaviors([I18nBehavior], PolymerElement);
+const CriticalErrorPageBase = I18nMixin(PolymerElement);
 
-/** @polymer */
 export class CriticalErrorPage extends CriticalErrorPageBase {
   static get is() {
-    return 'critical-error-page';
+    return 'critical-error-page' as const;
   }
 
   static get template() {
     return getTemplate();
   }
+
   static get properties() {
     return {
-      /**
-       * Set by shimless_rma.js.
-       * @type {boolean}
-       */
+      // Set by shimless_rma.ts.
       allButtonsDisabled: Boolean,
     };
   }
 
-  constructor() {
-    super();
-    /** @private {ShimlessRmaServiceInterface} */
-    this.shimlessRmaService = getShimlessRmaService();
-  }
+  shimlessRmaService: ShimlessRmaServiceInterface = getShimlessRmaService();
+  allButtonsDisabled: boolean;
 
-  /** @override */
-  ready() {
+  override ready() {
     super.ready();
 
     focusPageTitle(this);
   }
 
-  /** @protected */
-  onExitToLoginButtonClicked() {
+  protected onExitToLoginButtonClicked() {
     this.shimlessRmaService.criticalErrorExitToLogin();
     disableAllButtons(this, /* showBusyStateOverlay= */ true);
   }
 
-  /** @protected */
-  onRebootButtonClicked() {
+  protected onRebootButtonClicked() {
     this.shimlessRmaService.criticalErrorReboot();
     disableAllButtons(this, /* showBusyStateOverlay= */ true);
   }
 }
 
-customElements.define(CriticalErrorPage.is, CriticalErrorPage);
+declare global {
+  interface HTMLElementTagNameMap {
+    [CriticalErrorPage.is]: CriticalErrorPage;
+  }
+}
+
+customElements.define(
+    CriticalErrorPage.is, CriticalErrorPage);
diff --git a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts
similarity index 65%
rename from ash/webui/shimless_rma/resources/fake_shimless_rma_service.js
rename to ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts
index c58b419a..97f3fd0 100644
--- a/ash/webui/shimless_rma/resources/fake_shimless_rma_service.js
+++ b/ash/webui/shimless_rma/resources/fake_shimless_rma_service.ts
@@ -7,77 +7,65 @@
 import {FakeObservables} from 'chrome://resources/ash/common/fake_observables.js';
 import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
 
-import {CalibrationComponentStatus, CalibrationObserverRemote, CalibrationOverallStatus, CalibrationSetupInstruction, CalibrationStatus, Component, ComponentType, ErrorObserverRemote, ExternalDiskStateObserverRemote, FeatureLevel, FinalizationError, FinalizationObserverRemote, FinalizationStatus, HardwareVerificationStatusObserverRemote, HardwareWriteProtectionStateObserverRemote, OsUpdateObserverRemote, OsUpdateOperation, PowerCableStateObserverRemote, ProvisioningError, ProvisioningObserverRemote, ProvisioningStatus, QrCode, RmadErrorCode, Shimless3pDiagnosticsAppInfo, ShimlessRmaServiceInterface, Show3pDiagnosticsAppResult, ShutdownMethod, State, StateResult, UpdateErrorCode, UpdateRoFirmwareObserverRemote, UpdateRoFirmwareStatus, WriteProtectDisableCompleteAction} from './shimless_rma.mojom-webui.js';
+import {CalibrationComponentStatus, CalibrationObserverRemote, CalibrationOverallStatus, CalibrationSetupInstruction, CalibrationStatus, Component, ComponentType, ErrorObserverRemote, ExternalDiskStateObserverRemote, FeatureLevel, FinalizationError, FinalizationObserverRemote, FinalizationStatus, HardwareVerificationStatusObserverRemote, HardwareWriteProtectionStateObserverRemote, OsUpdateObserverRemote, OsUpdateOperation, PowerCableStateObserverRemote, ProvisioningError, ProvisioningObserverRemote, ProvisioningStatus, RmadErrorCode, Shimless3pDiagnosticsAppInfo, ShimlessRmaServiceInterface, Show3pDiagnosticsAppResult, State, StateResult, UpdateErrorCode, UpdateRoFirmwareObserverRemote, UpdateRoFirmwareStatus, WriteProtectDisableCompleteAction} from './shimless_rma.mojom-webui.js';
 
-/** @implements {ShimlessRmaServiceInterface} */
-export class FakeShimlessRmaService {
+export class FakeShimlessRmaService implements ShimlessRmaServiceInterface {
   constructor() {
     this.methods = new FakeMethodResolver();
     this.observables = new FakeObservables();
 
     /**
      * The list of states for this RMA flow.
-     * @private {!Array<!StateResult>}
      */
     this.states = [];
 
     /**
      * The index into states for the current fake state.
-     * @private {number}
      */
     this.stateIndex = 0;
 
     /**
      * The list of components.
-     * @private {!Array<!Component>}
      */
     this.components = [];
 
     /**
      * Control automatically triggering a HWWP disable observation.
-     * @private {boolean}
      */
     this.automaticallyTriggerDisableWriteProtectionObservation = false;
 
     /**
      * Control automatically triggering update RO firmware observations.
-     * @private {boolean}
      */
     this.automaticallyTriggerUpdateRoFirmwareObservation = false;
 
     /**
      * Control automatically triggering provisioning observations.
-     * @private {boolean}
      */
     this.automaticallyTriggerProvisioningObservation = false;
 
     /**
      * Control automatically triggering calibration observations.
-     * @private {boolean}
      */
     this.automaticallyTriggerCalibrationObservation = false;
 
     /**
      * Control automatically triggering OS update observations.
-     * @private {boolean}
      */
     this.automaticallyTriggerOsUpdateObservation = false;
 
     /**
      * Control automatically triggering a hardware verification observation.
-     * @private {boolean}
      */
     this.automaticallyTriggerHardwareVerificationStatusObservation = false;
 
     /**
      * Control automatically triggering a finalization observation.
-     * @private {boolean}
      */
     this.automaticallyTriggerFinalizationObservation = false;
 
     /**
      * Control automatically triggering power cable state observations.
-     * @private {boolean}
      */
     this.automaticallyTriggerPowerCableStateObservation = false;
 
@@ -85,33 +73,46 @@
      * Both abortRma and forward state transitions can have significant delays
      * that are useful to fake for manual testing.
      * Defaults to no delay for unit tests.
-     * @private {number}
      */
     this.resolveMethodDelayMs = 0;
 
     /**
      * The result of calling trackConfiguredNetworks.
-     * @private {boolean}
      */
     this.trackConfiguredNetworksCalled = false;
 
     /**
      * The approval of last call to completeLast3pDiagnosticsInstallation.
-     * @private {?boolean}
      */
     this.lastCompleteLast3pDiagnosticsInstallationApproval = null;
 
     /**
      * Has show3pDiagnosticsApp been called.
-     * @private {boolean}
      */
     this.wasShow3pDiagnosticsAppCalled = false;
 
     this.reset();
   }
 
-  /** @param {number} delayMs */
-  setAsyncOperationDelayMs(delayMs) {
+  private methods: FakeMethodResolver;
+  private observables: FakeObservables;
+  private states: StateResult[];
+  private stateIndex: number;
+  private components: Component[];
+  private automaticallyTriggerDisableWriteProtectionObservation: boolean;
+  private automaticallyTriggerUpdateRoFirmwareObservation: boolean;
+  private automaticallyTriggerProvisioningObservation: boolean;
+  private automaticallyTriggerCalibrationObservation: boolean;
+  private automaticallyTriggerOsUpdateObservation: boolean;
+  private automaticallyTriggerHardwareVerificationStatusObservation: boolean;
+  private automaticallyTriggerFinalizationObservation: boolean;
+  private automaticallyTriggerPowerCableStateObservation: boolean;
+  private resolveMethodDelayMs: number;
+  private trackConfiguredNetworksCalled: boolean;
+  private lastCompleteLast3pDiagnosticsInstallationApproval: boolean|null;
+  private wasShow3pDiagnosticsAppCalled: boolean;
+
+  setAsyncOperationDelayMs(delayMs: number) {
     this.resolveMethodDelayMs = delayMs;
   }
 
@@ -122,18 +123,13 @@
    * Next state functions and transitionPreviousState will move through the fake
    * state through the list, and return kTransitionFailed if it would move off
    * either end. getCurrentState always return the state at the current index.
-   *
-   * @param {!Array<!StateResult>} states
    */
-  setStates(states) {
+  setStates(states: StateResult[]) {
     this.states = states;
     this.stateIndex = 0;
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  getCurrentState() {
+  getCurrentState(): Promise<{stateResult: StateResult}> {
     // As next state functions and transitionPreviousState can modify the result
     // of this function the result must be set at the time of the call.
     if (this.states.length === 0) {
@@ -151,10 +147,7 @@
         'getCurrentState', this.resolveMethodDelayMs);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  transitionPreviousState() {
+  transitionPreviousState(): Promise<{stateResult: StateResult}> {
     // As next state methods and transitionPreviousState can modify the result
     // of this function the result must be set at the time of the call.
     if (this.states.length === 0) {
@@ -178,77 +171,54 @@
         'transitionPreviousState', this.resolveMethodDelayMs);
   }
 
-  /**
-   * @return {!Promise<!{error: !RmadErrorCode}>}
-   */
-  abortRma() {
+  abortRma(): Promise<{error: RmadErrorCode}> {
     return this.methods.resolveMethodWithDelay(
         'abortRma', this.resolveMethodDelayMs);
   }
 
   /**
    * Sets the value that will be returned when calling abortRma().
-   * @param {!RmadErrorCode} error
    */
-  setAbortRmaResult(error) {
+  setAbortRmaResult(error: RmadErrorCode): void {
     this.methods.setResult('abortRma', {error: error});
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  beginFinalization() {
+  beginFinalization(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'beginFinalization', State.kWelcomeScreen);
   }
 
-  trackConfiguredNetworks() {
+  trackConfiguredNetworks(): void {
     this.trackConfiguredNetworksCalled = true;
   }
 
-  getTrackConfiguredNetworks() {
+  getTrackConfiguredNetworks(): boolean {
     return this.trackConfiguredNetworksCalled;
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  networkSelectionComplete() {
+  networkSelectionComplete(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'networkSelectionComplete', State.kConfigureNetwork);
   }
 
-  /**
-   * @return {!Promise<!{version: string}>}
-   */
-  getCurrentOsVersion() {
+  getCurrentOsVersion(): Promise<{version: string}> {
     return this.methods.resolveMethod('getCurrentOsVersion');
   }
 
-  /**
-   * @param {null|string} version
-   */
-  setGetCurrentOsVersionResult(version) {
+  setGetCurrentOsVersionResult(version: string|null) {
     this.methods.setResult('getCurrentOsVersion', {version: version});
   }
 
-  /**
-   * @return {!Promise<!{updateAvailable: boolean, version: string}>}
-   */
-  checkForOsUpdates() {
+  checkForOsUpdates(): Promise<{updateAvailable: boolean, version: string}> {
     return this.methods.resolveMethod('checkForOsUpdates');
   }
 
-  /** @param {string} version */
-  setCheckForOsUpdatesResult(version) {
+  setCheckForOsUpdatesResult(version: string) {
     this.methods.setResult(
         'checkForOsUpdates', {updateAvailable: true, version});
   }
 
-  /**
-   * @return {!Promise<!{updateStarted: boolean}>}
-   */
-  updateOs() {
+  updateOs(): Promise<{updateStarted: boolean}> {
     if (this.automaticallyTriggerOsUpdateObservation) {
       this.triggerOsUpdateObserver(
           OsUpdateOperation.kCheckingForUpdate, 0.1, UpdateErrorCode.kSuccess,
@@ -269,401 +239,249 @@
     return this.methods.resolveMethod('updateOs');
   }
 
-  /**
-   * @param {boolean} started
-   */
-  setUpdateOsResult(started) {
+  setUpdateOsResult(started: boolean): void {
     this.methods.setResult('updateOs', {updateStarted: started});
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  updateOsSkipped() {
+  updateOsSkipped(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod('updateOsSkipped', State.kUpdateOs);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  setSameOwner() {
+  setSameOwner(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod('setSameOwner', State.kChooseDestination);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  setDifferentOwner() {
+  setDifferentOwner(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'setDifferentOwner', State.kChooseDestination);
   }
 
-  /**
-   * @param {boolean} shouldWipeDevice
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  setWipeDevice(shouldWipeDevice) {
+  setWipeDevice(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod('setWipeDevice', State.kChooseWipeDevice);
   }
 
-  /**
-   * @return {!Promise<!{available: boolean}>}
-   */
-  manualDisableWriteProtectAvailable() {
+  manualDisableWriteProtectAvailable(): Promise<{available: boolean}> {
     return this.methods.resolveMethod('manualDisableWriteProtectAvailable');
   }
 
-  /**
-   * @param {boolean} available
-   */
-  setManualDisableWriteProtectAvailableResult(available) {
+  setManualDisableWriteProtectAvailableResult(available: boolean) {
     this.methods.setResult(
         'manualDisableWriteProtectAvailable', {available: available});
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  chooseManuallyDisableWriteProtect() {
+  chooseManuallyDisableWriteProtect(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'chooseManuallyDisableWriteProtect',
         State.kChooseWriteProtectDisableMethod);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  chooseRsuDisableWriteProtect() {
+  chooseRsuDisableWriteProtect(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'chooseRsuDisableWriteProtect', State.kChooseWriteProtectDisableMethod);
   }
 
-  /**
-   * @return {!Promise<!{challenge: string}>}
-   */
-  getRsuDisableWriteProtectChallenge() {
+  getRsuDisableWriteProtectChallenge(): Promise<{challenge: string}> {
     return this.methods.resolveMethod('getRsuDisableWriteProtectChallenge');
   }
 
-  /**
-   * @param {string} challenge
-   */
-  setGetRsuDisableWriteProtectChallengeResult(challenge) {
+  setGetRsuDisableWriteProtectChallengeResult(challenge: string) {
     this.methods.setResult(
         'getRsuDisableWriteProtectChallenge', {challenge: challenge});
   }
 
-  /**
-   * @return {!Promise<!{hwid: string}>}
-   */
-  getRsuDisableWriteProtectHwid() {
+  getRsuDisableWriteProtectHwid(): Promise<{hwid: string}> {
     return this.methods.resolveMethod('getRsuDisableWriteProtectHwid');
   }
 
-  /**
-   * @param {string} hwid
-   */
-  setGetRsuDisableWriteProtectHwidResult(hwid) {
+  setGetRsuDisableWriteProtectHwidResult(hwid: string) {
     this.methods.setResult('getRsuDisableWriteProtectHwid', {hwid: hwid});
   }
 
-  /**
-   * @return {!Promise<!{qrCodeData: !Array<number>}>}
-   */
-  getRsuDisableWriteProtectChallengeQrCode() {
+  getRsuDisableWriteProtectChallengeQrCode(): Promise<{qrCodeData: number[]}> {
     return this.methods.resolveMethod(
         'getRsuDisableWriteProtectChallengeQrCode');
   }
 
-  /**
-   * @param {!Array<number>} qrCodeData
-   */
-  setGetRsuDisableWriteProtectChallengeQrCodeResponse(qrCodeData) {
+  setGetRsuDisableWriteProtectChallengeQrCodeResponse(qrCodeData: number[]) {
     this.methods.setResult(
         'getRsuDisableWriteProtectChallengeQrCode', {qrCodeData: qrCodeData});
   }
 
-  /**
-   * @param {string} code
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  setRsuDisableWriteProtectCode(code) {
+  setRsuDisableWriteProtectCode(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'setRsuDisableWriteProtectCode', State.kEnterRSUWPDisableCode);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  writeProtectManuallyDisabled() {
+  writeProtectManuallyDisabled(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'writeProtectManuallyDisabled', State.kWaitForManualWPDisable);
   }
 
-  /** @return {!Promise<!{action: !WriteProtectDisableCompleteAction}>} */
-  getWriteProtectDisableCompleteAction() {
+  getWriteProtectDisableCompleteAction():
+      Promise<{action: WriteProtectDisableCompleteAction}> {
     return this.methods.resolveMethod('getWriteProtectDisableCompleteAction');
   }
 
-  /** @param {!WriteProtectDisableCompleteAction} action */
-  setGetWriteProtectDisableCompleteAction(action) {
+  setGetWriteProtectDisableCompleteAction(
+      action: WriteProtectDisableCompleteAction): void {
     this.methods.setResult(
         'getWriteProtectDisableCompleteAction', {action: action});
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  confirmManualWpDisableComplete() {
+  confirmManualWpDisableComplete(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'confirmManualWpDisableComplete', State.kWPDisableComplete);
   }
 
-  /**
-   * @return {!Promise<!{components: !Array<!Component>}>}
-   */
-  getComponentList() {
+  getComponentList(): Promise<{components: Component[]}> {
     this.methods.setResult('getComponentList', {components: this.components});
     return this.methods.resolveMethod('getComponentList');
   }
 
-  /**
-   * @param {!Array<!Component>} components
-   */
-  setGetComponentListResult(components) {
+  setGetComponentListResult(components: Component[]): void {
     this.components = components;
   }
 
-  /**
-   * @param {!Array<!Component>} components
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  setComponentList(components) {
+  setComponentList(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'setComponentList', State.kSelectComponents);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  reworkMainboard() {
+  reworkMainboard(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'reworkMainboard', State.kSelectComponents);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  roFirmwareUpdateComplete() {
+  roFirmwareUpdateComplete(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'roFirmwareUpdateComplete', State.kUpdateRoFirmware);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   *
-   */
-  shutdownForRestock() {
+  shutdownForRestock(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod('shutdownForRestock', State.kRestock);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  continueFinalizationAfterRestock() {
+  continueFinalizationAfterRestock(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'continueFinalizationAfterRestock', State.kRestock);
   }
 
-  /**
-   * @return {!Promise<!{regions: !Array<string>}>}
-   */
-  getRegionList() {
+  getRegionList(): Promise<{regions: string[]}> {
     return this.methods.resolveMethod('getRegionList');
   }
 
-  /**
-   * @param {!Array<string>} regions
-   */
-  setGetRegionListResult(regions) {
+  setGetRegionListResult(regions: string[]): void {
     this.methods.setResult('getRegionList', {regions: regions});
   }
 
-  /**
-   * @return {!Promise<!{skus: !Array<bigint>}>}
-   */
-  getSkuList() {
+  getSkuList(): Promise<{skus: bigint[]}> {
     return this.methods.resolveMethod('getSkuList');
   }
 
-  /**
-   * @param {!Array<bigint>} skus
-   */
-  setGetSkuListResult(skus) {
+  setGetSkuListResult(skus: bigint[]): void {
     this.methods.setResult('getSkuList', {skus: skus});
   }
 
-  /**
-   * @return {!Promise<!{customLabels: !Array<string>}>}
-   */
-  getCustomLabelList() {
+  getCustomLabelList(): Promise<{customLabels: string[]}> {
     return this.methods.resolveMethod('getCustomLabelList');
   }
 
-  /**
-   * @param {!Array<string>} customLabels
-   */
-  setGetCustomLabelListResult(customLabels) {
+  setGetCustomLabelListResult(customLabels: string[]): void {
     this.methods.setResult('getCustomLabelList', {customLabels: customLabels});
   }
 
-  /**
-   * @return {!Promise<!{skuDescriptions: !Array<string>}>}
-   */
-  getSkuDescriptionList() {
+  getSkuDescriptionList(): Promise<{skuDescriptions: string[]}> {
     return this.methods.resolveMethod('getSkuDescriptionList');
   }
 
-  /**
-   * @param {!Array<string>} skuDescriptions
-   */
-  setGetSkuDescriptionListResult(skuDescriptions) {
+  setGetSkuDescriptionListResult(skuDescriptions: string[]): void {
     this.methods.setResult(
         'getSkuDescriptionList', {skuDescriptions: skuDescriptions});
   }
 
-  /**
-   * @return {!Promise<!{serialNumber: string}>}
-   */
-  getOriginalSerialNumber() {
+  getOriginalSerialNumber(): Promise<{serialNumber: string}> {
     return this.methods.resolveMethod('getOriginalSerialNumber');
   }
 
-  /**
-   * @param {string} serialNumber
-   */
-  setGetOriginalSerialNumberResult(serialNumber) {
+  setGetOriginalSerialNumberResult(serialNumber: string): void {
     this.methods.setResult(
         'getOriginalSerialNumber', {serialNumber: serialNumber});
   }
 
-  /**
-   * @return {!Promise<!{regionIndex: number}>}
-   */
-  getOriginalRegion() {
+  getOriginalRegion(): Promise<{regionIndex: number}> {
     return this.methods.resolveMethod('getOriginalRegion');
   }
 
-  /**
-   * @param {number} regionIndex
-   */
-  setGetOriginalRegionResult(regionIndex) {
+  setGetOriginalRegionResult(regionIndex: number): void {
     this.methods.setResult('getOriginalRegion', {regionIndex: regionIndex});
   }
 
-  /**
-   * @return {!Promise<!{skuIndex: number}>}
-   */
-  getOriginalSku() {
+  getOriginalSku(): Promise<{skuIndex: number}> {
     return this.methods.resolveMethod('getOriginalSku');
   }
 
-  /**
-   * @param {number} skuIndex
-   */
-  setGetOriginalSkuResult(skuIndex) {
+  setGetOriginalSkuResult(skuIndex: number): void {
     this.methods.setResult('getOriginalSku', {skuIndex: skuIndex});
   }
 
-  /**
-   * @return {!Promise<!{customLabelIndex: number}>}
-   */
-  getOriginalCustomLabel() {
+  getOriginalCustomLabel(): Promise<{customLabelIndex: number}> {
     return this.methods.resolveMethod('getOriginalCustomLabel');
   }
 
-  /**
-   * @param {number} customLabelIndex
-   */
-  setGetOriginalCustomLabelResult(customLabelIndex) {
+  setGetOriginalCustomLabelResult(customLabelIndex: number): void {
     this.methods.setResult(
         'getOriginalCustomLabel', {customLabelIndex: customLabelIndex});
   }
 
-  /**
-   * @return {!Promise<!{dramPartNumber: string}>}
-   */
-  getOriginalDramPartNumber() {
+  getOriginalDramPartNumber(): Promise<{dramPartNumber: string}> {
     return this.methods.resolveMethod('getOriginalDramPartNumber');
   }
 
-  /**
-   * @param {string} dramPartNumber
-   */
-  setGetOriginalDramPartNumberResult(dramPartNumber) {
+  setGetOriginalDramPartNumberResult(dramPartNumber: string): void {
     this.methods.setResult(
         'getOriginalDramPartNumber', {dramPartNumber: dramPartNumber});
   }
 
-  /**
-   * @return {!Promise<!{originalFeatureLevel: FeatureLevel}>}
-   */
-  getOriginalFeatureLevel() {
+  getOriginalFeatureLevel(): Promise<{originalFeatureLevel: FeatureLevel}> {
     return this.methods.resolveMethod('getOriginalFeatureLevel');
   }
 
-  /**
-   * @param {FeatureLevel} featureLevel
-   */
-  setGetOriginalFeatureLevelResult(featureLevel) {
+  setGetOriginalFeatureLevelResult(featureLevel: FeatureLevel): void {
     this.methods.setResult(
         'getOriginalFeatureLevel', {originalFeatureLevel: featureLevel});
   }
 
-  /**
-   * @param {string} serialNumber
-   * @param {number} regionIndex
-   * @param {number} skuIndex
-   * @param {number} customLabelIndex
-   * @param {string} dramPartNumber
-   * @param {boolean} isChassisBranded
-   * @param {number} hwComplianceVersion
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
   setDeviceInformation(
-      serialNumber, regionIndex, skuIndex, customLabelIndex, dramPartNumber,
-      isChassisBranded, hwComplianceVersion) {
-    // TODO(gavindodd): Validate range of region and sku.
+    _serialNumber: string,
+    _regionIndex: number,
+    _skuIndex: number,
+    _customLabelIndex: number,
+    _dramPartNumber: string,
+    _isChassisBranded: boolean,
+    _hwComplianceVersion: number,
+  ): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'setDeviceInformation', State.kUpdateDeviceInformation);
   }
 
-  /**
-   * @return {!Promise<!{components: !Array<!CalibrationComponentStatus>}>}
-   */
-  getCalibrationComponentList() {
+  getCalibrationComponentList():
+      Promise<{components: CalibrationComponentStatus[]}> {
     return this.methods.resolveMethod('getCalibrationComponentList');
   }
 
-  /**
-   * @param {!Array<!CalibrationComponentStatus>} components
-   */
-  setGetCalibrationComponentListResult(components) {
+  setGetCalibrationComponentListResult(components:
+                                           CalibrationComponentStatus[]): void {
     this.methods.setResult(
         'getCalibrationComponentList', {components: components});
   }
 
-  /**
-   * @return {!Promise<!{instructions: CalibrationSetupInstruction}>}
-   */
-  getCalibrationSetupInstructions() {
+  getCalibrationSetupInstructions():
+      Promise<{instructions: CalibrationSetupInstruction}> {
     return this.methods.resolveMethod('getCalibrationSetupInstructions');
   }
 
-  /**
-   * @param {CalibrationSetupInstruction} instructions
-   */
-  setGetCalibrationSetupInstructionsResult(instructions) {
+  setGetCalibrationSetupInstructionsResult(instructions:
+                                               CalibrationSetupInstruction): void {
     this.methods.setResult(
         'getCalibrationSetupInstructions', {instructions: instructions});
   }
@@ -671,246 +489,178 @@
   /**
    * The fake does not use the status list parameter, the fake data is never
    * updated.
-   * @param {!Array<!CalibrationComponentStatus>} unused
-   * @return {!Promise<!{stateResult: !StateResult}>}
    */
-  startCalibration(unused) {
+  startCalibration(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'startCalibration', State.kCheckCalibration);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  runCalibrationStep() {
+  runCalibrationStep(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'runCalibrationStep', State.kSetupCalibration);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  continueCalibration() {
+  continueCalibration(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'continueCalibration', State.kRunCalibration);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  calibrationComplete() {
+  calibrationComplete(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'calibrationComplete', State.kRunCalibration);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  retryProvisioning() {
+  retryProvisioning(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'retryProvisioning', State.kProvisionDevice);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  provisioningComplete() {
+  provisioningComplete(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'provisioningComplete', State.kProvisionDevice);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  finalizationComplete() {
+  finalizationComplete(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod('finalizationComplete', State.kFinalize);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  retryFinalization() {
+  retryFinalization(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod('retryFinalization', State.kFinalize);
   }
 
-  /**
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   */
-  writeProtectManuallyEnabled() {
+  writeProtectManuallyEnabled(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod(
         'writeProtectManuallyEnabled', State.kWaitForManualWPEnable);
   }
 
-  /** @return {!Promise<{log: string, error: !RmadErrorCode}>} */
-  getLog() {
+  getLog(): Promise<{log: string, error: RmadErrorCode}> {
     return this.methods.resolveMethod('getLog');
   }
 
-  /** @param {string} log */
-  setGetLogResult(log) {
+  setGetLogResult(log: string): void {
     this.methods.setResult('getLog', {log: log, error: RmadErrorCode.kOk});
   }
 
-  /**
-   * @return {!Promise<{savePath: !FilePath, error:
-   *     !RmadErrorCode}>}
-   */
-  saveLog() {
+  saveLog(): Promise<{savePath: FilePath, error: RmadErrorCode}> {
     return this.methods.resolveMethod('saveLog');
   }
 
-  /** @param {!FilePath} savePath */
-  setSaveLogResult(savePath) {
+  setSaveLogResult(savePath: FilePath): void {
     this.methods.setResult(
         'saveLog', {savePath: savePath, error: RmadErrorCode.kOk});
   }
 
-  /** @return {!Promise<{powerwashRequired: boolean, error: !RmadErrorCode}>} */
-  getPowerwashRequired() {
+  getPowerwashRequired():
+      Promise<{powerwashRequired: boolean, error: RmadErrorCode}> {
     return this.methods.resolveMethod('getPowerwashRequired');
   }
 
-  /** @param {boolean} powerwashRequired */
-  setGetPowerwashRequiredResult(powerwashRequired) {
+  setGetPowerwashRequiredResult(powerwashRequired: boolean): void {
     this.methods.setResult(
         'getPowerwashRequired',
         {powerwashRequired: powerwashRequired, error: RmadErrorCode.kOk});
   }
 
-  launchDiagnostics() {
+  launchDiagnostics(): void {
     console.log('(Fake) Launching diagnostics...');
   }
 
   /**
    * The fake does not use the status list parameter, the fake data is never
    * updated.
-   * @param {!ShutdownMethod} unused
-   * @return {!Promise<!{stateResult: !StateResult}>}
    */
-  endRma(unused) {
+  endRma(): Promise<{stateResult: StateResult}> {
     return this.getNextStateForMethod('endRma', State.kRepairComplete);
   }
 
-  /**
-   * @return {!Promise<!{error: !RmadErrorCode}>}
-   */
-  criticalErrorExitToLogin() {
+  criticalErrorExitToLogin(): Promise<{error: RmadErrorCode}> {
     return this.methods.resolveMethodWithDelay(
         'criticalErrorExitToLogin', this.resolveMethodDelayMs);
   }
 
-  /**
-   * @return {!Promise<!{error: !RmadErrorCode}>}
-   */
-  criticalErrorReboot() {
+  criticalErrorReboot(): Promise<{error: RmadErrorCode}> {
     return this.methods.resolveMethodWithDelay(
         'criticalErrorReboot', this.resolveMethodDelayMs);
   }
 
-  shutDownAfterHardwareError() {
+  shutDownAfterHardwareError(): void {
     console.log('(Fake) Shutting down...');
   }
 
-  /**
-   * @return {!Promise<!{provider: ?string}>}
-   */
-  get3pDiagnosticsProvider() {
+  get3pDiagnosticsProvider(): Promise<{provider: string | null}> {
     return this.methods.resolveMethodWithDelay(
         'get3pDiagnosticsProvider', this.resolveMethodDelayMs);
   }
 
-  /** @param {?string} provider */
-  setGet3pDiagnosticsProviderResult(provider) {
+  setGet3pDiagnosticsProviderResult(provider: string|null): void {
     this.methods.setResult('get3pDiagnosticsProvider', {provider});
   }
 
-  /**
-   * @return {!Promise<{appPath: FilePath}>}
-   */
-  getInstallable3pDiagnosticsAppPath() {
+  getInstallable3pDiagnosticsAppPath(): Promise<{appPath: FilePath}> {
     return this.methods.resolveMethod('getInstallable3pDiagnosticsAppPath');
   }
 
-  /** @param {FilePath} appPath */
-  setInstallable3pDiagnosticsAppPath(appPath) {
+  setInstallable3pDiagnosticsAppPath(appPath: FilePath): void {
     this.methods.setResult('getInstallable3pDiagnosticsAppPath', {appPath});
   }
 
-  /**
-   * @return {!Promise<{appInfo: Shimless3pDiagnosticsAppInfo}>}
-   */
-  installLastFound3pDiagnosticsApp() {
+  installLastFound3pDiagnosticsApp():
+      Promise<{appInfo: Shimless3pDiagnosticsAppInfo}> {
     return this.methods.resolveMethod('installLastFound3pDiagnosticsApp');
   }
 
-  /** @param {Shimless3pDiagnosticsAppInfo} appInfo */
-  setInstallLastFound3pDiagnosticsApp(appInfo) {
+  setInstallLastFound3pDiagnosticsApp(appInfo: Shimless3pDiagnosticsAppInfo): void {
     this.methods.setResult('installLastFound3pDiagnosticsApp', {appInfo});
   }
 
-  /**
-   * @param {boolean} isApproved
-   * @return {!Promise}
-   */
-  completeLast3pDiagnosticsInstallation(isApproved) {
+  completeLast3pDiagnosticsInstallation(isApproved: boolean): Promise<void> {
     this.lastCompleteLast3pDiagnosticsInstallationApproval = isApproved;
     return Promise.resolve();
   }
 
-  /** @return {?boolean} */
-  getLastCompleteLast3pDiagnosticsInstallationApproval() {
+  getLastCompleteLast3pDiagnosticsInstallationApproval(): boolean|null {
     return this.lastCompleteLast3pDiagnosticsInstallationApproval;
   }
 
-  /**
-   * @return {!Promise<{result: !Show3pDiagnosticsAppResult}>}
-   */
-  show3pDiagnosticsApp() {
+  show3pDiagnosticsApp(): Promise<{result: Show3pDiagnosticsAppResult}> {
     this.wasShow3pDiagnosticsAppCalled = true;
     return this.methods.resolveMethod('show3pDiagnosticsApp');
   }
 
-  /** @param {!Show3pDiagnosticsAppResult} result */
-  setShow3pDiagnosticsAppResult(result) {
+  setShow3pDiagnosticsAppResult(result: Show3pDiagnosticsAppResult): void {
     this.methods.setResult('show3pDiagnosticsApp', {result});
   }
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveError.
-   * @param {!ErrorObserverRemote} remote
    */
-  observeError(remote) {
-    this.observables.observe('ErrorObserver_onError', (error) => {
-      remote.onError(
-          /** @type {!RmadErrorCode} */ (error));
-    });
+  observeError(remote: ErrorObserverRemote): void {
+    this.observables.observe(
+        'ErrorObserver_onError', (error: RmadErrorCode) => {
+          remote.onError(error);
+        });
   }
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveOsUpdate.
-   * @param {!OsUpdateObserverRemote} remote
    */
-  observeOsUpdateProgress(remote) {
+  observeOsUpdateProgress(remote: OsUpdateObserverRemote): void {
     this.observables.observe(
         'OsUpdateObserver_onOsUpdateProgressUpdated',
-        (operation, progress, errorCode) => {
-          remote.onOsUpdateProgressUpdated(
-              /** @type {!OsUpdateOperation} */ (operation),
-              /** @type {number} */ (progress),
-              /** @type {!UpdateErrorCode} */ (errorCode));
+        (operation: OsUpdateOperation, progress: number,
+         errorCode: UpdateErrorCode) => {
+          remote.onOsUpdateProgressUpdated(operation, progress, errorCode);
         });
   }
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveRoFirmwareUpdateProgress.
-   * @param {!UpdateRoFirmwareObserverRemote} remote
    */
-  observeRoFirmwareUpdateProgress(remote) {
+  observeRoFirmwareUpdateProgress(remote: UpdateRoFirmwareObserverRemote): void {
     this.observables.observe(
         'UpdateRoFirmwareObserver_onUpdateRoFirmwareStatusChanged',
-        (status) => {
-          remote.onUpdateRoFirmwareStatusChanged(
-              /** @type {!UpdateRoFirmwareStatus} */ (status));
+        (status: UpdateRoFirmwareStatus) => {
+          remote.onUpdateRoFirmwareStatusChanged(status);
         });
     if (this.automaticallyTriggerUpdateRoFirmwareObservation) {
       this.triggerUpdateRoFirmwareObserver(UpdateRoFirmwareStatus.kWaitUsb, 0);
@@ -926,24 +676,23 @@
   /**
    * Trigger update ro firmware observations when an observer is added.
    */
-  automaticallyTriggerUpdateRoFirmwareObservation() {
+  enableAautomaticallyTriggerUpdateRoFirmwareObservation(): void {
     this.automaticallyTriggerUpdateRoFirmwareObservation = true;
   }
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveCalibration.
-   * @param {!CalibrationObserverRemote} remote
    */
-  observeCalibrationProgress(remote) {
+  observeCalibrationProgress(remote: CalibrationObserverRemote): void {
     this.observables.observe(
-        'CalibrationObserver_onCalibrationUpdated', (componentStatus) => {
-          remote.onCalibrationUpdated(
-              /** @type {!CalibrationComponentStatus} */ (componentStatus));
+        'CalibrationObserver_onCalibrationUpdated',
+        (componentStatus: CalibrationComponentStatus) => {
+          remote.onCalibrationUpdated(componentStatus);
         });
     this.observables.observe(
-        'CalibrationObserver_onCalibrationStepComplete', (status) => {
-          remote.onCalibrationStepComplete(
-              /** @type {!CalibrationOverallStatus} */ (status));
+        'CalibrationObserver_onCalibrationStepComplete',
+        (status: CalibrationOverallStatus) => {
+          remote.onCalibrationStepComplete(status);
         });
     if (this.automaticallyTriggerCalibrationObservation) {
       this.triggerCalibrationObserver(
@@ -1016,16 +765,13 @@
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveProvisioning.
-   * @param {!ProvisioningObserverRemote} remote
    */
-  observeProvisioningProgress(remote) {
+  observeProvisioningProgress(remote: ProvisioningObserverRemote): void {
     this.observables.observe(
         'ProvisioningObserver_onProvisioningUpdated',
-        (status, progress, error) => {
-          remote.onProvisioningUpdated(
-              /** @type {!ProvisioningStatus} */ (status),
-              /** @type {number} */ (progress),
-              /** @type {!ProvisioningError} */ (error));
+        (status: ProvisioningStatus, progress: number,
+         error: ProvisioningError) => {
+          remote.onProvisioningUpdated(status, progress, error);
         });
     if (this.automaticallyTriggerProvisioningObservation) {
       // Fake progress over 4 seconds.
@@ -1046,34 +792,33 @@
   /**
    * Trigger provisioning observations when an observer is added.
    */
-  automaticallyTriggerProvisioningObservation() {
+  enableAutomaticallyTriggerProvisioningObservation(): void {
     this.automaticallyTriggerProvisioningObservation = true;
   }
 
   /**
    * Trigger calibration observations when an observer is added.
    */
-  automaticallyTriggerCalibrationObservation() {
+  enableAutomaticallyTriggerCalibrationObservation(): void {
     this.automaticallyTriggerCalibrationObservation = true;
   }
 
   /**
    * Trigger OS update observations when an OS update is started.
    */
-  automaticallyTriggerOsUpdateObservation() {
+  enableAutomaticallyTriggerOsUpdateObservation(): void {
     this.automaticallyTriggerOsUpdateObservation = true;
   }
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveHardwareWriteProtectionState.
-   * @param {!HardwareWriteProtectionStateObserverRemote} remote
    */
-  observeHardwareWriteProtectionState(remote) {
+  observeHardwareWriteProtectionState(
+      remote: HardwareWriteProtectionStateObserverRemote): void {
     this.observables.observe(
         'HardwareWriteProtectionStateObserver_onHardwareWriteProtectionStateChanged',
-        (enabled) => {
-          remote.onHardwareWriteProtectionStateChanged(
-              /** @type {boolean} */ (enabled));
+        (enabled: boolean) => {
+          remote.onHardwareWriteProtectionStateChanged(enabled);
         });
     if (this.states &&
         this.automaticallyTriggerDisableWriteProtectionObservation) {
@@ -1087,18 +832,18 @@
   /**
    * Trigger a disable write protection observation when an observer is added.
    */
-  automaticallyTriggerDisableWriteProtectionObservation() {
+  enableAutomaticallyTriggerDisableWriteProtectionObservation(): void {
     this.automaticallyTriggerDisableWriteProtectionObservation = true;
   }
 
   /**
    * Implements ShimlessRmaServiceInterface.ObservePowerCableState.
-   * @param {!PowerCableStateObserverRemote} remote
    */
-  observePowerCableState(remote) {
+  observePowerCableState(remote: PowerCableStateObserverRemote): void {
     this.observables.observe(
-        'PowerCableStateObserver_onPowerCableStateChanged', (pluggedIn) => {
-          remote.onPowerCableStateChanged(/** @type {boolean} */ (pluggedIn));
+        'PowerCableStateObserver_onPowerCableStateChanged',
+        (pluggedIn: boolean) => {
+          remote.onPowerCableStateChanged(pluggedIn);
         });
     if (this.automaticallyTriggerPowerCableStateObservation) {
       this.triggerPowerCableObserver(false, 1000);
@@ -1110,18 +855,18 @@
   /**
    * Trigger a disable power cable state observations when an observer is added.
    */
-  automaticallyTriggerPowerCableStateObservation() {
+  enableAutomaticallyTriggerPowerCableStateObservation(): void {
     this.automaticallyTriggerPowerCableStateObservation = true;
   }
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveExternalDiskState.
-   * @param {!ExternalDiskStateObserverRemote} remote
    */
-  observeExternalDiskState(remote) {
+  observeExternalDiskState(remote: ExternalDiskStateObserverRemote): void {
     this.observables.observe(
-        'ExternalDiskStateObserver_onExternalDiskStateChanged', (detected) => {
-          remote.onExternalDiskStateChanged(/** @type {boolean} */ (detected));
+        'ExternalDiskStateObserver_onExternalDiskStateChanged',
+        (detected: boolean) => {
+          remote.onExternalDiskStateChanged(detected);
         });
 
     this.triggerExternalDiskObserver(true, 10000);
@@ -1130,15 +875,13 @@
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveHardwareVerificationStatus.
-   * @param {!HardwareVerificationStatusObserverRemote} remote
    */
-  observeHardwareVerificationStatus(remote) {
+  observeHardwareVerificationStatus(
+      remote: HardwareVerificationStatusObserverRemote): void {
     this.observables.observe(
         'HardwareVerificationStatusObserver_onHardwareVerificationResult',
-        (is_compliant, error_message) => {
-          remote.onHardwareVerificationResult(
-              /** @type {boolean} */ (is_compliant),
-              /** @type {string} */ (error_message));
+        (isCompliant: boolean, errorMessage: string) => {
+          remote.onHardwareVerificationResult(isCompliant, errorMessage);
         });
     if (this.automaticallyTriggerHardwareVerificationStatusObservation) {
       this.triggerHardwareVerificationStatusObserver(true, '', 3000);
@@ -1149,22 +892,19 @@
   /**
    * Trigger a hardware verification observation when an observer is added.
    */
-  automaticallyTriggerHardwareVerificationStatusObservation() {
+  enableAutomaticallyTriggerHardwareVerificationStatusObservation(): void {
     this.automaticallyTriggerHardwareVerificationStatusObservation = true;
   }
 
   /**
    * Implements ShimlessRmaServiceInterface.ObserveFinalizationStatus.
-   * @param {!FinalizationObserverRemote} remote
    */
-  observeFinalizationStatus(remote) {
+  observeFinalizationStatus(remote: FinalizationObserverRemote): void {
     this.observables.observe(
         'FinalizationObserver_onFinalizationUpdated',
-        (status, progress, error) => {
-          remote.onFinalizationUpdated(
-              /** @type {!FinalizationStatus} */ (status),
-              /** @type {number} */ (progress),
-              /** @type {!FinalizationError} */ (error));
+        (status: FinalizationStatus, progress: number,
+         error: FinalizationError) => {
+          remote.onFinalizationUpdated(status, progress, error);
         });
     if (this.automaticallyTriggerFinalizationObservation) {
       this.triggerFinalizationObserver(
@@ -1181,27 +921,23 @@
   /**
    * Trigger a finalization progress observation when an observer is added.
    */
-  automaticallyTriggerFinalizationObservation() {
+  enableAutomaticallyTriggerFinalizationObservation(): void {
     this.automaticallyTriggerFinalizationObservation = true;
   }
 
   /**
    * Causes the error observer to fire after a delay.
-   * @param {!RmadErrorCode} error
-   * @param {number} delayMs
    */
-  triggerErrorObserver(error, delayMs) {
+  triggerErrorObserver(error: RmadErrorCode, delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs('ErrorObserver_onError', error, delayMs);
   }
 
   /**
    * Causes the OS update observer to fire after a delay.
-   * @param {!OsUpdateOperation} operation
-   * @param {number} progress
-   * @param {UpdateErrorCode} error
-   * @param {number} delayMs
    */
-  triggerOsUpdateObserver(operation, progress, error, delayMs) {
+  triggerOsUpdateObserver(
+      operation: OsUpdateOperation, progress: number, error: UpdateErrorCode,
+      delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'OsUpdateObserver_onOsUpdateProgressUpdated',
         [operation, progress, error], delayMs);
@@ -1209,10 +945,9 @@
 
   /**
    * Causes the update RO firmware observer to fire after a delay.
-   * @param {!UpdateRoFirmwareStatus} status
-   * @param {number} delayMs
    */
-  triggerUpdateRoFirmwareObserver(status, delayMs) {
+  triggerUpdateRoFirmwareObserver(
+      status: UpdateRoFirmwareStatus, delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'UpdateRoFirmwareObserver_onUpdateRoFirmwareStatusChanged', status,
         delayMs);
@@ -1220,32 +955,28 @@
 
   /**
    * Causes the calibration observer to fire after a delay.
-   * @param {!CalibrationComponentStatus} componentStatus
-   * @param {number} delayMs
    */
-  triggerCalibrationObserver(componentStatus, delayMs) {
+  triggerCalibrationObserver(
+      componentStatus: CalibrationComponentStatus, delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'CalibrationObserver_onCalibrationUpdated', componentStatus, delayMs);
   }
 
   /**
    * Causes the calibration overall observer to fire after a delay.
-   * @param {!CalibrationOverallStatus} status
-   * @param {number} delayMs
    */
-  triggerCalibrationOverallObserver(status, delayMs) {
+  triggerCalibrationOverallObserver(
+      status: CalibrationOverallStatus, delayMs: number) {
     return this.triggerObserverAfterMs(
         'CalibrationObserver_onCalibrationStepComplete', status, delayMs);
   }
 
   /**
    * Causes the provisioning observer to fire after a delay.
-   * @param {!ProvisioningStatus} status
-   * @param {number} progress
-   * @param {!ProvisioningError} error
-   * @param {number} delayMs
    */
-  triggerProvisioningObserver(status, progress, error, delayMs) {
+  triggerProvisioningObserver(
+      status: ProvisioningStatus, progress: number, error: ProvisioningError,
+      delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'ProvisioningObserver_onProvisioningUpdated', [status, progress, error],
         delayMs);
@@ -1253,10 +984,8 @@
 
   /**
    * Causes the hardware write protection observer to fire after a delay.
-   * @param {boolean} enabled
-   * @param {number} delayMs
    */
-  triggerHardwareWriteProtectionObserver(enabled, delayMs) {
+  triggerHardwareWriteProtectionObserver(enabled: boolean, delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'HardwareWriteProtectionStateObserver_onHardwareWriteProtectionStateChanged',
         enabled, delayMs);
@@ -1264,20 +993,16 @@
 
   /**
    * Causes the power cable observer to fire after a delay.
-   * @param {boolean} pluggedIn
-   * @param {number} delayMs
    */
-  triggerPowerCableObserver(pluggedIn, delayMs) {
+  triggerPowerCableObserver(pluggedIn: boolean, delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'PowerCableStateObserver_onPowerCableStateChanged', pluggedIn, delayMs);
   }
 
   /**
    * Causes the external disk observer to fire after a delay.
-   * @param {boolean} detected
-   * @param {number} delayMs
    */
-  triggerExternalDiskObserver(detected, delayMs) {
+  triggerExternalDiskObserver(detected: boolean, delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'ExternalDiskStateObserver_onExternalDiskStateChanged', detected,
         delayMs);
@@ -1285,25 +1010,20 @@
 
   /**
    * Causes the hardware verification observer to fire after a delay.
-   * @param {boolean} is_compliant
-   * @param {string} error_message
-   * @param {number} delayMs
    */
   triggerHardwareVerificationStatusObserver(
-      is_compliant, error_message, delayMs) {
+      isCompliant: boolean, errorMessage: string, delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'HardwareVerificationStatusObserver_onHardwareVerificationResult',
-        [is_compliant, error_message], delayMs);
+        [isCompliant, errorMessage], delayMs);
   }
 
   /**
    * Causes the finalization observer to fire after a delay.
-   * @param {!FinalizationStatus} status
-   * @param {number} progress
-   * @param {!FinalizationError} error
-   * @param {number} delayMs
    */
-  triggerFinalizationObserver(status, progress, error, delayMs) {
+  triggerFinalizationObserver(
+      status: FinalizationStatus, progress: number, error: FinalizationError,
+      delayMs: number): Promise<unknown> {
     return this.triggerObserverAfterMs(
         'FinalizationObserver_onFinalizationUpdated', [status, progress, error],
         delayMs);
@@ -1311,13 +1031,10 @@
 
   /**
    * Causes an observer to fire after a delay.
-   * @param {string} method
-   * @param {!T} result
-   * @param {number} delayMs
-   * @template T
    */
-  triggerObserverAfterMs(method, result, delayMs) {
-    const setDataTriggerAndResolve = function(service, resolve) {
+  triggerObserverAfterMs<T>(method: string, result: T, delayMs: number): Promise<unknown> {
+    const setDataTriggerAndResolve = function(
+        service: FakeShimlessRmaService, resolve: any) {
       service.observables.setObservableData(method, [result]);
       service.observables.trigger(method);
       resolve();
@@ -1337,7 +1054,7 @@
   /**
    * Disables all observers and resets provider to its initial state.
    */
-  reset() {
+  reset(): void {
     this.registerMethods();
     this.registerObservables();
 
@@ -1357,9 +1074,8 @@
 
   /**
    * Setup method resolvers.
-   * @private
    */
-  registerMethods() {
+  private registerMethods(): void {
     this.methods = new FakeMethodResolver();
 
     this.methods.register('getCurrentState');
@@ -1451,9 +1167,8 @@
 
   /**
    * Setup observables.
-   * @private
    */
-  registerObservables() {
+  private registerObservables(): void {
     if (this.observables) {
       this.observables.stopAllTriggerIntervals();
     }
@@ -1476,13 +1191,8 @@
     this.observables.register('FinalizationObserver_onFinalizationUpdated');
   }
 
-  /**
-   * @param {string} method
-   * @param {!State} expectedState
-   * @return {!Promise<!{stateResult: !StateResult}>}
-   * @private
-   */
-  getNextStateForMethod(method, expectedState) {
+  private getNextStateForMethod(method: string, expectedState: State):
+      Promise<{stateResult: StateResult}> {
     if (this.states.length === 0) {
       this.setFakeStateForMethod(
           method, State.kUnknown, false, false, RmadErrorCode.kRmaNotRequired);
@@ -1518,13 +1228,10 @@
 
   /**
    * Sets the value that will be returned when calling getCurrent().
-   * @param {!State} state
-   * @param {boolean} canExit,
-   * @param {boolean} canGoBack,
-   * @param {!RmadErrorCode} error
-   * @private
    */
-  setFakeCurrentState(state, canExit, canGoBack, error) {
+  private setFakeCurrentState(
+      state: State, canExit: boolean, canGoBack: boolean,
+      error: RmadErrorCode): void {
     this.setFakeStateForMethod(
         'getCurrentState', state, canExit, canGoBack, error);
   }
@@ -1532,13 +1239,10 @@
   /**
    * Sets the value that will be returned when calling
    * transitionPreviousState().
-   * @param {!State} state
-   * @param {boolean} canExit,
-   * @param {boolean} canGoBack,
-   * @param {!RmadErrorCode} error
-   * @private
    */
-  setFakePrevState(state, canExit, canGoBack, error) {
+  private setFakePrevState(
+      state: State, canExit: boolean, canGoBack: boolean,
+      error: RmadErrorCode): void {
     this.setFakeStateForMethod(
         'transitionPreviousState', state, canExit, canGoBack, error);
   }
@@ -1546,21 +1250,17 @@
   /**
    * Sets the value that will be returned when calling state specific functions
    * that update state. e.g. setSameOwner()
-   * @param {string} method
-   * @param {!State} state
-   * @param {boolean} canExit,
-   * @param {boolean} canGoBack,
-   * @param {!RmadErrorCode} error
-   * @private
    */
-  setFakeStateForMethod(method, state, canExit, canGoBack, error) {
-    this.methods.setResult(method, /** @type {{stateResult: !StateResult}} */ ({
-                             stateResult: {
-                               state: state,
-                               canExit: canExit,
-                               canGoBack: canGoBack,
-                               error: error,
-                             },
-                           }));
+  private setFakeStateForMethod(
+      method: string, state: State, canExit: boolean, canGoBack: boolean,
+      error: RmadErrorCode): void {
+    this.methods.setResult(method, {
+      stateResult: {
+        state: state,
+        canExit: canExit,
+        canGoBack: canGoBack,
+        error: error,
+      },
+    });
   }
 }
diff --git a/ash/webui/shimless_rma/resources/mojo_interface_provider.js b/ash/webui/shimless_rma/resources/mojo_interface_provider.js
index bafb391..5c341107 100644
--- a/ash/webui/shimless_rma/resources/mojo_interface_provider.js
+++ b/ash/webui/shimless_rma/resources/mojo_interface_provider.js
@@ -45,17 +45,17 @@
 
   service.setAbortRmaResult(RmadErrorCode.kRmaNotRequired);
 
-  service.automaticallyTriggerHardwareVerificationStatusObservation();
+  service.enableAutomaticallyTriggerHardwareVerificationStatusObservation();
 
   service.setGetCurrentOsVersionResult(fakeChromeVersion[0]);
   service.setCheckForOsUpdatesResult('99.0.4844.74');
   service.setUpdateOsResult(true);
-  service.automaticallyTriggerOsUpdateObservation();
+  service.enableAutomaticallyTriggerOsUpdateObservation();
 
   service.setGetComponentListResult(fakeComponents);
-  service.automaticallyTriggerUpdateRoFirmwareObservation();
-  service.automaticallyTriggerDisableWriteProtectionObservation();
-  service.automaticallyTriggerCalibrationObservation();
+  service.enableAautomaticallyTriggerUpdateRoFirmwareObservation();
+  service.enableAutomaticallyTriggerDisableWriteProtectionObservation();
+  service.enableAutomaticallyTriggerCalibrationObservation();
 
   service.setGetRsuDisableWriteProtectChallengeResult(fakeRsuChallengeCode);
   service.setGetRsuDisableWriteProtectHwidResult('SAMUSTEST_2082');
@@ -81,10 +81,10 @@
   service.setGetCalibrationComponentListResult(
       fakeCalibrationComponentsWithFails);
 
-  service.automaticallyTriggerProvisioningObservation();
-  service.automaticallyTriggerFinalizationObservation();
+  service.enableAutomaticallyTriggerProvisioningObservation();
+  service.enableAutomaticallyTriggerFinalizationObservation();
 
-  service.automaticallyTriggerPowerCableStateObservation();
+  service.enableAutomaticallyTriggerPowerCableStateObservation();
   service.setGetLogResult(fakeLog);
   service.setSaveLogResult({'path': fakeLogSavePath});
   service.setGetPowerwashRequiredResult(true);
diff --git a/ash/wm/bounds_tracker/window_bounds_tracker.cc b/ash/wm/bounds_tracker/window_bounds_tracker.cc
index 68d75233..f129feb 100644
--- a/ash/wm/bounds_tracker/window_bounds_tracker.cc
+++ b/ash/wm/bounds_tracker/window_bounds_tracker.cc
@@ -8,6 +8,7 @@
 #include "ash/shell.h"
 #include "ash/wm/window_state.h"
 #include "ash/wm/window_util.h"
+#include "base/auto_reset.h"
 #include "ui/aura/window.h"
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/screen.h"
@@ -168,7 +169,7 @@
   if (iter == bounds_database_.end()) {
     return;
   }
-  const auto& window_bounds_map = iter->second;
+  auto& window_bounds_map = iter->second.window_bounds_map;
   CHECK(!window_bounds_map.empty());
   display::Display target_display =
       display::Screen::GetScreen()->GetDisplayNearestWindow(window);
@@ -178,24 +179,31 @@
   const auto bounds_iter = window_bounds_map.find(target_window_display_info);
   CHECK(bounds_iter != window_bounds_map.end());
 
-  window->SetBounds(bounds_iter->second);
+  window->SetBounds(bounds_iter->second.bounds_in_parent);
+  // Remove the stored non-restore-bounds from the database after it has been
+  // used. As the non-restore-bounds will never be used to restore the window
+  // later, the recalculation will be triggered instead.
+  if (!bounds_iter->second.is_restore_bounds) {
+    window_bounds_map.erase(bounds_iter);
+  }
 }
 
 void WindowBoundsTracker::OnWindowRemovingFromRootWindow(
     aura::Window* window,
     aura::Window* new_root) {
   // Check whether we should remap or restore `window` on its root window
-  // changes. Only needed if 1) the window was moved between displays through
-  // the shortcut `kMoveActiveWindowBetweenDisplays` 2) removing the window's
-  // host display and the window will be moved to the current primary display.
-  // As in these two scenarios, the window is moving to another display without
-  // user assigned bounds.
+  // changes. Needed if 1) the window was moved between displays through the
+  // shortcut `kMoveActiveWindowBetweenDisplays` or 2) removing the window's
+  // host display and the window will be moved to the current primary display or
+  // 3) restoring the window back to its previous host display on display
+  // reconnection.
   const bool is_moving_window_between_displays =
       window == moving_window_between_displays_;
   const bool should_remap_or_restore =
       is_moving_window_between_displays ||
       RootWindowController::ForWindow(window->GetRootWindow())
-          ->is_shutting_down();
+          ->is_shutting_down() ||
+      is_restoring_window_on_display_added_;
   if (!should_remap_or_restore) {
     return;
   }
@@ -233,18 +241,9 @@
   while (iter != window_to_display_map_.end()) {
     const auto candidate_old_display_id = iter->second;
     if (display_manager->IsDisplayIdValid(candidate_old_display_id)) {
-      auto* window = iter->first;
-      // TODO(b/314160218): Do not store the bounds if it is not user-assigned.
-      // Store the window's bounds in the source display before moving it to the
-      // target display.
-      const display::Display source_display =
-          display::Screen::GetScreen()->GetDisplayNearestWindow(window);
-      UpdateBoundsDatabaseOfWindow(
-          window,
-          WindowDisplayInfo(source_display.id(), source_display.rotation(),
-                            source_display.GetLocalWorkArea()),
-          window->bounds());
-      window_util::MoveWindowToDisplay(window, candidate_old_display_id);
+      base::AutoReset<bool> in_restoring(&is_restoring_window_on_display_added_,
+                                         true);
+      window_util::MoveWindowToDisplay(iter->first, candidate_old_display_id);
       iter = window_to_display_map_.erase(iter);
     } else {
       ++iter;
@@ -252,6 +251,29 @@
   }
 }
 
+void WindowBoundsTracker::SetWindowBoundsChangedByUser(
+    aura::Window* window,
+    bool bounds_changed_by_user) {
+  if (!window_observations_.IsObservingSource(window)) {
+    return;
+  }
+
+  // The window's current bounds will always be stored as a restore bounds at
+  // its the first time `WindowDisplayInfo` changes.
+  const auto iter = bounds_database_.find(window);
+  if (iter == bounds_database_.end()) {
+    return;
+  }
+
+  const int64_t display_id =
+      display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();
+  if (bounds_changed_by_user) {
+    iter->second.displays_with_window_user_assigned_bounds.insert(display_id);
+  } else {
+    iter->second.displays_with_window_user_assigned_bounds.erase(display_id);
+  }
+}
+
 // -----------------------------------------------------------------------------
 // WindowBoundsTracker::WindowDisplayInfo:
 
@@ -270,6 +292,35 @@
 }
 
 // -----------------------------------------------------------------------------
+// WindowBoundsTracker::WindowBoundsInfo:
+
+WindowBoundsTracker::WindowBoundsInfo::WindowBoundsInfo(
+    const gfx::Rect& given_bounds_in_parent,
+    bool given_is_restore_bounds)
+    : bounds_in_parent(given_bounds_in_parent),
+      is_restore_bounds(given_is_restore_bounds) {}
+
+// -----------------------------------------------------------------------------
+// WindowBoundsTracker::WindowBoundsEntry:
+
+WindowBoundsTracker::WindowBoundsEntry::WindowBoundsEntry() = default;
+
+WindowBoundsTracker::WindowBoundsEntry::WindowBoundsEntry(WindowBoundsEntry&&) =
+    default;
+
+WindowBoundsTracker::WindowBoundsEntry&
+WindowBoundsTracker::WindowBoundsEntry::operator=(WindowBoundsEntry&&) =
+    default;
+
+WindowBoundsTracker::WindowBoundsEntry::~WindowBoundsEntry() = default;
+
+bool WindowBoundsTracker::WindowBoundsEntry::
+    ShouldUseCurrentBoundsAsRestoreBounds(int64_t display_id) const {
+  return window_bounds_map.empty() ||
+         displays_with_window_user_assigned_bounds.contains(display_id);
+}
+
+// -----------------------------------------------------------------------------
 // WindowBoundsTracker:
 
 void WindowBoundsTracker::RemapOrRestore(aura::Window* window,
@@ -287,11 +338,12 @@
   const int64_t source_display_id = source_display.id();
   gfx::Rect source_work_area = source_display.GetLocalWorkArea();
 
-  const auto& window_bounds_map = UpdateBoundsDatabaseOfWindow(
+  const auto& window_bounds_entry = UpdateBoundsDatabaseOfWindow(
       window,
       WindowDisplayInfo(source_display_id, source_display.rotation(),
                         source_work_area),
-      window->bounds());
+      window->bounds(), /*is_current_bounds=*/true);
+  const auto& window_bounds_map = window_bounds_entry.window_bounds_map;
   CHECK(!window_bounds_map.empty());
 
   display::Display target_display;
@@ -302,7 +354,11 @@
       target_display_id, target_display.rotation(), target_work_area);
   const auto iter = window_bounds_map.find(target_window_display_info);
 
-  if (iter != window_bounds_map.end()) {
+  // If the current stored bounds for the `target_window_display_info` are
+  // already restore bounds, there's no need to recalculate the remapping bounds
+  // again as the restore bounds will be used to restore the window to its
+  // previous bounds when it goes back to the target `WindowDisplayInfo`.
+  if (iter != window_bounds_map.end() && iter->second.is_restore_bounds) {
     return;
   }
 
@@ -322,19 +378,25 @@
   remapped_bounds.AdjustToFit(target_work_area);
 
   UpdateBoundsDatabaseOfWindow(window, target_window_display_info,
-                               remapped_bounds);
+                               remapped_bounds, /*is_current_bounds=*/false);
   return;
 }
 
-base::flat_map<WindowBoundsTracker::WindowDisplayInfo, gfx::Rect>&
+WindowBoundsTracker::WindowBoundsEntry&
 WindowBoundsTracker::UpdateBoundsDatabaseOfWindow(
     aura::Window* window,
     const WindowDisplayInfo& window_display_info,
-    const gfx::Rect& bounds) {
-  auto& window_bounds_map = bounds_database_[window];
+    const gfx::Rect& bounds,
+    bool is_current_bounds) {
+  auto& window_bounds_entry = bounds_database_[window];
   CHECK(window_observations_.IsObservingSource(window));
-  window_bounds_map.insert_or_assign(window_display_info, bounds);
-  return window_bounds_map;
+  if (!is_current_bounds ||
+      window_bounds_entry.ShouldUseCurrentBoundsAsRestoreBounds(
+          window_display_info.display_id)) {
+    window_bounds_entry.window_bounds_map.insert_or_assign(
+        window_display_info, WindowBoundsInfo(bounds, is_current_bounds));
+  }
+  return window_bounds_entry;
 }
 
 }  // namespace ash
diff --git a/ash/wm/bounds_tracker/window_bounds_tracker.h b/ash/wm/bounds_tracker/window_bounds_tracker.h
index e4ef91a0..2979a3c 100644
--- a/ash/wm/bounds_tracker/window_bounds_tracker.h
+++ b/ash/wm/bounds_tracker/window_bounds_tracker.h
@@ -8,6 +8,7 @@
 #include <unordered_map>
 
 #include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
 #include "base/scoped_multi_source_observation.h"
 #include "chromeos/ui/base/display_util.h"
 #include "ui/aura/window_observer.h"
@@ -57,6 +58,11 @@
   // host display is the display that was just added.
   void MaybeRestoreWindowsOnDisplayAdded();
 
+  // Updates the window's `displays_with_window_user_assigned_bounds` on the
+  // given `bounds_changed_by_user`.
+  void SetWindowBoundsChangedByUser(aura::Window* window,
+                                    bool bounds_changed_by_user);
+
  private:
   // This defines the key of the window bounds database that stores the window's
   // bounds in each display configuration. It tracks the display's change,
@@ -87,7 +93,41 @@
     gfx::Rect local_work_area;
   };
 
-  using WindowBoundsMap = base::flat_map<WindowDisplayInfo, gfx::Rect>;
+  struct WindowBoundsInfo {
+    WindowBoundsInfo(const gfx::Rect& given_bounds_in_parent,
+                     bool given_is_restore_bounds);
+    WindowBoundsInfo(const WindowBoundsInfo&) = default;
+    WindowBoundsInfo& operator=(const WindowBoundsInfo&) = default;
+    ~WindowBoundsInfo() = default;
+
+    gfx::Rect bounds_in_parent;
+    // True if the stored `bounds_in_parent` is a restore bounds, which means a
+    // user-assigned bounds. And the window will be put back to this restore
+    // bounds directly instead of recalculation when being moved back to a
+    // display with the same `WindowDisplayInfo`.
+    bool is_restore_bounds;
+  };
+
+  // This defines all the info of a window stored in `bounds_database_`.
+  struct WindowBoundsEntry {
+    WindowBoundsEntry();
+    WindowBoundsEntry(WindowBoundsEntry&&);
+    WindowBoundsEntry& operator=(WindowBoundsEntry&&);
+    ~WindowBoundsEntry();
+
+    // Returns true if the window's current bounds should be stored as
+    // `is_restore_bounds` inside `WindowBoundsInfo`.
+    bool ShouldUseCurrentBoundsAsRestoreBounds(int64_t display_id) const;
+
+    // Includes the displays that the window's bounds there is a user-assigned
+    // bounds, which means the bounds is set by the user. See
+    // `WindowState::bounds_changed_by_user_` for more details.
+    base::flat_set<int64_t> displays_with_window_user_assigned_bounds;
+
+    // A map stores the window's bounds in each `WindowDisplayInfo` it has been
+    // there, which will be updated by `RemapOrRestore` on the window.
+    base::flat_map<WindowDisplayInfo, WindowBoundsInfo> window_bounds_map;
+  };
 
   // Stores the window's bounds in its current display for restoring the window
   // back to this display later. Calculates and stores the window's remapping
@@ -106,12 +146,16 @@
   void RemapOrRestore(aura::Window* window, int64_t target_display_id);
 
   // Updates the window's bounds stored in `bounds_database_` on the key
-  // `window_display_info` to the given `bounds`. Returns the bounds database of
-  // `window` stored in `bounds_database_`.
-  WindowBoundsMap& UpdateBoundsDatabaseOfWindow(
+  // `window_display_info`. `is_current_bounds` is true if the given `bounds` is
+  // the window's current bounds, only update it to the database if it is a
+  // restore bounds. If the given bounds is not window's current bounds, which
+  // means it is calculated by the system for future use, always update it to
+  // the database.
+  WindowBoundsEntry& UpdateBoundsDatabaseOfWindow(
       aura::Window* window,
       const WindowDisplayInfo& window_display_info,
-      const gfx::Rect& bounds);
+      const gfx::Rect& bounds,
+      bool is_current_bounds);
 
   // Stores the window's host display id when removing its host display, which
   // will be used to restore the window when its host display being reconnected
@@ -123,12 +167,17 @@
   raw_ptr<aura::Window, ExperimentalAsh> moving_window_between_displays_ =
       nullptr;
 
+  // True if restoring a window back to its host display on display
+  // reconnection. Will be used to see whether remapping or restoring should be
+  // triggered on the window on this root window changes.
+  bool is_restoring_window_on_display_added_ = false;
+
   // TODO: Figure out how we can redesign this data structure, then extra data
   // structures like `window_to_display_map_` above can be removed.
   // The database that stores the window's bounds in each display configuration.
   // `WindowDisplayInfo` defines the display configuration changes that we are
   // tracking. Note: stored window bounds are in parent coordinates.
-  std::unordered_map<aura::Window*, WindowBoundsMap> bounds_database_;
+  std::unordered_map<aura::Window*, WindowBoundsEntry> bounds_database_;
 
   base::ScopedMultiSourceObservation<aura::Window, aura::WindowObserver>
       window_observations_{this};
diff --git a/ash/wm/bounds_tracker/window_bounds_tracker_unittests.cc b/ash/wm/bounds_tracker/window_bounds_tracker_unittests.cc
index d16b44f..749468d 100644
--- a/ash/wm/bounds_tracker/window_bounds_tracker_unittests.cc
+++ b/ash/wm/bounds_tracker/window_bounds_tracker_unittests.cc
@@ -4,9 +4,12 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/display/screen_orientation_controller_test_api.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/bounds_tracker/window_bounds_tracker.h"
+#include "ash/wm/window_state.h"
 #include "ash/wm/work_area_insets.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/aura/window.h"
@@ -166,10 +169,8 @@
   EXPECT_EQ(second_top_left_center, window2->bounds().CenterPoint());
 }
 
-// Tests that window's bounds stored in the same display configuration can be
-// updated correctly. Window can be restored to the update bounds in the tracker
-// correctly as well.
-TEST_F(WindowBoundsTrackerTest, RestoreToUpdatedBounds) {
+// Tests that window should be restored to the user assigned bounds correctly.
+TEST_F(WindowBoundsTrackerTest, UserAssignedBounds) {
   UpdateDisplay("400x300,600x500");
 
   // Initially, the window is half-offscreen inside the 2nd display. Moving it
@@ -191,14 +192,118 @@
   // as well.
   const gfx::Rect new_bounds_in_1st(300, 0, 200, 100);
   window->SetBounds(new_bounds_in_1st);
+  WindowState::Get(window)->SetBoundsChangedByUser(true);
   // Move the window to 2nd display and then back to 1st display, it should
-  // restore to its updated bounds stored in the tracker.
+  // restore to its user-assigned bounds stored in the tracker.
   PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
   EXPECT_EQ(initial_bounds, window->GetBoundsInScreen());
   PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
   EXPECT_EQ(new_bounds_in_1st, window->bounds());
 }
 
+TEST_F(WindowBoundsTrackerTest, NonUserAssignedBounds) {
+  UpdateDisplay("400x300,600x500");
+
+  display::Display first_display = GetPrimaryDisplay();
+  display::Display secondary_display = GetSecondaryDisplay();
+  const gfx::Rect first_display_work_area = first_display.work_area();
+  const gfx::Rect second_display_work_area = secondary_display.work_area();
+
+  // Initially, the window is half-offscreen inside the 2nd display.
+  const gfx::Size window_size(200, 100);
+  const gfx::Rect initial_bounds(gfx::Point(900, 0), window_size);
+  aura::Window* window = CreateTestWindowInShellWithBounds(initial_bounds);
+  wm::ActivateWindow(window);
+  // Moving the window to the 1st display and back to the 2nd display.
+  PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
+  const gfx::Rect bounds_in_1st(200, 0, 200, 100);
+  EXPECT_EQ(bounds_in_1st, window->bounds());
+  PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
+  EXPECT_EQ(initial_bounds, window->GetBoundsInScreen());
+
+  // Moving the window inside the 2nd display from half-offscreen to the center
+  // of the display.
+  const gfx::Point second_center_point = second_display_work_area.CenterPoint();
+  window->SetBoundsInScreen(
+      gfx::Rect(gfx::Point(second_center_point.x() - window_size.width() / 2,
+                           second_center_point.y() - window_size.height() / 2),
+                window_size),
+      secondary_display);
+  // Moving the window to the 1st display, it should be remapped to the center
+  // of the 1st display. As its previous non-user-assigned bounds should not be
+  // stored in the bounds database.
+  PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
+  EXPECT_EQ(first_display_work_area.CenterPoint(),
+            window->bounds().CenterPoint());
+}
+
+TEST_F(WindowBoundsTrackerTest,
+       NonUserAssignedBoundsWithUpdatedDisplayWindowInfo) {
+  UpdateDisplay("400x300,600x500");
+
+  display::Display first_display = GetPrimaryDisplay();
+  display::Display secondary_display = GetSecondaryDisplay();
+  const gfx::Rect initial_first_display_work_area = first_display.work_area();
+  const gfx::Rect second_display_work_area = secondary_display.work_area();
+
+  // Initially, the window is half-offscreen inside the 2nd display.
+  const gfx::Size window_size(200, 100);
+  const gfx::Rect initial_bounds(gfx::Point(900, 0), window_size);
+  aura::Window* window = CreateTestWindowInShellWithBounds(initial_bounds);
+  wm::ActivateWindow(window);
+  // Moving the window to the 1st display, it will be remapped to be fully
+  // visible.
+  PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
+  const gfx::Rect bounds_in_1st(200, 0, 200, 100);
+  EXPECT_EQ(bounds_in_1st, window->bounds());
+
+  // Change shelf alignment, which will change the work area. The window's
+  // bounds should have no changes on this.
+  Shelf* shelf =
+      Shell::GetRootWindowControllerWithDisplayId(first_display.id())->shelf();
+  shelf->SetAlignment(ShelfAlignment::kLeft);
+  EXPECT_EQ(ShelfAlignment::kLeft, shelf->alignment());
+  const gfx::Rect left_shelf_first_display_work_area =
+      GetPrimaryDisplay().work_area();
+  EXPECT_NE(initial_first_display_work_area,
+            left_shelf_first_display_work_area);
+  EXPECT_EQ(bounds_in_1st, window->bounds());
+
+  // Move the window to the 2nd display when the shelf is still left aligned.
+  // And then update its bounds inside the 2nd display from half-offscreen to
+  // the center of the display.
+  PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
+  const gfx::Point second_center_point = second_display_work_area.CenterPoint();
+  window->SetBoundsInScreen(
+      gfx::Rect(gfx::Point(second_center_point.x() - window_size.width() / 2,
+                           second_center_point.y() - window_size.height() / 2),
+                window_size),
+      secondary_display);
+  WindowState::Get(window)->SetBoundsChangedByUser(true);
+
+  // Move the window from the 2nd to the 1st, it should be remapped to the
+  // center of the work area when the shelf is left aligned.
+  PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
+  EXPECT_EQ(left_shelf_first_display_work_area.CenterPoint(),
+            window->GetBoundsInScreen().CenterPoint());
+
+  // Move the window from the 1st display to the 2nd display again.
+  PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
+  EXPECT_EQ(second_center_point, window->GetBoundsInScreen().CenterPoint());
+
+  // Move the window from the 2nd display to the 1st display after its shelf has
+  // been changed back to bottom aligned. The window should be remapped to the
+  // center of the display instead of restoring back to its previous remapped
+  // bounds. As its previous remapped bounds should be removed regardless the
+  // display configuration (e.g, different work area) when it was moved to
+  // another display.
+  shelf->SetAlignment(ShelfAlignment::kBottom);
+  EXPECT_EQ(initial_first_display_work_area, GetPrimaryDisplay().work_area());
+  PressAndReleaseKey(ui::VKEY_M, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN);
+  EXPECT_EQ(initial_first_display_work_area.CenterPoint(),
+            window->GetBoundsInScreen().CenterPoint());
+}
+
 // Tests of moving a window from a landscape orientation display to a portrait
 // orientation display.
 TEST_F(WindowBoundsTrackerTest, LandscapeToPortrait) {
@@ -265,11 +370,15 @@
 TEST_F(WindowBoundsTrackerTest, RemoveNonPrimaryDisplay) {
   UpdateDisplay("400x300,600x500");
 
-  // Initially, the window is half-offscreen inside the 2nd display.
+  // Create 2 windows, one at the top left of the 1st display, another
+  // half-offscreen inside the 2nd display.
   const gfx::Size window_size(200, 100);
-  const gfx::Rect initial_bounds(gfx::Point(900, 0), window_size);
-  aura::Window* window = CreateTestWindowInShellWithBounds(initial_bounds);
-  wm::ActivateWindow(window);
+  const gfx::Rect initial_bounds_1st(window_size);
+  aura::Window* window1 = CreateTestWindowInShellWithBounds(initial_bounds_1st);
+  wm::ActivateWindow(window1);
+  const gfx::Rect initial_bounds_2nd(gfx::Point(900, 0), window_size);
+  aura::Window* window2 = CreateTestWindowInShellWithBounds(initial_bounds_2nd);
+  wm::ActivateWindow(window2);
 
   const int64_t primary_id = GetPrimaryDisplay().id();
   const int64_t secondary_id = GetSecondaryDisplay().id();
@@ -283,39 +392,43 @@
   std::vector<display::ManagedDisplayInfo> display_info_list;
   display_info_list.push_back(primary_info);
   display_manager()->OnNativeDisplaysChanged(display_info_list);
-  // `window` is fully visible inside the primary display.
+  // `window2` is fully visible inside the primary display.
   const gfx::Rect remapping_bounds_in_1st(gfx::Point(200, 0), window_size);
-  EXPECT_EQ(remapping_bounds_in_1st, window->GetBoundsInScreen());
+  EXPECT_EQ(remapping_bounds_in_1st, window2->GetBoundsInScreen());
 
   // Reconnect the 2nd display.
   display_info_list.push_back(secondary_info);
   display_manager()->OnNativeDisplaysChanged(display_info_list);
-  // `window` should be moved back to the 2nd display at its previous bounds.
-  EXPECT_EQ(initial_bounds, window->GetBoundsInScreen());
+  // `window1` should stay inside the 1st display while `window2` should be
+  // moved back to the 2nd display at its previous bounds.
+  EXPECT_EQ(initial_bounds_1st, window1->GetBoundsInScreen());
+  EXPECT_EQ(initial_bounds_2nd, window2->GetBoundsInScreen());
 
-  // Disconnect the 2nd display again. And change the window's bounds inside the
-  // primary display.
+  // Disconnect the 2nd display again. And change the bounds of `window2` inside
+  // the primary display.
   display_info_list.clear();
   display_info_list.push_back(primary_info);
   display_manager()->OnNativeDisplaysChanged(display_info_list);
-  EXPECT_EQ(remapping_bounds_in_1st, window->GetBoundsInScreen());
-  // Change the window's bounds inside the 1st display, make it half-offscreen
-  // as well.
+  EXPECT_EQ(remapping_bounds_in_1st, window2->GetBoundsInScreen());
+  // Change the bounds of `window2` inside the 1st display, make it
+  // half-offscreen as well.
   const gfx::Rect updated_bounds_in_1st(gfx::Point(300, 0), window_size);
-  window->SetBounds(updated_bounds_in_1st);
+  window2->SetBounds(updated_bounds_in_1st);
+  WindowState::Get(window2)->SetBoundsChangedByUser(true);
 
   // Reconnects the 2nd display. It should still restore back to its previous
   // bounds even though the user changed its bounds inside the 1st display.
   display_info_list.push_back(secondary_info);
   display_manager()->OnNativeDisplaysChanged(display_info_list);
-  EXPECT_EQ(initial_bounds, window->GetBoundsInScreen());
+  EXPECT_EQ(initial_bounds_1st, window1->GetBoundsInScreen());
+  EXPECT_EQ(initial_bounds_2nd, window2->GetBoundsInScreen());
 
-  // Disconnect the 2nd display, the window should restore to its updated bounds
-  // in the 1st display.
+  // Disconnect the 2nd display, `window2` should restore to its user-assigned
+  // bounds in the 1st display.
   display_info_list.clear();
   display_info_list.push_back(primary_info);
   display_manager()->OnNativeDisplaysChanged(display_info_list);
-  EXPECT_EQ(updated_bounds_in_1st, window->GetBoundsInScreen());
+  EXPECT_EQ(updated_bounds_in_1st, window2->GetBoundsInScreen());
 }
 
 TEST_F(WindowBoundsTrackerTest, RootWindowChanges) {
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_test_api.h b/ash/wm/tablet_mode/tablet_mode_controller_test_api.h
index 1566472..b40ecef2 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_test_api.h
+++ b/ash/wm/tablet_mode/tablet_mode_controller_test_api.h
@@ -99,8 +99,7 @@
   float GetLidAngle() const { return tablet_mode_controller_->lid_angle(); }
 
  private:
-  raw_ptr<TabletModeController, DanglingUntriaged | ExperimentalAsh>
-      tablet_mode_controller_;
+  raw_ptr<TabletModeController, ExperimentalAsh> tablet_mode_controller_;
 };
 
 }  // namespace ash
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index 935e17a8..4e964f80 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -142,6 +142,8 @@
 
   void TearDown() override {
     AccelerometerReader::GetInstance()->AddObserver(tablet_mode_controller());
+    // Rset before Shell destruction.
+    test_api_.reset();
     AshTestBase::TearDown();
   }
 
diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc
index f61df77..b26fa20 100644
--- a/ash/wm/window_state.cc
+++ b/ash/wm/window_state.cc
@@ -18,6 +18,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/wm/bounds_tracker/window_bounds_tracker.h"
 #include "ash/wm/collision_detection/collision_detection_utils.h"
 #include "ash/wm/default_state.h"
 #include "ash/wm/float/float_controller.h"
@@ -714,6 +715,11 @@
     persistent_window_info_of_display_removal_.reset();
     persistent_window_info_of_screen_rotation_.reset();
   }
+
+  if (auto* window_bounds_tracker = Shell::Get()->window_bounds_tracker()) {
+    window_bounds_tracker->SetWindowBoundsChangedByUser(window_,
+                                                        bounds_changed_by_user);
+  }
 }
 
 std::unique_ptr<PresentationTimeRecorder> WindowState::OnDragStarted(
diff --git a/base/allocator/partition_allocator/src/partition_alloc/partition_alloc_unittest.cc b/base/allocator/partition_allocator/src/partition_alloc/partition_alloc_unittest.cc
index df9bd33..72adf409 100644
--- a/base/allocator/partition_allocator/src/partition_alloc/partition_alloc_unittest.cc
+++ b/base/allocator/partition_allocator/src/partition_alloc/partition_alloc_unittest.cc
@@ -995,7 +995,15 @@
 
 // Test some corner cases relating to slot span transitions in the internal
 // free slot span list metadata bucket.
-TEST_P(PartitionAllocTest, FreeSlotSpanListSlotSpanTransitions) {
+// TODO(crbug.com/1512944): Test flaky on iPhone device.
+#if BUILDFLAG(IS_IOS) && !(TARGET_OS_SIMULATOR)
+#define MAYBE_FreeSlotSpanListSlotSpanTransitions \
+  DISABLED_FreeSlotSpanListSlotSpanTransitions
+#else
+#define MAYBE_FreeSlotSpanListSlotSpanTransitions \
+  FreeSlotSpanListSlotSpanTransitions
+#endif  // BUILDFLAG(IS_IOS) && !(TARGET_OS_SIMULATOR)
+TEST_P(PartitionAllocTest, MAYBE_FreeSlotSpanListSlotSpanTransitions) {
   PartitionRoot::Bucket* bucket =
       &allocator.root()->buckets[test_bucket_index_];
 
diff --git a/base/unguessable_token.cc b/base/unguessable_token.cc
index 44bfc3e..da65502 100644
--- a/base/unguessable_token.cc
+++ b/base/unguessable_token.cc
@@ -45,6 +45,17 @@
   return UnguessableToken(Token{high, low});
 }
 
+// static
+absl::optional<UnguessableToken> UnguessableToken::DeserializeFromString(
+    StringPiece string_representation) {
+  auto token = Token::FromString(string_representation);
+  // A zeroed out token means that it's not initialized via Create().
+  if (!token.has_value() || token.value().is_zero()) {
+    return absl::nullopt;
+  }
+  return UnguessableToken(token.value());
+}
+
 bool operator==(const UnguessableToken& lhs, const UnguessableToken& rhs) {
 #if BUILDFLAG(IS_NACL)
   // BoringSSL is unavailable for NaCl builds so it remains timing dependent.
diff --git a/base/unguessable_token.h b/base/unguessable_token.h
index 4efef5a..91f372f8 100644
--- a/base/unguessable_token.h
+++ b/base/unguessable_token.h
@@ -14,6 +14,7 @@
 #include "base/base_export.h"
 #include "base/check.h"
 #include "base/containers/span.h"
+#include "base/strings/string_piece.h"
 #include "base/token.h"
 
 namespace base {
@@ -66,6 +67,15 @@
   static absl::optional<UnguessableToken> Deserialize(uint64_t high,
                                                       uint64_t low);
 
+  // Returns an `UnguessableToken` built from its string representation. It
+  // should only be used in deserialization scenarios.
+  //
+  // NOTE: If the returned `absl::optional` does not have a value, it means that
+  // the given string does not represent a valid serialized `UnguessableToken`.
+  // This should be handled as a security issue.
+  static absl::optional<UnguessableToken> DeserializeFromString(
+      StringPiece string_representation);
+
   // Creates an empty UnguessableToken.
   // Assign to it with Create() before using it.
   constexpr UnguessableToken() = default;
diff --git a/base/unguessable_token_unittest.cc b/base/unguessable_token_unittest.cc
index 70285716..e190778d 100644
--- a/base/unguessable_token_unittest.cc
+++ b/base/unguessable_token_unittest.cc
@@ -146,6 +146,34 @@
   EXPECT_FALSE(token.has_value());
 }
 
+TEST(UnguessableTokenTest, VerifyDeserializeFromString) {
+  auto expected = UnguessableToken::CreateForTesting(1, 2);
+  auto actual = UnguessableToken::DeserializeFromString(
+      "00000000000000010000000000000002");
+  EXPECT_TRUE(actual.has_value());
+  EXPECT_TRUE(actual.value() == expected);
+}
+
+TEST(UnguessableTokenTest, VerifyDeserializeFromInvalidString) {
+  const char* invalid_representations[] = {
+      // Not a hex string representing 128 bits.
+      "1234",
+      // A string with valid length of 128 bits but 'X' is not a hex value.
+      "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
+      // A invalid hex string because of the lower case letters.
+      "0123456789abcdef0123456789abcdef",
+      // A zeroed out token is not a valid `UnguessableToken`.
+      "00000000000000000000000000000000"};
+  for (auto* invalid_representation : invalid_representations) {
+    auto actual =
+        UnguessableToken::DeserializeFromString(invalid_representation);
+    EXPECT_FALSE(actual.has_value())
+        << "'" << invalid_representation
+        << "' should not be deserialized to an UnguessableToken.";
+    ;
+  }
+}
+
 TEST(UnguessableTokenTest, VerifySmallerThanOperator) {
   // Deserialize is used for testing purposes.
   // Use UnguessableToken::Create() in production code instead.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index ea9ec5d..6a3a6cd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -1478,8 +1478,7 @@
     @VisibleForTesting
     boolean areExperimentsSupported(
             List<String> enabledExperiments, List<String> disabledExperiments) {
-        return enabledExperiments != null
-                && enabledExperiments.contains(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS);
+        return false;
     }
 
     // TODO(https://crbug.com/1458640): Remove this and other dynamic feature related methods.
@@ -1491,8 +1490,6 @@
      */
     public boolean isDynamicFeatureEnabled(String featureName) {
         if (mIsDynamicIntentFeatureOverridesEnabled) {
-            assert featureName.equals(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS)
-                    : "Unsupported Feature";
             if (mDynamicEnabledFeatures != null && mDynamicEnabledFeatures.contains(featureName)) {
                 return true;
             }
@@ -1501,9 +1498,6 @@
                 return false;
             }
         }
-        if (featureName.equals(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS)) {
-            return ChromeFeatureList.sCctRealTimeEngagementSignals.isEnabled();
-        }
         Log.e(TAG, "Unsupported Feature!");
         return false;
     }
@@ -1933,10 +1927,8 @@
             CustomTabsSessionToken sessionToken,
             EngagementSignalsCallback callback,
             Bundle extras) {
-        if (!isDynamicFeatureEnabled(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS)
-                || !isEngagementSignalsApiAvailableInternal(sessionToken)) {
-            return false;
-        }
+        if (!isEngagementSignalsApiAvailableInternal(sessionToken)) return false;
+
         var engagementSignalsHandler =
                 mClientManager.getEngagementSignalsHandlerForSession(sessionToken);
         if (engagementSignalsHandler == null) return false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
index 8c59a0f..cbe792c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabController.java
@@ -43,7 +43,6 @@
 import org.chromium.chrome.browser.customtabs.PageLoadMetricsObserver;
 import org.chromium.chrome.browser.customtabs.ReparentingTaskProvider;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
 import org.chromium.chrome.browser.lifecycle.InflationObserver;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -515,11 +514,6 @@
     }
 
     public void updateEngagementSignalsHandler() {
-        if (!CustomTabsConnection.getInstance()
-                .isDynamicFeatureEnabled(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS)) {
-            return;
-        }
-
         var handler = mConnection.getEngagementSignalsHandler(mSession);
         if (handler == null) return;
         handler.setTabObserverRegistrar(mTabObserverRegistrar);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserver.java
index aac2454b..94579fb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserver.java
@@ -23,7 +23,6 @@
 import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver;
 import org.chromium.chrome.browser.customtabs.features.TabInteractionRecorder;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.share.link_to_text.LinkToTextHelper;
 import org.chromium.chrome.browser.tab.Tab;
@@ -54,10 +53,6 @@
     // Limit the granularity of data the embedder receives.
     private static final int SCROLL_PERCENTAGE_GRANULARITY = 5;
 
-    // Feature param to decide whether to send real values with engagement signals.
-    @VisibleForTesting protected static final String REAL_VALUES = "real_values";
-    private static final int STUB_PERCENT = 0;
-
     // This value was chosen based on experiment data. 300ms covers about 98% of the scrolls while
     // trying to increase coverage further would require an unreasonably high threshold.
     @VisibleForTesting static final int DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS = 300;
@@ -70,8 +65,6 @@
     private final EngagementSignalsCallback mCallback;
     private final CustomTabsSessionToken mSession;
 
-    private final boolean mShouldSendRealValues;
-
     @Nullable private WebContents mWebContents;
     @Nullable private GestureStateListener mGestureStateListener;
     @Nullable private WebContentsObserver mEngagementSignalWebContentsObserver;
@@ -105,8 +98,6 @@
         mTabObserverRegistrar = tabObserverRegistrar;
         mCallback = callback;
 
-        mShouldSendRealValues = shouldSendRealValues();
-
         mPendingInitialUpdate = hadScrollDown;
         // Do not register observer via tab#addObserver, so it can change tabs when necessary.
         // If there is an active tab, registering the observer will immediately call
@@ -220,8 +211,7 @@
                         // Only send the event if there has been a down scroll.
                         if (!mScrollState.onScrollStarted(isDirectionUp)) return;
                         mScrollState.onScrollStarted(isDirectionUp);
-                        // If we shouldn't send the real values, always send false.
-                        notifyVerticalScrollEvent(mShouldSendRealValues && isDirectionUp);
+                        notifyVerticalScrollEvent(isDirectionUp);
                     }
 
                     @Override
@@ -249,7 +239,7 @@
                     public void onVerticalScrollDirectionChanged(
                             boolean directionUp, float currentScrollRatio) {
                         if (mScrollState.onScrollDirectionChanged(directionUp)) {
-                            notifyVerticalScrollEvent(mShouldSendRealValues && directionUp);
+                            notifyVerticalScrollEvent(directionUp);
                         }
                     }
 
@@ -259,15 +249,13 @@
                     }
 
                     /**
-                     * @param allowUpdateAfter Whether an |#onScrollOffsetOrExtentChanged()| should be
-                     *     allowed. If false, updates after |#onScrollEnded()| will be
-                     *     ignored.
+                     * @param allowUpdateAfter Whether an |#onScrollOffsetOrExtentChanged()| should
+                     *     be allowed. If false, updates after |#onScrollEnded()| will be ignored.
                      */
                     private void onScrollEndedInternal(boolean allowUpdateAfter) {
                         int resultPercentage = mScrollState.onScrollEnded(allowUpdateAfter);
                         if (resultPercentage != SCROLL_STATE_MAX_PERCENTAGE_NOT_INCREASING) {
-                            notifyGreatestScrollPercentageIncreased(
-                                    mShouldSendRealValues ? resultPercentage : STUB_PERCENT);
+                            notifyGreatestScrollPercentageIncreased(resultPercentage);
                         }
                     }
                 };
@@ -505,15 +493,4 @@
             ResettersForTesting.register(() -> sInstanceForTesting = null);
         }
     }
-
-    private static boolean shouldSendRealValues() {
-        boolean enabledWithOverride =
-                CustomTabsConnection.getInstance()
-                        .isDynamicFeatureEnabledWithOverrides(
-                                ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS);
-        if (enabledWithOverride) return true;
-
-        return ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
-                ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS, REAL_VALUES, true);
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
index e278ebf6..e3e2d23 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
@@ -325,7 +325,7 @@
     // TODO(crbug.com/1393116): Look into a more scalable pattern like
     // notifyPageOpened(String className).
     public void notifyRequestDesktopSiteSettingsPageOpened() {
-        RequestDesktopUtils.notifyRequestDesktopSiteSettingsPageOpened();
+        RequestDesktopUtils.notifyRequestDesktopSiteSettingsPageOpened(mProfile);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java
index 9ac9804..a64497cc 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/RequestDesktopUtils.java
@@ -908,8 +908,8 @@
     }
 
     /** Record event for feature engagement on desktop site settings page open. */
-    public static void notifyRequestDesktopSiteSettingsPageOpened() {
-        TrackerFactory.getTrackerForProfile(Profile.getLastUsedRegularProfile())
+    public static void notifyRequestDesktopSiteSettingsPageOpened(Profile profile) {
+        TrackerFactory.getTrackerForProfile(profile)
                 .notifyEvent(EventConstants.DESKTOP_SITE_SETTINGS_PAGE_OPENED);
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
index 68330a1fe..048039d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/NavigateTest.java
@@ -594,13 +594,11 @@
         HistogramWatcher startSurfaceHistogram =
                 HistogramWatcher.newSingleRecordWatcher(
                         histogram,
-                        BackPressManager.getHistogramValueForTesting(
-                                BackPressHandler.Type.START_SURFACE));
+                        BackPressManager.getHistogramValue(BackPressHandler.Type.START_SURFACE));
         HistogramWatcher tabSwitcherHistogram =
                 HistogramWatcher.newSingleRecordWatcher(
                         histogram,
-                        BackPressManager.getHistogramValueForTesting(
-                                BackPressHandler.Type.TAB_SWITCHER));
+                        BackPressManager.getHistogramValue(BackPressHandler.Type.TAB_SWITCHER));
 
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
         TabUiTestHelper.enterTabSwitcher(cta);
@@ -675,8 +673,8 @@
                     webServer.setResponseWithRunnableAction(
                             "/mockme.html",
                             "<html>  <head>    <meta name=\"viewport\"       "
-                                    + " content=\"initial-scale=0.75,maximum-scale=0.75,user-scalable=no\">"
-                                    + "  </head>  <body>Real</body></html>",
+                                + " content=\"initial-scale=0.75,maximum-scale=0.75,user-scalable=no\">"
+                                + "  </head>  <body>Real</body></html>",
                             null,
                             checkAction);
 
@@ -684,9 +682,9 @@
             mActivityTestRule.loadUrl(
                     UrlUtils.encodeHtmlDataUri(
                             "<head>  <meta name=\"viewport\"     "
-                                    + " content=\"initial-scale=0.5,maximum-scale=0.5,user-scalable=no\"></head><script>"
-                                    + "  function spoof() {    var w = open();    w.opener = null;   "
-                                    + " w.document.write('Spoofed');    w.location = '"
+                                + " content=\"initial-scale=0.5,maximum-scale=0.5,user-scalable=no\"></head><script>"
+                                + "  function spoof() {    var w = open();    w.opener = null;   "
+                                + " w.document.write('Spoofed');    w.location = '"
                                     + mockedUrl
                                     + "'"
                                     + "  }"
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index eb3ad71..1dcec89 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -2705,7 +2705,7 @@
         HistogramWatcher histogramWatcher =
                 HistogramWatcher.newSingleRecordWatcher(
                         "Android.BackPress.Failure",
-                        BackPressManager.getHistogramValueForTesting(
+                        BackPressManager.getHistogramValue(
                                 BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB));
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabModalDialogTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabModalDialogTest.java
index 9079b56..720a48a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabModalDialogTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabModalDialogTest.java
@@ -254,7 +254,7 @@
         HistogramWatcher histogramWatcher =
                 HistogramWatcher.newSingleRecordWatcher(
                         "Android.BackPress.Intercept",
-                        BackPressManager.getHistogramValueForTesting(
+                        BackPressManager.getHistogramValue(
                                 BackPressHandler.Type.TAB_MODAL_HANDLER));
 
         TestThreadUtils.runOnUiThreadBlocking(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
index 61e1e17b..7cd9bcd 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerTest.java
@@ -32,6 +32,7 @@
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.browser.app.ChromeActivity;
 import org.chromium.chrome.browser.back_press.BackPressManager;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsUtils;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.layouts.LayoutManager;
@@ -244,13 +245,16 @@
     private void launchOnFullscreenMode(String url) {
         mActivityTestRule.startMainActivityWithURL(url);
 
-        Tab tab = mActivityTestRule.getActivity().getActivityTab();
-        final TabWebContentsDelegateAndroid delegate = TabTestUtils.getTabWebContentsDelegate(tab);
+        var activity = mActivityTestRule.getActivity();
+        Tab tab = activity.getActivityTab();
+        var delegate = TabTestUtils.getTabWebContentsDelegate(tab);
 
-        FullscreenTestUtils.waitForFullscreenFlag(tab, false, mActivityTestRule.getActivity());
+        FullscreenTestUtils.waitForFullscreenFlag(tab, false, activity);
         FullscreenTestUtils.waitForPersistentFullscreen(delegate, false);
-        FullscreenTestUtils.togglePersistentFullscreenAndAssert(
-                tab, true, mActivityTestRule.getActivity());
+        FullscreenTestUtils.togglePersistentFullscreenAndAssert(tab, true, activity);
+        var browserControlsManager = activity.getBrowserControlsManager();
+        CriteriaHelper.pollUiThread(
+                () -> BrowserControlsUtils.areBrowserControlsOffScreen(browserControlsManager));
     }
 
     @Test
@@ -717,7 +721,6 @@
     public void testFullscreenPageHeight() throws Throwable {
         launchOnFullscreenMode(LONG_HTML_TEST_PAGE);
         Assert.assertTrue(getPersistentFullscreenMode());
-
         float pixelDensity =
                 InstrumentationRegistry.getInstrumentation()
                         .getContext()
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
index 0093a55d..90987bc 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarTest.java
@@ -620,8 +620,6 @@
     @MediumTest
     @CommandLineFlags.Add({
         "disable-features="
-                + ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR
-                + ","
                 + ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2
     })
     @Restriction(UiRestriction.RESTRICTION_TYPE_TABLET)
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java
index df420d16..7e97219 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareButtonControllerTest.java
@@ -36,7 +36,6 @@
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
 import org.chromium.chrome.test.util.ChromeTabUtils;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.chrome.test.util.browser.signin.SigninTestRule;
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -51,9 +50,6 @@
 /** Tests {@link ShareButtonController}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @Batch(Batch.PER_CLASS)
-@EnableFeatures({
-    ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR,
-})
 @CommandLineFlags.Add({
     ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
     "enable-features=" + ChromeFeatureList.START_SURFACE_ANDROID + "<Study",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataDeferredStartupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataDeferredStartupTest.java
index 3e5d2ad7..1a352ae38 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataDeferredStartupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataDeferredStartupTest.java
@@ -211,4 +211,69 @@
                 });
         ShoppingPersistedTabDataTestUtils.acquireSemaphore(newSemaphore);
     }
+
+    @SmallTest
+    @Test
+    @CommandLineFlags.Add({
+        "force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true/"
+                + "return_empty_price_drops_until_init/true"
+    })
+    @EnableFeatures({ChromeFeatureList.PRICE_CHANGE_MODULE})
+    public void testSkipDelayedInitialization_NotSkip() {
+        ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
+                mOptimizationGuideBridgeJniMock,
+                HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
+                ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
+                        .BUYABLE_PRODUCT_AND_PRODUCT_UPDATE);
+        final Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(0, mProfileMock);
+        final Semaphore semaphore = new Semaphore(0);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    ShoppingPersistedTabData.initialize(tab);
+                    ShoppingPersistedTabData.from(
+                            tab,
+                            (shoppingPersistedTabData) -> {
+                                Assert.assertNull(shoppingPersistedTabData);
+                                semaphore.release();
+                            },
+                            false);
+                });
+        ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
+    }
+
+    @SmallTest
+    @Test
+    @CommandLineFlags.Add({
+        "force-fieldtrial-params=Study.Group:price_tracking_with_optimization_guide/true/"
+                + "return_empty_price_drops_until_init/true"
+    })
+    @EnableFeatures({ChromeFeatureList.PRICE_CHANGE_MODULE})
+    public void testSkipDelayedInitialization_Skip() {
+        ShoppingPersistedTabDataTestUtils.mockOptimizationGuideResponse(
+                mOptimizationGuideBridgeJniMock,
+                HintsProto.OptimizationType.PRICE_TRACKING.getNumber(),
+                ShoppingPersistedTabDataTestUtils.MockPriceTrackingResponse
+                        .BUYABLE_PRODUCT_AND_PRODUCT_UPDATE);
+        final Tab tab = ShoppingPersistedTabDataTestUtils.createTabOnUiThread(0, mProfileMock);
+
+        final Semaphore semaphore = new Semaphore(0);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    ShoppingPersistedTabData.initialize(tab);
+                    ShoppingPersistedTabData.from(
+                            tab,
+                            (shoppingPersistedTabData) -> {
+                                Assert.assertNotNull(shoppingPersistedTabData);
+                                Assert.assertEquals(
+                                        ShoppingPersistedTabDataTestUtils.UPDATED_PRICE_MICROS,
+                                        shoppingPersistedTabData.getPriceMicros());
+                                Assert.assertEquals(
+                                        ShoppingPersistedTabDataTestUtils.PRICE_MICROS,
+                                        shoppingPersistedTabData.getPreviousPriceMicros());
+                                semaphore.release();
+                            },
+                            true);
+                });
+        ShoppingPersistedTabDataTestUtils.acquireSemaphore(semaphore);
+    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuRenderTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuRenderTest.java
index 187b1b0e..c9ec29f3 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuRenderTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuRenderTest.java
@@ -26,12 +26,10 @@
 import org.chromium.base.test.params.ParameterizedRunner;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
 import org.chromium.chrome.test.R;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.listmenu.ListMenuButton;
 import org.chromium.ui.test.util.BlankUiTestActivity;
@@ -43,10 +41,8 @@
 /** Render tests for adaptive test long-press menu popup. */
 @RunWith(ParameterizedRunner.class)
 @ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
-@EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
 @CommandLineFlags.Add({
     ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
-    "enable-features=" + ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR + "<Study",
     "force-fieldtrials=Study/Group",
     "force-fieldtrial-params=Study.Group:mode/always-share"
 })
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTabletTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTabletTest.java
index ba8e33e6..f7f9266e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTabletTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/adaptive/OptionalNewTabButtonControllerTabletTest.java
@@ -22,14 +22,12 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Restriction;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
 import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
 import org.chromium.chrome.test.util.ActivityTestUtils;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.ui.test.util.UiRestriction;
 import org.chromium.ui.test.util.ViewUtils;
 
@@ -39,10 +37,8 @@
  */
 @RunWith(ChromeJUnit4ClassRunner.class)
 @Batch(Batch.PER_CLASS)
-@EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
 @CommandLineFlags.Add({
     ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
-    "enable-features=" + ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR + "<Study",
     "force-fieldtrials=Study/Group",
     "force-fieldtrial-params=Study.Group:mode/always-new-tab"
 })
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
index a3a18cd..bb26821d5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhoneTest.java
@@ -1068,7 +1068,8 @@
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     mToolbar.setNtpSearchBoxScrollFractionForTesting(1);
-                    mToolbar.updateLocationBarForSurfacePolish(VisualState.NEW_TAB_NORMAL, false);
+                    mToolbar.updateLocationBarForSurfacePolish(
+                            VisualState.NEW_TAB_NORMAL, /* hasFocus= */ false);
                 });
         if (nightModeEnabled) {
             assertEquals(View.INVISIBLE, iconBackground.getVisibility());
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
index d5bf56cd..28dca3d5 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
@@ -175,9 +175,6 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS
-    })
     public void setEngagementSignalsCallback_Available() {
         initSession();
         when(mPrivacyPreferencesManager.isUsageAndCrashReportingPermitted()).thenReturn(true);
@@ -190,9 +187,6 @@
     }
 
     @Test
-    @EnableFeatures({
-        ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS
-    })
     public void setEngagementSignalsCallback_NotAvailable() {
         initSession();
         when(mPrivacyPreferencesManager.isUsageAndCrashReportingPermitted()).thenReturn(false);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java
index d04a84f..af345fdb 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityNavigationControllerTest.java
@@ -86,7 +86,7 @@
                                 MinimizeAppAndCloseTabType.MINIMIZE_APP)
                         .expectIntRecord(
                                 BackPressManager.getHistogramForTesting(),
-                                BackPressManager.getHistogramValueForTesting(
+                                BackPressManager.getHistogramValue(
                                         BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB))
                         .build();
         when(mTabController.onlyOneTabRemaining()).thenReturn(true);
@@ -133,7 +133,7 @@
                                 MinimizeAppAndCloseTabType.CLOSE_TAB)
                         .expectIntRecord(
                                 BackPressManager.getHistogramForTesting(),
-                                BackPressManager.getHistogramValueForTesting(
+                                BackPressManager.getHistogramValue(
                                         BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB))
                         .build();
         doAnswer(
@@ -186,7 +186,7 @@
                                 MinimizeAppAndCloseTabType.CLOSE_TAB)
                         .expectIntRecord(
                                 BackPressManager.getHistogramForTesting(),
-                                BackPressManager.getHistogramValueForTesting(
+                                BackPressManager.getHistogramValue(
                                         BackPressHandler.Type.MINIMIZE_APP_AND_CLOSE_TAB))
                         .build();
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java
index dc90bda..20c9df0 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityTabControllerUnitTest.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.customtabs.content;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -15,7 +14,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -30,20 +28,15 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.embedder_support.util.ShadowUrlUtilities;
 import org.chromium.content_public.browser.WebContents;
 
@@ -52,7 +45,6 @@
 @Config(
         manifest = Config.NONE,
         shadows = {ShadowUrlUtilities.class})
-@DisableFeatures(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS)
 public class CustomTabActivityTabControllerUnitTest {
     @Rule
     public final CustomTabActivityContentTestEnvironment env =
@@ -212,21 +204,6 @@
     }
 
     @Test
-    @DisableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS})
-    public void doesNotSetGreatestScrollPercentageSupplierIfFeatureIsDisabled() {
-        env.reachNativeInit(mTabController);
-
-        ArgumentCaptor<TabObserver> tabObservers = ArgumentCaptor.forClass(TabObserver.class);
-        verify(env.tabObserverRegistrar, atLeastOnce()).registerTabObserver(tabObservers.capture());
-        for (TabObserver observer : tabObservers.getAllValues()) {
-            assertFalse(
-                    "RealtimeEngagementSignalObserver is not attached.",
-                    observer instanceof RealtimeEngagementSignalObserver);
-        }
-    }
-
-    @Test
-    @EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS})
     public void setsTabObserverRegistrarOnEngagementSignalsHandler() {
         var handler = mock(EngagementSignalsHandler.class);
         when(env.connection.getEngagementSignalsHandler(eq(env.session))).thenReturn(handler);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
index ea7ec61..1d32ca5b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/CustomTabActivityUrlLoadingTest.java
@@ -35,11 +35,9 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.JniMocker;
 import org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.embedder_support.util.UrlUtilitiesJni;
 import org.chromium.content_public.browser.LoadUrlParams;
@@ -54,7 +52,6 @@
 @Config(
         manifest = Config.NONE,
         shadows = {CustomTabActivityUrlLoadingTest.ShadowOrigin.class})
-@DisableFeatures(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS)
 public class CustomTabActivityUrlLoadingTest {
     @Implements(Origin.class)
     public static class ShadowOrigin {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsInitialScrollObserverUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsInitialScrollObserverUnitTest.java
index 59ac8462..c4264fa 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsInitialScrollObserverUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/EngagementSignalsInitialScrollObserverUnitTest.java
@@ -25,11 +25,9 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabHidingType;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content.browser.GestureListenerManagerImpl;
 import org.chromium.content_public.browser.GestureStateListener;
 import org.chromium.content_public.browser.LoadCommittedDetails;
@@ -40,7 +38,6 @@
 /** Unit tests for {@link EngagementSignalsInitialScrollObserver}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
-@EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS})
 public class EngagementSignalsInitialScrollObserverUnitTest {
     private static final int SCROLL_EXTENT = 100;
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserverUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserverUnitTest.java
index bf3deee2..16d62f80 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserverUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/RealtimeEngagementSignalObserverUnitTest.java
@@ -22,7 +22,6 @@
 
 import static org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency.ON_SCROLL_END;
 import static org.chromium.chrome.browser.customtabs.content.RealtimeEngagementSignalObserver.DEFAULT_AFTER_SCROLL_END_THRESHOLD_MS;
-import static org.chromium.chrome.browser.customtabs.content.RealtimeEngagementSignalObserver.REAL_VALUES;
 
 import android.os.Bundle;
 import android.os.SystemClock;
@@ -43,19 +42,16 @@
 import org.robolectric.shadows.ShadowSystemClock;
 
 import org.chromium.base.FeatureList;
-import org.chromium.base.FeatureList.TestValues;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.cc.mojom.RootScrollOffsetUpdateFrequency;
 import org.chromium.chrome.browser.customtabs.content.RealtimeEngagementSignalObserver.ScrollState;
 import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar.CustomTabTabObserver;
 import org.chromium.chrome.browser.customtabs.features.TabInteractionRecorder;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabHidingType;
 import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.content.browser.GestureListenerManagerImpl;
 import org.chromium.content.browser.RenderCoordinatesImpl;
 import org.chromium.content_public.browser.GestureStateListener;
@@ -70,7 +66,6 @@
 /** Unit test for {@link RealtimeEngagementSignalObserver}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(shadows = {ShadowSystemClock.class})
-@EnableFeatures({ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS})
 public class RealtimeEngagementSignalObserverUnitTest {
     @Rule
     public final CustomTabActivityContentTestEnvironment env =
@@ -119,7 +114,6 @@
 
     @Test
     public void addsListenersForSignalsIfFeatureIsEnabled_alternativeImpl() {
-        setFeatureParams(null);
         initializeTabForTest();
 
         verify(mGestureListenerManagerImpl)
@@ -526,70 +520,6 @@
     }
 
     @Test
-    public void sendsFalseForScrollDirectionIfSendingFakeValues() {
-        setFeatureParams(false);
-        initializeTabForTest();
-        GestureStateListener listener = captureGestureStateListener();
-
-        // Start by scrolling down.
-        listener.onScrollStarted(0, SCROLL_EXTENT, false);
-        verify(mEngagementSignalsCallback).onVerticalScrollEvent(eq(false), any(Bundle.class));
-        // Change direction to up at 10%.
-        listener.onVerticalScrollDirectionChanged(true, .1f);
-        verify(mEngagementSignalsCallback, times(2))
-                .onVerticalScrollEvent(eq(false), any(Bundle.class));
-        // Change direction to down at 5%.
-        listener.onVerticalScrollDirectionChanged(false, .05f);
-        verify(mEngagementSignalsCallback, times(3))
-                .onVerticalScrollEvent(eq(false), any(Bundle.class));
-        // End scrolling at 50%.
-        listener.onScrollEnded(50, SCROLL_EXTENT);
-        // We shouldn't make any more calls.
-        verify(mEngagementSignalsCallback, times(3))
-                .onVerticalScrollEvent(anyBoolean(), any(Bundle.class));
-    }
-
-    @Test
-    public void sendsZeroForMaxScrollSignalsIfSendingFakeValues() {
-        setFeatureParams(false);
-        initializeTabForTest();
-        GestureStateListener listener = captureGestureStateListener();
-
-        // Start by scrolling down.
-        listener.onScrollStarted(0, SCROLL_EXTENT, false);
-        // Scroll down to 3%.
-        when(mRenderCoordinatesImpl.getScrollYPixInt()).thenReturn(3);
-        listener.onScrollOffsetOrExtentChanged(3, SCROLL_EXTENT);
-        // End scrolling.
-        listener.onScrollEnded(3, SCROLL_EXTENT);
-        // We shouldn't make any calls at this point.
-        verify(mEngagementSignalsCallback, never())
-                .onGreatestScrollPercentageIncreased(anyInt(), any(Bundle.class));
-
-        // Start scrolling down again.
-        listener.onScrollStarted(3, SCROLL_EXTENT, false);
-        // Scroll down to 8%.
-        when(mRenderCoordinatesImpl.getScrollYPixInt()).thenReturn(8);
-        listener.onScrollOffsetOrExtentChanged(8, SCROLL_EXTENT);
-        // End scrolling.
-        listener.onScrollEnded(8, SCROLL_EXTENT);
-        // We should make a call, but it will be 0.
-        verify(mEngagementSignalsCallback, times(1))
-                .onGreatestScrollPercentageIncreased(eq(0), any(Bundle.class));
-
-        // Start scrolling down again.
-        listener.onScrollStarted(8, SCROLL_EXTENT, false);
-        // Scroll down to 94%.
-        when(mRenderCoordinatesImpl.getScrollYPixInt()).thenReturn(94);
-        listener.onScrollOffsetOrExtentChanged(94, SCROLL_EXTENT);
-        // End scrolling.
-        listener.onScrollEnded(94, SCROLL_EXTENT);
-        // We should make a call, 0 again.
-        verify(mEngagementSignalsCallback, times(2))
-                .onGreatestScrollPercentageIncreased(eq(0), any(Bundle.class));
-    }
-
-    @Test
     public void sendsSignalWithAlternativeImpl_updateBeforeEnd() {
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
@@ -608,7 +538,6 @@
 
     @Test
     public void sendsSignalWithAlternativeImpl_updateAfterEnd() {
-        setFeatureParams(null);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
 
@@ -632,7 +561,6 @@
 
     @Test
     public void doesNotSendLowerPercentWithAlternativeImpl() {
-        setFeatureParams(null);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
 
@@ -660,7 +588,6 @@
 
     @Test
     public void doNotSendSignalWithAlternativeImplAfterThreshold() {
-        setFeatureParams(null);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
 
@@ -679,7 +606,6 @@
 
     @Test
     public void doNotSendSignalWithAlternativeImplIfScrollStartReceived() {
-        setFeatureParams(null);
         initializeTabForTest();
         GestureStateListener listener = captureGestureStateListener(ON_SCROLL_END);
 
@@ -874,21 +800,6 @@
         SystemClock.setCurrentTimeMillis(CURRENT_TIME_MS + millis);
     }
 
-    /**
-     * @param realValues CCT_REAL_TIME_ENGAGEMENT_SIGNALS real_values.
-     */
-    private void setFeatureParams(Boolean realValues) {
-        if (realValues == null) return;
-
-        TestValues testValues = new TestValues();
-        testValues.addFeatureFlagOverride(ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS, true);
-        testValues.addFieldTrialParamOverride(
-                ChromeFeatureList.CCT_REAL_TIME_ENGAGEMENT_SIGNALS,
-                REAL_VALUES,
-                realValues.toString());
-        FeatureList.setTestValues(testValues);
-    }
-
     private void initializeTabForTest(boolean hadScrollDown) {
         Tab initialTab = env.prepareTab();
         doAnswer(
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 9fca194..49b4ff9 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-122.0.6191.0_rc-r1-merged.afdo.bz2
+chromeos-chrome-amd64-122.0.6192.0_rc-r1-merged.afdo.bz2
diff --git a/chrome/app/chrome_main.cc b/chrome/app/chrome_main.cc
index 3b8eec6d..82fdce6 100644
--- a/chrome/app/chrome_main.cc
+++ b/chrome/app/chrome_main.cc
@@ -24,10 +24,6 @@
 #include "headless/public/headless_shell.h"
 #include "headless/public/switches.h"
 
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-#include "base/base_switches.h"
-#endif
-
 #if BUILDFLAG(IS_MAC)
 #include "chrome/app/chrome_main_mac.h"
 #include "chrome/app/notification_metrics.h"
@@ -178,7 +174,7 @@
     BUILDFLAG(IS_WIN)
     if (headless::IsOldHeadlessMode()) {
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-      command_line->AppendSwitch(::switches::kEnableCrashReporter);
+      command_line->AppendSwitch(::headless::switches::kEnableCrashReporter);
 #endif
       return headless::HeadlessShellMain(std::move(params));
     }
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 6efea0b..1440992 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -11673,7 +11673,7 @@
       </message>
 
       <message name="IDS_PAGE_SPECIFIC_SITE_DATA_DIALOG_FIRST_PARTY_SUBTITLE" desc="This is a subtitle of a section that displayed a list of sites that have accessed storage and are considered first-party to the site the user is currently visiting. It provides more information about what first party sites means. The label is shown in the `Cookies and site data` dialog.">
-        A site might save your preferred language or items you want to buy. This info is avaiable to the site and its subdomains.
+        A site might save your preferred language or items you want to buy. This info is available to the site and its subdomains.
       </message>
 
       <message name="IDS_PAGE_SPECIFIC_SITE_DATA_DIALOG_THIRD_PARTY_TITLE" desc="This is a title of a section that displayed a list of sites that have accessed storage and are considered third-party to the site the user is currently visiting. The label is shown in the `Cookies and site data` dialog.">
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 0a2b1ab..f2f8f28 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -5356,7 +5356,7 @@
     <ph name="percentage">$1<ex>56</ex></ph>%
   </message>
   <message name="IDS_SETTINGS_POWER_BATTERY_SAVER_LABEL" desc="In Device Settings > Power, label for toggling battery saver mode">
-    Battery Saver
+    Battery saver
   </message>
   <message name="IDS_SETTINGS_POWER_BATTERY_SAVER_SUBTEXT" desc="In Device Settings > Power, description of the behavior of battery saver mode">
     Extends battery life by reducing brightness, limiting background activity and visual effects, delaying notifications, and turning on Chrome Energy Saver.
diff --git a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_POWER_BATTERY_SAVER_LABEL.png.sha1 b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_POWER_BATTERY_SAVER_LABEL.png.sha1
index 12128cb..790635f 100644
--- a/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_POWER_BATTERY_SAVER_LABEL.png.sha1
+++ b/chrome/app/os_settings_strings_grdp/IDS_SETTINGS_POWER_BATTERY_SAVER_LABEL.png.sha1
@@ -1 +1 @@
-fde396a096e37f11d38c7a91759c7d4116025844
\ No newline at end of file
+0d10cc753f8b5fff20ac1149c4ea59dcd933a44c
\ No newline at end of file
diff --git a/chrome/browser/DEPS b/chrome/browser/DEPS
index 418f79f4..64ae3f7b 100644
--- a/chrome/browser/DEPS
+++ b/chrome/browser/DEPS
@@ -207,7 +207,7 @@
   "+components/mirroring/browser",
   "+components/mirroring/mojom",
   "+components/mirroring/service",
-  "+components/ml/webnn/features.h",
+  "+components/ml/webnn/features.mojom-features.h",
   "+components/module_installer/android",
   "+components/nacl/broker",
   "+components/nacl/browser",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 002379c8..b076446 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -113,7 +113,7 @@
 #include "components/lens/lens_features.h"
 #include "components/manta/features.h"
 #include "components/mirroring/service/mirroring_features.h"
-#include "components/ml/webnn/features.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #include "components/nacl/common/buildflags.h"
 #include "components/nacl/common/nacl_switches.h"
 #include "components/network_session_configurator/common/network_features.h"
@@ -522,11 +522,6 @@
         {"Use Denylist", kCCTResizablePolicyParamUseDenylist,
          std::size(kCCTResizablePolicyParamUseDenylist), nullptr}};
 
-const FeatureEntry::FeatureParam
-    kCCTRealTimeEngagementSignalsParamRealValues[] = {{"real_values", "true"}};
-const FeatureEntry::FeatureParam
-    kCCTRealTimeEngagementSignalsParamFakeValues[] = {{"real_values", "false"}};
-
 const FeatureEntry::FeatureParam kCCTPageInsightsHubFastPeekTriggerParam = {
     "page_insights_can_autotrigger_after_end", "1000"};  // 1s
 const FeatureEntry::FeatureParam kCCTPageInsightsHubShorterFullSizeParam = {
@@ -555,12 +550,6 @@
     {"with both", kCCTPageInsightsHubBothParams,
      std::size(kCCTPageInsightsHubBothParams), nullptr}};
 
-const FeatureEntry::FeatureVariation kCCTRealTimeEngagementSignalsVariations[] =
-    {{"Send real values", kCCTRealTimeEngagementSignalsParamRealValues,
-      std::size(kCCTRealTimeEngagementSignalsParamRealValues), nullptr},
-     {"Send fake values", kCCTRealTimeEngagementSignalsParamFakeValues,
-      std::size(kCCTRealTimeEngagementSignalsParamFakeValues), nullptr}};
-
 const FeatureEntry::Choice kReaderModeHeuristicsChoices[] = {
     {flags_ui::kGenericExperimentChoiceDefault, "", ""},
     {flag_descriptions::kReaderModeHeuristicsMarkup,
@@ -623,28 +612,6 @@
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(IS_ANDROID)
-const FeatureEntry::FeatureParam kAdaptiveButton_AlwaysNone[] = {
-    {"mode", "always-none"}};
-const FeatureEntry::FeatureParam kAdaptiveButton_AlwaysNewTab[] = {
-    {"mode", "always-new-tab"}};
-const FeatureEntry::FeatureParam kAdaptiveButton_AlwaysShare[] = {
-    {"mode", "always-share"}};
-const FeatureEntry::FeatureParam kAdaptiveButton_AlwaysVoice[] = {
-    {"mode", "always-voice"}};
-const FeatureEntry::FeatureParam kAdaptiveButton_AlwaysTranslate[] = {
-    {"mode", "always-translate"}};
-const FeatureEntry::FeatureVariation kAdaptiveButtonInTopToolbarVariations[] = {
-    {"Always None", kAdaptiveButton_AlwaysNone,
-     std::size(kAdaptiveButton_AlwaysNone), nullptr},
-    {"Always New Tab", kAdaptiveButton_AlwaysNewTab,
-     std::size(kAdaptiveButton_AlwaysNewTab), nullptr},
-    {"Always Share", kAdaptiveButton_AlwaysShare,
-     std::size(kAdaptiveButton_AlwaysShare), nullptr},
-    {"Always Voice", kAdaptiveButton_AlwaysVoice,
-     std::size(kAdaptiveButton_AlwaysVoice), nullptr},
-    {"Always Translate", kAdaptiveButton_AlwaysTranslate,
-     std::size(kAdaptiveButton_AlwaysTranslate), nullptr},
-};
 
 const FeatureEntry::FeatureParam kAdaptiveButtonCustomization_NewTab[] = {
     {"default_segment", "new-tab"},
@@ -4692,13 +4659,6 @@
      FEATURE_VALUE_TYPE(features::kSystemNotifications)},
 #endif  // BUILDFLAG(ENABLE_SYSTEM_NOTIFICATIONS) && !BUILDFLAG(IS_CHROMEOS_ASH)
 #if BUILDFLAG(IS_ANDROID)
-    {"adaptive-button-in-top-toolbar",
-     flag_descriptions::kAdaptiveButtonInTopToolbarName,
-     flag_descriptions::kAdaptiveButtonInTopToolbarDescription, kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         chrome::android::kAdaptiveButtonInTopToolbar,
-         kAdaptiveButtonInTopToolbarVariations,
-         "OptionalToolbarButton")},
     {"adaptive-button-in-top-toolbar-translate",
      flag_descriptions::kAdaptiveButtonInTopToolbarTranslateName,
      flag_descriptions::kAdaptiveButtonInTopToolbarTranslateDescription,
@@ -7192,16 +7152,6 @@
          chrome::android::kCCTResizableSideSheetForThirdParties)},
 #endif
 
-#if BUILDFLAG(IS_ANDROID)
-    {"cct-real-time-engagement-signals",
-     flag_descriptions::kCCTRealTimeEngagementSignalsName,
-     flag_descriptions::kCCTRealTimeEngagementSignalsDescription, kOsAndroid,
-     FEATURE_WITH_PARAMS_VALUE_TYPE(
-         chrome::android::kCCTRealTimeEngagementSignals,
-         kCCTRealTimeEngagementSignalsVariations,
-         "CCTRealTimeEngagementSignals")},
-#endif
-
 #if BUILDFLAG(IS_CHROMEOS)
     {"allow-dsp-based-aec", flag_descriptions::kCrOSDspBasedAecAllowedName,
      flag_descriptions::kCrOSDspBasedAecAllowedDescription, kOsCrOS | kOsLacros,
@@ -8208,7 +8158,8 @@
     {"web-machine-learning-neural-network",
      flag_descriptions::kWebMachineLearningNeuralNetworkName,
      flag_descriptions::kWebMachineLearningNeuralNetworkDescription, kOsAll,
-     FEATURE_VALUE_TYPE(webnn::features::kWebMachineLearningNeuralNetwork)},
+     FEATURE_VALUE_TYPE(
+         webnn::mojom::features::kWebMachineLearningNeuralNetwork)},
 
     {"one-time-permission", flag_descriptions::kOneTimePermissionName,
      flag_descriptions::kOneTimePermissionDescription, kOsAll,
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
index 08476ab..871717b 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
@@ -798,13 +798,6 @@
   }
 }
 
-void DisplayOverlayController::MayShowEduNudgeForEditingTip() {
-  DCHECK(editing_list_widget_);
-  if (auto* editing_list = GetEditingList()) {
-    editing_list->MayShowEduNudgeForEditingTip();
-  }
-}
-
 void DisplayOverlayController::UpdateButtonOptionsMenuWidgetBounds() {
   // There is no `button_options_widget_` in view mode.
   if (!button_options_widget_) {
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
index 8ca6ed8..bd63df4 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
@@ -123,10 +123,6 @@
   void RemoveActionHighlightWidget();
   void HideActionHighlightWidget();
 
-  // Show education nudge for editing tip. It only shows up for the first new
-  // action after closing `ButtonOptionsMenu`.
-  void MayShowEduNudgeForEditingTip();
-
   // Update widget bounds if the view content is changed or the app window
   // bounds are changed.
   void UpdateButtonOptionsMenuWidgetBounds();
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc
index 66a1776..21f59a13 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc
@@ -22,6 +22,10 @@
 
 ActionViewListItem::~ActionViewListItem() = default;
 
+void ActionViewListItem::PerformPulseAnimation() {
+  labels_view_->PerformPulseAnimationOnFirstLabel();
+}
+
 void ActionViewListItem::OnActionNameUpdated() {
   NOTIMPLEMENTED();
 }
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h
index 80718bf..9ff8cbb 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h
@@ -25,6 +25,8 @@
   ActionViewListItem& operator=(const ActionViewListItem&) = delete;
   ~ActionViewListItem() override;
 
+  void PerformPulseAnimation();
+
   // ActionEditView:
   void OnActionNameUpdated() override;
 
diff --git a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc
index 3f278f2..455e0b3 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/button_options_menu.cc
@@ -298,7 +298,6 @@
 
 void ButtonOptionsMenu::OnDoneButtonPressed() {
   controller_->SaveToProtoFile();
-  controller_->MayShowEduNudgeForEditingTip();
 
   // Remove this view at last.
   controller_->RemoveButtonOptionsMenuWidget();
diff --git a/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc b/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc
index 48fe52d9..04723c670 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/edit_label.cc
@@ -7,6 +7,7 @@
 #include "ash/bubble/bubble_utils.h"
 #include "ash/style/typography.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ash/arc/input_overlay/actions/action.h"
 #include "chrome/browser/ash/arc/input_overlay/actions/input_element.h"
@@ -22,12 +23,28 @@
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/base/models/image_model.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
+#include "ui/compositor/layer.h"
+#include "ui/gfx/geometry/transform.h"
+#include "ui/gfx/geometry/transform_util.h"
+#include "ui/views/animation/animation_builder.h"
 #include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/button/button.h"
 
 namespace arc::input_overlay {
 
+namespace {
+
+constexpr float kCornerRadius = 8.0f;
+constexpr int kLabelSize = 32;
+
+// Pulse animation specs.
+constexpr int kPulseTimes = 3;
+constexpr int kPulseExtraHalfSize = 32;
+constexpr base::TimeDelta kPulseDuration = base::Seconds(2);
+
+}  // namespace
+
 EditLabel::EditLabel(DisplayOverlayController* controller,
                      Action* action,
                      size_t index)
@@ -52,9 +69,58 @@
   SetLabelContent();
 }
 
+void EditLabel::PerformPulseAnimation(int pulse_count) {
+  // Destroy the pulse layer if it pulses after `kPulseTimes` times.
+  if (pulse_count >= kPulseTimes) {
+    pulse_layer_.reset();
+    return;
+  }
+
+  auto* widget = GetWidget();
+  DCHECK(widget);
+
+  // Initiate pulse layer if it starts to pulse for the first time.
+  if (pulse_count == 0) {
+    pulse_layer_ = std::make_unique<ui::Layer>(ui::LAYER_SOLID_COLOR);
+    widget->GetLayer()->Add(pulse_layer_.get());
+    pulse_layer_->SetColor(widget->GetColorProvider()->GetColor(
+        cros_tokens::kCrosSysHighlightText));
+  }
+
+  DCHECK(pulse_layer_);
+
+  // Initial bounds in its widget coordinate.
+  auto view_bounds = ConvertRectToWidget(bounds());
+
+  // Set initial properties.
+  pulse_layer_->SetBounds(view_bounds);
+  pulse_layer_->SetOpacity(1.0f);
+  pulse_layer_->SetRoundedCornerRadius(gfx::RoundedCornersF(kCornerRadius));
+
+  // Animate from a square to a circle with larger target bounds and to a
+  // smaller opacity.
+  view_bounds.Outset(kPulseExtraHalfSize);
+
+  views::AnimationBuilder()
+      .SetPreemptionStrategy(
+          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
+      .OnEnded(base::BindOnce(&EditLabel::PerformPulseAnimation,
+                              base::Unretained(this), pulse_count + 1))
+      .Once()
+      .SetDuration(kPulseDuration)
+      .SetBounds(pulse_layer_.get(), view_bounds,
+                 gfx::Tween::ACCEL_0_40_DECEL_100)
+      .SetOpacity(pulse_layer_.get(), /*opacity=*/0.0f,
+                  gfx::Tween::ACCEL_0_80_DECEL_80)
+      .SetRoundedCorners(
+          pulse_layer_.get(),
+          gfx::RoundedCornersF(kPulseExtraHalfSize + kLabelSize / 2.0f),
+          gfx::Tween::ACCEL_0_40_DECEL_100);
+}
+
 void EditLabel::Init() {
   SetHorizontalAlignment(gfx::ALIGN_CENTER);
-  SetPreferredSize(gfx::Size(32, 32));
+  SetPreferredSize(gfx::Size(kLabelSize, kLabelSize));
   SetAccessibilityProperties(ax::mojom::Role::kLabelText,
                              CalculateAccessibleName());
   SetFocusBehavior(FocusBehavior::ALWAYS);
@@ -95,7 +161,7 @@
       text == kUnknownBind && !action_->is_new()
           ? cros_tokens::kCrosSysErrorHighlight
           : cros_tokens::kCrosSysHighlightShape,
-      /*radius=*/8));
+      kCornerRadius));
   if (HasFocus()) {
     SetToFocused();
   } else {
@@ -128,7 +194,7 @@
                              ? cros_tokens::kCrosSysError
                              : cros_tokens::kCrosSysHighlightText);
   SetBorder(views::CreateThemedRoundedRectBorder(
-      /*thickness=*/2, /*corner_radius=*/8, cros_tokens::kCrosSysPrimary));
+      /*thickness=*/2, kCornerRadius, cros_tokens::kCrosSysPrimary));
 }
 
 void EditLabel::OnFocus() {
diff --git a/chrome/browser/ash/arc/input_overlay/ui/edit_label.h b/chrome/browser/ash/arc/input_overlay/ui/edit_label.h
index dab38fc..c79955a64 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/edit_label.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/edit_label.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_ASH_ARC_INPUT_OVERLAY_UI_EDIT_LABEL_H_
 #define CHROME_BROWSER_ASH_ARC_INPUT_OVERLAY_UI_EDIT_LABEL_H_
 
+#include <memory>
 #include <string>
 
 #include "base/memory/raw_ptr.h"
@@ -33,6 +34,8 @@
   bool IsInputUnbound();
   void RemoveNewState();
 
+  void PerformPulseAnimation(int pulse_count);
+
  private:
   friend class ButtonOptionsMenuTest;
   friend class EditLabelTest;
@@ -52,6 +55,9 @@
   void OnBlur() override;
   bool OnKeyPressed(const ui::KeyEvent& event) override;
 
+  // Layer for edit label pulse animation.
+  std::unique_ptr<ui::Layer> pulse_layer_;
+
   raw_ptr<DisplayOverlayController> controller_ = nullptr;
   raw_ptr<Action, DanglingUntriaged> action_ = nullptr;
 
diff --git a/chrome/browser/ash/arc/input_overlay/ui/edit_labels.cc b/chrome/browser/ash/arc/input_overlay/ui/edit_labels.cc
index 2b7ffdb..53959f0cc 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/edit_labels.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/edit_labels.cc
@@ -133,6 +133,11 @@
   return prefix_string + key_string;
 }
 
+void EditLabels::PerformPulseAnimationOnFirstLabel() {
+  DCHECK_GE(labels_.size(), 1u);
+  labels_[0]->PerformPulseAnimation(/*pulse_count=*/0);
+}
+
 void EditLabels::InitForActionTapKeyboard() {
   SetUseDefaultFillLayout(true);
   labels_.emplace_back(
diff --git a/chrome/browser/ash/arc/input_overlay/ui/edit_labels.h b/chrome/browser/ash/arc/input_overlay/ui/edit_labels.h
index 33488e7c..83c3672 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/edit_labels.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/edit_labels.h
@@ -59,10 +59,11 @@
   // Called when this view is clicked upon.
   void FocusLabel();
 
-
   // Returns Action name, such as "Joystick WASD".
   std::u16string CalculateActionName();
 
+  void PerformPulseAnimationOnFirstLabel();
+
   void set_should_update_title(bool should_update_title) {
     should_update_title_ = should_update_title;
   }
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc b/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
index 36be6fa..a41ca609 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list.cc
@@ -17,6 +17,7 @@
 #include "ash/style/style_util.h"
 #include "ash/style/typography.h"
 #include "ash/system/toast/anchored_nudge_manager_impl.h"
+#include "base/check_op.h"
 #include "base/notreached.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ash/arc/input_overlay/actions/action.h"
@@ -43,6 +44,7 @@
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/table_layout_view.h"
 #include "ui/views/view_class_properties.h"
+#include "ui/views/view_utils.h"
 
 namespace arc::input_overlay {
 
@@ -85,64 +87,6 @@
       widget, gfx::Rect(GetWidgetMagneticPositionLocal(), GetPreferredSize()));
 }
 
-void EditingList::MayShowEduNudgeForEditingTip() {
-  // If key edit nudge has already shown once, no need to show it again.
-  if (!show_nudge_) {
-    return;
-  }
-
-  const auto& list_children = scroll_content_->children();
-  DCHECK_EQ(list_children.size(), 1u);
-
-  // TODO(b/274690042): Replace it with localized strings.
-  auto nudge_data = ash::AnchoredNudgeData(
-      kKeyEditNudgeID, ash::NudgeCatalogName::kGameDashboardControlsNudge,
-      u"Reassign by selecting a new key", list_children[0]);
-  nudge_data.title_text = u"Quickly switch keys";
-  nudge_data.image_model =
-      ui::ResourceBundle::GetSharedInstance().GetThemedLottieImageNamed(
-          IDR_ARC_INPUT_OVERLAY_KEY_EDIT_NUDGE_JSON);
-  nudge_data.background_color_id = cros_tokens::kCrosSysBaseHighlight;
-  nudge_data.image_background_color_id = cros_tokens::kCrosSysOnBaseHighlight;
-  nudge_data.arrow = views::BubbleBorder::LEFT_CENTER;
-  ash::Shell::Get()->anchored_nudge_manager()->Show(nudge_data);
-  show_nudge_ = false;
-}
-
-bool EditingList::OnMousePressed(const ui::MouseEvent& event) {
-  OnDragStart(event);
-  return true;
-}
-
-bool EditingList::OnMouseDragged(const ui::MouseEvent& event) {
-  OnDragUpdate(event);
-  return true;
-}
-
-void EditingList::OnMouseReleased(const ui::MouseEvent& event) {
-  OnDragEnd(event);
-}
-
-void EditingList::OnGestureEvent(ui::GestureEvent* event) {
-  switch (event->type()) {
-    case ui::ET_GESTURE_SCROLL_BEGIN:
-      OnDragStart(*event);
-      event->SetHandled();
-      break;
-    case ui::ET_GESTURE_SCROLL_UPDATE:
-      OnDragUpdate(*event);
-      event->SetHandled();
-      break;
-    case ui::ET_GESTURE_SCROLL_END:
-    case ui::ET_SCROLL_FLING_START:
-      OnDragEnd(*event);
-      event->SetHandled();
-      break;
-    default:
-      return;
-  }
-}
-
 void EditingList::Init() {
   SetBackground(views::CreateThemedRoundedRectBackground(
       cros_tokens::kCrosSysSystemBaseElevatedOpaque, /*radius=*/24));
@@ -312,6 +256,42 @@
   }
 }
 
+void EditingList::MaybeApplyEduDecoration() {
+  // Show education decoration only once.
+  if (show_edu_) {
+    ShowKeyEditNudge();
+    PerformPulseAnimation();
+    show_edu_ = false;
+  }
+}
+
+void EditingList::ShowKeyEditNudge() {
+  const auto& list_children = scroll_content_->children();
+  DCHECK_EQ(list_children.size(), 1u);
+
+  // TODO(b/274690042): Replace it with localized strings.
+  auto nudge_data = ash::AnchoredNudgeData(
+      kKeyEditNudgeID, ash::NudgeCatalogName::kGameDashboardControlsNudge,
+      u"Reassign by selecting a new key", list_children[0]);
+  nudge_data.title_text = u"Quickly switch keys";
+  nudge_data.image_model =
+      ui::ResourceBundle::GetSharedInstance().GetThemedLottieImageNamed(
+          IDR_ARC_INPUT_OVERLAY_KEY_EDIT_NUDGE_JSON);
+  nudge_data.background_color_id = cros_tokens::kCrosSysBaseHighlight;
+  nudge_data.image_background_color_id = cros_tokens::kCrosSysOnBaseHighlight;
+  nudge_data.arrow = views::BubbleBorder::LEFT_CENTER;
+  ash::Shell::Get()->anchored_nudge_manager()->Show(nudge_data);
+}
+
+void EditingList::PerformPulseAnimation() {
+  const auto& scroll_children = scroll_content_->children();
+  DCHECK_EQ(scroll_children.size(), 1u);
+  if (auto* list_item =
+          views::AsViewClass<ActionViewListItem>(scroll_children[0])) {
+    list_item->PerformPulseAnimation();
+  }
+}
+
 void EditingList::UpdateOnZeroState(bool is_zero_state) {
   is_zero_state_ = is_zero_state;
 
@@ -497,12 +477,52 @@
   focus_ring->SetHaloThickness(kHaloThickness);
 }
 
+bool EditingList::OnMousePressed(const ui::MouseEvent& event) {
+  OnDragStart(event);
+  return true;
+}
+
+bool EditingList::OnMouseDragged(const ui::MouseEvent& event) {
+  OnDragUpdate(event);
+  return true;
+}
+
+void EditingList::OnMouseReleased(const ui::MouseEvent& event) {
+  OnDragEnd(event);
+}
+
+void EditingList::OnGestureEvent(ui::GestureEvent* event) {
+  switch (event->type()) {
+    case ui::ET_GESTURE_SCROLL_BEGIN:
+      OnDragStart(*event);
+      event->SetHandled();
+      break;
+    case ui::ET_GESTURE_SCROLL_UPDATE:
+      OnDragUpdate(*event);
+      event->SetHandled();
+      break;
+    case ui::ET_GESTURE_SCROLL_END:
+    case ui::ET_SCROLL_FLING_START:
+      OnDragEnd(*event);
+      event->SetHandled();
+      break;
+    default:
+      return;
+  }
+}
+void EditingList::VisibilityChanged(views::View* starting_from,
+                                    bool is_visible) {
+  if (is_visible) {
+    MaybeApplyEduDecoration();
+  }
+}
+
 void EditingList::OnActionAdded(Action& action) {
   DCHECK(scroll_content_);
   if (controller_->GetActiveActionsSize() == 1u) {
     // Clear the zero-state.
     UpdateOnZeroState(/*is_zero_state=*/false);
-    show_nudge_ = true;
+    show_edu_ = true;
   }
   scroll_content_->AddChildView(
       std::make_unique<ActionViewListItem>(controller_, &action));
diff --git a/chrome/browser/ash/arc/input_overlay/ui/editing_list.h b/chrome/browser/ash/arc/input_overlay/ui/editing_list.h
index 380865b6..20e0d56 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/editing_list.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/editing_list.h
@@ -51,14 +51,6 @@
 
   void UpdateWidget();
 
-  void MayShowEduNudgeForEditingTip();
-
-  // views::View:
-  bool OnMousePressed(const ui::MouseEvent& event) override;
-  bool OnMouseDragged(const ui::MouseEvent& event) override;
-  void OnMouseReleased(const ui::MouseEvent& event) override;
-  void OnGestureEvent(ui::GestureEvent* event) override;
-
  private:
   friend class ButtonOptionsMenuTest;
   friend class EditingListTest;
@@ -75,6 +67,11 @@
   // Add the list view for the actions / controls.
   void AddControlListContent();
 
+  // These are called after adding the first new action.
+  void MaybeApplyEduDecoration();
+  void ShowKeyEditNudge();
+  void PerformPulseAnimation();
+
   // Updates changes depending on whether `is_zero_state` is true.
   void UpdateOnZeroState(bool is_zero_state);
 
@@ -112,6 +109,11 @@
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
   void OnThemeChanged() override;
+  bool OnMousePressed(const ui::MouseEvent& event) override;
+  bool OnMouseDragged(const ui::MouseEvent& event) override;
+  void OnMouseReleased(const ui::MouseEvent& event) override;
+  void OnGestureEvent(ui::GestureEvent* event) override;
+  void VisibilityChanged(views::View* starting_from, bool is_visible) override;
 
   // TouchInjectorObserver:
   void OnActionAdded(Action& action) override;
@@ -125,7 +127,7 @@
   bool IsKeyEditNudgeShownForTesting() const;
   ash::AnchoredNudge* GetKeyEditNudgeForTesting() const;
 
-  raw_ptr<DisplayOverlayController> controller_;
+  const raw_ptr<DisplayOverlayController> controller_;
 
   // It wraps ActionViewListItem.
   raw_ptr<views::View> scroll_content_;
@@ -142,8 +144,8 @@
 
   // Used to tell if the zero state view shows up.
   bool is_zero_state_ = false;
-  // Show nudge once after adding the first action.
-  bool show_nudge_ = false;
+  // Show education decoration once after adding the first action.
+  bool show_edu_ = false;
 
   // LocatedEvent's position when drag starts.
   gfx::Point start_drag_event_pos_;
diff --git a/chrome/browser/ash/browser_accelerator_configuration.cc b/chrome/browser/ash/browser_accelerator_configuration.cc
index 727691c..29a1d1d 100644
--- a/chrome/browser/ash/browser_accelerator_configuration.cc
+++ b/chrome/browser/ash/browser_accelerator_configuration.cc
@@ -17,7 +17,7 @@
 
 BrowserAcceleratorConfiguration::~BrowserAcceleratorConfiguration() = default;
 
-const std::vector<ui::Accelerator>&
+base::optional_ref<const std::vector<ui::Accelerator>>
 BrowserAcceleratorConfiguration::GetAcceleratorsForAction(
     AcceleratorActionId action_id) {
   // TODO(jimmyxgong): Implement stub.
diff --git a/chrome/browser/ash/browser_accelerator_configuration.h b/chrome/browser/ash/browser_accelerator_configuration.h
index 673fdbf1..8fb68ff 100644
--- a/chrome/browser/ash/browser_accelerator_configuration.h
+++ b/chrome/browser/ash/browser_accelerator_configuration.h
@@ -9,6 +9,7 @@
 #include "ash/public/cpp/accelerator_configuration.h"
 #include "ash/public/mojom/accelerator_configuration.mojom.h"
 #include "ash/public/mojom/accelerator_info.mojom.h"
+#include "base/types/optional_ref.h"
 #include "ui/base/accelerators/accelerator.h"
 
 #include <vector>
@@ -31,8 +32,8 @@
   ~BrowserAcceleratorConfiguration() override;
 
   // AcceleratorConfiguration:
-  const std::vector<ui::Accelerator>& GetAcceleratorsForAction(
-      AcceleratorActionId action_id) override;
+  base::optional_ref<const std::vector<ui::Accelerator>>
+  GetAcceleratorsForAction(AcceleratorActionId action_id) override;
   bool IsMutable() const override;
   bool IsDeprecated(const ui::Accelerator& accelerator) const override;
   mojom::AcceleratorConfigResult AddUserAccelerator(
diff --git a/chrome/browser/ash/browser_context_keyed_service_factories.cc b/chrome/browser/ash/browser_context_keyed_service_factories.cc
index 691e307..8b95ca7 100644
--- a/chrome/browser/ash/browser_context_keyed_service_factories.cc
+++ b/chrome/browser/ash/browser_context_keyed_service_factories.cc
@@ -114,6 +114,7 @@
 #include "chrome/browser/sharesheet/sharesheet_service_factory.h"
 #include "chrome/browser/speech/cros_speech_recognition_service_factory.h"
 #include "chrome/browser/speech/extension_api/tts_engine_extension_observer_chromeos.h"
+#include "chrome/browser/ui/ash/birch/birch_keyed_service_factory.h"
 #include "chrome/browser/ui/ash/calendar/calendar_keyed_service_factory.h"
 #include "chrome/browser/ui/ash/desks/admin_template_service_factory.h"
 #include "chrome/browser/ui/ash/glanceables/glanceables_keyed_service_factory.h"
@@ -146,6 +147,7 @@
   ArcKioskAppServiceFactory::GetInstance();
   AuthErrorObserverFactory::GetInstance();
   ax::AccessibilityServiceRouterFactory::EnsureFactoryBuilt();
+  BirchKeyedServiceFactory::GetInstance();
   bluetooth::DebugLogsManagerFactory::GetInstance();
   borealis::BorealisServiceFactory::GetInstance();
   BrowserProcessPlatformPart::EnsureFactoryBuilt();
diff --git a/chrome/browser/ash/crosapi/browser_launcher.cc b/chrome/browser/ash/crosapi/browser_launcher.cc
index d78e95c91f..00567705 100644
--- a/chrome/browser/ash/crosapi/browser_launcher.cc
+++ b/chrome/browser/ash/crosapi/browser_launcher.cc
@@ -10,7 +10,9 @@
 
 #include "ash/constants/ash_switches.h"
 #include "base/base_switches.h"
+#include "base/check_is_test.h"
 #include "base/command_line.h"
+#include "base/containers/contains.h"
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -18,6 +20,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/logging.h"
+#include "base/no_destructor.h"
 #include "base/process/launch.h"
 #include "base/process/process.h"
 #include "base/strings/string_split.h"
@@ -46,6 +49,12 @@
 
 namespace {
 
+// Returns additional test-only arguments for Lacros.
+std::vector<std::string>& GetLacrosTestArguments() {
+  static base::NoDestructor<std::vector<std::string>> args;
+  return *args;
+}
+
 base::FilePath LacrosPostLoginLogPath() {
   return browser_util::GetUserDataDir().Append("lacros.log");
 }
@@ -170,6 +179,21 @@
   return LaunchProcessWithParameters(command_line, options);
 }
 
+// static
+void BrowserLauncher::AddLacrosArgumentsForTest(
+    const std::vector<std::string>& args) {
+  CHECK_IS_TEST();
+  for (const std::string& arg : args) {
+    CHECK(!base::Contains(arg, switches::kEnableFeatures) &&
+          !base::Contains(arg, switches::kDisableFeatures))
+        << "AddLacrosArgumentsForTest() does not support "  // IN-TEST
+           "--enable-features or --disable-features. Use the ash switch "
+           "kLacrosChromeAdditionalArgs instead.";
+  }
+  std::vector<std::string>& test_args = GetLacrosTestArguments();
+  test_args.insert(test_args.end(), args.begin(), args.end());
+}
+
 std::vector<std::string> BrowserLauncher::InitializeArgv(
     const base::FilePath& chrome_path,
     const LaunchParamsFromBackground& params,
@@ -234,6 +258,10 @@
   argv.insert(argv.end(), params.lacros_additional_args.begin(),
               params.lacros_additional_args.end());
 
+  // Provide any test switches.
+  const auto& test_args = GetLacrosTestArguments();
+  argv.insert(argv.end(), test_args.begin(), test_args.end());
+
   // Forward flag for zero copy video capture to Lacros if it is enabled.
   if (switches::IsVideoCaptureUseGpuMemoryBufferEnabled()) {
     argv.emplace_back(
diff --git a/chrome/browser/ash/crosapi/browser_launcher.h b/chrome/browser/ash/crosapi/browser_launcher.h
index 2ee5349..17c26a32 100644
--- a/chrome/browser/ash/crosapi/browser_launcher.h
+++ b/chrome/browser/ash/crosapi/browser_launcher.h
@@ -109,6 +109,9 @@
   bool LaunchProcessForTesting(const base::CommandLine& command_line,
                                const base::LaunchOptions& options);
 
+  // Adds command line arguments that will be passed to Lacros.
+  static void AddLacrosArgumentsForTest(const std::vector<std::string>& args);
+
  private:
   // Initializes argv for making the command line.
   // TODO(mayukoaiba): The process of making `command_line` is separated into 2
diff --git a/chrome/browser/ash/crosapi/prefs_ash.cc b/chrome/browser/ash/crosapi/prefs_ash.cc
index 3ac016a8..339a57d6 100644
--- a/chrome/browser/ash/crosapi/prefs_ash.cc
+++ b/chrome/browser/ash/crosapi/prefs_ash.cc
@@ -71,6 +71,8 @@
            media_router::prefs::kAccessCodeCastDeviceAdditionTime},
           {mojom::PrefPath::kDefaultSearchProviderDataPrefName,
            DefaultSearchManager::kDefaultSearchProviderDataPrefName},
+          {mojom::PrefPath::kIsolatedWebAppsEnabled,
+           ash::prefs::kIsolatedWebAppsEnabled},
       });
   auto* pref_name = kProfilePrefPathToName.find(path);
   DCHECK(pref_name != kProfilePrefPathToName.end());
@@ -292,7 +294,8 @@
     case mojom::PrefPath::kMultitaskMenuNudgeClamshellLastShown:
     case mojom::PrefPath::kAccessCodeCastDevices:
     case mojom::PrefPath::kAccessCodeCastDeviceAdditionTime:
-    case mojom::PrefPath::kDefaultSearchProviderDataPrefName: {
+    case mojom::PrefPath::kDefaultSearchProviderDataPrefName:
+    case mojom::PrefPath::kIsolatedWebAppsEnabled: {
       if (!profile_prefs_registrar_) {
         LOG(WARNING) << "Primary profile is not yet initialized";
         return std::nullopt;
diff --git a/chrome/browser/ash/login/app_mode/test/kiosk_ash_browser_test_starter.cc b/chrome/browser/ash/login/app_mode/test/kiosk_ash_browser_test_starter.cc
index 25200dd..8be4679 100644
--- a/chrome/browser/ash/login/app_mode/test/kiosk_ash_browser_test_starter.cc
+++ b/chrome/browser/ash/login/app_mode/test/kiosk_ash_browser_test_starter.cc
@@ -10,6 +10,7 @@
 #include "base/check.h"
 #include "base/command_line.h"
 #include "base/environment.h"
+#include "chrome/browser/ash/crosapi/browser_launcher.h"
 #include "chrome/browser/ash/crosapi/browser_manager.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/crosapi/fake_device_ownership_waiter.h"
@@ -43,9 +44,7 @@
       // environment.
       // See details in crbug/1483530.
       "--disable-gpu-sandbox"};
-  base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
-      ash::switches::kLacrosChromeAdditionalArgs,
-      base::JoinString(lacros_args, "####"));
+  crosapi::BrowserLauncher::AddLacrosArgumentsForTest(lacros_args);
 }
 
 void KioskAshBrowserTestStarter::SetLacrosAvailabilityPolicy() {
diff --git a/chrome/browser/ash/login/session/user_session_initializer.cc b/chrome/browser/ash/login/session/user_session_initializer.cc
index 6016938..2828e194 100644
--- a/chrome/browser/ash/login/session/user_session_initializer.cc
+++ b/chrome/browser/ash/login/session/user_session_initializer.cc
@@ -42,6 +42,7 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/scalable_iph/scalable_iph_factory.h"
 #include "chrome/browser/screen_ai/screen_ai_dlc_installer.h"
+#include "chrome/browser/ui/ash/birch/birch_keyed_service_factory.h"
 #include "chrome/browser/ui/ash/calendar/calendar_keyed_service_factory.h"
 #include "chrome/browser/ui/ash/glanceables/glanceables_keyed_service_factory.h"
 #include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service_factory.h"
@@ -267,6 +268,10 @@
   // Ensure that the `HoldingSpaceKeyedService` for `profile` is created.
   HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile);
 
+  // Ensure that the `BirchKeyedService` for `profile` is created. It is created
+  // one per user in a multiprofile session.
+  BirchKeyedServiceFactory::GetInstance()->GetService(profile);
+
   // Ensure that the `CalendarKeyedService` for `profile` is created. It is
   // created one per user in a multiprofile session.
   CalendarKeyedServiceFactory::GetInstance()->GetService(profile);
diff --git a/chrome/browser/ash/net/network_portal_detector_impl.cc b/chrome/browser/ash/net/network_portal_detector_impl.cc
index fcb8643d7..398de7d 100644
--- a/chrome/browser/ash/net/network_portal_detector_impl.cc
+++ b/chrome/browser/ash/net/network_portal_detector_impl.cc
@@ -23,7 +23,6 @@
 #include "chromeos/ash/components/network/network_handler.h"
 #include "chromeos/ash/components/network/network_state.h"
 #include "chromeos/ash/components/network/network_state_handler.h"
-#include "content/public/browser/notification_service.h"
 #include "net/http/http_status_code.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -39,9 +38,6 @@
 // is used (for detecting proxy auth or when Shill portal state is unknown).
 constexpr base::TimeDelta kDefaultAttemptDelay = base::Seconds(1);
 
-// Delay before portal detection caused by changes in proxy settings.
-constexpr int kProxyChangeDelaySec = 1;
-
 // Timeout for attempts.
 constexpr base::TimeDelta kAttemptTimeout = base::Seconds(10);
 
@@ -110,11 +106,6 @@
   captive_portal_detector_ =
       std::make_unique<CaptivePortalDetector>(loader_factory);
 
-  registrar_.Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
-                 content::NotificationService::AllSources());
-  registrar_.Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
-                 content::NotificationService::AllSources());
-
   network_state_handler_observer_.Observe(
       NetworkHandler::Get()->network_state_handler());
 }
@@ -417,19 +408,6 @@
   ScheduleAttempt(results.retry_after_delta);
 }
 
-void NetworkPortalDetectorImpl::Observe(
-    int type,
-    const content::NotificationSource& source,
-    const content::NotificationDetails& details) {
-  if (type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
-      type == chrome::NOTIFICATION_AUTH_CANCELLED) {
-    NET_LOG(EVENT) << "Restarting portal detection due to auth change"
-                   << " id=" << NetworkGuidId(default_network_id_);
-    StopDetection();
-    ScheduleAttempt(base::Seconds(kProxyChangeDelaySec));
-  }
-}
-
 void NetworkPortalDetectorImpl::DetectionCompleted(
     const NetworkState* network,
     const CaptivePortalStatus& status) {
diff --git a/chrome/browser/ash/net/network_portal_detector_impl.h b/chrome/browser/ash/net/network_portal_detector_impl.h
index 8675982..1adb236d 100644
--- a/chrome/browser/ash/net/network_portal_detector_impl.h
+++ b/chrome/browser/ash/net/network_portal_detector_impl.h
@@ -22,8 +22,6 @@
 #include "chromeos/ash/components/network/portal_detector/network_portal_detector.h"
 #include "components/captive_portal/core/captive_portal_detector.h"
 #include "components/captive_portal/core/captive_portal_types.h"
-#include "content/public/browser/notification_observer.h"
-#include "content/public/browser/notification_registrar.h"
 #include "url/gurl.h"
 
 namespace network {
@@ -50,8 +48,7 @@
 // The status reflects the combined Shill + Chrome detection results
 // (as does NetworkState::GetPortalState()).
 class NetworkPortalDetectorImpl : public NetworkPortalDetector,
-                                  public NetworkStateHandlerObserver,
-                                  public content::NotificationObserver {
+                                  public NetworkStateHandlerObserver {
  public:
   explicit NetworkPortalDetectorImpl(
       network::mojom::URLLoaderFactory* loader_factory_for_testing = nullptr);
@@ -106,11 +103,6 @@
   void PortalStateChanged(const NetworkState* default_network,
                           NetworkState::PortalState portal_state) override;
 
-  // content::NotificationObserver implementation:
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override;
-
   void DetectionCompleted(const NetworkState* network,
                           const CaptivePortalStatus& results);
 
@@ -189,8 +181,6 @@
 
   SEQUENCE_CHECKER(sequence_checker_);
 
-  content::NotificationRegistrar registrar_;
-
   base::ScopedObservation<NetworkStateHandler, NetworkStateHandlerObserver>
       network_state_handler_observer_{this};
 
diff --git a/chrome/browser/ash/preferences.cc b/chrome/browser/ash/preferences.cc
index cbe42c6d..1be0dc2 100644
--- a/chrome/browser/ash/preferences.cc
+++ b/chrome/browser/ash/preferences.cc
@@ -171,7 +171,6 @@
                                 false);
   registry->RegisterBooleanPref(prefs::kDeviceSwitchFunctionKeysBehaviorEnabled,
                                 false);
-  registry->RegisterBooleanPref(prefs::kIsolatedWebAppsEnabled, false);
 
   RegisterLocalStatePrefs(registry);
   ash::hid_detection_revamp_field_trial::RegisterLocalStatePrefs(registry);
@@ -613,6 +612,7 @@
   registry->RegisterBooleanPref(prefs::kShowHumanPresenceSensorScreenEnabled,
                                 true);
   registry->RegisterListPref(prefs::kUserFeedbackWithLowLevelDebugDataAllowed);
+  registry->RegisterBooleanPref(prefs::kIsolatedWebAppsEnabled, false);
 }
 
 void Preferences::InitUserPrefs(sync_preferences::PrefServiceSyncable* prefs) {
diff --git a/chrome/browser/back_press/android/BUILD.gn b/chrome/browser/back_press/android/BUILD.gn
index 388d6de..c5ae466 100644
--- a/chrome/browser/back_press/android/BUILD.gn
+++ b/chrome/browser/back_press/android/BUILD.gn
@@ -8,6 +8,7 @@
   sources = [
     "java/src/org/chromium/chrome/browser/back_press/BackPressHelper.java",
     "java/src/org/chromium/chrome/browser/back_press/BackPressManager.java",
+    "java/src/org/chromium/chrome/browser/back_press/BackPressMetrics.java",
     "java/src/org/chromium/chrome/browser/back_press/CloseListenerManager.java",
     "java/src/org/chromium/chrome/browser/back_press/MinimizeAppAndCloseTabBackPressHandler.java",
     "java/src/org/chromium/chrome/browser/back_press/SecondaryActivityBackPressUma.java",
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
index 4ebec7c..27dbafb 100644
--- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
+++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManager.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.back_press;
 
+import android.annotation.SuppressLint;
 import android.text.format.DateUtils;
 import android.util.SparseIntArray;
 
@@ -81,11 +82,25 @@
     private final OnBackPressedCallback mCallback =
             new OnBackPressedCallback(false) {
                 private BackPressHandler mActiveHandler;
+                private BackEventCompat mLastBackEvent;
 
+                @SuppressLint("WrongConstant") // Suppress mLastCalledHandlerType assignment warning
                 @Override
                 public void handleOnBackPressed() {
+                    mLastCalledHandlerType = -1;
                     BackPressManager.this.handleBackPress();
+                    // This means this back is triggered by a gesture rather than the back button.
+                    if (mLastBackEvent != null && mLastCalledHandlerType != -1) {
+                        BackPressMetrics.recordBackPressFromEdge(
+                                mLastCalledHandlerType, mLastBackEvent.getSwipeEdge());
+
+                        if (mLastCalledHandlerType == Type.TAB_HISTORY) {
+                            BackPressMetrics.recordTabNavigationSwipedFromEdge(
+                                    mLastBackEvent.getSwipeEdge());
+                        }
+                    }
                     mActiveHandler = null;
+                    mLastBackEvent = null;
                 }
 
                 // Following methods are only triggered on API 34+.
@@ -94,6 +109,7 @@
                     mActiveHandler = getEnabledBackPressHandler();
                     assert mActiveHandler != null;
                     mActiveHandler.handleOnBackStarted(backEvent);
+                    mLastBackEvent = backEvent;
                 }
 
                 @Override
@@ -101,6 +117,7 @@
                     if (mActiveHandler == null) return;
                     mActiveHandler.handleOnBackCancelled();
                     mActiveHandler = null;
+                    mLastBackEvent = null;
                 }
 
                 @Override
@@ -120,7 +137,7 @@
 
     private final Callback<Boolean>[] mObserverCallbacks = new Callback[Type.NUM_TYPES];
     private Runnable mFallbackOnBackPressed;
-    private int mLastCalledHandlerForTesting = -1;
+    private int mLastCalledHandlerType = -1;
     // Do not use static; otherwise the data might be corrupted because of multi-window usage.
     private long mLastPressMs = -1;
 
@@ -170,6 +187,14 @@
     }
 
     /**
+     * @param type The {@link Type} of the back press handler.
+     * @return The corresponding histogram value.
+     */
+    public static int getHistogramValue(@Type int type) {
+        return sMetricsMap.get(type);
+    }
+
+    /**
      * Record the interval between two consecutive back press events. Should be called when
      * a back press event is intercepted.
      */
@@ -308,7 +333,7 @@
             Boolean enabled = handler.getHandleBackPressChangedSupplier().get();
             if (enabled != null && enabled) {
                 int res = handler.handleBackPress();
-                mLastCalledHandlerForTesting = i;
+                mLastCalledHandlerType = i;
                 if (res == BackPressResult.FAILURE) {
                     failed.add(i + "");
                     recordFailure(i);
@@ -357,18 +382,15 @@
     }
 
     public int getLastCalledHandlerForTesting() {
-        return mLastCalledHandlerForTesting;
+        return mLastCalledHandlerType;
     }
 
     public void resetLastCalledHandlerForTesting() {
-        mLastCalledHandlerForTesting = -1;
+        mLastCalledHandlerType = -1;
     }
 
     public static String getHistogramForTesting() {
         return HISTOGRAM;
     }
 
-    public static int getHistogramValueForTesting(int type) {
-        return sMetricsMap.get(type);
-    }
 }
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerTest.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerTest.java
index ed134c7..dcbd97e 100644
--- a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerTest.java
+++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressManagerTest.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.back_press;
 
+import androidx.activity.BackEventCompat;
 import androidx.test.filters.SmallTest;
 
 import org.junit.AfterClass;
@@ -155,11 +156,11 @@
                 HistogramWatcher.newBuilder()
                         .expectIntRecord(
                                 BackPressManager.FAILURE_HISTOGRAM,
-                                BackPressManager.getHistogramValueForTesting(
+                                BackPressManager.getHistogramValue(
                                         BackPressHandler.Type.VR_DELEGATE))
                         .expectIntRecord(
                                 BackPressManager.HISTOGRAM,
-                                BackPressManager.getHistogramValueForTesting(
+                                BackPressManager.getHistogramValue(
                                         BackPressHandler.Type.XR_DELEGATE))
                         .build();
         triggerBackPressWithoutAssertionError(manager);
@@ -184,7 +185,7 @@
                 HistogramWatcher.newBuilder()
                         .expectIntRecord(
                                 BackPressManager.FAILURE_HISTOGRAM,
-                                BackPressManager.getHistogramValueForTesting(
+                                BackPressManager.getHistogramValue(
                                         BackPressHandler.Type.VR_DELEGATE))
                         .build();
         triggerBackPressWithoutAssertionError(manager);
@@ -224,6 +225,161 @@
                         + " intercepted");
     }
 
+    @Test
+    @SmallTest
+    public void testNoRecordWhenBackIsCancelled() {
+        BackPressManager manager = new BackPressManager();
+
+        EmptyBackPressHandler h1 =
+                TestThreadUtils.runOnUiThreadBlockingNoException(EmptyBackPressHandler::new);
+
+        var record =
+                HistogramWatcher.newBuilder()
+                        .expectNoRecords("Android.BackPress.SwipeEdge")
+                        .expectNoRecords("Android.BackPress.Intercept")
+                        .expectNoRecords("Android.BackPress.Intercept.LeftEdge")
+                        .expectNoRecords("Android.BackPress.Intercept.RightEdge")
+                        .expectNoRecords("Android.BackPress.SwipeEdge.TabHistoryNavigation")
+                        .build();
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    manager.addHandler(h1, BackPressHandler.Type.TAB_HISTORY);
+                    h1.getHandleBackPressChangedSupplier().set(true);
+
+                    var backEvent = new BackEventCompat(0, 0, 0, BackEventCompat.EDGE_LEFT);
+                    manager.getCallback().handleOnBackStarted(backEvent);
+                    backEvent = new BackEventCompat(1, 0, .5f, BackEventCompat.EDGE_LEFT);
+                    manager.getCallback().handleOnBackProgressed(backEvent);
+
+                    manager.getCallback().handleOnBackCancelled();
+                });
+
+        record.assertExpected("No record when back gesture is cancelled.");
+    }
+
+    @Test
+    @SmallTest
+    public void testRecordSwipeEdge() {
+        BackPressManager manager = new BackPressManager();
+
+        EmptyBackPressHandler h1 =
+                TestThreadUtils.runOnUiThreadBlockingNoException(EmptyBackPressHandler::new);
+        EmptyBackPressHandler h2 =
+                TestThreadUtils.runOnUiThreadBlockingNoException(EmptyBackPressHandler::new);
+
+        var edgeRecords =
+                HistogramWatcher.newBuilder()
+                        .expectIntRecord("Android.BackPress.SwipeEdge", 0) // from left
+                        .expectIntRecord(
+                                "Android.BackPress.Intercept.LeftEdge",
+                                BackPressManager.getHistogramValue(
+                                        BackPressHandler.Type.XR_DELEGATE))
+                        .expectNoRecords("Android.BackPress.Intercept.RightEdge")
+                        .expectNoRecords("Android.BackPress.SwipeEdge.TabHistoryNavigation")
+                        .build();
+        // Trigger XR delegate back press handler from left side.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    manager.addHandler(h1, BackPressHandler.Type.VR_DELEGATE);
+                    manager.addHandler(h2, BackPressHandler.Type.XR_DELEGATE);
+                    h1.getHandleBackPressChangedSupplier().set(false);
+                    h2.getHandleBackPressChangedSupplier().set(true);
+
+                    var backEvent = new BackEventCompat(0, 0, 0, BackEventCompat.EDGE_LEFT);
+                    manager.getCallback().handleOnBackStarted(backEvent);
+                    backEvent = new BackEventCompat(1, 0, .5f, BackEventCompat.EDGE_LEFT);
+                    manager.getCallback().handleOnBackProgressed(backEvent);
+
+                    manager.getCallback().handleOnBackPressed();
+                });
+
+        edgeRecords.assertExpected("Wrong histogram records for XR delegate.");
+
+        var edgeRecords2 =
+                HistogramWatcher.newBuilder()
+                        .expectIntRecord("Android.BackPress.SwipeEdge", 1) // from right
+                        .expectIntRecord(
+                                "Android.BackPress.Intercept.RightEdge",
+                                BackPressManager.getHistogramValue(
+                                        BackPressHandler.Type.VR_DELEGATE))
+                        .expectNoRecords("Android.BackPress.Intercept.LeftEdge")
+                        .expectNoRecords("Android.BackPress.SwipeEdge.TabHistoryNavigation")
+                        .build();
+        // Trigger VR delegate back press handler from left side.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    h1.getHandleBackPressChangedSupplier().set(true);
+                    h2.getHandleBackPressChangedSupplier().set(true);
+
+                    var backEvent = new BackEventCompat(0, 0, 0, BackEventCompat.EDGE_RIGHT);
+                    manager.getCallback().handleOnBackStarted(backEvent);
+                    backEvent = new BackEventCompat(1, 0, .5f, BackEventCompat.EDGE_RIGHT);
+                    manager.getCallback().handleOnBackProgressed(backEvent);
+
+                    manager.getCallback().handleOnBackPressed();
+                });
+
+        edgeRecords2.assertExpected("Wrong histogram records for VR delegate.");
+    }
+
+    @Test
+    @SmallTest
+    public void testRecordSwipeEdgeOfTabHistoryNavigation() {
+        BackPressManager manager = new BackPressManager();
+
+        EmptyBackPressHandler h1 =
+                TestThreadUtils.runOnUiThreadBlockingNoException(EmptyBackPressHandler::new);
+
+        var edgeRecords =
+                HistogramWatcher.newBuilder()
+                        .expectIntRecord("Android.BackPress.SwipeEdge", 0) // from left
+                        .expectIntRecord("Android.BackPress.SwipeEdge.TabHistoryNavigation", 0)
+                        .expectIntRecord(
+                                "Android.BackPress.Intercept.LeftEdge",
+                                BackPressManager.getHistogramValue(
+                                        BackPressHandler.Type.TAB_HISTORY))
+                        .expectNoRecords("Android.BackPress.Intercept.RightEdge")
+                        .build();
+        // Trigger tab history navigation from left side.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    manager.addHandler(h1, BackPressHandler.Type.TAB_HISTORY);
+                    h1.getHandleBackPressChangedSupplier().set(true);
+
+                    var backEvent = new BackEventCompat(0, 0, 0, BackEventCompat.EDGE_LEFT);
+                    manager.getCallback().handleOnBackStarted(backEvent);
+                    backEvent = new BackEventCompat(1, 0, .5f, BackEventCompat.EDGE_LEFT);
+                    manager.getCallback().handleOnBackProgressed(backEvent);
+
+                    manager.getCallback().handleOnBackPressed();
+                });
+
+        edgeRecords.assertExpected("Wrong histogram records for tab history navigation.");
+
+        var edgeRecords2 =
+                HistogramWatcher.newBuilder()
+                        .expectIntRecord("Android.BackPress.SwipeEdge", 1) // from right
+                        .expectIntRecord("Android.BackPress.SwipeEdge.TabHistoryNavigation", 1)
+                        .expectIntRecord(
+                                "Android.BackPress.Intercept.RightEdge",
+                                BackPressManager.getHistogramValue(
+                                        BackPressHandler.Type.TAB_HISTORY))
+                        .expectNoRecords("Android.BackPress.Intercept.LeftEdge")
+                        .build();
+        // Trigger tab history navigation from right side.
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    var backEvent = new BackEventCompat(0, 0, 0, BackEventCompat.EDGE_RIGHT);
+                    manager.getCallback().handleOnBackStarted(backEvent);
+                    backEvent = new BackEventCompat(1, 0, .5f, BackEventCompat.EDGE_RIGHT);
+                    manager.getCallback().handleOnBackProgressed(backEvent);
+
+                    manager.getCallback().handleOnBackPressed();
+                });
+
+        edgeRecords2.assertExpected("Wrong histogram records for VR delegate.");
+    }
+
     // Trigger back press ignoring built-in assertion errors.
     private void triggerBackPressWithoutAssertionError(BackPressManager manager) {
         TestThreadUtils.runOnUiThreadBlocking(
diff --git a/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressMetrics.java b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressMetrics.java
new file mode 100644
index 0000000..eeeb5b7
--- /dev/null
+++ b/chrome/browser/back_press/android/java/src/org/chromium/chrome/browser/back_press/BackPressMetrics.java
@@ -0,0 +1,46 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.back_press;
+
+import androidx.activity.BackEventCompat;
+
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.components.browser_ui.widget.gesture.BackPressHandler.Type;
+
+/**
+ * A utility class to record back press related histograms. TODO(https://crbug.com/1509190): Move
+ * other histogram recording to this class.
+ */
+public class BackPressMetrics {
+    private static final String EDGE_HISTOGRAM = "Android.BackPress.SwipeEdge";
+    private static final String TAB_HISTORY_EDGE_HISTOGRAM =
+            "Android.BackPress.SwipeEdge.TabHistoryNavigation";
+    private static final String INTERCEPT_FROM_LEFT_HISTOGRAM =
+            "Android.BackPress.Intercept.LeftEdge";
+    private static final String INTERCEPT_FROM_RIGHT_HISTOGRAM =
+            "Android.BackPress.Intercept.RightEdge";
+
+    /**
+     * @param type The {@link Type} of the back press handler.
+     * @param edge The edge from which the gesture is swiped from {@link BackEventCompat}.
+     */
+    public static void recordBackPressFromEdge(@Type int type, int edge) {
+        RecordHistogram.recordEnumeratedHistogram(EDGE_HISTOGRAM, edge, 2);
+
+        String histogram =
+                edge == BackEventCompat.EDGE_LEFT
+                        ? INTERCEPT_FROM_LEFT_HISTOGRAM
+                        : INTERCEPT_FROM_RIGHT_HISTOGRAM;
+        RecordHistogram.recordEnumeratedHistogram(
+                histogram, BackPressManager.getHistogramValue(type), Type.NUM_TYPES);
+    }
+
+    /**
+     * @param edge The edge from which the gesture is swiped from {@link BackEventCompat}.
+     */
+    public static void recordTabNavigationSwipedFromEdge(int edge) {
+        RecordHistogram.recordEnumeratedHistogram(TAB_HISTORY_EDGE_HISTOGRAM, edge, 2);
+    }
+}
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index bdf1aba3..ae0697b3 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -448,6 +448,7 @@
 #endif
 
 #if BUILDFLAG(ENABLE_COMPOSE)
+#include "chrome/browser/compose/compose_enabling.h"
 #include "chrome/browser/ui/webui/compose/compose_ui.h"
 #include "chrome/common/compose/compose.mojom.h"
 #include "components/compose/core/browser/compose_features.h"  // nogncheck crbug.com/1125897
@@ -1723,7 +1724,8 @@
 #endif
 
 #if BUILDFLAG(ENABLE_COMPOSE)
-  if (base::FeatureList::IsEnabled(compose::features::kEnableCompose)) {
+  if (ComposeEnabling::IsEnabledForProfile(Profile::FromBrowserContext(
+          render_frame_host->GetBrowserContext()))) {
     RegisterWebUIControllerInterfaceBinder<
         compose::mojom::ComposeSessionPageHandlerFactory, ComposeUI>(map);
   }
diff --git a/chrome/browser/commerce/price_change/android/BUILD.gn b/chrome/browser/commerce/price_change/android/BUILD.gn
index 5110d4c..f2bac7909 100644
--- a/chrome/browser/commerce/price_change/android/BUILD.gn
+++ b/chrome/browser/commerce/price_change/android/BUILD.gn
@@ -37,7 +37,6 @@
 
 android_resources("java_resources") {
   sources = [
-    "java/res/drawable/price_change_module_background.xml",
     "java/res/drawable/price_change_module_favicon_background.xml",
     "java/res/layout/price_change_module_layout.xml",
     "java/res/values/dimens.xml",
diff --git a/chrome/browser/commerce/price_change/android/java/res/drawable/price_change_module_background.xml b/chrome/browser/commerce/price_change/android/java/res/drawable/price_change_module_background.xml
deleted file mode 100644
index ad95ab8..0000000
--- a/chrome/browser/commerce/price_change/android/java/res/drawable/price_change_module_background.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-Copyright 2023 The Chromium Authors
-Use of this source code is governed by a BSD-style license that can be
-found in the LICENSE file.
--->
-<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    tools:ignore="UnusedResources"
-    android:shape="rectangle"
-    app:surfaceElevation="@dimen/price_change_module_background_color_elevation">
-  <corners android:radius="@dimen/price_change_module_background_radius"/>
-</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/commerce/price_change/android/java/res/drawable/price_change_module_favicon_background.xml b/chrome/browser/commerce/price_change/android/java/res/drawable/price_change_module_favicon_background.xml
index e33d54ad..453c10b6 100644
--- a/chrome/browser/commerce/price_change/android/java/res/drawable/price_change_module_favicon_background.xml
+++ b/chrome/browser/commerce/price_change/android/java/res/drawable/price_change_module_favicon_background.xml
@@ -10,6 +10,6 @@
     xmlns:tools="http://schemas.android.com/tools"
     tools:ignore="UnusedResources"
     android:shape="rectangle"
-    app:surfaceElevation="@dimen/price_change_module_background_color_elevation">
+    app:surfaceElevation="@dimen/home_surface_ui_background_color_elevation">
   <corners android:radius="@dimen/price_change_module_favicon_background_radius"/>
 </org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
\ No newline at end of file
diff --git a/chrome/browser/commerce/price_change/android/java/res/layout/price_change_module_layout.xml b/chrome/browser/commerce/price_change/android/java/res/layout/price_change_module_layout.xml
index f401fe1..28e6718 100644
--- a/chrome/browser/commerce/price_change/android/java/res/layout/price_change_module_layout.xml
+++ b/chrome/browser/commerce/price_change/android/java/res/layout/price_change_module_layout.xml
@@ -19,7 +19,7 @@
         android:layout_height="match_parent"
         android:layout_margin="@dimen/price_change_module_margin"
         android:orientation="vertical"
-        android:background="@drawable/price_change_module_background">
+        android:background="@drawable/home_surface_ui_background">
         <TextView
             android:id="@+id/header_text"
             android:layout_width="wrap_content"
@@ -40,8 +40,12 @@
             <RelativeLayout
                 android:layout_height="@dimen/price_change_module_product_image_size"
                 android:layout_width="@dimen/price_change_module_product_image_size">
-                <org.chromium.ui.widget.ChromeImageView
+                <org.chromium.components.browser_ui.widget.RoundedCornerImageView
                     android:id="@+id/product_image"
+                    app:cornerRadiusBottomEnd="@dimen/price_change_module_product_image_corner_radius"
+                    app:cornerRadiusBottomStart="@dimen/price_change_module_product_image_corner_radius"
+                    app:cornerRadiusTopEnd="@dimen/price_change_module_product_image_corner_radius"
+                    app:cornerRadiusTopStart="@dimen/price_change_module_product_image_corner_radius"
                     android:layout_height="@dimen/price_change_module_product_image_size"
                     android:layout_width="@dimen/price_change_module_product_image_size"
                     android:scaleType="centerCrop" />
@@ -58,10 +62,9 @@
                     android:padding="4dp" />
             </RelativeLayout>
             <LinearLayout
-                android:layout_height="wrap_content"
+                android:layout_height="@dimen/price_change_module_product_image_size"
                 android:layout_width="wrap_content"
                 android:layout_marginStart="16dp"
-                android:layout_marginEnd="20dp"
                 android:orientation="vertical">
                 <LinearLayout
                     android:id="@+id/price_info_box"
@@ -78,7 +81,7 @@
                         android:layout_marginEnd="4dp"
                         android:layout_marginBottom="2dp"
                         android:singleLine="true"
-                        android:textAppearance="@style/TextAppearance.TextMediumThick"
+                        android:textAppearance="@style/TextAppearance.ShoppingPriceDropText"
                         android:textAlignment="viewStart" />
                     <TextView
                         android:id="@+id/previous_price"
@@ -88,7 +91,7 @@
                         android:layout_marginEnd="8dp"
                         android:layout_marginBottom="2dp"
                         android:singleLine="true"
-                        android:textAppearance="@style/TextAppearance.TextMedium.Secondary.Baseline.Dark"
+                        android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
                         android:textAlignment="viewStart" />
                 </LinearLayout>
                 <TextView
@@ -99,19 +102,20 @@
                     android:layout_marginTop="8dp"
                     android:textAppearance="@style/TextAppearance.TextLarge.Primary"
                     android:maxLines="2"
-                    android:ellipsize="end"
-                    app:tint="@color/default_icon_color_secondary_tint_list"/>
-                <TextView
-                    android:id="@+id/price_drop_domain"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textAlignment="viewStart"
-                    android:layout_marginTop="16dp"
-                    android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
-                    android:layout_gravity="bottom|start"
-                    android:maxLines="1"
-                    android:ellipsize="end"
-                    app:tint="@color/default_icon_color_secondary_tint_list"/>
+                    android:ellipsize="end" />
+                <RelativeLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent">
+                    <TextView
+                        android:id="@+id/price_drop_domain"
+                        android:layout_alignParentBottom="true"
+                        android:layout_alignParentStart="true"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
+                        android:maxLines="1"
+                        android:ellipsize="end" />
+                </RelativeLayout>
             </LinearLayout>
         </LinearLayout>
     </LinearLayout>
diff --git a/chrome/browser/commerce/price_change/android/java/res/values/dimens.xml b/chrome/browser/commerce/price_change/android/java/res/values/dimens.xml
index 26d422b..2b32b0a 100644
--- a/chrome/browser/commerce/price_change/android/java/res/values/dimens.xml
+++ b/chrome/browser/commerce/price_change/android/java/res/values/dimens.xml
@@ -6,10 +6,9 @@
 <resources xmlns:tools="http://schemas.android.com/tools"
     tools:ignore="UnusedResources">
   <!-- Price change module dimens -->
-    <dimen name="price_change_module_background_color_elevation">@dimen/default_elevation_0</dimen>
-    <dimen name="price_change_module_background_radius">24dp</dimen>
     <dimen name="price_change_module_favicon_background_radius">4dp</dimen>
     <dimen name="price_change_module_favicon_image_size">24dp</dimen>
     <dimen name="price_change_module_margin">20dp</dimen>
     <dimen name="price_change_module_product_image_size">116dp</dimen>
+    <dimen name="price_change_module_product_image_corner_radius">12dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/chrome/browser/commerce/price_change/android/java/src/org/chromium/chrome/browser/price_change/PriceChangeModuleView.java b/chrome/browser/commerce/price_change/android/java/src/org/chromium/chrome/browser/price_change/PriceChangeModuleView.java
index d889340..3b25f7e 100644
--- a/chrome/browser/commerce/price_change/android/java/src/org/chromium/chrome/browser/price_change/PriceChangeModuleView.java
+++ b/chrome/browser/commerce/price_change/android/java/src/org/chromium/chrome/browser/price_change/PriceChangeModuleView.java
@@ -55,18 +55,10 @@
     }
 
     void setCurrentPrice(String price) {
-        // TODO(crbug.com/1501138): Add the color to pre-defined text appearance styles when it's
-        // finalized.
-        mPreviousPriceView.setTextColor(
-                getContext().getColor(R.color.price_drop_annotation_text_green));
         mCurrentPriceView.setText(price);
     }
 
     void setPreviousPrice(String price) {
-        // TODO(crbug.com/1501138): Add the color to pre-defined text appearance styles when it's
-        // finalized.
-        mPreviousPriceView.setTextColor(
-                getContext().getColor(R.color.chip_text_color_secondary_list));
         mPreviousPriceView.setPaintFlags(
                 mPreviousPriceView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
         mPreviousPriceView.setText(price);
diff --git a/chrome/browser/compose/chrome_compose_client.cc b/chrome/browser/compose/chrome_compose_client.cc
index d2bcc30..948b64c 100644
--- a/chrome/browser/compose/chrome_compose_client.cc
+++ b/chrome/browser/compose/chrome_compose_client.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_dialogs.h"
@@ -71,11 +72,13 @@
   opt_guide_ = OptimizationGuideKeyedServiceFactory::GetForProfile(profile_);
   pref_service_ = profile_->GetPrefs();
   compose_enabling_ = std::make_unique<ComposeEnabling>(
-      translate_language_provider_.get(), profile_);
+      translate_language_provider_.get(), profile_,
+      IdentityManagerFactory::GetForProfileIfExists(profile_),
+      OptimizationGuideKeyedServiceFactory::GetForProfile(profile_));
 
   if (GetOptimizationGuide()) {
     std::vector<optimization_guide::proto::OptimizationType> types;
-    if (compose_enabling_->IsEnabledForProfile(profile_).has_value()) {
+    if (compose_enabling_->IsEnabled().has_value()) {
       types.push_back(optimization_guide::proto::OptimizationType::COMPOSE);
     }
 
diff --git a/chrome/browser/compose/compose_dialog_browsertest.cc b/chrome/browser/compose/compose_dialog_browsertest.cc
index 3244ff4..9e7eba4 100644
--- a/chrome/browser/compose/compose_dialog_browsertest.cc
+++ b/chrome/browser/compose/compose_dialog_browsertest.cc
@@ -59,7 +59,7 @@
       browser(), embedded_test_server()->GetURL("/compose/test2.html")));
   ASSERT_NE(nullptr, ChromeComposeClient::FromWebContents(web_contents));
   auto* client = ChromeComposeClient::FromWebContents(web_contents);
-  client->GetComposeEnabling().SetEnabledForTesting();
+  client->GetComposeEnabling().SetEnabledForTesting(true);
 
   // get point of element
   gfx::PointF textarea_center =
@@ -83,7 +83,7 @@
       browser(), embedded_test_server()->GetURL("/compose/test2.html")));
   ASSERT_NE(nullptr, ChromeComposeClient::FromWebContents(web_contents));
   auto* client = ChromeComposeClient::FromWebContents(web_contents);
-  client->GetComposeEnabling().SetEnabledForTesting();
+  client->GetComposeEnabling().SetEnabledForTesting(true);
 
   // get point of element
   gfx::PointF textarea_center =
diff --git a/chrome/browser/compose/compose_enabling.cc b/chrome/browser/compose/compose_enabling.cc
index 1bb5683..c3ce4e9 100644
--- a/chrome/browser/compose/compose_enabling.cc
+++ b/chrome/browser/compose/compose_enabling.cc
@@ -10,6 +10,8 @@
 #include "chrome/browser/about_flags.h"
 #include "chrome/browser/compose/proto/compose_optimization_guide.pb.h"
 #include "chrome/browser/flag_descriptions.h"
+#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "components/compose/buildflags.h"
 #include "components/compose/core/browser/compose_features.h"
@@ -30,16 +32,21 @@
 
 }  // namespace
 
+bool ComposeEnabling::enabled_for_testing_{false};
+bool ComposeEnabling::skip_user_check_for_testing_{false};
+
 ComposeEnabling::ComposeEnabling(
     TranslateLanguageProvider* translate_language_provider,
-    Profile* profile)
+    Profile* profile,
+    signin::IdentityManager* identity_manager,
+    OptimizationGuideKeyedService* opt_guide)
     : optimization_guide::SettingsEnabledObserver(
           optimization_guide::proto::MODEL_EXECUTION_FEATURE_COMPOSE),
-      profile_(profile) {
-  // TODO(b/314325398): Use this instance member profile in other methods.
+      profile_(profile),
+      opt_guide_(opt_guide),
+      identity_manager_(identity_manager) {
   DCHECK(profile_);
   translate_language_provider_ = translate_language_provider;
-  opt_guide_ = OptimizationGuideKeyedServiceFactory::GetForProfile(profile_);
   if (opt_guide_) {
     // TODO(b/314199871): Add test when this call becomes mock-able.
     opt_guide_->AddModelExecutionSettingsEnabledObserver(this);
@@ -53,16 +60,19 @@
   if (opt_guide_) {
     opt_guide_->RemoveModelExecutionSettingsEnabledObserver(this);
   }
+
+  opt_guide_ = nullptr;
+  identity_manager_ = nullptr;
+  translate_language_provider_ = nullptr;
+  profile_ = nullptr;
 }
 
-void ComposeEnabling::SetEnabledForTesting() {
-  enabled_for_testing_ = true;
+// Static.
+void ComposeEnabling::SetEnabledForTesting(bool enabled) {
+  enabled_for_testing_ = enabled;
 }
 
-void ComposeEnabling::ClearEnabledForTesting() {
-  enabled_for_testing_ = false;
-}
-
+// Static.
 void ComposeEnabling::SkipUserEnabledCheckForTesting(bool skip) {
   skip_user_check_for_testing_ = skip;
 }
@@ -99,21 +109,27 @@
   return compose_metadata->decision();
 }
 
-base::expected<void, compose::ComposeShowStatus>
-ComposeEnabling::IsEnabledForProfile(Profile* profile) {
-#if BUILDFLAG(ENABLE_COMPOSE)
-  signin::IdentityManager* identity_manager =
-      IdentityManagerFactory::GetForProfile(profile);
-  return IsEnabled(profile, identity_manager);
-#else
-  return base::unexpected(compose::ComposeShowStatus::kGenericBlocked);
-#endif
+// Member function public entry point.
+base::expected<void, compose::ComposeShowStatus> ComposeEnabling::IsEnabled() {
+  return CheckEnabling(profile_, opt_guide_, identity_manager_);
 }
 
-base::expected<void, compose::ComposeShowStatus> ComposeEnabling::IsEnabled(
+// Static public entry point.
+bool ComposeEnabling::IsEnabledForProfile(Profile* profile) {
+  OptimizationGuideKeyedService* opt_guide =
+      OptimizationGuideKeyedServiceFactory::GetForProfile(profile);
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfileIfExists(profile);
+  return CheckEnabling(profile, opt_guide, identity_manager).has_value();
+}
+
+// Internal static.
+base::expected<void, compose::ComposeShowStatus> ComposeEnabling::CheckEnabling(
     Profile* profile,
+    OptimizationGuideKeyedService* opt_guide,
     signin::IdentityManager* identity_manager) {
   if (enabled_for_testing_) {
+    DVLOG(2) << "enabled for testing";
     return base::ok();
   }
 
@@ -155,7 +171,7 @@
 
   // TODO(b/314199871): Remove test bypass once this check becomes mock-able.
   if (!skip_user_check_for_testing_ &&
-      !opt_guide_->ShouldFeatureBeCurrentlyEnabledForUser(
+      !opt_guide->ShouldFeatureBeCurrentlyEnabledForUser(
           optimization_guide::proto::ModelExecutionFeature::
               MODEL_EXECUTION_FEATURE_COMPOSE)) {
     DVLOG(2) << "Feature not available for this user";
@@ -163,8 +179,7 @@
         compose::ComposeShowStatus::kUserNotAllowedByOptimizationGuide);
   }
 
-  // TODO(b/305245736): Check consent once it is available to check.
-
+  DVLOG(2) << "enabled";
   return base::ok();
 }
 
@@ -192,7 +207,7 @@
     return false;
   }
 
-  if (!PageLevelChecks(profile, translate_manager, top_level_frame_origin,
+  if (!PageLevelChecks(translate_manager, top_level_frame_origin,
                        element_frame_origin)
            .has_value()) {
     return false;
@@ -247,7 +262,7 @@
   }
 
   auto show_status = PageLevelChecks(
-      profile, translate_manager, rfh->GetMainFrame()->GetLastCommittedOrigin(),
+      translate_manager, rfh->GetMainFrame()->GetLastCommittedOrigin(),
       params.frame_origin);
   if (show_status.has_value()) {
     compose::LogComposeContextMenuShowStatus(
@@ -288,11 +303,10 @@
 }
 
 base::expected<void, compose::ComposeShowStatus>
-ComposeEnabling::PageLevelChecks(Profile* profile,
-                                 translate::TranslateManager* translate_manager,
+ComposeEnabling::PageLevelChecks(translate::TranslateManager* translate_manager,
                                  const url::Origin& top_level_frame_origin,
                                  const url::Origin& element_frame_origin) {
-  if (auto profile_show_status = IsEnabledForProfile(profile);
+  if (auto profile_show_status = IsEnabled();
       !profile_show_status.has_value()) {
     DVLOG(2) << "not enabled";
     return profile_show_status;
diff --git a/chrome/browser/compose/compose_enabling.h b/chrome/browser/compose/compose_enabling.h
index f48eb14..c31048a7 100644
--- a/chrome/browser/compose/compose_enabling.h
+++ b/chrome/browser/compose/compose_enabling.h
@@ -22,20 +22,25 @@
  public:
   explicit ComposeEnabling(
       TranslateLanguageProvider* translate_language_provider,
-      Profile* profile);
+      Profile* profile,
+      signin::IdentityManager* identity_manager,
+      OptimizationGuideKeyedService* opt_guide);
   ~ComposeEnabling() override;
 
   ComposeEnabling(const ComposeEnabling&) = delete;
   ComposeEnabling& operator=(const ComposeEnabling&) = delete;
 
-  base::expected<void, compose::ComposeShowStatus> IsEnabledForProfile(
-      Profile* profile);
-  base::expected<void, compose::ComposeShowStatus> IsEnabled(
-      Profile* profile,
-      signin::IdentityManager* identity_manager);
-  void SetEnabledForTesting();
-  void ClearEnabledForTesting();
-  void SkipUserEnabledCheckForTesting(bool skip);
+  // Static method that verifies that the feature can be enabled on the given
+  // profile. Doesn't take advantage of for test opt_guide or identity_manager,
+  // use member function version if you need to mock them out.
+  static bool IsEnabledForProfile(Profile* profile);
+
+  // Instance method that verifies that the feature be enabled for profile
+  // provided associated with this instance.
+  base::expected<void, compose::ComposeShowStatus> IsEnabled();
+
+  static void SetEnabledForTesting(bool enabled);
+  static void SkipUserEnabledCheckForTesting(bool skip);
   bool ShouldTriggerPopup(std::string_view autocomplete_attribute,
                           Profile* profile,
                           translate::TranslateManager* translate_manager,
@@ -57,17 +62,22 @@
   void PrepareToEnableOnRestart() override;
 
  private:
-  raw_ptr<TranslateLanguageProvider> translate_language_provider_;
-  raw_ptr<Profile> profile_;
-  raw_ptr<OptimizationGuideKeyedService> opt_guide_;
-  bool enabled_for_testing_{false};
-  bool skip_user_check_for_testing_{false};
-
   base::expected<void, compose::ComposeShowStatus> PageLevelChecks(
-      Profile* profile,
       translate::TranslateManager* translate_manager,
       const url::Origin& top_level_frame_origin,
       const url::Origin& element_frame_origin);
+
+  static base::expected<void, compose::ComposeShowStatus> CheckEnabling(
+      Profile* profile,
+      OptimizationGuideKeyedService* opt_guide,
+      signin::IdentityManager* identity_manager);
+
+  raw_ptr<TranslateLanguageProvider> translate_language_provider_;
+  raw_ptr<Profile> profile_;
+  raw_ptr<OptimizationGuideKeyedService> opt_guide_;
+  raw_ptr<signin::IdentityManager> identity_manager_;
+  static bool enabled_for_testing_;
+  static bool skip_user_check_for_testing_;
 };
 
 #endif  // CHROME_BROWSER_COMPOSE_COMPOSE_ENABLING_H_
diff --git a/chrome/browser/compose/compose_enabling_browsertest.cc b/chrome/browser/compose/compose_enabling_browsertest.cc
index c19637d..ff669b2 100644
--- a/chrome/browser/compose/compose_enabling_browsertest.cc
+++ b/chrome/browser/compose/compose_enabling_browsertest.cc
@@ -65,8 +65,7 @@
       unified_consent::prefs::kUrlKeyedAnonymizedDataCollectionEnabled, true);
 
   // Checks that Compose is disabled.
-  EXPECT_NE(base::ok(),
-            GetComposeEnabling().IsEnabledForProfile(browser()->profile()));
+  EXPECT_NE(base::ok(), GetComposeEnabling().IsEnabled());
   EXPECT_FALSE(GetOptimizationGuide()->ShouldFeatureBeCurrentlyEnabledForUser(
       optimization_guide::proto::ModelExecutionFeature::
           MODEL_EXECUTION_FEATURE_COMPOSE));
@@ -88,8 +87,7 @@
   EXPECT_TRUE(base::FeatureList::IsEnabled(
       autofill::features::kAutofillContentEditables));
 
-  EXPECT_EQ(base::ok(),
-            GetComposeEnabling().IsEnabledForProfile(browser()->profile()));
+  EXPECT_EQ(base::ok(), GetComposeEnabling().IsEnabled());
   EXPECT_TRUE(GetOptimizationGuide()->ShouldFeatureBeCurrentlyEnabledForUser(
       optimization_guide::proto::ModelExecutionFeature::
           MODEL_EXECUTION_FEATURE_COMPOSE));
diff --git a/chrome/browser/compose/compose_enabling_unittest.cc b/chrome/browser/compose/compose_enabling_unittest.cc
index 2414cdd4..361b79f9 100644
--- a/chrome/browser/compose/compose_enabling_unittest.cc
+++ b/chrome/browser/compose/compose_enabling_unittest.cc
@@ -128,6 +128,9 @@
         std::make_unique<testing::NiceMock<MockTranslateManager>>(
             mock_translate_client_.get());
 
+    // Note that AddTab makes its own ComposeEnabling as part of
+    // ChromeComposeClient. This can cause confusion when debugging tests.
+    // Don't confuse the two ComposeEnabling objects when debugging.
     AddTab(browser(), GURL(kExampleURL));
     context_menu_params_.is_content_editable_for_autofill = true;
     context_menu_params_.frame_origin = GetOrigin();
@@ -136,9 +139,21 @@
         testing::NiceMock<CustomMockOptimizationGuideKeyedService>*>(
         OptimizationGuideKeyedServiceFactory::GetForProfile(GetProfile()));
     ASSERT_TRUE(opt_guide_);
+
+    // Build the ComposeEnabling object the tests will use, providing it with
+    // mocks for its dependencies.
+    // TODO(b/316625561) Simplify these tests more now that we have dependency
+    // injection.
+    compose_enabling_ = std::make_unique<ComposeEnabling>(
+        &mock_translate_language_provider_, GetProfile(),
+        identity_test_env_.identity_manager(), &opt_guide());
   }
 
   void TearDown() override {
+    // We must destroy the ComposeEnabling while the opt_guide object is still
+    // valid so we can call unregister in the destructor.
+    compose_enabling_ = nullptr;
+    // We must null out the opt_guide_ before calling ResetForTesting.
     opt_guide_ = nullptr;
     compose::ResetConfigForTesting();
     BrowserWithTestWindowTest::TearDown();
@@ -179,10 +194,9 @@
         ->GetPrimaryMainFrame();
   }
 
-  void CheckIsEnabledError(ComposeEnabling& compose_enabling,
+  void CheckIsEnabledError(ComposeEnabling* compose_enabling,
                            compose::ComposeShowStatus error_show_status) {
-    EXPECT_EQ(compose_enabling.IsEnabled(GetProfile(),
-                                         identity_test_env_.identity_manager()),
+    EXPECT_EQ(compose_enabling->IsEnabled(),
               base::unexpected(error_show_status));
   }
 
@@ -202,58 +216,53 @@
 
   testing::NiceMock<MockTranslateLanguageProvider>
       mock_translate_language_provider_;
+
+  std::unique_ptr<ComposeEnabling> compose_enabling_;
 };
 
 TEST_F(ComposeEnablingTest, EverythingDisabledTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   scoped_feature_list_.Reset();
-  scoped_feature_list_.InitAndDisableFeature(compose::features::kEnableCompose);
+  scoped_feature_list_.InitWithFeatures(
+      {}, {compose::features::kEnableCompose,
+           compose::features::kEnableComposeNudge});
   // We intentionally don't call sign in to make our state not signed in.
   SetMsbbState(false);
-  EXPECT_NE(compose_enabling.IsEnabled(GetProfile(),
-                                       identity_test_env_.identity_manager()),
-            base::ok());
+  EXPECT_NE(compose_enabling_->IsEnabled(), base::ok());
 }
 
 TEST_F(ComposeEnablingTest, FeatureNotEnabledTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Ensure feature flag is off.
   scoped_feature_list_.Reset();
-  scoped_feature_list_.InitAndDisableFeature(compose::features::kEnableCompose);
+  scoped_feature_list_.InitWithFeatures(
+      {}, {compose::features::kEnableCompose,
+           compose::features::kEnableComposeNudge});
   // Sign in, with sync turned on.
   SignIn(signin::ConsentLevel::kSync);
   // Turn on MSBB.
   SetMsbbState(true);
 
-  CheckIsEnabledError(compose_enabling,
+  CheckIsEnabledError(compose_enabling_.get(),
                       compose::ComposeShowStatus::kGenericBlocked);
 }
 
 TEST_F(ComposeEnablingTest, MsbbDisabledTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Sign in, with sync turned on.
   SignIn(signin::ConsentLevel::kSync);
   // MSBB turned off.
   SetMsbbState(false);
-  CheckIsEnabledError(compose_enabling,
+  CheckIsEnabledError(compose_enabling_.get(),
                       compose::ComposeShowStatus::kDisabledMsbb);
 }
 
 TEST_F(ComposeEnablingTest, NotSignedInTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
+  // Intentionally skip the signin step.
   // Turn on MSBB.
   SetMsbbState(true);
-  CheckIsEnabledError(compose_enabling, compose::ComposeShowStatus::kSignedOut);
+  CheckIsEnabledError(compose_enabling_.get(),
+                      compose::ComposeShowStatus::kSignedOut);
 }
 
 TEST_F(ComposeEnablingTest, SignedInErrorTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
-
   // Sign in, with error.
   AccountInfo account_info = identity_test_env_.MakePrimaryAccountAvailable(
       kEmail, signin::ConsentLevel::kSync);
@@ -264,7 +273,8 @@
 
   // Turn on MSBB.
   SetMsbbState(true);
-  CheckIsEnabledError(compose_enabling, compose::ComposeShowStatus::kSignedOut);
+  CheckIsEnabledError(compose_enabling_.get(),
+                      compose::ComposeShowStatus::kSignedOut);
 }
 
 TEST_F(ComposeEnablingTest, ComposeEligibleTest) {
@@ -274,57 +284,64 @@
       {compose::features::kEnableCompose,
        compose::features::kEnableComposeNudge},
       {compose::features::kComposeEligible});
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
-  compose_enabling.SkipUserEnabledCheckForTesting(true);
+  ComposeEnabling::SkipUserEnabledCheckForTesting(true);
   // Sign in, with sync turned on.
   SignIn(signin::ConsentLevel::kSync);
   // Turn on MSBB.
   SetMsbbState(true);
 
   // The ComposeEligible switch should win, and disable the feature.
-  CheckIsEnabledError(compose_enabling,
+  CheckIsEnabledError(compose_enabling_.get(),
                       compose::ComposeShowStatus::kNotComposeEligible);
 }
 
 TEST_F(ComposeEnablingTest, EverythingEnabledTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
-  compose_enabling.SkipUserEnabledCheckForTesting(true);
+  ComposeEnabling::SkipUserEnabledCheckForTesting(true);
   // Sign in, with sync turned on.
   SignIn(signin::ConsentLevel::kSync);
   // Turn on MSBB.
   SetMsbbState(true);
-  EXPECT_EQ(compose_enabling.IsEnabled(GetProfile(),
-                                       identity_test_env_.identity_manager()),
-            base::ok());
+  EXPECT_EQ(compose_enabling_->IsEnabled(), base::ok());
+}
+
+TEST_F(ComposeEnablingTest, StaticMethodEverythingDisabledTest) {
+  scoped_feature_list_.Reset();
+  scoped_feature_list_.InitWithFeatures(
+      {}, {compose::features::kEnableCompose,
+           compose::features::kEnableComposeNudge});
+  // We intentionally don't call sign in to make our state not signed in.
+  SetMsbbState(false);
+  EXPECT_FALSE(ComposeEnabling::IsEnabledForProfile(GetProfile()));
+}
+
+TEST_F(ComposeEnablingTest, StaticMethodFlagsNotEnabledTest) {
+  scoped_feature_list_.Reset();
+  EXPECT_FALSE(ComposeEnabling::IsEnabledForProfile(GetProfile()));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerContextMenuDisabledTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
-
   // We intentionally disable the feature.
   scoped_feature_list_.Reset();
-  scoped_feature_list_.InitAndDisableFeature(compose::features::kEnableCompose);
+  scoped_feature_list_.InitWithFeatures(
+      {}, {compose::features::kEnableCompose,
+           compose::features::kEnableComposeNudge});
 
   // Set the language to something we support. "en" is English.
   SetLanguage("en");
 
-  EXPECT_FALSE(compose_enabling.ShouldTriggerContextMenu(
-      GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
-      context_menu_params_));
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerContextMenu(
+      GetProfile(), mock_translate_manager_.get(),
+      /*rfh=*/GetRenderFrameHost(), context_menu_params_));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerContextMenuLanguageTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
 
+  // Set the mock language to something we don't support. "eo" is Esperanto.
   SetLanguage("eo");
 
-  EXPECT_FALSE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 }
@@ -335,54 +352,46 @@
       {compose::features::kEnableCompose,
        compose::features::kEnableComposeLanguageBypass},
       {});
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
 
   // Set the mock language to something we don't support. "eo" is Esperanto.
   SetLanguage("eo");
 
   // Although the language is unsupported, ShouldTrigger should return true as
   // the bypass is enabled.
-  EXPECT_TRUE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_TRUE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerContextMenuEmptyLangugeTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
 
   // Set the language to the empty string - translate doesn't have the answer
   // yet.
   SetLanguage("");
 
-  EXPECT_TRUE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_TRUE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerContextMenuUndeterminedLangugeTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
 
   // Set the language to the "und" for a page where translate could not
   // determine the language.
   SetLanguage("und");
 
-  EXPECT_TRUE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_TRUE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerContextMenuFieldTypeTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Set ContextMenuParams to non-contenteditable and non-textarea, which we do
   // not support.
   context_menu_params_.is_content_editable_for_autofill = false;
@@ -392,31 +401,27 @@
   // Set the language to something we support. Not expected to be called.
   SetLanguage("en");
 
-  EXPECT_FALSE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 }
 
 TEST_F(ComposeEnablingTest,
        ShouldTriggerContextMenuAllEnabledContentEditableTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
 
   // Set the language to something we support.
   SetLanguage("en");
 
-  EXPECT_TRUE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_TRUE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerContextMenuAllEnabledTextAreaTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
 
   // Set the language to something we support.
   SetLanguage("en");
@@ -426,18 +431,17 @@
   context_menu_params_.form_control_type =
       blink::mojom::FormControlType::kTextArea;
 
-  EXPECT_TRUE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_TRUE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerPopupDisabledTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
-
   // We intentionally disable the feature.
   scoped_feature_list_.Reset();
-  scoped_feature_list_.InitAndDisableFeature(compose::features::kEnableCompose);
+  scoped_feature_list_.InitWithFeatures(
+      {}, {compose::features::kEnableCompose,
+           compose::features::kEnableComposeNudge});
 
   std::string autocomplete_attribute;
   bool has_saved_state = false;
@@ -445,23 +449,21 @@
   // Set the language to something we support. "en" is English.
   SetLanguage("en");
 
-  EXPECT_FALSE(compose_enabling.ShouldTriggerPopup(
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerPopup(
       autocomplete_attribute, GetProfile(), mock_translate_manager_.get(),
       has_saved_state, GetOrigin(), GetOrigin(), GURL(kExampleURL)));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerPopupLanguageTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable the feature.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
   std::string autocomplete_attribute;
   bool has_saved_state = false;
 
   // Set the mock language to something we don't support. "eo" is Esperanto.
   SetLanguage("eo");
 
-  EXPECT_FALSE(compose_enabling.ShouldTriggerPopup(
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerPopup(
       autocomplete_attribute, GetProfile(), mock_translate_manager_.get(),
       has_saved_state, GetOrigin(), GetOrigin(), GURL(kExampleURL)));
 }
@@ -474,10 +476,8 @@
        compose::features::kEnableComposeLanguageBypass},
       {});
 
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable the feature.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
   std::string autocomplete_attribute;
   bool has_saved_state = true;
 
@@ -487,16 +487,14 @@
 
   // Although the language is unsupported, ShouldTrigger should return true as
   // the bypass is enabled.
-  EXPECT_TRUE(compose_enabling.ShouldTriggerPopup(
+  EXPECT_TRUE(compose_enabling_->ShouldTriggerPopup(
       autocomplete_attribute, GetProfile(), mock_translate_manager_.get(),
       has_saved_state, GetOrigin(), GetOrigin(), GURL(kExampleURL)));
 }
 
 TEST_F(ComposeEnablingTest, ShouldNotTriggerProactivePopupAutocompleteOffTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
   // Autocomplete is set to off for this page.
   std::string autocomplete_attribute("off");
   bool has_saved_state = false;
@@ -504,16 +502,14 @@
   // Set the language to something we support.
   SetLanguage("en");
 
-  EXPECT_FALSE(compose_enabling.ShouldTriggerPopup(
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerPopup(
       autocomplete_attribute, GetProfile(), mock_translate_manager_.get(),
       has_saved_state, GetOrigin(), GetOrigin(), GURL(kExampleURL)));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerSavedStatePopupAutocompleteOffTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
   // Autocomplete is set to off for this page.
   std::string autocomplete_attribute("off");
   bool has_saved_state = true;
@@ -521,16 +517,14 @@
   // Set the language to something we support.
   SetLanguage("en");
 
-  EXPECT_TRUE(compose_enabling.ShouldTriggerPopup(
+  EXPECT_TRUE(compose_enabling_->ShouldTriggerPopup(
       autocomplete_attribute, GetProfile(), mock_translate_manager_.get(),
       has_saved_state, GetOrigin(), GetOrigin(), GURL(kExampleURL)));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerPopupWithSavedStateTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
   std::string autocomplete_attribute;
 
   // test all variants of: popup with, popup without state.
@@ -544,13 +538,13 @@
 
     SetLanguage("en");
 
-    EXPECT_EQ(popup_with_state, compose_enabling.ShouldTriggerPopup(
+    EXPECT_EQ(popup_with_state, compose_enabling_->ShouldTriggerPopup(
                                     autocomplete_attribute, GetProfile(),
                                     mock_translate_manager_.get(),
                                     /*has_saved_state=*/true, GetOrigin(),
                                     GetOrigin(), GURL(kExampleURL)));
 
-    EXPECT_EQ(popup_without_state, compose_enabling.ShouldTriggerPopup(
+    EXPECT_EQ(popup_without_state, compose_enabling_->ShouldTriggerPopup(
                                        autocomplete_attribute, GetProfile(),
                                        mock_translate_manager_.get(),
                                        /*has_saved_state=*/false, GetOrigin(),
@@ -564,10 +558,8 @@
       {compose::features::kEnableCompose},
       {compose::features::kEnableComposeNudge});
 
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
   std::string autocomplete_attribute;
 
   std::vector<std::pair<bool, bool>> tests = {
@@ -585,11 +577,11 @@
 
     SetLanguage("en");
 
-    EXPECT_FALSE(compose_enabling.ShouldTriggerPopup(
+    EXPECT_FALSE(compose_enabling_->ShouldTriggerPopup(
         autocomplete_attribute, GetProfile(), mock_translate_manager_.get(),
         /*has_saved_state=*/true, GetOrigin(), GetOrigin(), GURL(kExampleURL)));
 
-    EXPECT_FALSE(compose_enabling.ShouldTriggerPopup(
+    EXPECT_FALSE(compose_enabling_->ShouldTriggerPopup(
         autocomplete_attribute, GetProfile(), mock_translate_manager_.get(),
         /*has_saved_state=*/false, GetOrigin(), GetOrigin(),
         GURL(kExampleURL)));
@@ -597,32 +589,28 @@
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerPopupCrossOrigin) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
   std::string autocomplete_attribute;
   bool has_saved_state = false;
 
   // Set the language to something we support.
   SetLanguage("en");
-  EXPECT_FALSE(compose_enabling.ShouldTriggerPopup(
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerPopup(
       autocomplete_attribute, GetProfile(), mock_translate_manager_.get(),
       has_saved_state, GetOrigin(), url::Origin(), GURL(kExampleURL)));
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerContextMenuCrossOrigin) {
   base::HistogramTester histogram_tester;
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
 
   // Set the language to something we support.
   SetLanguage("en");
 
   context_menu_params_.frame_origin = url::Origin();
-  EXPECT_FALSE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 
@@ -633,8 +621,6 @@
 }
 
 TEST_F(ComposeEnablingTest, GetOptimizationGuidanceShowNudgeTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Set up a fake metadata to return from the mock.
   optimization_guide::OptimizationMetadata test_metadata;
   compose::ComposeHintMetadata compose_hint_metadata;
@@ -654,7 +640,7 @@
 
   GURL example(kExampleURL);
   compose::ComposeHintDecision decision =
-      compose_enabling.GetOptimizationGuidanceForUrl(example, GetProfile());
+      compose_enabling_->GetOptimizationGuidanceForUrl(example, GetProfile());
 
   // Verify response from CanApplyOptimization is as we expect.
   EXPECT_EQ(compose::ComposeHintDecision::COMPOSE_HINT_DECISION_ENABLED,
@@ -662,8 +648,6 @@
 }
 
 TEST_F(ComposeEnablingTest, GetOptimizationGuidanceNoFeedbackTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Set up a fake metadata to return from the mock.
   optimization_guide::OptimizationMetadata test_metadata;
   compose::ComposeHintMetadata compose_hint_metadata;
@@ -683,7 +667,7 @@
 
   GURL example(kExampleURL);
   compose::ComposeHintDecision decision =
-      compose_enabling.GetOptimizationGuidanceForUrl(example, GetProfile());
+      compose_enabling_->GetOptimizationGuidanceForUrl(example, GetProfile());
 
   // Verify response from CanApplyOptimization is as we expect.
   EXPECT_EQ(compose::ComposeHintDecision::COMPOSE_HINT_DECISION_UNSPECIFIED,
@@ -691,8 +675,6 @@
 }
 
 TEST_F(ComposeEnablingTest, GetOptimizationGuidanceNoComposeMetadataTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Set up a fake metadata to return from the mock.
   optimization_guide::OptimizationMetadata test_metadata;
   compose::ComposeHintMetadata compose_hint_metadata;
@@ -710,7 +692,7 @@
 
   GURL example(kExampleURL);
   compose::ComposeHintDecision decision =
-      compose_enabling.GetOptimizationGuidanceForUrl(example, GetProfile());
+      compose_enabling_->GetOptimizationGuidanceForUrl(example, GetProfile());
 
   // Verify response from CanApplyOptimization is as we expect.
   EXPECT_EQ(compose::ComposeHintDecision::COMPOSE_HINT_DECISION_UNSPECIFIED,
@@ -718,10 +700,8 @@
 }
 
 TEST_F(ComposeEnablingTest, ShouldTriggerContextMenuOutOfPolicyURLTest) {
-  ComposeEnabling compose_enabling(&mock_translate_language_provider_,
-                                   GetProfile());
   // Enable everything.
-  compose_enabling.SetEnabledForTesting();
+  ComposeEnabling::SetEnabledForTesting(true);
   // Set the language to something we support.
   SetLanguage("en");
 
@@ -749,7 +729,7 @@
           testing::Return(
               optimization_guide::OptimizationGuideDecision::kTrue)));
 
-  EXPECT_FALSE(compose_enabling.ShouldTriggerContextMenu(
+  EXPECT_FALSE(compose_enabling_->ShouldTriggerContextMenu(
       GetProfile(), mock_translate_manager_.get(), /*rfh=*/GetRenderFrameHost(),
       context_menu_params_));
 
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 ca5b0a5..51851ad 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -23,13 +23,13 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/run_until.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "base/time/time_override.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/auth_notification_types.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/devtools/protocol/devtools_protocol_test_support.h"
 #include "chrome/browser/devtools/url_constants.h"
@@ -80,8 +80,6 @@
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/navigation_handle.h"
-#include "content/public/browser/notification_registrar.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/render_view_host.h"
@@ -157,31 +155,6 @@
 constexpr char kOriginTrialPublicKeyForTesting[] =
     "dRCs+TocuKkocNKa0AtZ4awrt9XKH2SQCI6o4FY6BNA=";
 
-class CancelLoginDialog : public content::NotificationObserver {
- public:
-  CancelLoginDialog() {
-    registrar_.Add(this,
-                   chrome::NOTIFICATION_AUTH_NEEDED,
-                   content::NotificationService::AllSources());
-  }
-
-  CancelLoginDialog(const CancelLoginDialog&) = delete;
-  CancelLoginDialog& operator=(const CancelLoginDialog&) = delete;
-
-  ~CancelLoginDialog() override {}
-
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override {
-    LoginHandler* handler =
-        content::Details<LoginNotificationDetails>(details).ptr()->handler();
-    handler->CancelAuth();
-  }
-
- private:
-  content::NotificationRegistrar registrar_;
-};
-
 // Observer that listens for messages from chrome.test.sendMessage to allow them
 // to be used to trigger browser initiated naviagations from the javascript for
 // testing purposes.
@@ -913,8 +886,6 @@
 
 IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest,
                        WebRequestAuthRequired) {
-  CancelLoginDialog login_dialog_helper;
-
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   // If running in incognito, create an incognito browser so the test
@@ -947,8 +918,6 @@
 
 IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest,
                        WebRequestAuthRequiredAsync) {
-  CancelLoginDialog login_dialog_helper;
-
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   // If running in incognito, create an incognito browser so the tests
@@ -978,8 +947,6 @@
 // https://crbug.com/998369). See https://crbug.com/1026001.
 IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiAuthRequiredTest,
                        DISABLED_WebRequestAuthRequiredParallel) {
-  CancelLoginDialog login_dialog_helper;
-
   const bool incognito = GetEnableIncognito();
   if (incognito)
     CreateIncognitoBrowser(profile());
@@ -1054,8 +1021,6 @@
 // Flaky on all platforms: https://crbug.com/1003661
 IN_PROC_BROWSER_TEST_P(ExtensionWebRequestApiTestWithContextType,
                        DISABLED_WebRequestExtraHeaders_Auth) {
-  CancelLoginDialog login_dialog_helper;
-
   ASSERT_TRUE(StartEmbeddedTestServer());
   ASSERT_TRUE(RunExtensionTest("webrequest/test_extra_headers_auth"))
       << message_;
@@ -5141,34 +5106,6 @@
       delete;
 
  protected:
-  class ProceedLoginDialog : public content::NotificationObserver {
-   public:
-    ProceedLoginDialog(const std::string& user, const std::string& password)
-        : user_(base::ASCIIToUTF16(user)),
-          password_(base::ASCIIToUTF16(password)) {
-      registrar_.Add(this, chrome::NOTIFICATION_AUTH_NEEDED,
-                     content::NotificationService::AllSources());
-    }
-
-    ~ProceedLoginDialog() override = default;
-
-   private:
-    ProceedLoginDialog(const ProceedLoginDialog&) = delete;
-    ProceedLoginDialog& operator=(const ProceedLoginDialog&) = delete;
-
-    void Observe(int type,
-                 const content::NotificationSource& source,
-                 const content::NotificationDetails& details) override {
-      LoginHandler* handler =
-          content::Details<LoginNotificationDetails>(details).ptr()->handler();
-      handler->SetAuth(user_, password_);
-    }
-
-    content::NotificationRegistrar registrar_;
-    std::u16string user_;
-    std::u16string password_;
-  };
-
   void SetUpOnMainThread() override {
     ExtensionApiTest::SetUpOnMainThread();
     ASSERT_TRUE(StartEmbeddedTestServer());
@@ -5287,7 +5224,6 @@
 // successfully instead of being cancelled after proxy auth required response.
 IN_PROC_BROWSER_TEST_P(ProxyCORSWebRequestApiTest,
                        PreflightCompletesSuccessfully) {
-  ProceedLoginDialog login_dialog(kCORSProxyUser, kCORSProxyPass);
   ExtensionTestMessageListener ready_listener("ready");
   const Extension* extension =
       LoadExtension(test_data_dir_.AppendASCII("webrequest_cors_preflight"));
@@ -5309,9 +5245,18 @@
         xhr.send();
       });
       )";
-  EXPECT_EQ(true, EvalJs(web_contents->GetPrimaryMainFrame(),
-                         base::StringPrintf(kCORSPreflightedRequest, kCORSUrl,
-                                            kCustomPreflightHeader)));
+
+  ExecuteScriptAsyncWithoutUserGesture(
+      web_contents->GetPrimaryMainFrame(),
+      base::StringPrintf(kCORSPreflightedRequest, kCORSUrl,
+                         kCustomPreflightHeader));
+
+  ASSERT_TRUE(base::test::RunUntil(
+      []() { return LoginHandler::GetAllLoginHandlersForTest().size() == 1; }));
+  LoginHandler::GetAllLoginHandlersForTest().front()->SetAuth(
+      base::ASCIIToUTF16(std::string(kCORSProxyUser)),
+      base::ASCIIToUTF16(std::string(kCORSProxyPass)));
+
   EXPECT_TRUE(preflight_listener.WaitUntilSatisfied());
   EXPECT_EQ(1, GetCountFromBackgroundScript(
                    extension, profile(), "self.preflightHeadersReceivedCount"));
@@ -6113,7 +6058,6 @@
 // Tests that an MV3 extension can use the `webRequestAuthProvider` permission
 // to intercept and handle `onAuthRequired` events coming from a tab.
 IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest, TestOnAuthRequiredTab) {
-  CancelLoginDialog login_dialog_helper;
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   static constexpr char kManifest[] =
@@ -6246,10 +6190,6 @@
 //   (5) Checks that the fetch succeeded.
 IN_PROC_BROWSER_TEST_F(OnAuthRequiredApiTest,
                        TestOnAuthRequiredExtensionServiceWorker) {
-  // If the login dialog is shown, it is immediately rejected causing the test
-  // to fail rather than hang indefinitely.
-  CancelLoginDialog login_dialog_helper;
-
   // After the extension loads, trigger an async request to fetch an http auth
   // resource.
   std::string additional_js =
@@ -6282,8 +6222,6 @@
 // service worker is hosted by a website instead of the extension istelf.
 IN_PROC_BROWSER_TEST_F(OnAuthRequiredApiTest,
                        TestOnAuthRequiredWebsiteServiceWorker) {
-  CancelLoginDialog login_dialog_helper;
-
   // Load the extension.
   LoadExtensionWithAdditionalJs("");
 
@@ -6491,7 +6429,6 @@
 // permission cannot use blocking listeners for `onAuthRequired`.
 IN_PROC_BROWSER_TEST_F(ManifestV3WebRequestApiTest,
                        TestOnAuthRequired_NoPermission) {
-  CancelLoginDialog login_dialog_helper;
   ASSERT_TRUE(StartEmbeddedTestServer());
 
   static constexpr char kManifest[] =
diff --git a/chrome/browser/extensions/service_worker_event_dispatching_browsertest.cc b/chrome/browser/extensions/service_worker_event_dispatching_browsertest.cc
index 6c42d4be..4b48385 100644
--- a/chrome/browser/extensions/service_worker_event_dispatching_browsertest.cc
+++ b/chrome/browser/extensions/service_worker_event_dispatching_browsertest.cc
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <utility>
+
 #include "chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "content/public/browser/service_worker_context.h"
-#include "content/public/browser/service_worker_context_observer.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -15,6 +16,8 @@
 #include "extensions/browser/service_worker/service_worker_test_utils.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/service_worker/embedded_worker_status.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-forward.h"
 
 namespace extensions {
 
@@ -22,37 +25,27 @@
 
 constexpr char kTestExtensionId[] = "iegclhlplifhodhkoafiokenjoapiobj";
 
-// Broadcasts a webNavigation.onBeforeNavigate event.
-void DispatchWebNavigationEvent(content::BrowserContext* browser_context,
-                                content::WebContents* web_contents) {
-  EventRouter* router = EventRouter::EventRouter::Get(browser_context);
-  testing::NiceMock<content::MockNavigationHandle> handle(web_contents);
-  auto event = web_navigation_api_helpers::CreateOnBeforeNavigateEvent(&handle);
-  router->BroadcastEvent(std::move(event));
-}
-
 // TODO(crbug.com/1467015): Combine with service_worker_apitest.cc
-// TestWorkerObserver?
-// Tracks when a worker finishes starting
-// (`blink::EmbeddedWorkerStatus::kRunning) and is stopped
-// (`blink::EmbeddedWorkerStatus::kStopping`).
-class TestWorkerStatusObserver : public content::ServiceWorkerContextObserver {
+// TestWorkerObserver.
+// Test class that monitors a newly started worker and obtains the worker's
+// version ID when it starts and allows the caller to wait for the worker to
+// stop (after requesting the worker to stop).
+class TestServiceWorkerContextObserver
+    : public content::ServiceWorkerContextObserver {
  public:
-  TestWorkerStatusObserver(content::BrowserContext* browser_context,
-                           const ExtensionId& extension_id)
-      : browser_context_(browser_context),
-        extension_url_(Extension::GetBaseURLFromExtensionId(extension_id)),
+  TestServiceWorkerContextObserver(content::BrowserContext* browser_context,
+                                   const ExtensionId& extension_id)
+      : extension_url_(Extension::GetBaseURLFromExtensionId(extension_id)),
         sw_context_(service_worker_test_utils::GetServiceWorkerContext(
             browser_context)) {
     scoped_observation_.Observe(sw_context_);
   }
 
-  TestWorkerStatusObserver(const TestWorkerStatusObserver&) = delete;
-  TestWorkerStatusObserver& operator=(const TestWorkerStatusObserver&) = delete;
+  TestServiceWorkerContextObserver(const TestServiceWorkerContextObserver&) =
+      delete;
+  TestServiceWorkerContextObserver& operator=(
+      const TestServiceWorkerContextObserver&) = delete;
 
-  void WaitForWorkerStarting() { starting_worker_run_loop_.Run(); }
-  void WaitForWorkerStarted() { started_worker_run_loop_.Run(); }
-  void WaitForWorkerStopping() { stopping_worker_run_loop_.Run(); }
   void WaitForWorkerStopped() { stopped_worker_run_loop_.Run(); }
 
   int64_t test_worker_version_id = blink::mojom::kInvalidServiceWorkerVersionId;
@@ -60,17 +53,8 @@
   // ServiceWorkerContextObserver:
 
   // Called when a worker has entered the
-  // `blink::EmbeddedWorkerStatus::kStarting` status. Used to indicate when our
-  // test extension is beginning to start.
-  void OnVersionStartingRunning(int64_t version_id) override {
-    test_worker_version_id = version_id;
-
-    starting_worker_run_loop_.Quit();
-  }
-
-  // Called when a worker has entered the
-  // `blink::EmbeddedWorkerStatus::kRunning` status. Used to indicate when our
-  // test extension is now running.
+  // `blink::EmbeddedWorkerStatus::kRunning` status. Used to obtain the new
+  // worker's version ID for later use/comparison.
   void OnVersionStartedRunning(
       int64_t version_id,
       const content::ServiceWorkerRunningInfo& running_info) override {
@@ -79,23 +63,10 @@
     }
 
     test_worker_version_id = version_id;
-    EXPECT_TRUE(content::CheckServiceWorkerIsRunning(sw_context_, version_id));
-    started_worker_run_loop_.Quit();
   }
 
   // Called when a worker has entered the
-  // `blink::EmbeddedWorkerStatus::kStopping` status. Used to indicate when our
-  // test extension is beginning to stop.
-  void OnVersionStoppingRunning(int64_t version_id) override {
-    // `test_worker_version_id` is the previously running version's id.
-    if (test_worker_version_id != version_id) {
-      return;
-    }
-    stopping_worker_run_loop_.Quit();
-  }
-
-  // Called when a worker has entered the
-  // `blink::EmbeddedWorkerStatus::kStopping` status. Used to indicate when our
+  // `blink::EmbeddedWorkerStatus::kStopped` status. Used to indicate when our
   // test extension has stopped.
   void OnVersionStoppedRunning(int64_t version_id) override {
     // `test_worker_version_id` is the previously running version's id.
@@ -105,11 +76,7 @@
     stopped_worker_run_loop_.Quit();
   }
 
-  base::RunLoop starting_worker_run_loop_;
-  base::RunLoop started_worker_run_loop_;
-  base::RunLoop stopping_worker_run_loop_;
   base::RunLoop stopped_worker_run_loop_;
-  const raw_ptr<content::BrowserContext> browser_context_;
   const GURL extension_url_;
   const raw_ptr<content::ServiceWorkerContext> sw_context_;
   base::ScopedObservation<content::ServiceWorkerContext,
@@ -117,6 +84,65 @@
       scoped_observation_{this};
 };
 
+// Monitors the worker's running status and allows a callback to be run when the
+// running status matches a specific `blink::EmbeddedWorkerStatus` running
+// status.
+class TestExtensionServiceWorkerRunningStatusObserver
+    : public content::ServiceWorkerTestHelper {
+ public:
+  explicit TestExtensionServiceWorkerRunningStatusObserver(
+      content::ServiceWorkerContext* sw_context,
+      int64_t worker_version_id = blink::mojom::kInvalidServiceWorkerVersionId)
+      : ServiceWorkerTestHelper(sw_context, worker_version_id),
+        test_worker_version_id_(worker_version_id) {}
+
+  TestExtensionServiceWorkerRunningStatusObserver(
+      const TestExtensionServiceWorkerRunningStatusObserver&) = delete;
+  TestExtensionServiceWorkerRunningStatusObserver& operator=(
+      const TestExtensionServiceWorkerRunningStatusObserver&) = delete;
+
+  // Set the worker status to watch for before running
+  // `test_event_dispatch_callback_`.
+  void SetDispatchCallbackOnStatus(
+      blink::EmbeddedWorkerStatus dispatch_status) {
+    dispatch_callback_on_status_ = dispatch_status;
+  }
+
+  // Set the callback to run when `dispatch_callback_on_status_` matches
+  // worker's current running status.
+  void SetDispatchTestEventCallback(base::OnceCallback<void()> callback) {
+    test_event_dispatch_callback_ = std::move(callback);
+  }
+
+ protected:
+  void OnDidRunningStatusChange(blink::EmbeddedWorkerStatus running_status,
+                                int64_t version_id) override {
+    worker_running_status_ = running_status;
+    // We assume the next worker that arrives here is the one we're testing.
+    // This would be an incorrect assumption if we ever allowed multiple workers
+    // for an extension.
+    test_worker_version_id_ = version_id;
+    CheckWorkerStatusAndMaybeDispatchTestEvent(version_id);
+  }
+
+  // If running status matches desired running status then run the test event
+  // callback.
+  void CheckWorkerStatusAndMaybeDispatchTestEvent(
+      int64_t target_worker_version_id) {
+    if (!test_event_dispatch_callback_.is_null() &&
+        worker_running_status_ == dispatch_callback_on_status_) {
+      std::move(test_event_dispatch_callback_).Run();
+    }
+  }
+
+ private:
+  int64_t test_worker_version_id_ =
+      blink::mojom::kInvalidServiceWorkerVersionId;
+  blink::EmbeddedWorkerStatus worker_running_status_;
+  blink::EmbeddedWorkerStatus dispatch_callback_on_status_;
+  base::OnceCallback<void()> test_event_dispatch_callback_;
+};
+
 class ServiceWorkerEventDispatchingBrowserTest : public ExtensionBrowserTest {
  public:
   ServiceWorkerEventDispatchingBrowserTest() = default;
@@ -126,7 +152,6 @@
   ServiceWorkerEventDispatchingBrowserTest& operator=(
       const ServiceWorkerEventDispatchingBrowserTest&) = delete;
 
-  // ExtensionBrowserTest overrides:
   void SetUpOnMainThread() override {
     ExtensionBrowserTest::SetUpOnMainThread();
     ASSERT_TRUE(embedded_test_server()->Start());
@@ -136,15 +161,22 @@
   void TearDownOnMainThread() override {
     ExtensionBrowserTest::TearDownOnMainThread();
     sw_context_ = nullptr;
-    extension = nullptr;
   }
 
   content::WebContents* web_contents() const {
     return browser()->tab_strip_model()->GetActiveWebContents();
   }
 
+  // Broadcasts a webNavigation.onBeforeNavigate event.
+  void DispatchWebNavigationEvent() {
+    EventRouter* router = EventRouter::EventRouter::Get(profile());
+    testing::NiceMock<content::MockNavigationHandle> handle(web_contents());
+    auto event =
+        web_navigation_api_helpers::CreateOnBeforeNavigateEvent(&handle);
+    router->BroadcastEvent(std::move(event));
+  }
+
  protected:
-  raw_ptr<const Extension> extension = nullptr;
   raw_ptr<content::ServiceWorkerContext> sw_context_ = nullptr;
 };
 
@@ -152,7 +184,8 @@
 // `blink::EmbeddedWorkerStatus::kRunning` succeeds.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerEventDispatchingBrowserTest,
                        DispatchToRunningWorker) {
-  TestWorkerStatusObserver test_event_observer(profile(), kTestExtensionId);
+  TestServiceWorkerContextObserver sw_started_observer(profile(),
+                                                       kTestExtensionId);
   ExtensionTestMessageListener extension_oninstall_listener_fired(
       "installed listener fired");
   const Extension* extension = LoadExtension(
@@ -160,19 +193,49 @@
       {.wait_for_registration_stored = true});
   ASSERT_TRUE(extension);
   ASSERT_EQ(kTestExtensionId, extension->id());
-  test_event_observer.WaitForWorkerStarted();
-  ASSERT_TRUE(content::CheckServiceWorkerIsRunning(
-      sw_context_, test_event_observer.test_worker_version_id));
   // This ensures that we wait until the the browser receives the ack from the
   // renderer. This prevents unexpected histogram emits later.
   ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());
+  ASSERT_TRUE(content::CheckServiceWorkerIsRunning(
+      sw_context_, sw_started_observer.test_worker_version_id));
 
+  // Stop the worker, and wait for it to stop. We must stop it first before we
+  // can observe the kRunning status.
+  browsertest_util::StopServiceWorkerForExtensionGlobalScope(
+      browser()->profile(), extension->id());
+  sw_started_observer.WaitForWorkerStopped();
+  ASSERT_TRUE(content::CheckServiceWorkerIsStopped(
+      sw_context_, sw_started_observer.test_worker_version_id));
+
+  // Add observer that will watch for changes to the running status of the
+  // worker.
+  TestExtensionServiceWorkerRunningStatusObserver test_event_observer(
+      GetServiceWorkerContext());
+  // Setup to run the test event when kRunning status is encountered.
+  test_event_observer.SetDispatchTestEventCallback(base::BindOnce(
+      &ServiceWorkerEventDispatchingBrowserTest::DispatchWebNavigationEvent,
+      base::Unretained(this)));
+  test_event_observer.SetDispatchCallbackOnStatus(
+      blink::EmbeddedWorkerStatus::kRunning);
+
+  // Setup listeners for confirming the event ran successfully.
   base::HistogramTester histogram_tester;
   ExtensionTestMessageListener extension_event_listener_fired("listener fired");
-  DispatchWebNavigationEvent(profile(), web_contents());
+
+  // Start the worker.
+  sw_context_->StartWorkerForScope(/*scope=*/extension->url(),
+                                   /*key=*/
+                                   blink::StorageKey::CreateFirstParty(
+                                       url::Origin::Create(extension->url())),
+                                   /*info_callback=*/base::DoNothing(),
+                                   /*failure_callback=*/base::DoNothing());
+
+  // During the above start request we catch the kRunning status with
+  // TestExtensionServiceWorkerRunningStatusObserver::OnDidRunningStatusChange()
+  // then synchronously dispatch the test event there.
 
   // The histogram expect checks that we get an ack from the renderer to the
-  // browser for the event. The wait confirms that extension worker listener
+  // browser for the event. The wait confirms that the extension worker listener
   // finished. The wait is first (despite temporally possibly being after the
   // ack) because it is currently the most convenient to wait on.
   EXPECT_TRUE(extension_event_listener_fired.WaitUntilSatisfied());
@@ -183,10 +246,13 @@
 }
 
 // Tests that dispatching an event to a worker with status
-// `blink::EmbeddedWorkerStatus::kStopped` succeeds.
+// `blink::EmbeddedWorkerStatus::kStopped` succeeds. This logic is laid out
+// differently than in the other test cases because we can't currently detect
+// precisely when a worker enters the stopped status.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerEventDispatchingBrowserTest,
                        DispatchToStoppedWorker) {
-  TestWorkerStatusObserver test_event_observer(profile(), kTestExtensionId);
+  TestServiceWorkerContextObserver sw_started_stopped_observer(
+      profile(), kTestExtensionId);
   ExtensionTestMessageListener extension_oninstall_listener_fired(
       "installed listener fired");
   const Extension* extension = LoadExtension(
@@ -194,30 +260,42 @@
       {.wait_for_registration_stored = true});
   ASSERT_TRUE(extension);
   ASSERT_EQ(kTestExtensionId, extension->id());
-  test_event_observer.WaitForWorkerStarted();
   // This ensures that we wait until the the browser receives the ack from the
   // renderer. This prevents unexpected histogram emits later.
   ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());
-  // Workers must be kStarting or kRunning before being stopped.
   ASSERT_TRUE(content::CheckServiceWorkerIsRunning(
-      GetServiceWorkerContext(profile()),
-      test_event_observer.test_worker_version_id));
+      sw_context_, sw_started_stopped_observer.test_worker_version_id));
 
-  // Stop the worker and wait until it's stopped.
-  ExtensionTestMessageListener extension_event_listener_fired("listener fired");
-  browsertest_util::StopServiceWorkerForExtensionGlobalScope(profile(),
-                                                             extension->id());
-  test_event_observer.WaitForWorkerStopped();
+  // ServiceWorkerVersion is destroyed async when we stop the worker so we can't
+  // precisely check when the worker stopped. So instead, wait for when we
+  // notice a stopping worker, confirm the worker didn't restart, and check the
+  // worker's status to confirm kStopped occurred to be as certain that we can
+  // that the worker is stopped when we dispatch the event.
+  TestExtensionServiceWorkerRunningStatusObserver worker_restarted_observer(
+      GetServiceWorkerContext());
+  // Stop the worker, and wait for it to stop.
+  browsertest_util::StopServiceWorkerForExtensionGlobalScope(
+      browser()->profile(), extension->id());
+  sw_started_stopped_observer.WaitForWorkerStopped();
+  // TODO(crbug.com/1467015): Add a more guaranteed check that the worker was
+  // stopped when we dispatch the event. This check confirms the worker is
+  // currently stopped, but doesn't guarantee that when we dispatch the event
+  // below that it is still stopped.
   ASSERT_TRUE(content::CheckServiceWorkerIsStopped(
-      sw_context_, test_event_observer.test_worker_version_id));
+      sw_context_,
+      // Service workers keep the same version id across restarts.
+      sw_started_stopped_observer.test_worker_version_id));
 
+  // Setup listeners for confirming the event ran successfully.
   base::HistogramTester histogram_tester;
-  DispatchWebNavigationEvent(profile(), web_contents());
+  ExtensionTestMessageListener extension_event_listener_fired("listener fired");
+
+  DispatchWebNavigationEvent();
 
   // The histogram expect checks that we get an ack from the renderer to the
-  // browser for the event. The wait confirms that extension worker listener
-  // finished. The wait is first (despite temporally possibly being after the
-  // ack) because it is currently the most convenient to wait on.
+  // browser for the event. The wait confirms that the extension worker
+  // listener finished. The wait is first (despite temporally possibly being
+  // after the ack) because it is currently the most convenient to wait on.
   EXPECT_TRUE(extension_event_listener_fired.WaitUntilSatisfied());
   // Call to webNavigation.onBeforeNavigate expected.
   histogram_tester.ExpectTotalCount(
@@ -226,17 +304,17 @@
 }
 
 // Tests that dispatching an event to a worker with status
-// `blink::EmbeddedWorkerStatus::kStarting` succeeds. This test first installs
-// the extensions and waits for the worker to fully start. Then stops it and
-// starts it again to catch the kStarting status. This is to avoid event
+// `blink::EmbeddedWorkerStatus::kStarting` succeeds. This test first
+// installs the extension and waits for the worker to fully start. Then stops it
+// and starts it again to catch the kStarting status. This is to avoid event
 // acknowledgments on install we aren't trying to test for.
 // TODO(jlulejian): If we suspect or see worker bugs that occur on extension
 // install then create test cases where we dispatch events immediately on
 // extension install.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerEventDispatchingBrowserTest,
                        DispatchToStartingWorker) {
-  // Install the extension and ensure the worker is started.
-  TestWorkerStatusObserver start_worker_observer(profile(), kTestExtensionId);
+  TestServiceWorkerContextObserver sw_started_stopped_observer(
+      profile(), kTestExtensionId);
   ExtensionTestMessageListener extension_oninstall_listener_fired(
       "installed listener fired");
   const Extension* extension = LoadExtension(
@@ -244,24 +322,33 @@
       {.wait_for_registration_stored = true});
   ASSERT_TRUE(extension);
   ASSERT_EQ(kTestExtensionId, extension->id());
-  start_worker_observer.WaitForWorkerStarted();
   // This ensures that we wait until the the browser receives the ack from the
   // renderer. This prevents unexpected histogram emits later.
   ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());
-  // Workers must be kStarting or kRunning before being stopped.
   ASSERT_TRUE(content::CheckServiceWorkerIsRunning(
-      GetServiceWorkerContext(profile()),
-      start_worker_observer.test_worker_version_id));
+      sw_context_, sw_started_stopped_observer.test_worker_version_id));
 
-  // Stop the worker so that we may catch the kStarting afterwards.
-  content::StopServiceWorkerForScope(sw_context_, extension->url(),
-                                     base::DoNothing());
-  start_worker_observer.WaitForWorkerStopped();
-  ASSERT_TRUE(content::CheckServiceWorkerIsStopped(
-      GetServiceWorkerContext(profile()),
-      start_worker_observer.test_worker_version_id));
+  // Stop the worker, and wait for it to stop. We must stop it first before we
+  // can start and observe the kStarting status.
+  browsertest_util::StopServiceWorkerForExtensionGlobalScope(
+      browser()->profile(), extension->id());
+  sw_started_stopped_observer.WaitForWorkerStopped();
 
-  TestWorkerStatusObserver test_event_observer(profile(), kTestExtensionId);
+  // Add observer that will watch for changes to the running status of the
+  // worker.
+  TestExtensionServiceWorkerRunningStatusObserver test_event_observer(
+      GetServiceWorkerContext());
+  // Setup to run the test event when kStarting status is encountered.
+  test_event_observer.SetDispatchTestEventCallback(base::BindOnce(
+      &ServiceWorkerEventDispatchingBrowserTest::DispatchWebNavigationEvent,
+      base::Unretained(this)));
+  test_event_observer.SetDispatchCallbackOnStatus(
+      blink::EmbeddedWorkerStatus::kStarting);
+
+  // Setup listeners for confirming the event ran successfully.
+  base::HistogramTester histogram_tester;
+  ExtensionTestMessageListener extension_event_listener_fired("listener fired");
+
   // Start the worker and wait until the worker is kStarting.
   sw_context_->StartWorkerForScope(/*scope=*/extension->url(),
                                    /*key=*/
@@ -269,18 +356,13 @@
                                        url::Origin::Create(extension->url())),
                                    /*info_callback=*/base::DoNothing(),
                                    /*failure_callback=*/base::DoNothing());
-  test_event_observer.WaitForWorkerStarting();
-  ASSERT_TRUE(content::CheckServiceWorkerIsStarting(
-      GetServiceWorkerContext(profile()),
-      test_event_observer.test_worker_version_id));
 
-  // Fire the test event with the worker in kStarting status.
-  base::HistogramTester histogram_tester;
-  ExtensionTestMessageListener extension_event_listener_fired("listener fired");
-  DispatchWebNavigationEvent(profile(), web_contents());
+  // During the above start request we catch the transient kStarting status with
+  // TestExtensionServiceWorkerRunningStatusObserver::OnDidRunningStatusChange()
+  // then synchronously dispatch the test event there.
 
   // The histogram expect checks that we get an ack from the renderer to the
-  // browser for the event. The wait confirms that extension worker listener
+  // browser for the event. The wait confirms that the extension worker listener
   // finished. The wait is first (despite temporally possibly being after the
   // ack) because it is currently the most convenient to wait on.
   EXPECT_TRUE(extension_event_listener_fired.WaitUntilSatisfied());
@@ -290,16 +372,12 @@
       /*expected_count=*/1);
 }
 
-// TODO(crbug.com/1467015): Re-enable once we determine how to fix flakiness.
-// This test is flaky on the content::CheckServiceWorkerIsStopping(...) assert
-// because the worker can fully stop anytime after
-// content::StopServiceWorkerForScope(...).
-
 // Tests that dispatching an event to a
 // worker with status `blink::EmbeddedWorkerStatus::kStopping` succeeds.
 IN_PROC_BROWSER_TEST_F(ServiceWorkerEventDispatchingBrowserTest,
-                       DISABLED_DispatchToStoppingWorker) {
-  TestWorkerStatusObserver test_event_observer(profile(), kTestExtensionId);
+                       DispatchToStoppingWorker) {
+  TestServiceWorkerContextObserver sw_started_observer(profile(),
+                                                       kTestExtensionId);
   ExtensionTestMessageListener extension_oninstall_listener_fired(
       "installed listener fired");
   const Extension* extension = LoadExtension(
@@ -307,29 +385,38 @@
       {.wait_for_registration_stored = true});
   ASSERT_TRUE(extension);
   ASSERT_EQ(kTestExtensionId, extension->id());
-  test_event_observer.WaitForWorkerStarted();
   // This ensures that we wait until the the browser receives the ack from the
   // renderer. This prevents unexpected histogram emits later.
   ASSERT_TRUE(extension_oninstall_listener_fired.WaitUntilSatisfied());
-  // Workers must be kStarting or kRunning before being stopped.
   ASSERT_TRUE(content::CheckServiceWorkerIsRunning(
-      GetServiceWorkerContext(profile()),
-      test_event_observer.test_worker_version_id));
+      sw_context_, sw_started_observer.test_worker_version_id));
 
-  // Stop the worker, but don't wait for it to stop. We want to catch the state
-  // change to stopping when we dispatch the event.
-  content::StopServiceWorkerForScope(sw_context_, extension->url(),
-                                     base::DoNothing());
-  test_event_observer.WaitForWorkerStopping();
-  ASSERT_TRUE(content::CheckServiceWorkerIsStopping(
-      sw_context_, test_event_observer.test_worker_version_id));
+  // Add observer that will watch for changes to the running status of the
+  // worker.
+  TestExtensionServiceWorkerRunningStatusObserver test_event_observer(
+      GetServiceWorkerContext(), sw_started_observer.test_worker_version_id);
+  // Setup to run the test event when kStopping status is encountered.
+  test_event_observer.SetDispatchTestEventCallback(base::BindOnce(
+      &ServiceWorkerEventDispatchingBrowserTest::DispatchWebNavigationEvent,
+      base::Unretained(this)));
+  test_event_observer.SetDispatchCallbackOnStatus(
+      blink::EmbeddedWorkerStatus::kStopping);
 
+  // Setup listeners for confirming the event ran successfully.
   base::HistogramTester histogram_tester;
   ExtensionTestMessageListener extension_event_listener_fired("listener fired");
-  DispatchWebNavigationEvent(profile(), web_contents());
+
+  // Stop the worker, but don't wait for it to stop. We want to catch the state
+  // change to kStopping status when we dispatch the event.
+  content::StopServiceWorkerForScope(sw_context_, extension->url(),
+                                     base::DoNothing());
+
+  // During the above stop request we catch the kStopped status with
+  // TestExtensionServiceWorkerRunningStatusObserver::OnDidRunningStatusChange()
+  // then synchronously dispatch the test event there.
 
   // The histogram expect checks that we get an ack from the renderer to the
-  // browser for the event. The wait confirms that extension worker listener
+  // browser for the event. The wait confirms that the extension worker listener
   // finished. The wait is first (despite temporally possibly being after the
   // ack) because it is currently the most convenient to wait on.
   EXPECT_TRUE(extension_event_listener_fired.WaitUntilSatisfied());
diff --git a/chrome/browser/extensions/updater/update_service_browsertest.cc b/chrome/browser/extensions/updater/update_service_browsertest.cc
index 73ee719..da427f7c 100644
--- a/chrome/browser/extensions/updater/update_service_browsertest.cc
+++ b/chrome/browser/extensions/updater/update_service_browsertest.cc
@@ -668,6 +668,7 @@
   }
 }
 
+#if !(defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_CHROMEOS))
 // We want to test what happens at startup with a corroption-disabled policy
 // force installed extension. So we set that up in the PRE test here.
 IN_PROC_BROWSER_TEST_F(PolicyUpdateServiceTest, PRE_PolicyCorruptedOnStartup) {
@@ -736,5 +737,6 @@
       CHECK_DEREF(app.FindList("disabled"))[0].GetDict();
   EXPECT_EQ(disable_reason::DISABLE_CORRUPTED, disabled.FindInt("reason"));
 }
+#endif  // !(defined(ADDRESS_SANITIZER) && BUILDFLAG(IS_CHROMEOS))
 
 }  // namespace extensions
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 432ba53..7a0c7c0 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1105,11 +1105,6 @@
     "expiry_milestone": 140
   },
   {
-    "name": "cct-real-time-engagement-signals",
-    "owners": ["sinansahin@google.com", "jinsukkim@chromium.org"],
-    "expiry_milestone": 120
-  },
-  {
     "name": "cct-resizable-90-maximum-height",
     "owners": ["jinsukkim@chromium.org", "twellington@chromium.org"],
     "expiry_milestone": 110
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index ac4fefc..b4f7532 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3904,11 +3904,6 @@
     "Side sheet Custom Tabs (third party)";
 const char kCCTResizableSideSheetForThirdPartiesDescription[] =
     "Enable side sheet Custom Tabs for third party apps.";
-const char kCCTRealTimeEngagementSignalsName[] =
-    "Enable CCT real-time engagement signals.";
-const char kCCTRealTimeEngagementSignalsDescription[] =
-    "Enables sending real-time engagement signals (e.g. scroll) through "
-    "CustomTabsCallback.";
 
 const char kAccountReauthenticationRecentTimeWindowName[] =
     "Account Reauthentication Recent Time Window";
@@ -4522,10 +4517,6 @@
 const char kVideoTutorialsName[] = "Enable video tutorials";
 const char kVideoTutorialsDescription[] = "Show video tutorials in Chrome";
 
-const char kAdaptiveButtonInTopToolbarName[] = "Adaptive button in top toolbar";
-const char kAdaptiveButtonInTopToolbarDescription[] =
-    "Enables showing an adaptive action button in the top toolbar";
-
 const char kAdaptiveButtonInTopToolbarTranslateName[] =
     "Adaptive button in top toolbar - Translate button";
 const char kAdaptiveButtonInTopToolbarTranslateDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index acba0d3..65b8bcb 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2263,9 +2263,6 @@
 extern const char kCCTResizableSideSheetForThirdPartiesName[];
 extern const char kCCTResizableSideSheetForThirdPartiesDescription[];
 
-extern const char kCCTRealTimeEngagementSignalsName[];
-extern const char kCCTRealTimeEngagementSignalsDescription[];
-
 extern const char kAccountReauthenticationRecentTimeWindowName[];
 extern const char kAccountReauthenticationRecentTimeWindowDescription[];
 extern const char kAccountReauthenticationRecentTimeWindowDefault[];
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 6ff0e2f..f9936d23 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -149,7 +149,6 @@
     &history_clusters::internal::kJourneys,
     &history_clusters::internal::kOmniboxAction,
     &history_clusters::internal::kOmniboxHistoryClusterProvider,
-    &kAdaptiveButtonInTopToolbar,
     &kAdaptiveButtonInTopToolbarTranslate,
     &kAdaptiveButtonInTopToolbarAddToBookmarks,
     &kAdaptiveButtonInTopToolbarCustomizationV2,
@@ -161,7 +160,6 @@
     &kAndroidAppIntegration,
     &kAndroidHatsRefactor,
     &kAndroidHub,
-    &kAndroidSearchEngineChoiceNotification,
     &kAndroidImprovedBookmarks,
     &kAndroidNoVisibleHintForTablets,
     &kAndroidVisibleUrlTruncation,
@@ -186,7 +184,6 @@
     &kCCTMinimized,
     &kCCTPageInsightsHub,
     &kCCTPageInsightsHubBetterScroll,
-    &kCCTRealTimeEngagementSignals,
     &kCCTReportParallelRequestStatus,
     &kCCTResizableForThirdParties,
     &kCCTResizableSideSheet,
@@ -384,10 +381,6 @@
 
 // Alphabetical:
 
-BASE_FEATURE(kAdaptiveButtonInTopToolbar,
-             "AdaptiveButtonInTopToolbar",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kAdaptiveButtonInTopToolbarTranslate,
              "AdaptiveButtonInTopToolbarTranslate",
              base::FEATURE_DISABLED_BY_DEFAULT);
@@ -434,10 +427,6 @@
 
 BASE_FEATURE(kAndroidHub, "AndroidHub", base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kAndroidSearchEngineChoiceNotification,
-             "AndroidSearchEngineChoiceNotification",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kAndroidImprovedBookmarks,
              "AndroidImprovedBookmarks",
              base::FEATURE_ENABLED_BY_DEFAULT);
@@ -517,10 +506,6 @@
              "CCTPageInsightsHubBetterScroll",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-BASE_FEATURE(kCCTRealTimeEngagementSignals,
-             "CCTRealTimeEngagementSignals",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kCCTReportParallelRequestStatus,
              "CCTReportParallelRequestStatus",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index c1d817e..a3055000 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -25,7 +25,6 @@
 BASE_DECLARE_FEATURE(kAndroidAppIntegration);
 BASE_DECLARE_FEATURE(kAndroidHatsRefactor);
 BASE_DECLARE_FEATURE(kAndroidHub);
-BASE_DECLARE_FEATURE(kAndroidSearchEngineChoiceNotification);
 BASE_DECLARE_FEATURE(kAndroidImprovedBookmarks);
 BASE_DECLARE_FEATURE(kAndroidNoVisibleHintForTablets);
 BASE_DECLARE_FEATURE(kAndroidVisibleUrlTruncation);
@@ -51,7 +50,6 @@
 BASE_DECLARE_FEATURE(kCCTPageInsightsHub);
 BASE_DECLARE_FEATURE(kCCTPageInsightsHubBetterScroll);
 BASE_DECLARE_FEATURE(kCCTPrefetchDelayShowOnStart);
-BASE_DECLARE_FEATURE(kCCTRealTimeEngagementSignals);
 BASE_DECLARE_FEATURE(kCCTReportParallelRequestStatus);
 BASE_DECLARE_FEATURE(kCCTResizableForThirdParties);
 BASE_DECLARE_FEATURE(kCCTResizableSideSheet);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 389e3e1..8c66150 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -102,8 +102,6 @@
         return ChromeFeatureMap.getInstance().mutableFlagWithSafeDefault(featureName, defaultValue);
     }
 
-    /* Alphabetical: */
-    public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR = "AdaptiveButtonInTopToolbar";
     public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_TRANSLATE =
             "AdaptiveButtonInTopToolbarTranslate";
     public static final String ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_ADD_TO_BOOKMARKS =
@@ -118,8 +116,6 @@
     public static final String ANDROID_APP_INTEGRATION = "AndroidAppIntegration";
     public static final String ANDROID_HATS_REFACTOR = "AndroidHatsRefactor";
     public static final String ANDROID_HUB = "AndroidHub";
-    public static final String ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION =
-            "AndroidSearchEngineChoiceNotification";
     public static final String ANDROID_IMPROVED_BOOKMARKS = "AndroidImprovedBookmarks";
     public static final String ANDROID_VISIBLE_URL_TRUNCATION = "AndroidVisibleUrlTruncation";
     public static final String ANDROID_NO_VISIBLE_HINT_FOR_TABLETS =
@@ -181,7 +177,6 @@
     public static final String CCT_PAGE_INSIGHTS_HUB = "CCTPageInsightsHub";
     public static final String CCT_PAGE_INSIGHTS_HUB_BETTER_SCROLL =
             "CCTPageInsightsHubBetterScroll";
-    public static final String CCT_REAL_TIME_ENGAGEMENT_SIGNALS = "CCTRealTimeEngagementSignals";
     public static final String CCT_REPORT_PARALLEL_REQUEST_STATUS =
             "CCTReportParallelRequestStatus";
     public static final String CCT_RESIZABLE_FOR_THIRD_PARTIES = "CCTResizableForThirdParties";
@@ -683,8 +678,6 @@
             newMutableFlagWithSafeDefault(ADVANCED_PERIPHERALS_SUPPORT_TAB_STRIP, true);
     public static final MutableFlagWithSafeDefault sAndroidImprovedBookmarks =
             newMutableFlagWithSafeDefault(ANDROID_IMPROVED_BOOKMARKS, false);
-    public static final MutableFlagWithSafeDefault sCctRealTimeEngagementSignals =
-            newMutableFlagWithSafeDefault(CCT_REAL_TIME_ENGAGEMENT_SIGNALS, false);
     public static final MutableFlagWithSafeDefault sDeferKeepScreenOnDuringGesture =
             newMutableFlagWithSafeDefault(DEFER_KEEP_SCREEN_ON_DURING_GESTURE, false);
     public static final MutableFlagWithSafeDefault sDeferNotifyInMotion =
diff --git a/chrome/browser/net/proxy_browsertest.cc b/chrome/browser/net/proxy_browsertest.cc
index 434812e3..87eafb3 100644
--- a/chrome/browser/net/proxy_browsertest.cc
+++ b/chrome/browser/net/proxy_browsertest.cc
@@ -10,9 +10,9 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/run_until.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
-#include "chrome/browser/auth_notification_types.h"
 #include "chrome/browser/net/proxy_test_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -23,8 +23,6 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "content/public/browser/browser_thread.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_source.h"
 #include "content/public/browser/storage_partition.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -61,35 +59,6 @@
                       "hasError;"));
 }
 
-// This class observes chrome::NOTIFICATION_AUTH_NEEDED and supplies
-// the credential which is required by the test proxy server.
-// "foo:bar" is the required username and password for our test proxy server.
-class LoginPromptObserver : public content::NotificationObserver {
- public:
-  LoginPromptObserver() : auth_handled_(false) {}
-
-  LoginPromptObserver(const LoginPromptObserver&) = delete;
-  LoginPromptObserver& operator=(const LoginPromptObserver&) = delete;
-
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override {
-    if (type == chrome::NOTIFICATION_AUTH_NEEDED) {
-      LoginNotificationDetails* login_details =
-          content::Details<LoginNotificationDetails>(details).ptr();
-      // |login_details->handler()| is the associated LoginHandler object.
-      // SetAuth() will close the login dialog.
-      login_details->handler()->SetAuth(u"foo", u"bar");
-      auth_handled_ = true;
-    }
-  }
-
-  bool auth_handled() const { return auth_handled_; }
-
- private:
-  bool auth_handled_;
-};
-
 // Test that the browser can establish a WebSocket connection via a proxy
 // that requires basic authentication. This test also checks the headers
 // arrive at WebSocket server.
@@ -101,13 +70,6 @@
 
   content::WebContents* tab =
       browser()->tab_strip_model()->GetActiveWebContents();
-  content::NavigationController* controller = &tab->GetController();
-  content::NotificationRegistrar registrar;
-  // The proxy server will request basic authentication.
-  // |observer| supplies the credential.
-  LoginPromptObserver observer;
-  registrar.Add(&observer, chrome::NOTIFICATION_AUTH_NEEDED,
-                content::Source<content::NavigationController>(controller));
 
   content::TitleWatcher watcher(tab, u"PASS");
   watcher.AlsoWaitForTitle(u"FAIL");
@@ -120,9 +82,12 @@
       browser(), ws_server.GetURL("proxied_request_check.html")
                      .ReplaceComponents(replacements)));
 
+  ASSERT_TRUE(base::test::RunUntil(
+      []() { return LoginHandler::GetAllLoginHandlersForTest().size() == 1; }));
+  LoginHandler::GetAllLoginHandlersForTest().front()->SetAuth(u"foo", u"bar");
+
   const std::u16string result = watcher.WaitAndGetTitle();
   EXPECT_TRUE(base::EqualsASCII(result, "PASS"));
-  EXPECT_TRUE(observer.auth_handled());
 }
 
 // Fetches a PAC script via an http:// URL, and ensures that requests to
diff --git a/chrome/browser/net/websocket_browsertest.cc b/chrome/browser/net/websocket_browsertest.cc
index 9e5b4e7..23616b3 100644
--- a/chrome/browser/net/websocket_browsertest.cc
+++ b/chrome/browser/net/websocket_browsertest.cc
@@ -22,7 +22,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
 #include "build/build_config.h"
-#include "chrome/browser/auth_notification_types.h"
 #include "chrome/browser/content_settings/cookie_settings_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -35,10 +34,6 @@
 #include "components/content_settings/core/common/features.h"
 #include "components/content_settings/core/common/pref_names.h"
 #include "components/prefs/pref_service.h"
-#include "content/public/browser/navigation_controller.h"
-#include "content/public/browser/notification_details.h"
-#include "content/public/browser/notification_registrar.h"
-#include "content/public/browser/notification_source.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
@@ -268,46 +263,6 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
-// Automatically fill in any login prompts that appear with the supplied
-// credentials.
-class AutoLogin : public content::NotificationObserver {
- public:
-  AutoLogin(const std::string& username,
-            const std::string& password,
-            content::NavigationController* navigation_controller)
-      : username_(base::UTF8ToUTF16(username)),
-        password_(base::UTF8ToUTF16(password)),
-        logged_in_(false) {
-    registrar_.Add(
-        this,
-        chrome::NOTIFICATION_AUTH_NEEDED,
-        content::Source<content::NavigationController>(navigation_controller));
-  }
-
-  AutoLogin(const AutoLogin&) = delete;
-  AutoLogin& operator=(const AutoLogin&) = delete;
-
-  // NotificationObserver implementation
-  void Observe(int type,
-               const content::NotificationSource& source,
-               const content::NotificationDetails& details) override {
-    DCHECK_EQ(chrome::NOTIFICATION_AUTH_NEEDED, type);
-    LoginHandler* login_handler =
-        content::Details<LoginNotificationDetails>(details)->handler();
-    login_handler->SetAuth(username_, password_);
-    logged_in_ = true;
-  }
-
-  bool logged_in() const { return logged_in_; }
-
- private:
-  const std::u16string username_;
-  const std::u16string password_;
-  bool logged_in_;
-
-  content::NotificationRegistrar registrar_;
-};
-
 // Test that the browser can handle a WebSocket frame split into multiple TCP
 // segments.
 IN_PROC_BROWSER_TEST_F(WebSocketBrowserTest, WebSocketSplitSegments) {
@@ -415,14 +370,12 @@
   // Launch a basic-auth-protected WebSocket server.
   ws_server_.set_websocket_basic_auth(true);
   ASSERT_TRUE(ws_server_.Start());
-
-  content::NavigationController* navigation_controller =
-      &browser()->tab_strip_model()->GetActiveWebContents()->GetController();
-  AutoLogin auto_login("test", "test", navigation_controller);
-
   NavigateToHTTP("connect_check.html");
 
-  ASSERT_TRUE(base::test::RunUntil([&]() { return auto_login.logged_in(); }));
+  ASSERT_TRUE(base::test::RunUntil(
+      []() { return LoginHandler::GetAllLoginHandlersForTest().size() == 1; }));
+  LoginHandler::GetAllLoginHandlersForTest().front()->SetAuth(u"test", u"test");
+
   EXPECT_EQ("PASS", WaitAndGetTitle());
 }
 
diff --git a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
index 739629f..dd21de6 100644
--- a/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
+++ b/chrome/browser/performance_manager/chrome_browser_main_extra_parts_performance_manager.cc
@@ -212,12 +212,8 @@
 
   graph->PassToGraph(
       std::make_unique<performance_manager::metrics::MemoryPressureMetrics>());
-
-  if (base::FeatureList::IsEnabled(
-          performance_manager::features::kPageTimelineMonitor)) {
-    graph->PassToGraph(
-        std::make_unique<performance_manager::metrics::PageResourceMonitor>());
-  }
+  graph->PassToGraph(
+      std::make_unique<performance_manager::metrics::PageResourceMonitor>());
 
   if (base::FeatureList::IsEnabled(
           performance_manager::features::kBFCachePerformanceManagerPolicy)) {
diff --git a/chrome/browser/performance_manager/metrics/page_resource_monitor.cc b/chrome/browser/performance_manager/metrics/page_resource_monitor.cc
index 2d13062..59f3d277 100644
--- a/chrome/browser/performance_manager/metrics/page_resource_monitor.cc
+++ b/chrome/browser/performance_manager/metrics/page_resource_monitor.cc
@@ -7,40 +7,34 @@
 #include <stdint.h>
 #include <algorithm>
 #include <array>
+#include <functional>
+#include <iterator>
 #include <limits>
-#include <map>
-#include <memory>
 #include <numeric>
 #include <utility>
 #include <vector>
 
 #include "base/check.h"
-#include "base/containers/contains.h"
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
-#include "base/memory/raw_ptr.h"
 #include "base/metrics/histogram_functions.h"
-#include "base/notreached.h"
-#include "base/rand_util.h"
 #include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/system/sys_info.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "build/build_config.h"
 #include "chrome/browser/performance_manager/metrics/cpu_probe/cpu_probe.h"
-#include "components/content_settings/core/common/content_settings_types.h"
-#include "components/performance_manager/public/decorators/page_live_state_decorator.h"
 #include "components/performance_manager/public/features.h"
 #include "components/performance_manager/public/graph/page_node.h"
-#include "services/metrics/public/cpp/metrics_utils.h"
+#include "components/performance_manager/public/resource_attribution/cpu_measurement_delegate.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_recorder.h"
+#include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
-#if !BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/performance_manager/policies/memory_saver_mode_policy.h"
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 namespace performance_manager::metrics {
 
 namespace {
@@ -83,18 +77,8 @@
 }  // namespace
 
 PageResourceMonitor::PageResourceMonitor(bool enable_system_cpu_probe)
-    // These counters are initialized to a random value due to privacy concerns,
-    // so that we cannot tie either the startup time of a specific tab or the
-    // recording time of a specific slice to the browser startup time.
-    : slice_id_counter_(base::RandInt(1, 32767)),
-      system_cpu_probe_(enable_system_cpu_probe ? CpuProbe::Create()
+    : system_cpu_probe_(enable_system_cpu_probe ? CpuProbe::Create()
                                                 : nullptr) {
-  collect_slice_timer_.Start(
-      FROM_HERE,
-      performance_manager::features::kPageTimelineStateIntervalTime.Get(), this,
-      &PageResourceMonitor::CollectSlice);
-
-  // PageResourceUsage is collected on a different schedule from PageTimeline.
   collect_page_resource_usage_timer_.Start(
       FROM_HERE, base::Minutes(2),
       base::BindRepeating(&PageResourceMonitor::CollectPageResourceUsage,
@@ -106,25 +90,6 @@
 
 PageResourceMonitor::~PageResourceMonitor() = default;
 
-PageResourceMonitor::PageState
-PageResourceMonitor::PageNodeInfo::GetPageState() {
-  switch (current_lifecycle) {
-    case PageNode::LifecycleState::kRunning: {
-      if (currently_visible) {
-        return PageState::kVisible;
-      } else {
-        return PageState::kBackground;
-      }
-    }
-    case PageNode::LifecycleState::kFrozen: {
-      return PageState::kFrozen;
-    }
-    case PageNode::LifecycleState::kDiscarded: {
-      return PageState::kDiscarded;
-    }
-  }
-}
-
 void PageResourceMonitor::CollectPageResourceUsage(
     base::OnceClosure done_closure) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -211,114 +176,6 @@
 #endif
 }
 
-void PageResourceMonitor::CollectSlice() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // We only collect a slice randomly every ~20 times this gets called for
-  // privacy purposes. Always fall through when we're in a test.
-  if (!ShouldCollectSlice()) {
-    return;
-  }
-
-  const base::TimeTicks now = base::TimeTicks::Now();
-  const int slice_id = slice_id_counter_++;
-  base::TimeDelta time_since_last_slice = now - time_of_last_slice_;
-
-  time_of_last_slice_ = now;
-
-  for (auto const& pair : page_node_info_map_) {
-    const PageNode* page_node = pair.first->page_node();
-    const std::unique_ptr<PageNodeInfo>& curr_info = pair.second;
-    CheckPageState(page_node, *curr_info);
-
-    const PageNode::LifecycleState lifecycle_state =
-        page_node->GetLifecycleState();
-    const bool is_visible = page_node->IsVisible();
-    const ukm::SourceId source_id = page_node->GetUkmSourceID();
-
-    DCHECK_EQ(is_visible, curr_info->currently_visible);
-    DCHECK(curr_info->current_lifecycle == mojom::LifecycleState::kDiscarded ||
-           lifecycle_state == curr_info->current_lifecycle);
-
-    if (is_visible) {
-      curr_info->total_foreground_milliseconds +=
-          (now - curr_info->time_of_last_foreground_millisecond_update)
-              .InMilliseconds();
-      curr_info->time_of_last_foreground_millisecond_update = now;
-    }
-
-    bool is_active_tab = false;
-    bool has_notification_permission = false;
-    bool is_capturing_media = false;
-    bool is_connected_to_device = false;
-    bool updated_title_or_favicon_in_background = false;
-
-    const auto* page_live_state_data =
-        PageLiveStateDecorator::Data::FromPageNode(page_node);
-    if (page_live_state_data) {
-      is_active_tab = page_live_state_data->IsActiveTab();
-      has_notification_permission =
-          page_live_state_data->IsContentSettingTypeAllowed(
-              ContentSettingsType::NOTIFICATIONS);
-      is_capturing_media = page_live_state_data->IsCapturingVideo() ||
-                           page_live_state_data->IsCapturingAudio() ||
-                           page_live_state_data->IsBeingMirrored() ||
-                           page_live_state_data->IsCapturingWindow() ||
-                           page_live_state_data->IsCapturingDisplay();
-      is_connected_to_device =
-          page_live_state_data->IsConnectedToUSBDevice() ||
-          page_live_state_data->IsConnectedToBluetoothDevice();
-      updated_title_or_favicon_in_background =
-          page_live_state_data->UpdatedTitleOrFaviconInBackground();
-    }
-
-    ukm::builders::PerformanceManager_PageTimelineState builder(source_id);
-
-    builder.SetSliceId(slice_id)
-        .SetIsActiveTab(is_active_tab)
-        .SetTimeSinceLastSlice(ukm::GetSemanticBucketMinForDurationTiming(
-            time_since_last_slice.InMilliseconds()))
-        .SetTimeSinceCreation(ukm::GetSemanticBucketMinForDurationTiming(
-            (now - curr_info->time_of_creation).InMilliseconds()))
-        .SetCurrentState(static_cast<uint64_t>(curr_info->GetPageState()))
-        .SetTimeInCurrentState(ukm::GetSemanticBucketMinForDurationTiming(
-            (now - curr_info->time_of_most_recent_state_change)
-                .InMilliseconds()))
-        .SetTotalForegroundTime(ukm::GetSemanticBucketMinForDurationTiming(
-            curr_info->total_foreground_milliseconds))
-        .SetChangedFaviconOrTitleInBackground(
-            updated_title_or_favicon_in_background)
-        .SetHasNotificationPermission(has_notification_permission)
-        .SetIsCapturingMedia(is_capturing_media)
-        .SetIsConnectedToDevice(is_connected_to_device)
-        .SetIsPlayingAudio(page_node->IsAudible())
-        .SetPrivateFootprint(page_node->EstimatePrivateFootprintSize())
-        .SetResidentSetSize(page_node->EstimateResidentSetSize())
-        .SetTabId(curr_info->tab_id);
-
-#if !BUILDFLAG(IS_ANDROID)
-    bool memory_saver_mode_active =
-        (policies::MemorySaverModePolicy::GetInstance() &&
-         policies::MemorySaverModePolicy::GetInstance()
-             ->IsMemorySaverDiscardingEnabled());
-
-    builder.SetHighEfficiencyMode(memory_saver_mode_active)
-        .SetBatterySaverMode(battery_saver_enabled_);
-#endif  // !BUILDFLAG(IS_ANDROID)
-
-    builder.Record(ukm::UkmRecorder::Get());
-  }
-}
-
-bool PageResourceMonitor::ShouldCollectSlice() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (should_collect_slice_callback_) {
-    return should_collect_slice_callback_.Run();
-  }
-
-  // The default if not overridden by tests is to report ~1 out of 20 slices.
-  return base::RandInt(0, 19) == 1;
-}
-
 void PageResourceMonitor::CheckDelayedCPUInterventionMetrics() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CHECK(performance_manager::features::kUseResourceAttributionCPUMonitor.Get());
@@ -536,17 +393,19 @@
                             absl::optional<PressureSample>)> callback,
     const PageResourceCPUMonitor::CPUUsageMap& cpu_usage_map) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK(graph_);
 
   // Calculate the overall CPU usage.
   PageCPUUsageVector page_cpu_usage;
-  page_cpu_usage.reserve(page_node_info_map_.size());
-  for (const auto& [tab_handle, info_ptr] : page_node_info_map_) {
-    const PageNode* page_node = tab_handle->page_node();
-    CheckPageState(page_node, *info_ptr);
-    double cpu_usage =
-        PageResourceCPUMonitor::EstimatePageCPUUsage(page_node, cpu_usage_map);
-    page_cpu_usage.emplace_back(page_node->GetResourceContext(), cpu_usage);
-  }
+  graph_->VisitAllPageNodes([&page_cpu_usage,
+                             &cpu_usage_map](const PageNode* page_node) {
+    if (page_node->GetType() == PageType::kTab) {
+      page_cpu_usage.emplace_back(page_node->GetResourceContext(),
+                                  PageResourceCPUMonitor::EstimatePageCPUUsage(
+                                      page_node, cpu_usage_map));
+    }
+    return true;
+  });
 
   // Now fetch the system CPU usage if available.
   CpuProbe* cpu_probe = use_delayed_system_cpu_probe
@@ -562,17 +421,10 @@
 
 void PageResourceMonitor::SetTriggerCollectionManuallyForTesting() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  collect_slice_timer_.Stop();
   collect_page_resource_usage_timer_.Stop();
   log_cpu_on_delay_timer_.Stop();
 }
 
-void PageResourceMonitor::SetShouldCollectSliceCallbackForTesting(
-    base::RepeatingCallback<bool()> should_collect_slice_callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  should_collect_slice_callback_ = should_collect_slice_callback;
-}
-
 void PageResourceMonitor::SetCPUMeasurementDelegateFactoryForTesting(
     Graph* graph,
     PageResourceCPUMonitor::CPUMeasurementDelegate::Factory* factory) {
@@ -585,143 +437,16 @@
       graph, factory);
 }
 
-PageResourceMonitor::PageNodeInfoMap&
-PageResourceMonitor::GetPageNodeInfoForTesting() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return page_node_info_map_;
-}
-
 void PageResourceMonitor::OnPassedToGraph(Graph* graph) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   graph_ = graph;
-  graph_->AddPageNodeObserver(this);
-  graph_->RegisterObject(this);
-  graph->GetRegisteredObjectAs<TabPageDecorator>()->AddObserver(this);
   cpu_monitor_.StartMonitoring(graph_);
 }
 
 void PageResourceMonitor::OnTakenFromGraph(Graph* graph) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   cpu_monitor_.StopMonitoring(graph_);
-
-  // GraphOwned object destruction order is undefined, so only remove ourselves
-  // as observers if the decorator still exists.
-  TabPageDecorator* tab_page_decorator =
-      graph->GetRegisteredObjectAs<TabPageDecorator>();
-  if (tab_page_decorator) {
-    tab_page_decorator->RemoveObserver(this);
-  }
-
-  graph_->UnregisterObject(this);
-  graph_->RemovePageNodeObserver(this);
   graph_ = nullptr;
 }
 
-void PageResourceMonitor::OnTabAdded(TabPageDecorator::TabHandle* tab_handle) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  page_node_info_map_[tab_handle] = std::make_unique<PageNodeInfo>(
-      base::TimeTicks::Now(), tab_handle->page_node(), slice_id_counter_++);
-}
-
-void PageResourceMonitor::OnTabAboutToBeDiscarded(
-    const PageNode* old_page_node,
-    TabPageDecorator::TabHandle* tab_handle) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  auto it = page_node_info_map_.find(tab_handle);
-  CHECK(it != page_node_info_map_.end());
-
-  it->second->current_lifecycle = mojom::LifecycleState::kDiscarded;
-  CheckPageState(tab_handle->page_node(), *it->second);
-}
-
-void PageResourceMonitor::OnBeforeTabRemoved(
-    TabPageDecorator::TabHandle* tab_handle) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  page_node_info_map_.erase(tab_handle);
-}
-
-void PageResourceMonitor::OnIsVisibleChanged(const PageNode* page_node) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (page_node->GetType() != performance_manager::PageType::kTab) {
-    return;
-  }
-
-  TabPageDecorator::TabHandle* tab_handle =
-      TabPageDecorator::FromPageNode(page_node);
-  // It's possible for this to happen when a tab is discarded. The sequence of
-  // events is:
-  // 1. New web contents (and page node) created
-  // 2. AboutToBeDiscarded(old_page_node, new_page_node) is invoked
-  // 3. Tab is detached from the tabstrip, causing its web contents to become
-  // "occluded", which triggers a visibility change notification
-  // 4. The old web contents (and page node) are deleted
-  // In the case of PageResourceMonitor, the page_node is removed from the map
-  // on step 2, so the notification from step 3 has to be ignored.
-  if (!tab_handle) {
-    return;
-  }
-
-  auto it = page_node_info_map_.find(tab_handle);
-  CHECK(it != page_node_info_map_.end());
-
-  std::unique_ptr<PageNodeInfo>& info = it->second;
-  base::TimeTicks now = base::TimeTicks::Now();
-  if (info->currently_visible && !page_node->IsVisible()) {
-    // Increase total foreground seconds by the time since we entered the
-    // foreground now that we are entering the background.
-    info->total_foreground_milliseconds +=
-        (now - info->time_of_last_foreground_millisecond_update)
-            .InMilliseconds();
-    info->time_of_last_foreground_millisecond_update = now;
-  } else if (!info->currently_visible && page_node->IsVisible()) {
-    // Update time_of_last[...] without increasing
-    // total_foreground_milliseconds because we've just entered the
-    // foreground.
-    info->time_of_last_foreground_millisecond_update = now;
-  }
-  info->currently_visible = page_node->IsVisible();
-  info->time_of_most_recent_state_change = now;
-}
-
-void PageResourceMonitor::OnPageLifecycleStateChanged(
-    const PageNode* page_node) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (page_node->GetType() != performance_manager::PageType::kTab) {
-    return;
-  }
-
-  TabPageDecorator::TabHandle* tab_handle =
-      TabPageDecorator::FromPageNode(page_node);
-  if (!tab_handle) {
-    // This function is called by the tab freezing apparatus between the time a
-    // page is discarded and when its PageNode is removed from the graph. In
-    // that situation, it's not in the map anymore, it doesn't have a tab
-    // handle, and another PageNode is being tracked in its place. It's safe to
-    // return early.
-    return;
-  }
-
-  auto it = page_node_info_map_.find(tab_handle);
-  CHECK(it != page_node_info_map_.end());
-
-  it->second->current_lifecycle = page_node->GetLifecycleState();
-  it->second->time_of_most_recent_state_change = base::TimeTicks::Now();
-}
-
-void PageResourceMonitor::SetBatterySaverEnabled(bool enabled) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  battery_saver_enabled_ = enabled;
-}
-
-void PageResourceMonitor::CheckPageState(const PageNode* page_node,
-                                         const PageNodeInfo& info) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // There's a window after OnAboutToBeDiscarded() where a discarded placeholder
-  // page is in the map with type kUnknown, before it's updated to kTab in
-  // OnTypeChanged().
-  CHECK(page_node->GetType() == PageType::kTab ||
-        page_node->GetType() == PageType::kUnknown &&
-            info.current_lifecycle == mojom::LifecycleState::kDiscarded);
-}
-
 }  // namespace performance_manager::metrics
diff --git a/chrome/browser/performance_manager/metrics/page_resource_monitor.h b/chrome/browser/performance_manager/metrics/page_resource_monitor.h
index f6e2a00..a4765c9 100644
--- a/chrome/browser/performance_manager/metrics/page_resource_monitor.h
+++ b/chrome/browser/performance_manager/metrics/page_resource_monitor.h
@@ -5,13 +5,11 @@
 #ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_METRICS_PAGE_RESOURCE_MONITOR_H_
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_METRICS_PAGE_RESOURCE_MONITOR_H_
 
-#include <map>
 #include <memory>
 #include <utility>
 #include <vector>
 
 #include "base/functional/callback_forward.h"
-#include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
@@ -19,10 +17,7 @@
 #include "base/timer/timer.h"
 #include "chrome/browser/performance_manager/metrics/cpu_probe/pressure_sample.h"
 #include "chrome/browser/performance_manager/metrics/page_resource_cpu_monitor.h"
-#include "components/performance_manager/public/decorators/tab_page_decorator.h"
 #include "components/performance_manager/public/graph/graph.h"
-#include "components/performance_manager/public/graph/graph_registered.h"
-#include "components/performance_manager/public/graph/page_node.h"
 #include "components/performance_manager/public/resource_attribution/page_context.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -31,27 +26,10 @@
 class CpuProbe;
 class PageResourceMonitorUnitTest;
 
-// Periodically reports tab state via UKM, to enable analysis of usage patterns
-// over time.
-class PageResourceMonitor : public PageNode::ObserverDefaultImpl,
-                            public GraphOwned,
-                            public GraphRegisteredImpl<PageResourceMonitor>,
-                            public TabPageObserver {
+// Periodically reports tab resource usage via UKM.
+class PageResourceMonitor : public GraphOwned {
  public:
   // These values are logged to UKM. Entries should not be renumbered and
-  // numeric values should never be reused. Please keep in sync with PageState
-  // in enums.xml.
-  enum class PageState {
-    kFocused = 0,
-    kVisible = 1,
-    kBackground = 2,
-    kThrottled = 3,
-    kFrozen = 4,
-    kDiscarded = 5,
-    kMaxValue = kDiscarded,
-  };
-
-  // These values are logged to UKM. Entries should not be renumbered and
   // numeric values should never be reused. Please keep in sync with
   // PageMeasurementBackgroundState in enums.xml.
   enum class PageMeasurementBackgroundState {
@@ -75,57 +53,8 @@
   void OnPassedToGraph(Graph* graph) override;
   void OnTakenFromGraph(Graph* graph) override;
 
-  // TabPageObserver:
-  void OnTabAdded(TabPageDecorator::TabHandle* tab_handle) override;
-  void OnTabAboutToBeDiscarded(
-      const PageNode* old_page_node,
-      TabPageDecorator::TabHandle* tab_handle) override;
-  void OnBeforeTabRemoved(TabPageDecorator::TabHandle* tab_handle) override;
-
-  // PageNode::Observer:
-  void OnIsVisibleChanged(const PageNode* page_node) override;
-  void OnPageLifecycleStateChanged(const PageNode* page_node) override;
-
-  void SetBatterySaverEnabled(bool enabled);
-
  private:
-  friend class PageResourceMonitorBrowserTest;
   friend PageResourceMonitorUnitTest;
-  FRIEND_TEST_ALL_PREFIXES(
-      PageResourceMonitorUnitTest,
-      TestPageTimelineDoesntRecordIfShouldCollectSliceReturnsFalse);
-  FRIEND_TEST_ALL_PREFIXES(PageResourceMonitorUnitTest,
-                           TestUpdateFaviconInBackground);
-  FRIEND_TEST_ALL_PREFIXES(PageResourceMonitorUnitTest,
-                           TestUpdateTitleInBackground);
-  FRIEND_TEST_ALL_PREFIXES(PageResourceMonitorUnitTest,
-                           TestUpdateLifecycleState);
-  FRIEND_TEST_ALL_PREFIXES(PageResourceMonitorUnitTest,
-                           TestUpdatePageNodeBeforeTypeChange);
-
-  struct PageNodeInfo {
-    base::TimeTicks time_of_creation;
-    bool currently_visible;
-    PageNode::LifecycleState current_lifecycle;
-    base::TimeTicks time_of_most_recent_state_change;
-    base::TimeTicks time_of_last_foreground_millisecond_update;
-    int total_foreground_milliseconds{0};
-    int tab_id;
-
-    PageResourceMonitor::PageState GetPageState();
-
-    explicit PageNodeInfo(base::TimeTicks time_of_creation,
-                          const PageNode* page_node,
-                          int tab_id)
-        : time_of_creation(time_of_creation),
-          currently_visible(page_node->IsVisible()),
-          current_lifecycle(page_node->GetLifecycleState()),
-          time_of_most_recent_state_change(base::TimeTicks::Now()),
-          time_of_last_foreground_millisecond_update(
-              time_of_most_recent_state_change),
-          tab_id(tab_id) {}
-    ~PageNodeInfo() = default;
-  };
 
   // Suffix for CPU intervention histograms.
   enum class CPUInterventionSuffix {
@@ -140,9 +69,6 @@
   using PageCPUUsageVector =
       std::vector<std::pair<resource_attribution::PageContext, double>>;
 
-  using PageNodeInfoMap = std::map<const TabPageDecorator::TabHandle*,
-                                   std::unique_ptr<PageNodeInfo>>;
-
   // Asynchronously collects the PageResourceUsage UKM. Calls `done_closure`
   // when finished.
   void CollectPageResourceUsage(base::OnceClosure done_closure);
@@ -152,11 +78,6 @@
   void OnPageResourceUsageResult(const PageCPUUsageVector& page_cpu_usage,
                                  absl::optional<PressureSample> system_cpu);
 
-  // Method collecting a slice for the PageTimelineState UKM.
-  void CollectSlice();
-
-  bool ShouldCollectSlice() const;
-
   // Asynchronously checks if the CPU metrics are still above the threshold
   // after a delay.
   void CheckDelayedCPUInterventionMetrics();
@@ -196,39 +117,17 @@
                               absl::optional<PressureSample>)> callback,
       const PageResourceCPUMonitor::CPUUsageMap& cpu_usage_map);
 
-  // If this is called, CollectSlice() and CollectPageResourceUsage() will not
-  // be called on a timer. Tests can call them manually.
+  // If this is called, CollectPageResourceUsage() will not be called on a
+  // timer. Tests can call it manually.
   void SetTriggerCollectionManuallyForTesting();
 
-  // If this is called, the given callback will be called instead of
-  // ShouldCollectSlice().
-  void SetShouldCollectSliceCallbackForTesting(base::RepeatingCallback<bool()>);
-
   // Passes the given `factory` to PageResourceCPUMonitor.
   void SetCPUMeasurementDelegateFactoryForTesting(
       Graph* graph,
       PageResourceCPUMonitor::CPUMeasurementDelegate::Factory* factory);
 
-  // Lets tests examine the contents of `page_node_info_map_`.
-  PageNodeInfoMap& GetPageNodeInfoForTesting();
-
-  // CHECK's that `page_node` and `info` are in the right state to be
-  // mapped to each other in `page_node_info_map_`.
-  void CheckPageState(const PageNode* page_node, const PageNodeInfo& info);
-
   SEQUENCE_CHECKER(sequence_checker_);
 
-  // Monotonically increasing counters for tabs and slices.
-  int slice_id_counter_ GUARDED_BY_CONTEXT(sequence_checker_);
-
-  // A map in which we store info about PageNodes to keep track of their state,
-  // as well as the timing of their state transitions.
-  PageNodeInfoMap page_node_info_map_ GUARDED_BY_CONTEXT(sequence_checker_);
-
-  // Timer which is used to trigger CollectSlice(), which records the UKM.
-  base::RepeatingTimer collect_slice_timer_
-      GUARDED_BY_CONTEXT(sequence_checker_);
-
   // Timer which is used to trigger CollectPageResourceUsage().
   base::RepeatingTimer collect_page_resource_usage_timer_
       GUARDED_BY_CONTEXT(sequence_checker_);
@@ -244,21 +143,10 @@
   // Pointer to this process' graph.
   raw_ptr<Graph> graph_ GUARDED_BY_CONTEXT(sequence_checker_) = nullptr;
 
-  // Time when last slice was run.
-  base::TimeTicks time_of_last_slice_ GUARDED_BY_CONTEXT(sequence_checker_) =
-      base::TimeTicks::Now();
-
   // Time of last PageResourceUsage collection.
   base::TimeTicks time_of_last_resource_usage_
       GUARDED_BY_CONTEXT(sequence_checker_) = base::TimeTicks::Now();
 
-  // Function which is called to determine whether a PageTimelineState slice
-  // should be collected. Overridden in tests.
-  base::RepeatingCallback<bool()> should_collect_slice_callback_
-      GUARDED_BY_CONTEXT(sequence_checker_);
-
-  bool battery_saver_enabled_ GUARDED_BY_CONTEXT(sequence_checker_) = false;
-
   // Helper to take CPU measurements for the UKM.
   PageResourceCPUMonitor cpu_monitor_ GUARDED_BY_CONTEXT(sequence_checker_);
 
diff --git a/chrome/browser/performance_manager/metrics/page_resource_monitor_browsertest.cc b/chrome/browser/performance_manager/metrics/page_resource_monitor_browsertest.cc
deleted file mode 100644
index 1165643..0000000
--- a/chrome/browser/performance_manager/metrics/page_resource_monitor_browsertest.cc
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/run_loop.h"
-#include "base/task/sequenced_task_runner.h"
-#include "base/test/scoped_feature_list.h"
-#include "chrome/browser/performance_manager/mechanisms/page_discarder.h"
-#include "chrome/browser/performance_manager/metrics/page_resource_monitor.h"
-#include "chrome/browser/resource_coordinator/lifecycle_unit_state.mojom.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/performance_manager/public/features.h"
-#include "components/performance_manager/public/performance_manager.h"
-#include "components/ukm/test_ukm_recorder.h"
-#include "content/public/test/browser_test.h"
-#include "services/metrics/public/cpp/ukm_builders.h"
-#include "services/metrics/public/cpp/ukm_recorder.h"
-
-#include "components/performance_manager/graph/frame_node_impl.h"
-#include "components/performance_manager/graph/page_node_impl.h"
-
-namespace performance_manager::metrics {
-
-class PageResourceMonitorBrowserTest : public InProcessBrowserTest {
- public:
-  PageResourceMonitorBrowserTest() {
-    feature_list_.InitAndEnableFeature(features::kPageTimelineMonitor);
-  }
-
-  void SetUpOnMainThread() override { SetUpOnPMSequence(); }
-
-  void SetUpOnPMSequence() {
-    PerformanceManager::CallOnGraph(
-        FROM_HERE, base::BindOnce([](Graph* graph) {
-          auto* monitor = graph->GetRegisteredObjectAs<PageResourceMonitor>();
-          monitor->SetTriggerCollectionManuallyForTesting();
-          monitor->SetShouldCollectSliceCallbackForTesting(
-              base::BindRepeating([]() { return true; }));
-        }));
-  }
-
-  // Triggers slice collection on the PM sequence, validates that
-  // `expected_entries_count` UKM entries have been recorded, then tries to find
-  // a UKM entry in a state matching `expected_entry_state`. Finding the entry
-  // is necessary because the order in which the entries are recorded is not
-  // deterministic. Returns the `tab_id` field of the found entry.
-  int CollectSliceOnPMSequence(size_t expected_entries_count,
-                               int64_t expected_entry_state) {
-    int tab_id = 0;
-    base::RunLoop run_loop;
-    PerformanceManager::CallOnGraph(
-        FROM_HERE,
-        base::BindOnce(
-            [](base::OnceClosure quit_closure, size_t expected_entries_count,
-               int64_t expected_entry_state, int* tab_id, Graph* graph) {
-              ukm::TestAutoSetUkmRecorder ukm_recorder;
-
-              auto* monitor =
-                  graph->GetRegisteredObjectAs<PageResourceMonitor>();
-              monitor->CollectSlice();
-
-              auto entries = ukm_recorder.GetEntriesByName(
-                  ukm::builders::PerformanceManager_PageTimelineState::
-                      kEntryName);
-              EXPECT_EQ(entries.size(), expected_entries_count);
-
-              for (size_t i = 0; i < expected_entries_count; ++i) {
-                if (*(ukm_recorder.GetEntryMetric(
-                        entries[i], "CurrentState")) == expected_entry_state) {
-                  *tab_id = *(ukm_recorder.GetEntryMetric(entries[i], "TabId"));
-                  break;
-                }
-              }
-
-              // Getting here with `tab_id` == 0 means there was not entry that
-              // matched the expected value.
-              EXPECT_NE(*tab_id, 0);
-
-              std::move(quit_closure).Run();
-            },
-            run_loop.QuitClosure(), expected_entries_count,
-            expected_entry_state, &tab_id));
-    run_loop.Run();
-
-    return tab_id;
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(PageResourceMonitorBrowserTest,
-                       TestDiscardedTabsRecordedInCorrectState) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  content::RenderFrameHost* frame_host = NavigateToURLWithDisposition(
-      browser(), embedded_test_server()->GetURL("/title1.html"),
-      WindowOpenDisposition::NEW_BACKGROUND_TAB,
-      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
-  ASSERT_TRUE(frame_host);
-  auto* contents = content::WebContents::FromRenderFrameHost(frame_host);
-
-  // Expect 2 entries (one for the background and one for the foreground page).
-  // The background page is what we care about, it should be in the "background"
-  // state.
-  int tab_id = CollectSliceOnPMSequence(2UL, /*background*/ 2);
-
-  base::RunLoop run_loop;
-  auto quit_closure = run_loop.QuitClosure();
-  PerformanceManager::CallOnGraph(
-      FROM_HERE,
-      base::BindOnce(
-          [](base::WeakPtr<PageNode> page_node,
-             base::OnceClosure quit_closure) {
-            EXPECT_TRUE(page_node);
-
-            mechanism::PageDiscarder discarder;
-            discarder.DiscardPageNodes(
-                {page_node.get()},
-                ::mojom::LifecycleUnitDiscardReason::PROACTIVE,
-                base::BindOnce(
-                    [](base::OnceClosure quit_closure, bool success) {
-                      EXPECT_TRUE(success);
-                      std::move(quit_closure).Run();
-                    },
-                    std::move(quit_closure)));
-          },
-          PerformanceManager::GetPrimaryPageNodeForWebContents(contents),
-          std::move(quit_closure)));
-  run_loop.Run();
-
-  auto* new_contents = browser()->tab_strip_model()->GetWebContentsAt(1);
-  EXPECT_TRUE(new_contents->WasDiscarded());
-
-  // Expect new 2 entries (1 for the background and 1 for the foreground page).
-  // The background page is what we care about, it should be in the "discarded"
-  // state. The discarded tab should also have the same tab id it had before it
-  // was discarded.
-  EXPECT_EQ(tab_id, CollectSliceOnPMSequence(2UL, /*discarded*/ 5));
-}
-
-IN_PROC_BROWSER_TEST_F(PageResourceMonitorBrowserTest,
-                       TestFrozenToDiscardedTab) {
-  ASSERT_TRUE(embedded_test_server()->Start());
-
-  content::RenderFrameHost* frame_host = NavigateToURLWithDisposition(
-      browser(), embedded_test_server()->GetURL("/title1.html"),
-      WindowOpenDisposition::NEW_BACKGROUND_TAB,
-      ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
-  ASSERT_TRUE(frame_host);
-  auto* contents = content::WebContents::FromRenderFrameHost(frame_host);
-
-  // Expect 2 entries (one for the background and one for the foreground page).
-  // The background page is what we care about, it should be in the "background"
-  // state.
-  int tab_id = CollectSliceOnPMSequence(2UL, /*background*/ 2);
-
-  base::RunLoop run_loop;
-  auto quit_closure = run_loop.QuitClosure();
-  PerformanceManager::CallOnGraph(
-      FROM_HERE,
-      base::BindOnce(
-          [](base::WeakPtr<PageNode> page_node,
-             base::OnceClosure quit_closure) {
-            EXPECT_TRUE(page_node);
-
-            static_cast<PageNodeImpl*>(page_node.get())
-                ->main_frame_node()
-                ->SetLifecycleState(mojom::LifecycleState::kFrozen);
-
-            mechanism::PageDiscarder discarder;
-            discarder.DiscardPageNodes(
-                {page_node.get()},
-                ::mojom::LifecycleUnitDiscardReason::PROACTIVE,
-                base::BindOnce(
-                    [](base::OnceClosure quit_closure, bool success) {
-                      EXPECT_TRUE(success);
-                      std::move(quit_closure).Run();
-                    },
-                    std::move(quit_closure)));
-          },
-          PerformanceManager::GetPrimaryPageNodeForWebContents(contents),
-          std::move(quit_closure)));
-  run_loop.Run();
-
-  auto* new_contents = browser()->tab_strip_model()->GetWebContentsAt(1);
-  EXPECT_TRUE(new_contents->WasDiscarded());
-
-  // Expect new 2 entries (1 for the background and 1 for the foreground page).
-  // The background page is what we care about, it should be in the "discarded"
-  // state. The discarded tab should also have the same tab id it had before it
-  // was discarded.
-  EXPECT_EQ(tab_id, CollectSliceOnPMSequence(2UL, /*discarded*/ 5));
-}
-
-}  // namespace performance_manager::metrics
diff --git a/chrome/browser/performance_manager/metrics/page_resource_monitor_unittest.cc b/chrome/browser/performance_manager/metrics/page_resource_monitor_unittest.cc
index 1c21b96..4e9ebb5 100644
--- a/chrome/browser/performance_manager/metrics/page_resource_monitor_unittest.cc
+++ b/chrome/browser/performance_manager/metrics/page_resource_monitor_unittest.cc
@@ -8,9 +8,10 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/containers/fixed_flat_map.h"
-#include "base/functional/bind.h"
+#include "base/functional/callback.h"
 #include "base/location.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
@@ -24,28 +25,24 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_timeouts.h"
 #include "base/threading/platform_thread.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
-#include "chrome/browser/performance_manager/metrics/page_resource_cpu_monitor.h"
 #include "components/performance_manager/embedder/graph_features.h"
-#include "components/performance_manager/public/decorators/page_live_state_decorator.h"
-#include "components/performance_manager/public/decorators/tab_page_decorator.h"
 #include "components/performance_manager/public/features.h"
-#include "components/performance_manager/public/mojom/lifecycle.mojom-shared.h"
-#include "components/performance_manager/public/user_tuning/prefs.h"
+#include "components/performance_manager/public/graph/frame_node.h"
+#include "components/performance_manager/public/graph/graph.h"
+#include "components/performance_manager/public/graph/page_node.h"
+#include "components/performance_manager/public/graph/process_node.h"
 #include "components/performance_manager/test_support/graph_test_harness.h"
 #include "components/performance_manager/test_support/mock_graphs.h"
 #include "components/performance_manager/test_support/resource_attribution/measurement_delegates.h"
-#include "components/prefs/testing_pref_service.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
+#include "services/metrics/public/mojom/ukm_interface.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-#if !BUILDFLAG(IS_ANDROID)
-#include "chrome/browser/performance_manager/policies/memory_saver_mode_policy.h"
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 namespace performance_manager::metrics {
 
 namespace {
@@ -184,9 +181,6 @@
   void SetUp() override {
     GraphTestHarness::SetUp();
 
-    graph()->PassToGraph(
-        std::make_unique<performance_manager::TabPageDecorator>());
-
     // Return 50% CPU used by default.
     cpu_delegate_factory_.SetDefaultCPUUsage(0.5);
 
@@ -194,8 +188,6 @@
         std::make_unique<PageResourceMonitor>(enable_system_cpu_probe_);
     monitor_ = monitor.get();
     monitor_->SetTriggerCollectionManuallyForTesting();
-    monitor_->SetShouldCollectSliceCallbackForTesting(
-        base::BindRepeating([]() { return true; }));
     monitor_->SetCPUMeasurementDelegateFactoryForTesting(
         graph(), &cpu_delegate_factory_);
     graph()->PassToGraph(std::move(monitor));
@@ -219,8 +211,6 @@
   ukm::TestUkmRecorder* test_ukm_recorder() { return test_ukm_recorder_.get(); }
   PageResourceMonitor* monitor() { return monitor_; }
 
-  void TriggerCollectSlice() { monitor_->CollectSlice(); }
-
   void TriggerCollectPageResourceUsage() {
     base::RunLoop run_loop;
     monitor_->CollectPageResourceUsage(run_loop.QuitClosure());
@@ -328,67 +318,20 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-TEST_F(PageResourceMonitorUnitTest, TestPageTimeline) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  TriggerCollectSlice();
-
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-
-  // Unsliced resource usage metrics should not be collected along with the
-  // slice.
-  auto entries2 = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageResourceUsage2::kEntryName);
-  EXPECT_TRUE(entries2.empty());
-}
-
 TEST_F(PageResourceMonitorUnitTest, TestPageResourceUsage) {
   MockSinglePageInSingleProcessGraph mock_graph(graph());
   ukm::SourceId mock_source_id = ukm::NoURLSourceId();
   mock_graph.page->SetType(performance_manager::PageType::kTab);
   mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
 
   TriggerCollectPageResourceUsage();
 
   auto entries = test_ukm_recorder()->GetEntriesByName(
       ukm::builders::PerformanceManager_PageResourceUsage2::kEntryName);
   EXPECT_EQ(entries.size(), 1UL);
-
-  // Sliced resource usage metrics should not be collected along with
-  // PageResourceUsage.
-  auto entries2 = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_TRUE(entries2.empty());
 }
 
-TEST_F(PageResourceMonitorUnitTest,
-       TestPageTimelineDoesntRecordIfShouldCollectSliceReturnsFalse) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  monitor()->SetShouldCollectSliceCallbackForTesting(
-      base::BindRepeating([]() { return false; }));
-  TriggerCollectSlice();
-
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 0UL);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestPageTimelineNavigation) {
+TEST_F(PageResourceMonitorUnitTest, TestPageResourceUsageNavigation) {
   MockSinglePageInSingleProcessGraph mock_graph(graph());
   ukm::SourceId mock_source_id =
       ukm::AssignNewSourceId();  // ukm::NoURLSourceId();
@@ -396,30 +339,20 @@
 
   mock_graph.page->SetUkmSourceId(mock_source_id);
   mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
 
-  TriggerCollectSlice();
   TriggerCollectPageResourceUsage();
 
   auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  auto entries2 = test_ukm_recorder()->GetEntriesByName(
       ukm::builders::PerformanceManager_PageResourceUsage2::kEntryName);
-  EXPECT_EQ(entries2.size(), 1UL);
+  EXPECT_EQ(entries.size(), 1UL);
 
   mock_graph.page->SetUkmSourceId(mock_source_id_2);
 
-  TriggerCollectSlice();
   TriggerCollectPageResourceUsage();
 
   entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-  entries2 = test_ukm_recorder()->GetEntriesByName(
       ukm::builders::PerformanceManager_PageResourceUsage2::kEntryName);
-  EXPECT_EQ(entries2.size(), 2UL);
+  EXPECT_EQ(entries.size(), 2UL);
 
   std::vector<ukm::SourceId> ids;
   for (const ukm::mojom::UkmEntry* entry : entries) {
@@ -433,301 +366,14 @@
   MockSinglePageInSingleProcessGraph mock_graph(graph());
   ukm::SourceId mock_source_id = ukm::NoURLSourceId();
   mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
 
-  TriggerCollectSlice();
   TriggerCollectPageResourceUsage();
 
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 0UL);
   auto entries2 = test_ukm_recorder()->GetEntriesByName(
       ukm::builders::PerformanceManager_PageResourceUsage2::kEntryName);
   EXPECT_EQ(entries2.size(), 0UL);
 }
 
-TEST_F(PageResourceMonitorUnitTest, TestUpdateTitleOrFaviconInBackground) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(false);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  // Collect one slice before updating, one after.
-  TriggerCollectSlice();
-
-  PageLiveStateDecorator::Data* data =
-      PageLiveStateDecorator::Data::GetOrCreateForPageNode(
-          mock_graph.page.get());
-  data->SetUpdatedTitleOrFaviconInBackgroundForTesting(true);
-
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-  test_ukm_recorder()->ExpectEntryMetric(
-      entries[0], "ChangedFaviconOrTitleInBackground", false);
-  test_ukm_recorder()->ExpectEntryMetric(
-      entries[1], "ChangedFaviconOrTitleInBackground", true);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestUpdateLifecycleState) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kFrozen);
-  mock_graph.page->SetIsVisible(false);
-
-  EXPECT_EQ(monitor()
-                ->GetPageNodeInfoForTesting()[TabPageDecorator::FromPageNode(
-                    mock_graph.page.get())]
-                ->current_lifecycle,
-            mojom::LifecycleState::kFrozen);
-}
-
-#if !BUILDFLAG(IS_ANDROID)
-TEST_F(PageResourceMonitorUnitTest, TestMemorySaverMode) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  // Collecting without an installed HEM policy reports it as disabled.
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0], "HighEfficiencyMode", 0);
-
-  graph()->PassToGraph(
-      std::make_unique<performance_manager::policies::MemorySaverModePolicy>());
-
-  TriggerCollectSlice();
-  entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[1], "HighEfficiencyMode", 0);
-
-  performance_manager::policies::MemorySaverModePolicy* policy =
-      performance_manager::policies::MemorySaverModePolicy::GetInstance();
-  policy->SetTimeBeforeDiscard(base::Hours(2));
-  policy->OnMemorySaverModeChanged(true);
-
-  TriggerCollectSlice();
-  entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 3UL);
-
-  test_ukm_recorder()->ExpectEntryMetric(entries[2], "HighEfficiencyMode", 1);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestBatterySaverMode) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0], "BatterySaverMode", 0);
-
-  monitor()->SetBatterySaverEnabled(true);
-
-  TriggerCollectSlice();
-  entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-
-  test_ukm_recorder()->ExpectEntryMetric(entries[1], "BatterySaverMode", 1);
-}
-#endif  // !BUILDFLAG(IS_ANDROID)
-
-TEST_F(PageResourceMonitorUnitTest, TestHasNotificationsPermission) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  PageLiveStateDecorator::Data* data =
-      PageLiveStateDecorator::Data::GetOrCreateForPageNode(
-          mock_graph.page.get());
-  data->SetContentSettingsForTesting(
-      {{ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW}});
-
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0],
-                                         "HasNotificationPermission", 1);
-
-  data->SetContentSettingsForTesting(
-      {{ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_BLOCK}});
-
-  TriggerCollectSlice();
-  entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-
-  test_ukm_recorder()->ExpectEntryMetric(entries[1],
-                                         "HasNotificationPermission", 0);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestCapturingMedia) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  PageLiveStateDecorator::Data* data =
-      PageLiveStateDecorator::Data::GetOrCreateForPageNode(
-          mock_graph.page.get());
-  data->SetIsCapturingVideoForTesting(false);
-
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0], "IsCapturingMedia", 0);
-
-  data->SetIsCapturingVideoForTesting(true);
-  TriggerCollectSlice();
-  entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[1], "IsCapturingMedia", 1);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestConnectedToDevice) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  PageLiveStateDecorator::Data* data =
-      PageLiveStateDecorator::Data::GetOrCreateForPageNode(
-          mock_graph.page.get());
-  data->SetIsConnectedToUSBDeviceForTesting(false);
-
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0], "IsConnectedToDevice", 0);
-
-  data->SetIsConnectedToUSBDeviceForTesting(true);
-  TriggerCollectSlice();
-  entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[1], "IsConnectedToDevice", 1);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestAudible) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  mock_graph.page->SetIsAudible(false);
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0], "IsPlayingAudio", 0);
-
-  mock_graph.page->SetIsAudible(true);
-  TriggerCollectSlice();
-  entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[1], "IsPlayingAudio", 1);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestIsActiveTab) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-
-  PageLiveStateDecorator::Data* data =
-      PageLiveStateDecorator::Data::GetOrCreateForPageNode(
-          mock_graph.page.get());
-  data->SetIsActiveTabForTesting(false);
-
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0], "IsActiveTab", 0);
-
-  data->SetIsActiveTabForTesting(true);
-  TriggerCollectSlice();
-  entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 2UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[1], "IsActiveTab", 1);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestMemory) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetIsVisible(true);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kRunning);
-  mock_graph.frame->SetResidentSetKbEstimate(123);
-  mock_graph.frame->SetPrivateFootprintKbEstimate(456);
-
-  TriggerCollectSlice();
-  auto entries = test_ukm_recorder()->GetEntriesByName(
-      ukm::builders::PerformanceManager_PageTimelineState::kEntryName);
-  EXPECT_EQ(entries.size(), 1UL);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0], "ResidentSetSize", 123);
-  test_ukm_recorder()->ExpectEntryMetric(entries[0], "PrivateFootprint", 456);
-}
-
-TEST_F(PageResourceMonitorUnitTest, TestUpdatePageNodeBeforeTypeChange) {
-  MockSinglePageInSingleProcessGraph mock_graph(graph());
-  ukm::SourceId mock_source_id = ukm::NoURLSourceId();
-  mock_graph.page->SetIsVisible(false);
-  mock_graph.page->SetUkmSourceId(mock_source_id);
-  mock_graph.page->SetLifecycleStateForTesting(mojom::LifecycleState::kFrozen);
-  mock_graph.page->SetType(performance_manager::PageType::kTab);
-
-  TabPageDecorator::TabHandle* tab_handle =
-      TabPageDecorator::FromPageNode(mock_graph.page.get());
-
-  EXPECT_EQ(
-      monitor()->GetPageNodeInfoForTesting()[tab_handle]->current_lifecycle,
-      mojom::LifecycleState::kFrozen);
-  EXPECT_EQ(
-      monitor()->GetPageNodeInfoForTesting()[tab_handle]->currently_visible,
-      false);
-
-  // making sure no DCHECKs are hit
-  TriggerCollectSlice();
-}
-
 TEST_P(PageResourceMonitorWithFeatureTest, TestResourceUsage) {
   MockMultiplePagesWithMultipleProcessesGraph mock_graph(graph());
   const ukm::SourceId mock_source_id = ukm::AssignNewSourceId();
@@ -1191,6 +837,6 @@
   delayed.ExpectNone("SystemCPUError");
 }
 
-#endif
+#endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace performance_manager::metrics
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 c5f5e08..0473893 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
@@ -16,9 +16,6 @@
 #include "base/power_monitor/power_observer.h"
 #include "base/run_loop.h"
 #include "base/values.h"
-#include "chrome/browser/performance_manager/metrics/page_resource_monitor.h"
-#include "components/performance_manager/public/features.h"
-#include "components/performance_manager/public/performance_manager.h"
 #include "components/performance_manager/public/user_tuning/prefs.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/frame_rate_throttling.h"
@@ -63,32 +60,13 @@
  public:
   void StartThrottlingAllFrameSinks() override {
     content::StartThrottlingAllFrameSinks(base::Hertz(30));
-    NotifyPageResourceMonitor(/*battery_saver_mode_enabled=*/true);
   }
 
   void StopThrottlingAllFrameSinks() override {
     content::StopThrottlingAllFrameSinks();
-    NotifyPageResourceMonitor(/*battery_saver_mode_enabled=*/false);
   }
 
   ~FrameThrottlingDelegateImpl() override = default;
-
- private:
-  void NotifyPageResourceMonitor(bool battery_saver_mode_enabled) {
-    performance_manager::PerformanceManager::CallOnGraph(
-        FROM_HERE,
-        base::BindOnce(
-            [](bool enabled, performance_manager::Graph* graph) {
-              auto* monitor = graph->GetRegisteredObjectAs<
-                  performance_manager::metrics::PageResourceMonitor>();
-              // It's possible for this to be null if the PageTimeline finch
-              // feature is disabled.
-              if (monitor) {
-                monitor->SetBatterySaverEnabled(enabled);
-              }
-            },
-            battery_saver_mode_enabled));
-  }
 };
 
 }  // namespace
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 3bb3c80..86a09cf 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -956,6 +956,11 @@
 constexpr char kDownloadDuplicateFilePromptEnabled[] =
     "download_duplicate_file_prompt_enabled";
 
+// Deprecated 12/2023.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+constexpr char kIsolatedWebAppsEnabled[] = "ash.isolated_web_apps_enabled";
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
 // Register local state used only for migration (clearing or moving to a new
 // key).
 void RegisterLocalStatePrefsForMigration(PrefRegistrySimple* registry) {
@@ -1071,6 +1076,11 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   registry->RegisterBooleanPref(kGestureEducationNotificationShown, true);
 #endif
+
+  // Deprecated 11/2023.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  registry->RegisterBooleanPref(kIsolatedWebAppsEnabled, false);
+#endif
 }
 
 // Register prefs used only for migration (clearing or moving to a new key).
@@ -2213,6 +2223,11 @@
   local_state->ClearPref(kGestureEducationNotificationShown);
 #endif
 
+// Added 11/2023.
+#if BUILDFLAG(IS_CHROMEOS_ASH)
+  local_state->ClearPref(kIsolatedWebAppsEnabled);
+#endif
+
   // Please don't delete the following line. It is used by PRESUBMIT.py.
   // END_MIGRATE_OBSOLETE_LOCAL_STATE_PREFS
 
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/TrackingProtectionBridge.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/TrackingProtectionBridge.java
index b5a63169..2233613 100644
--- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/TrackingProtectionBridge.java
+++ b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/TrackingProtectionBridge.java
@@ -7,6 +7,7 @@
 import org.jni_zero.NativeMethods;
 
 /** Bridge, providing access to the native-side Tracking Protection configuration. */
+// TODO(crbug.com/1410601): Pass in the profile and remove GetActiveUserProfile in C++.
 public class TrackingProtectionBridge {
 
     public static @NoticeType int getRequiredNotice() {
diff --git a/chrome/browser/repost_form_warning_browsertest.cc b/chrome/browser/repost_form_warning_browsertest.cc
index 2cc8fdac..399505b 100644
--- a/chrome/browser/repost_form_warning_browsertest.cc
+++ b/chrome/browser/repost_form_warning_browsertest.cc
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/test/run_until.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
-#include "chrome/browser/auth_notification_types.h"
 #include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/login/login_handler.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/common/url_constants.h"
@@ -89,14 +90,11 @@
 
   // Navigate to a page that requires authentication, bringing up another
   // tab-modal sheet.
-  content::NavigationController& controller = web_contents->GetController();
-  content::WindowedNotificationObserver observer(
-      chrome::NOTIFICATION_AUTH_NEEDED,
-      content::Source<content::NavigationController>(&controller));
   browser()->OpenURL(content::OpenURLParams(
       embedded_test_server()->GetURL("/auth-basic"), content::Referrer(),
       WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false));
-  observer.Wait();
+  ASSERT_TRUE(base::test::RunUntil(
+      []() { return LoginHandler::GetAllLoginHandlersForTest().size() == 1; }));
 
   // Try to reload it again.
   web_contents->GetController().Reload(content::ReloadType::NORMAL, true);
diff --git a/chrome/browser/resources/accessibility/BUILD.gn b/chrome/browser/resources/accessibility/BUILD.gn
index 6c2d768..e0343f9 100644
--- a/chrome/browser/resources/accessibility/BUILD.gn
+++ b/chrome/browser/resources/accessibility/BUILD.gn
@@ -5,8 +5,8 @@
 import("//build/config/chromeos/ui_mode.gni")
 import("//ui/webui/resources/tools/build_webui.gni")
 if (is_chromeos_lacros) {
-  import("//chrome/browser/resources/chromeos/accessibility/tools/manifest.gni")
   import("strings/extension_strings.gni")
+  import("tools/manifest.gni")
 }
 
 build_webui("build") {
diff --git a/chrome/browser/resources/accessibility/OWNERS b/chrome/browser/resources/accessibility/OWNERS
index 976b955..03bd397 100644
--- a/chrome/browser/resources/accessibility/OWNERS
+++ b/chrome/browser/resources/accessibility/OWNERS
@@ -1 +1,2 @@
 file://ui/accessibility/OWNERS
+anastasi@google.com
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/tools/generate_manifest.py b/chrome/browser/resources/accessibility/tools/generate_manifest.py
similarity index 91%
rename from chrome/browser/resources/chromeos/accessibility/chromevox/tools/generate_manifest.py
rename to chrome/browser/resources/accessibility/tools/generate_manifest.py
index 978224c..855eb5c 100755
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/tools/generate_manifest.py
+++ b/chrome/browser/resources/accessibility/tools/generate_manifest.py
@@ -10,16 +10,15 @@
 import os
 import sys
 
+src_dir_path = os.path.normpath(
+    os.path.join(os.path.abspath(__file__), *[os.path.pardir] * 6))
+
 jinja2_path = os.path.normpath(
-    os.path.join(
-        os.path.abspath(__file__), *[os.path.pardir] * 8 + ['third_party']))
+    os.path.join(src_dir_path, 'third_party'))
 nom_path = os.path.normpath(
-    os.path.join(
-        os.path.abspath(__file__),
-        *[os.path.pardir] * 8 + ['tools/json_comment_eater']))
+    os.path.join(src_dir_path, 'tools/json_comment_eater'))
 version_py_path = os.path.normpath(
-    os.path.join(
-        os.path.abspath(__file__), *[os.path.pardir] * 8 + ['build/util']))
+    os.path.join(src_dir_path, 'build/util'))
 sys.path.insert(0, jinja2_path)
 sys.path.insert(0, nom_path)
 sys.path.insert(0, version_py_path)
diff --git a/chrome/browser/resources/chromeos/accessibility/tools/manifest.gni b/chrome/browser/resources/accessibility/tools/manifest.gni
similarity index 90%
rename from chrome/browser/resources/chromeos/accessibility/tools/manifest.gni
rename to chrome/browser/resources/accessibility/tools/manifest.gni
index 5b44aef..36bb5a01 100644
--- a/chrome/browser/resources/chromeos/accessibility/tools/manifest.gni
+++ b/chrome/browser/resources/accessibility/tools/manifest.gni
@@ -10,7 +10,7 @@
   output_file = invoker.output_file
   key = invoker.key
   action(target_name) {
-    script = "//chrome/browser/resources/chromeos/accessibility/chromevox/tools/generate_manifest.py"
+    script = "//chrome/browser/resources/accessibility/tools/generate_manifest.py"
     inputs = [
       version_file,
       version_script,
diff --git a/chrome/browser/resources/ash/settings/crostini_page/crostini_arc_adb.ts b/chrome/browser/resources/ash/settings/crostini_page/crostini_arc_adb.ts
index 8757c5f..66c9d0c 100644
--- a/chrome/browser/resources/ash/settings/crostini_page/crostini_arc_adb.ts
+++ b/chrome/browser/resources/ash/settings/crostini_page/crostini_arc_adb.ts
@@ -23,6 +23,7 @@
 
 import {DeepLinkingMixin} from '../common/deep_linking_mixin.js';
 import {RouteObserverMixin} from '../common/route_observer_mixin.js';
+import {PrefsState} from '../common/types.js';
 import {Setting} from '../mojom-webui/setting.mojom-webui.js';
 import {Route, routes} from '../router.js';
 
@@ -50,6 +51,11 @@
 
   static get properties() {
     return {
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+
       arcAdbEnabled_: {
         type: Boolean,
         value: false,
@@ -99,6 +105,7 @@
     };
   }
 
+  prefs: PrefsState;
   private arcAdbEnabled_: boolean;
   private arcAdbNeedPowerwash_: boolean;
   private browserProxy_: CrostiniBrowserProxy;
diff --git a/chrome/browser/resources/ash/settings/crostini_page/crostini_export_import.ts b/chrome/browser/resources/ash/settings/crostini_page/crostini_export_import.ts
index a786e85..9c4fb4ba 100644
--- a/chrome/browser/resources/ash/settings/crostini_page/crostini_export_import.ts
+++ b/chrome/browser/resources/ash/settings/crostini_page/crostini_export_import.ts
@@ -17,6 +17,7 @@
 
 import {DeepLinkingMixin} from '../common/deep_linking_mixin.js';
 import {RouteObserverMixin} from '../common/route_observer_mixin.js';
+import {PrefsState} from '../common/types.js';
 import {ContainerInfo, GuestId} from '../guest_os/guest_os_browser_proxy.js';
 import {equalContainerId} from '../guest_os/guest_os_container_select.js';
 import {Setting} from '../mojom-webui/setting.mojom-webui.js';
@@ -40,6 +41,11 @@
 
   static get properties() {
     return {
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+
       showImportConfirmationDialog_: {
         type: Boolean,
         value: false,
@@ -123,6 +129,7 @@
     };
   }
 
+  prefs: PrefsState;
   private allContainers_: ContainerInfo[];
   private browserProxy_: CrostiniBrowserProxy;
   private defaultVmName_: string;
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 3f8a314..66cbf4a 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
@@ -84,5 +84,6 @@
 <key-combination-input-dialog
     id="keyCombinationInputDialog"
     button-remapping-list="{{buttonRemappingList}}"
-    remapping-index="[[selectedButtonIndex_]]">
+    remapping-index="[[selectedButtonIndex_]]"
+    has-launcher-button="[[hasLauncherButton]]">
 </key-combination-input-dialog>
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 0407535..9636c28 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
@@ -96,11 +96,16 @@
         type: Boolean,
         value: false,
       },
+
+      hasLauncherButton: {
+        type: Boolean,
+      },
     };
   }
 
   buttonRemappingList: ButtonRemapping[];
   actionList: ActionChoice[];
+  hasLauncherButton: boolean;
   private selectedButton_: ButtonRemapping;
   private selectedButtonIndex_: number;
   private shouldShowRenamingDialog_: boolean;
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 487d871..07409e8 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
@@ -10,7 +10,8 @@
 </settings-toggle-button>
 <customize-buttons-subsection
     button-remapping-list="{{selectedMouse.settings.buttonRemappings}}"
-    action-list$="[[buttonActionList_]]">
+    action-list$="[[buttonActionList_]]"
+    has-launcher-button="[[hasLauncherButton_]]">
 </customize-buttons-subsection>
 <div id="description" class="subpage-description">
   <iron-icon icon="os-settings:info-outline"></iron-icon>
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 886137d..1dce918 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
@@ -66,6 +66,13 @@
           };
         },
       },
+
+      /**
+       * Use hasLauncherButton to decide which meta key icon to display.
+       */
+      hasLauncherButton_: {
+        type: Boolean,
+      },
     };
   }
 
@@ -86,11 +93,15 @@
   private previousRoute_: Route|null = null;
   private primaryRightPref_: chrome.settingsPrivate.PrefObject;
   private isInitialized_: boolean = false;
+  private hasLauncherButton_: boolean;
 
-  override connectedCallback(): void {
+  override async connectedCallback(): Promise<void> {
     super.connectedCallback();
 
     this.addEventListener('button-remapping-changed', this.onSettingsChanged);
+    this.hasLauncherButton_ =
+        (await this.inputDeviceSettingsProvider_.hasLauncherButton())
+            ?.hasLauncherButton;
   }
 
   override disconnectedCallback(): void {
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_pen_buttons_subpage.html b/chrome/browser/resources/ash/settings/device_page/customize_pen_buttons_subpage.html
index fca7f15..36785c6a 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_pen_buttons_subpage.html
+++ b/chrome/browser/resources/ash/settings/device_page/customize_pen_buttons_subpage.html
@@ -2,7 +2,8 @@
 </style>
 <customize-buttons-subsection
   button-remapping-list="{{selectedTablet.settings.penButtonRemappings}}"
-  action-list$="[[buttonActionList_]]">
+  action-list$="[[buttonActionList_]]"
+  has-launcher-button="[[hasLauncherButton_]]">
 </customize-buttons-subsection>
 <div id="description" class="subpage-description">
   <iron-icon icon="os-settings:info-outline"></iron-icon>
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_pen_buttons_subpage.ts b/chrome/browser/resources/ash/settings/device_page/customize_pen_buttons_subpage.ts
index aad7f71..4a798ea9 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_pen_buttons_subpage.ts
+++ b/chrome/browser/resources/ash/settings/device_page/customize_pen_buttons_subpage.ts
@@ -46,6 +46,13 @@
       graphicsTablets: {
         type: Array,
       },
+
+      /**
+       * Use hasLauncherButton to decide which meta key icon to display.
+       */
+      hasLauncherButton_: {
+        type: Boolean,
+      },
     };
   }
 
@@ -62,11 +69,15 @@
       getInputDeviceSettingsProvider();
   private previousRoute_: Route|null = null;
   private isInitialized_: boolean = false;
+  private hasLauncherButton_: boolean;
 
-  override connectedCallback(): void {
+  override async connectedCallback(): Promise<void> {
     super.connectedCallback();
 
     this.addEventListener('button-remapping-changed', this.onSettingsChanged);
+    this.hasLauncherButton_ =
+        (await this.inputDeviceSettingsProvider_.hasLauncherButton())
+            ?.hasLauncherButton;
   }
 
   override disconnectedCallback(): void {
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_tablet_buttons_subpage.html b/chrome/browser/resources/ash/settings/device_page/customize_tablet_buttons_subpage.html
index 08fc160..ab54f110 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_tablet_buttons_subpage.html
+++ b/chrome/browser/resources/ash/settings/device_page/customize_tablet_buttons_subpage.html
@@ -2,7 +2,8 @@
 </style>
 <customize-buttons-subsection
   button-remapping-list="{{selectedTablet.settings.tabletButtonRemappings}}"
-  action-list$="[[buttonActionList_]]">
+  action-list$="[[buttonActionList_]]"
+  has-launcher-button="[[hasLauncherButton_]]">
 </customize-buttons-subsection>
 <div id="description" class="subpage-description">
   <iron-icon icon="os-settings:info-outline"></iron-icon>
diff --git a/chrome/browser/resources/ash/settings/device_page/customize_tablet_buttons_subpage.ts b/chrome/browser/resources/ash/settings/device_page/customize_tablet_buttons_subpage.ts
index 8a44478f..ebee6460 100644
--- a/chrome/browser/resources/ash/settings/device_page/customize_tablet_buttons_subpage.ts
+++ b/chrome/browser/resources/ash/settings/device_page/customize_tablet_buttons_subpage.ts
@@ -45,6 +45,13 @@
       graphicsTablets: {
         type: Array,
       },
+
+      /**
+       * Use hasLauncherButton to decide which meta key icon to display.
+       */
+      hasLauncherButton_: {
+        type: Boolean,
+      },
     };
   }
 
@@ -61,11 +68,15 @@
       getInputDeviceSettingsProvider();
   private previousRoute_: Route|null = null;
   private isInitialized_: boolean = false;
+  private hasLauncherButton_: boolean;
 
-  override connectedCallback(): void {
+  override async connectedCallback(): Promise<void> {
     super.connectedCallback();
 
     this.addEventListener('button-remapping-changed', this.onSettingsChanged);
+    this.hasLauncherButton_ =
+        (await this.inputDeviceSettingsProvider_.hasLauncherButton())
+            ?.hasLauncherButton;
   }
 
   override disconnectedCallback(): void {
diff --git a/chrome/browser/resources/ash/settings/device_page/fake_input_device_settings_provider.ts b/chrome/browser/resources/ash/settings/device_page/fake_input_device_settings_provider.ts
index cc51599..06587aae 100644
--- a/chrome/browser/resources/ash/settings/device_page/fake_input_device_settings_provider.ts
+++ b/chrome/browser/resources/ash/settings/device_page/fake_input_device_settings_provider.ts
@@ -21,6 +21,7 @@
   fakeGraphicsTablets: GraphicsTablet[];
   fakeMouseButtonActions: {options: ActionChoice[]};
   fakeGraphicsTabletButtonActions: {options: ActionChoice[]};
+  fakeHasLauncherButton: {hasLauncherButton: boolean};
 }
 
 class FakeMethodState {
@@ -102,6 +103,7 @@
     this.methods.register('fakeGraphicsTablets');
     this.methods.register('fakeMouseButtonActions');
     this.methods.register('fakeGraphicsTabletButtonActions');
+    this.methods.register('fakeHasLauncherButton');
   }
 
   setFakeKeyboards(keyboards: Keyboard[]): void {
@@ -363,4 +365,13 @@
       observer.onButtonPressed(button);
     }
   }
+
+  hasLauncherButton(): Promise<{hasLauncherButton: boolean}> {
+    return this.methods.resolveMethod('fakeHasLauncherButton');
+  }
+
+  setFakeHasLauncherButton(hasLauncherButton: boolean): void {
+    this.methods.setResult(
+        'fakeHasLauncherButton', {hasLauncherButton: hasLauncherButton});
+  }
 }
diff --git a/chrome/browser/resources/ash/settings/device_page/input_device_mojo_interface_provider.ts b/chrome/browser/resources/ash/settings/device_page/input_device_mojo_interface_provider.ts
index 3838e4cf..bb267c2 100644
--- a/chrome/browser/resources/ash/settings/device_page/input_device_mojo_interface_provider.ts
+++ b/chrome/browser/resources/ash/settings/device_page/input_device_mojo_interface_provider.ts
@@ -34,6 +34,7 @@
   provider.setFakeActionsForGraphicsTabletButtonCustomization(
       fakeGraphicsTabletButtonActions);
   provider.setFakeActionsForMouseButtonCustomization(fakeMouseButtonActions);
+  provider.setFakeHasLauncherButton(true);
   inputDeviceSettingsProvider = provider;
 }
 
diff --git a/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.html b/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.html
index 778e1bb4..403a911 100644
--- a/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.html
+++ b/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.html
@@ -35,7 +35,8 @@
     <div id="shortcut-input-container" class="flex-row" tabindex="-1">
       <shortcut-input id="shortcutInput"
           shortcut-input-provider="[[getShortcutProvider()]]"
-          show-separator="true">
+          show-separator="true"
+          has-launcher-button="[[hasLauncherButton]]">
       </shortcut-input>
     </div>
   </div>
diff --git a/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.ts b/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.ts
index 225da98..10b9b138 100644
--- a/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.ts
+++ b/chrome/browser/resources/ash/settings/device_page/key_combination_input_dialog.ts
@@ -83,6 +83,10 @@
       inputKeyEvent: {
         type: Object,
       },
+
+      hasLauncherButton: {
+        type: Boolean,
+      },
     };
   }
 
@@ -98,6 +102,7 @@
   shortcutInput: ShortcutInputElement;
   inputKeyEvent: KeyEvent|undefined;
   isCapturing: boolean = false;
+  hasLauncherButton: boolean;
   private buttonRemapping_: ButtonRemapping;
   private eventTracker_: EventTracker = new EventTracker();
 
diff --git a/chrome/browser/resources/ash/settings/os_settings_ui/os_settings_ui.html b/chrome/browser/resources/ash/settings/os_settings_ui/os_settings_ui.html
index d9ce265..4dbd1a28 100644
--- a/chrome/browser/resources/ash/settings/os_settings_ui/os_settings_ui.html
+++ b/chrome/browser/resources/ash/settings/os_settings_ui/os_settings_ui.html
@@ -78,8 +78,9 @@
   }
 
   :host-context(body.revamp-wayfinding-enabled) #drawer {
-    --cr-drawer-border-start-end-radius: 20px;
-    --cr-drawer-border-end-end-radius: 20px;
+    /* TODO(b/316088424) Use window border radius token if available */
+    --cr-drawer-border-start-end-radius: 12px;
+    --cr-drawer-border-end-end-radius: 12px;
     --cr-drawer-header-color: var(--cros-sys-primary);
     --cr-drawer-header-font: var(--cros-title-1-font);
     --cr-drawer-header-padding: 22px;
diff --git a/chrome/browser/resources/chromeos/accessibility/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
index 57b1985f..0390035 100644
--- a/chrome/browser/resources/chromeos/accessibility/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
@@ -4,11 +4,11 @@
 
 import("//build/config/chromeos/ui_mode.gni")
 import("//build/config/features.gni")
+import("//chrome/browser/resources/accessibility/tools/manifest.gni")
 import("//chrome/common/features.gni")
 import("//chrome/test/base/ash/js2gtest.gni")
 import("//tools/typescript/ts_library.gni")
 import("strings/accessibility_strings.gni")
-import("tools/manifest.gni")
 import("tools/run_jsbundler.gni")
 
 assert(is_chromeos_ash)
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
index 91e12825..980933c 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/BUILD.gn
@@ -18,7 +18,7 @@
 tsc_out_dir = "$target_gen_dir/tsc"
 
 # TS files to compile.
-ts_modules = []
+ts_modules = [ "switch_access_constants.ts" ]
 
 # JS files needed to compile TS.
 js_deps = []
@@ -107,7 +107,6 @@
     "point_scan_manager.js",
     "settings_manager.js",
     "switch_access.js",
-    "switch_access_constants.js",
     "switch_access_predicate.js",
     "text_navigation_manager.js",
   ]
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.js b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.js
deleted file mode 100644
index 52e1bbc..0000000
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Constants used in Switch Access.
- */
-const AutomationNode = chrome.automation.AutomationNode;
-
-/**
- * When an action is performed, how the menu should respond.
- * @enum {number}
- */
-export const ActionResponse = {
-  NO_ACTION_TAKEN: -1,
-  REMAIN_OPEN: 0,
-  CLOSE_MENU: 1,
-  EXIT_SUBMENU: 2,
-  RELOAD_MENU: 3,
-  OPEN_TEXT_NAVIGATION_MENU: 4,
-};
-
-/**
- * The types of error or unexpected state that can be encountered by Switch
- * Access.
- * These values are persisted to logs and should not be renumbered or re-used.
- * See tools/metrics/histograms/enums.xml.
- * @enum {number}
- */
-export const ErrorType = {
-  UNKNOWN: 0,
-  PREFERENCE_TYPE: 1,
-  UNTRANSLATED_STRING: 2,
-  INVALID_COLOR: 3,
-  NEXT_UNDEFINED: 4,
-  PREVIOUS_UNDEFINED: 5,
-  NULL_CHILD: 6,
-  NO_CHILDREN: 7,
-  MALFORMED_DESKTOP: 8,
-  MISSING_LOCATION: 9,
-  MISSING_KEYBOARD: 10,
-  ROW_TOO_SHORT: 11,
-  MISSING_BASE_NODE: 12,
-  NEXT_INVALID: 13,
-  PREVIOUS_INVALID: 14,
-  INVALID_SELECTION_BOUNDS: 15,
-  UNINITIALIZED: 16,
-  DUPLICATE_INITIALIZATION: 17,
-};
-
-/**
- * The different types of menus and sub-menus that can be shown.
- * @enum {number}
- */
-export const MenuType = {
-  MAIN_MENU: 0,
-  TEXT_NAVIGATION: 1,
-  POINT_SCAN_MENU: 2,
-};
-
-/**
- * The modes of interaction the user can select for how to interact with the
- * device.
- * @enum {number}
- */
-export const Mode = {
-  ITEM_SCAN: 0,
-  POINT_SCAN: 1,
-};
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.ts b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.ts
new file mode 100644
index 0000000..63d40317
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/switch_access_constants.ts
@@ -0,0 +1,56 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/** When an action is performed, how the menu should respond. */
+export enum ActionResponse {
+  NO_ACTION_TAKEN = -1,
+  REMAIN_OPEN = 0,
+  CLOSE_MENU = 1,
+  EXIT_SUBMENU = 2,
+  RELOAD_MENU = 3,
+  OPEN_TEXT_NAVIGATION_MENU = 4,
+}
+
+/**
+ * The types of error or unexpected state that can be encountered by Switch
+ * Access.
+ * These values are persisted to logs and should not be renumbered or re-used.
+ * See tools/metrics/histograms/enums.xml.
+ */
+export enum ErrorType {
+  UNKNOWN = 0,
+  PREFERENCE_TYPE = 1,
+  UNTRANSLATED_STRING = 2,
+  INVALID_COLOR = 3,
+  NEXT_UNDEFINED = 4,
+  PREVIOUS_UNDEFINED = 5,
+  NULL_CHILD = 6,
+  NO_CHILDREN = 7,
+  MALFORMED_DESKTOP = 8,
+  MISSING_LOCATION = 9,
+  MISSING_KEYBOARD = 10,
+  ROW_TOO_SHORT = 11,
+  MISSING_BASE_NODE = 12,
+  NEXT_INVALID = 13,
+  PREVIOUS_INVALID = 14,
+  INVALID_SELECTION_BOUNDS = 15,
+  UNINITIALIZED = 16,
+  DUPLICATE_INITIALIZATION = 17,
+}
+
+/** The different types of menus and sub-menus that can be shown. */
+export enum MenuType {
+  MAIN_MENU = 0,
+  TEXT_NAVIGATION = 1,
+  POINT_SCAN_MENU = 2,
+}
+
+/**
+ * The modes of interaction the user can select for how to interact with the
+ * device.
+ */
+export enum Mode {
+  ITEM_SCAN = 0,
+  POINT_SCAN = 1,
+}
diff --git a/chrome/browser/resources/settings/BUILD.gn b/chrome/browser/resources/settings/BUILD.gn
index 1e48e2a..91492f2d 100644
--- a/chrome/browser/resources/settings/BUILD.gn
+++ b/chrome/browser/resources/settings/BUILD.gn
@@ -261,6 +261,9 @@
   if (is_win || is_mac) {
     web_component_files += [ "autofill_page/passkeys_subpage.ts" ]
   }
+  if (is_win || is_mac || is_linux) {
+    web_component_files += [ "a11y_page/pdf_ocr_toggle.ts" ]
+  }
   if (is_chrome_branded) {
     web_component_files += [ "get_most_chrome_page/get_most_chrome_page.ts" ]
   }
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_browser_proxy.ts b/chrome/browser/resources/settings/a11y_page/a11y_browser_proxy.ts
index 74f213a..3c32921 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_browser_proxy.ts
+++ b/chrome/browser/resources/settings/a11y_page/a11y_browser_proxy.ts
@@ -2,9 +2,28 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// <if expr="is_win or is_linux or is_macosx">
+import {sendWithPromise} from 'chrome://resources/js/cr.js';
+
+/**
+ * Numerical values should not be changed because they must stay in sync with
+ * screen_ai::ScreenAIInstallState::State defined in screen_ai_install_state.h.
+ */
+export enum ScreenAiInstallStatus {
+  NOT_DOWNLOADED = 0,
+  DOWNLOADING = 1,
+  FAILED = 2,
+  DOWNLOADED = 3,
+  READY = 4,
+}
+// </if>
+
 export interface AccessibilityBrowserProxy {
   openTrackpadGesturesSettings(): void;
   recordOverscrollHistoryNavigationChanged(enabled: boolean): void;
+  // <if expr="is_win or is_linux or is_macosx">
+  getScreenAiInstallState(): Promise<ScreenAiInstallStatus>;
+  // </if>
 }
 
 export class AccessibilityBrowserProxyImpl implements
@@ -17,6 +36,11 @@
     chrome.metricsPrivate.recordBoolean(
         'Settings.OverscrollHistoryNavigation.Enabled', enabled);
   }
+  // <if expr="is_win or is_linux or is_macosx">
+  getScreenAiInstallState() {
+    return sendWithPromise('getScreenAiInstallState');
+  }
+  // </if>
 
   static getInstance(): AccessibilityBrowserProxy {
     return instance || (instance = new AccessibilityBrowserProxyImpl());
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page.html b/chrome/browser/resources/settings/a11y_page/a11y_page.html
index 3fd8c5f6..d8d8b34 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_page.html
+++ b/chrome/browser/resources/settings/a11y_page/a11y_page.html
@@ -58,14 +58,10 @@
             sub-label="$i18n{accessibleImageLabelsSubtitle}">
         </settings-toggle-button>
 <if expr="is_win or is_linux or is_macosx">
-        <settings-toggle-button id="pdfOcrToggle"
-            class="hr"
-            hidden$="[[!showPdfOcrToggle_]]"
-            pref="{{prefs.settings.a11y.pdf_ocr_always_active}}"
-            label="$i18n{pdfOcrTitle}"
-            sub-label="[[getPdfOcrToggleSublabel_(
-                pdfOcrStatus_, pdfOcrProgress_)]]">
-        </settings-toggle-button>
+        <template is="dom-if" if="[[showPdfOcrToggle_]]">
+          <settings-pdf-ocr-toggle id="pdfOcrToggle" prefs="{{prefs}}">
+          </settings-pdf-ocr-toggle>
+        </template>
 </if>
 <if expr="is_win or is_linux">
         <settings-toggle-button
diff --git a/chrome/browser/resources/settings/a11y_page/a11y_page.ts b/chrome/browser/resources/settings/a11y_page/a11y_page.ts
index 38874061..76c7646 100644
--- a/chrome/browser/resources/settings/a11y_page/a11y_page.ts
+++ b/chrome/browser/resources/settings/a11y_page/a11y_page.ts
@@ -18,6 +18,10 @@
 import '../settings_page/settings_subpage.js';
 // </if>
 
+// <if expr="is_win or is_linux or is_macosx">
+import './pdf_ocr_toggle.js';
+// </if>
+
 // <if expr="is_win or is_macosx">
 import './live_caption_section.js';
 
@@ -26,7 +30,6 @@
 // clang-format on
 import {SettingsToggleButtonElement} from '/shared/settings/controls/settings_toggle_button.js';
 import {PrefsMixin} from 'chrome://resources/cr_components/settings_prefs/prefs_mixin.js';
-import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
 import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
@@ -45,24 +48,8 @@
 // </if>
 // clang-format on
 
-// TODO(crbug.com/1442928): Encapsulate all PDF OCR toggle logic to a dedicated
-// pdf-ocr-toggle-button element.
-// <if expr="is_win or is_linux or is_macosx">
-/**
- * Numerical values should not be changed because they must stay in sync with
- * screen_ai::ScreenAIInstallState::State defined in screen_ai_install_state.h.
- */
-export enum ScreenAiInstallStatus {
-  NOT_DOWNLOADED = 0,
-  DOWNLOADING = 1,
-  FAILED = 2,
-  DOWNLOADED = 3,
-  READY = 4,
-}
-// </if>
-
 const SettingsA11yPageElementBase =
-    PrefsMixin(WebUiListenerMixin(I18nMixin(BaseMixin(PolymerElement))));
+    PrefsMixin(WebUiListenerMixin(BaseMixin(PolymerElement)));
 
 export class SettingsA11yPageElement extends SettingsA11yPageElementBase {
   static get is() {
@@ -133,18 +120,7 @@
 
       // <if expr="is_win or is_linux or is_macosx">
       /**
-       * `pdfOcrProgress_` stores the downloading progress in percentage of
-       * the ScreenAI library, which ranges from 0.0 to 100.0.
-       */
-      pdfOcrProgress_: Number,
-
-      /**
-       * `pdfOcrStatus_` stores the ScreenAI library install state.
-       */
-      pdfOcrStatus_: Number,
-
-      /**
-       * Whether to show pdf ocr settings.
+       * Whether to show the PDF OCR toggle.
        */
       showPdfOcrToggle_: {
         type: Boolean,
@@ -213,10 +189,7 @@
   private captionSettingsOpensExternally_: boolean;
   private hasScreenReader_: boolean;
   private showOverscrollHistoryNavigationToggle_: boolean;
-
   // <if expr="is_win or is_linux or is_macosx">
-  private pdfOcrProgress_: number;
-  private pdfOcrStatus_: ScreenAiInstallStatus;
   private showPdfOcrToggle_: boolean;
   // </if>
 
@@ -227,18 +200,6 @@
         'screen-reader-state-changed', (hasScreenReader: boolean) => {
           this.hasScreenReader_ = hasScreenReader;
         });
-    // <if expr="is_win or is_linux or is_macosx">
-    if (loadTimeData.getBoolean('pdfOcrEnabled')) {
-      this.addWebUiListener(
-          'pdf-ocr-state-changed', (pdfOcrState: ScreenAiInstallStatus) => {
-            this.pdfOcrStatus_ = pdfOcrState;
-          });
-      this.addWebUiListener(
-          'pdf-ocr-downloading-progress-changed', (progress: number) => {
-            this.pdfOcrProgress_ = progress;
-          });
-    }
-    // </if>
 
     // Enables javascript and gets the screen reader state.
     chrome.send('a11yPageReady');
@@ -263,26 +224,9 @@
   }
 
   // <if expr="is_win or is_linux or is_macosx">
-  private getPdfOcrToggleSublabel_(): string {
-    switch (this.pdfOcrStatus_) {
-      case ScreenAiInstallStatus.DOWNLOADING:
-        return this.pdfOcrProgress_ > 0 && this.pdfOcrProgress_ < 100 ?
-            this.i18n('pdfOcrDownloadProgressLabel', this.pdfOcrProgress_) :
-            this.i18n('pdfOcrDownloadingLabel');
-      case ScreenAiInstallStatus.FAILED:
-        return this.i18n('pdfOcrDownloadErrorLabel');
-      case ScreenAiInstallStatus.DOWNLOADED:
-        return this.i18n('pdfOcrDownloadCompleteLabel');
-      case ScreenAiInstallStatus.READY:  // fallthrough
-      case ScreenAiInstallStatus.NOT_DOWNLOADED:
-        // No subtitle update, so show a generic subtitle describing PDF OCR.
-        return this.i18n('pdfOcrSubtitle');
-    }
-  }
-
   /**
-   * Return whether to show a PDF OCR toggle button based on:
-   *    1. A PDF OCR feature flag is enabled.
+   * Return whether to show the PDF OCR toggle button based on:
+   *    1. The PDF OCR feature flag is enabled.
    *    2. Whether a screen reader is enabled.
    * Note: on ChromeOS, the PDF OCR toggle is shown on a different settings
    * page; i.e. Settings > Accessibility > Text-to-Speech.
diff --git a/chrome/browser/resources/settings/a11y_page/pdf_ocr_toggle.html b/chrome/browser/resources/settings/a11y_page/pdf_ocr_toggle.html
new file mode 100644
index 0000000..326eec63
--- /dev/null
+++ b/chrome/browser/resources/settings/a11y_page/pdf_ocr_toggle.html
@@ -0,0 +1,8 @@
+<style include="settings-shared"></style>
+<settings-toggle-button id="toggle"
+    class="hr"
+    pref="{{prefs.settings.a11y.pdf_ocr_always_active}}"
+    label="$i18n{pdfOcrTitle}"
+    sub-label="[[getPdfOcrToggleSublabel_(
+        pdfOcrStatus_, pdfOcrProgress_)]]">
+</settings-toggle-button>
diff --git a/chrome/browser/resources/settings/a11y_page/pdf_ocr_toggle.ts b/chrome/browser/resources/settings/a11y_page/pdf_ocr_toggle.ts
new file mode 100644
index 0000000..ed8a199
--- /dev/null
+++ b/chrome/browser/resources/settings/a11y_page/pdf_ocr_toggle.ts
@@ -0,0 +1,116 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * 'settings-pdf-ocr-toggle' is a toggle component for PDF OCR. It appears on
+ * the accessibility subpage (chrome://settings/accessibility) on Windows,
+ * macOS, and Linux.
+ */
+
+import '/shared/settings/controls/settings_toggle_button.js';
+import '../settings_shared.css.js';
+
+import {SettingsToggleButtonElement} from '/shared/settings/controls/settings_toggle_button.js';
+import {PrefsMixin} from 'chrome://resources/cr_components/settings_prefs/prefs_mixin.js';
+import {I18nMixin} from 'chrome://resources/cr_elements/i18n_mixin.js';
+import {WebUiListenerMixin} from 'chrome://resources/cr_elements/web_ui_listener_mixin.js';
+import {assert} from 'chrome://resources/js/assert.js';
+import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {loadTimeData} from '../i18n_setup.js';
+
+import {AccessibilityBrowserProxy, AccessibilityBrowserProxyImpl, ScreenAiInstallStatus} from './a11y_browser_proxy.js';
+import {getTemplate} from './pdf_ocr_toggle.html.js';
+
+const SettingsPdfOcrToggleBaseElement =
+    PrefsMixin(WebUiListenerMixin(I18nMixin(PolymerElement)));
+
+export interface SettingsPdfOcrToggleElement {
+  $: {
+    toggle: SettingsToggleButtonElement,
+  };
+}
+
+export class SettingsPdfOcrToggleElement extends
+    SettingsPdfOcrToggleBaseElement {
+  static get is() {
+    return 'settings-pdf-ocr-toggle';
+  }
+
+  static get template() {
+    return getTemplate();
+  }
+
+  static get properties() {
+    return {
+      /**
+       * Preferences state.
+       */
+      prefs: {
+        type: Object,
+        notify: true,
+      },
+
+      /**
+       * `pdfOcrProgress_` stores the downloading progress in percentage of
+       * the ScreenAI library, which ranges from 0.0 to 100.0.
+       */
+      pdfOcrProgress_: Number,
+
+      /**
+       * `pdfOcrStatus_` stores the ScreenAI library install state.
+       */
+      pdfOcrStatus_: Number,
+    };
+  }
+
+  private browserProxy_: AccessibilityBrowserProxy =
+      AccessibilityBrowserProxyImpl.getInstance();
+
+  private pdfOcrProgress_: number;
+  private pdfOcrStatus_: ScreenAiInstallStatus;
+
+  override connectedCallback() {
+    super.connectedCallback();
+
+    assert(loadTimeData.getBoolean('pdfOcrEnabled'));
+
+    const updatePdfOcrState = (pdfOcrState: ScreenAiInstallStatus) => {
+      this.pdfOcrStatus_ = pdfOcrState;
+    };
+    this.browserProxy_.getScreenAiInstallState().then(updatePdfOcrState);
+    this.addWebUiListener('pdf-ocr-state-changed', updatePdfOcrState);
+    this.addWebUiListener(
+        'pdf-ocr-downloading-progress-changed', (progress: number) => {
+          this.pdfOcrProgress_ = progress;
+        });
+  }
+
+  private getPdfOcrToggleSublabel_(): string {
+    switch (this.pdfOcrStatus_) {
+      case ScreenAiInstallStatus.DOWNLOADING:
+        return this.pdfOcrProgress_ > 0 && this.pdfOcrProgress_ < 100 ?
+            this.i18n('pdfOcrDownloadProgressLabel', this.pdfOcrProgress_) :
+            this.i18n('pdfOcrDownloadingLabel');
+      case ScreenAiInstallStatus.FAILED:
+        return this.i18n('pdfOcrDownloadErrorLabel');
+      case ScreenAiInstallStatus.DOWNLOADED:
+        return this.i18n('pdfOcrDownloadCompleteLabel');
+      case ScreenAiInstallStatus.READY:  // fallthrough
+      case ScreenAiInstallStatus.NOT_DOWNLOADED:
+        // No subtitle update, so show a generic subtitle describing PDF OCR.
+        return this.i18n('pdfOcrSubtitle');
+    }
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'settings-pdf-ocr-toggle': SettingsPdfOcrToggleElement;
+  }
+}
+
+customElements.define(
+    SettingsPdfOcrToggleElement.is, SettingsPdfOcrToggleElement);
diff --git a/chrome/browser/resources/settings/lazy_load.ts b/chrome/browser/resources/settings/lazy_load.ts
index f4bcad0..e73b50cc 100644
--- a/chrome/browser/resources/settings/lazy_load.ts
+++ b/chrome/browser/resources/settings/lazy_load.ts
@@ -108,7 +108,7 @@
 export {AccessibilityBrowserProxy, AccessibilityBrowserProxyImpl} from './a11y_page/a11y_browser_proxy.js';
 // clang-format off
 // <if expr="is_win or is_linux or is_macosx">
-export {ScreenAiInstallStatus} from './a11y_page/a11y_page.js';
+export {ScreenAiInstallStatus} from './a11y_page/a11y_browser_proxy.js';
 // </if>
 // clang-format on
 export {SettingsA11yPageElement} from './a11y_page/a11y_page.js';
@@ -116,6 +116,9 @@
 export {SettingsLiveCaptionElement} from './a11y_page/live_caption_section.js';
 export {SettingsLiveTranslateElement} from './a11y_page/live_translate_section.js';
 // </if>
+// <if expr="is_win or is_linux or is_macosx">
+export {SettingsPdfOcrToggleElement} from './a11y_page/pdf_ocr_toggle.js';
+// </if>
 
 export {SettingsAppearanceFontsPageElement} from './appearance_page/appearance_fonts_page.js';
 export {CountryDetailManager, CountryDetailManagerImpl, SettingsAddressEditDialogElement} from './autofill_page/address_edit_dialog.js';
diff --git a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotification.java b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotification.java
index 30f483a..11429ef 100644
--- a/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotification.java
+++ b/chrome/browser/search_engines/android/java/src/org/chromium/chrome/browser/search_engines/SearchEngineChoiceNotification.java
@@ -9,7 +9,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.omaha.VersionNumber;
 import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
 import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
@@ -28,16 +27,8 @@
  * User is only meant to be prompted once, hence the fact of prompting is saved to preferences.
  */
 public final class SearchEngineChoiceNotification {
-    /** Variations parameter name for notification snackbar duration (in seconds). */
-    private static final String PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS =
-            "notification-snackbar-duration-seconds";
-
-    /** Default value for notification snackbar duration (in seconds). */
-    private static final int PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS_DEFAULT = 10;
-
-    /** Variations parameter name for invalidating version number. */
-    private static final String PARAM_NOTIFICATION_INVALIDATING_VERSION_NUMBER =
-            "notification-invalidating-version-number";
+    /** Notification snackbar duration (in seconds). */
+    private static final int NOTIFICATION_SNACKBAR_DURATION_SECONDS = 10;
 
     /**
      * Snackbar controller for search engine choice notification. It takes the user to the settings
@@ -107,19 +98,14 @@
 
     private static Snackbar buildSnackbarNotification(
             @NonNull Context context, @NonNull SettingsLauncher settingsLauncher) {
-        int durationSeconds =
-                ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
-                        ChromeFeatureList.ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION,
-                        PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS,
-                        PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS_DEFAULT);
-
         return Snackbar.make(
                         context.getString(R.string.search_engine_choice_prompt),
                         new NotificationSnackbarController(context, settingsLauncher),
                         Snackbar.TYPE_NOTIFICATION,
                         Snackbar.UMA_SEARCH_ENGINE_CHOICE_NOTIFICATION)
                 .setAction(context.getString(R.string.settings), null)
-                .setDuration((int) TimeUnit.SECONDS.toMillis(durationSeconds))
+                .setDuration(
+                        (int) TimeUnit.SECONDS.toMillis(NOTIFICATION_SNACKBAR_DURATION_SECONDS))
                 .setSingleLine(false)
                 .setTheme(Snackbar.Theme.GOOGLE);
     }
@@ -144,13 +130,7 @@
     }
 
     private static boolean wasSearchEngineChoicePresented() {
-        VersionNumber lastPresentedVersionNumber = getLastPresentedVersionNumber();
-        if (lastPresentedVersionNumber == null) return false;
-
-        VersionNumber lowestAcceptedVersionNumber = getLowestAcceptedVersionNumber();
-        if (lowestAcceptedVersionNumber == null) return true;
-
-        return !lastPresentedVersionNumber.isSmallerThan(lowestAcceptedVersionNumber);
+        return getLastPresentedVersionNumber() != null;
     }
 
     private static @Nullable VersionNumber getLastPresentedVersionNumber() {
@@ -159,18 +139,4 @@
                         .readString(
                                 ChromePreferenceKeys.SEARCH_ENGINE_CHOICE_PRESENTED_VERSION, null));
     }
-
-    private static @Nullable VersionNumber getLowestAcceptedVersionNumber() {
-        return VersionNumber.fromString(
-                ChromeFeatureList.getFieldTrialParamByFeature(
-                        ChromeFeatureList.ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION,
-                        PARAM_NOTIFICATION_INVALIDATING_VERSION_NUMBER));
-    }
-
-    private static int getNotificationSnackbarDuration() {
-        return ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
-                ChromeFeatureList.ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION,
-                PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS,
-                PARAM_NOTIFICATION_SNACKBAR_DURATION_SECONDS_DEFAULT);
-    }
 }
diff --git a/chrome/browser/search_engines/template_url_service_unittest.cc b/chrome/browser/search_engines/template_url_service_unittest.cc
index a122cfb..56b3437 100644
--- a/chrome/browser/search_engines/template_url_service_unittest.cc
+++ b/chrome/browser/search_engines/template_url_service_unittest.cc
@@ -219,6 +219,7 @@
 
   // Verifies the two TemplateURLs are equal.
   void AssertEquals(const TemplateURL& expected, const TemplateURL& actual);
+  void AssertEquals(const TemplateURL* expected, const TemplateURL* actual);
 
   // Verifies the two timestamps are equal, within the expected degree of
   // precision.
@@ -344,6 +345,17 @@
   ASSERT_EQ(expected.sync_guid(), actual.sync_guid());
 }
 
+void TemplateURLServiceTest::AssertEquals(const TemplateURL* expected,
+                                          const TemplateURL* actual) {
+  ASSERT_TRUE(expected);
+  ASSERT_TRUE(actual);
+  if (expected == actual) {
+    return;
+  }
+
+  AssertEquals(*expected, *actual);
+}
+
 void TemplateURLServiceTest::AssertTimesEqual(const Time& expected,
                                               const Time& actual) {
   // Because times are stored with a granularity of one second, there is a loss
@@ -2832,6 +2844,261 @@
     AssertEquals(*user_engine, *actual_turl);
   }
 }
+
+TEST_P(TemplateURLServiceTest, NonFeaturedSiteSearchPolicyConflictWithDSP) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(omnibox::kSiteSearchSettingsPolicy);
+
+  // Reset the model to ensure an `EnterpriseSiteSearchManager` instance is
+  // created (it depends on `kSiteSearchSettingsPolicy` being enabled).
+  test_util()->ResetModel(/*verify_load=*/true);
+
+  const TemplateURL* dse = model()->GetDefaultSearchProvider();
+  ASSERT_TRUE(dse);
+
+  AssertEquals(dse, model()->GetTemplateURLForKeyword(dse->keyword()));
+
+  // Set a managed preference that establishes a site search provider
+  // conflicting with pre-defined default search engine not customized by the
+  // user.
+  EnterpriseSiteSearchManager::OwnedTemplateURLDataVector site_search_engines;
+  site_search_engines.push_back(
+      CreateTestSiteSearchEntry(base::UTF16ToUTF8(dse->keyword())));
+
+  SetManagedSiteSearchSettingsPreference(site_search_engines,
+                                         test_util()->profile());
+
+  // Expect no change in default search engine.
+  EXPECT_EQ(dse, model()->GetDefaultSearchProvider());
+  // Override DES for keyword search because `safe_for_autoreplace` is true.
+  ExpectSimilar(site_search_engines[0].get(),
+                &model()->GetTemplateURLForKeyword(dse->keyword())->data());
+
+  // Reset the policy.
+  SetManagedSiteSearchSettingsPreference(
+      EnterpriseSiteSearchManager::OwnedTemplateURLDataVector(),
+      test_util()->profile());
+
+  // No changes to the DSE once the policy is no longer applied.
+  EXPECT_EQ(dse, model()->GetDefaultSearchProvider());
+  AssertEquals(dse, model()->GetTemplateURLForKeyword(dse->keyword()));
+}
+
+TEST_P(TemplateURLServiceTest,
+       NonFeaturedSiteSearchPolicyConflictWithUserDefinedDSP) {
+  constexpr char kKeyword[] = "keyword";
+  constexpr char16_t kKeywordU16[] = u"keyword";
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(omnibox::kSiteSearchSettingsPolicy);
+
+  // Reset the model to ensure an `EnterpriseSiteSearchManager` instance is
+  // created (it depends on `kSiteSearchSettingsPolicy` being enabled).
+  test_util()->ResetModel(/*verify_load=*/true);
+
+  TemplateURL* user_dse = AddKeywordWithDate(
+      "DSE name", kKeyword, "http://www.goo.com/s?q={searchTerms}",
+      std::string(), std::string(), std::string(),
+      /*safe_for_autoreplace=*/false);
+  model()->SetUserSelectedDefaultSearchProvider(user_dse);
+  EXPECT_EQ(user_dse, model()->GetDefaultSearchProvider());
+  AssertEquals(user_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+
+  // Set a managed preference that establishes a site search provider
+  // conflicting with user-defined default search engine.
+  EnterpriseSiteSearchManager::OwnedTemplateURLDataVector site_search_engines;
+  site_search_engines.push_back(CreateTestSiteSearchEntry(kKeyword));
+
+  SetManagedSiteSearchSettingsPreference(site_search_engines,
+                                         test_util()->profile());
+
+  // Expect no change in default search engine.
+  EXPECT_EQ(user_dse, model()->GetDefaultSearchProvider());
+  // Do not override DES for keyword search because `safe_for_autoreplace` is
+  // false.
+  AssertEquals(*user_dse, *model()->GetTemplateURLForKeyword(kKeywordU16));
+
+  // Reset the policy.
+  SetManagedSiteSearchSettingsPreference(
+      EnterpriseSiteSearchManager::OwnedTemplateURLDataVector(),
+      test_util()->profile());
+
+  // No changes to the DSE once the policy is no longer applied.
+  EXPECT_EQ(user_dse, model()->GetDefaultSearchProvider());
+  AssertEquals(user_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+}
+
+TEST_P(TemplateURLServiceTest,
+       NonFeaturedSiteSearchPolicyConflictWithDSPSetByExtension) {
+  constexpr char kKeyword[] = "keyword";
+  constexpr char16_t kKeywordU16[] = u"keyword";
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(omnibox::kSiteSearchSettingsPolicy);
+
+  // Reset the model to ensure an `EnterpriseSiteSearchManager` instance is
+  // created (it depends on `kSiteSearchSettingsPolicy` being enabled).
+  test_util()->ResetModel(/*verify_load=*/true);
+
+  TemplateURL* extension_dse =
+      AddExtensionSearchEngine(kKeyword, "extension_id", true);
+  EXPECT_EQ(extension_dse, model()->GetDefaultSearchProvider());
+  AssertEquals(extension_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+
+  // Set a managed preference that establishes a site search provider
+  // conflicting with default search engine set by extension.
+  EnterpriseSiteSearchManager::OwnedTemplateURLDataVector site_search_engines;
+  site_search_engines.push_back(CreateTestSiteSearchEntry(kKeyword));
+
+  SetManagedSiteSearchSettingsPreference(site_search_engines,
+                                         test_util()->profile());
+
+  // Expect no change in default search engine.
+  EXPECT_EQ(extension_dse, model()->GetDefaultSearchProvider());
+  // Do not override DSE for keyword search because `safe_for_autoreplace` is
+  // false.
+  AssertEquals(extension_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+
+  // Reset the policy.
+  SetManagedSiteSearchSettingsPreference(
+      EnterpriseSiteSearchManager::OwnedTemplateURLDataVector(),
+      test_util()->profile());
+
+  // No changes to the DSE once the policy is no longer applied.
+  EXPECT_EQ(extension_dse, model()->GetDefaultSearchProvider());
+  AssertEquals(extension_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+}
+
+TEST_P(TemplateURLServiceTest,
+       FeaturedSiteSearchPolicyConflictWithUserDefinedDSP) {
+  constexpr char kKeyword[] = "@keyword";
+  constexpr char16_t kKeywordU16[] = u"@keyword";
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(omnibox::kSiteSearchSettingsPolicy);
+
+  // Reset the model to ensure an `EnterpriseSiteSearchManager` instance is
+  // created (it depends on `kSiteSearchSettingsPolicy` being enabled).
+  test_util()->ResetModel(/*verify_load=*/true);
+
+  TemplateURL* user_dse = AddKeywordWithDate(
+      "DSE name", kKeyword, "http://www.goo.com/s?q={searchTerms}",
+      std::string(), std::string(), std::string(),
+      /*safe_for_autoreplace=*/false);
+  model()->SetUserSelectedDefaultSearchProvider(user_dse);
+  EXPECT_EQ(user_dse, model()->GetDefaultSearchProvider());
+  AssertEquals(user_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+
+  // Set a managed preference that establishes a site search provider
+  // conflicting with user-defined default search engine.
+  EnterpriseSiteSearchManager::OwnedTemplateURLDataVector site_search_engines;
+  site_search_engines.push_back(
+      CreateTestSiteSearchEntry(kKeyword, /*featured_by_policy=*/true));
+
+  SetManagedSiteSearchSettingsPreference(site_search_engines,
+                                         test_util()->profile());
+
+  // Expect no change in default search engine.
+  EXPECT_EQ(user_dse, model()->GetDefaultSearchProvider());
+  // Override DES for keyword search because the site search engine is featured.
+  ExpectSimilar(site_search_engines[0].get(),
+                &model()->GetTemplateURLForKeyword(kKeywordU16)->data());
+
+  // Reset the policy.
+  SetManagedSiteSearchSettingsPreference(
+      EnterpriseSiteSearchManager::OwnedTemplateURLDataVector(),
+      test_util()->profile());
+
+  // No changes to the DSE once the policy is no longer applied.
+  EXPECT_EQ(user_dse, model()->GetDefaultSearchProvider());
+  AssertEquals(user_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+}
+
+TEST_P(TemplateURLServiceTest,
+       FeaturedSiteSearchPolicyConflictWithDSPSetByExtension) {
+  constexpr char kKeyword[] = "@keyword";
+  constexpr char16_t kKeywordU16[] = u"@keyword";
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(omnibox::kSiteSearchSettingsPolicy);
+
+  // Reset the model to ensure an `EnterpriseSiteSearchManager` instance is
+  // created (it depends on `kSiteSearchSettingsPolicy` being enabled).
+  test_util()->ResetModel(/*verify_load=*/true);
+
+  TemplateURL* extension_dse =
+      AddExtensionSearchEngine(kKeyword, "extension_id", true);
+  EXPECT_EQ(extension_dse, model()->GetDefaultSearchProvider());
+  AssertEquals(extension_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+
+  // Set a managed preference that establishes a site search provider
+  // conflicting with default search engine set by extension.
+  EnterpriseSiteSearchManager::OwnedTemplateURLDataVector site_search_engines;
+  site_search_engines.push_back(
+      CreateTestSiteSearchEntry(kKeyword, /*featured_by_policy=*/true));
+
+  SetManagedSiteSearchSettingsPreference(site_search_engines,
+                                         test_util()->profile());
+
+  // Expect no change in default search engine.
+  EXPECT_EQ(extension_dse, model()->GetDefaultSearchProvider());
+  // Override DES for keyword search because the site search engine is featured.
+  ExpectSimilar(site_search_engines[0].get(),
+                &model()->GetTemplateURLForKeyword(kKeywordU16)->data());
+
+  // Reset the policy.
+  SetManagedSiteSearchSettingsPreference(
+      EnterpriseSiteSearchManager::OwnedTemplateURLDataVector(),
+      test_util()->profile());
+
+  // No changes to the DSE once the policy is no longer applied.
+  EXPECT_EQ(extension_dse, model()->GetDefaultSearchProvider());
+  AssertEquals(extension_dse, model()->GetTemplateURLForKeyword(kKeywordU16));
+}
+
+TEST_P(TemplateURLServiceTest,
+       FeaturedSiteSearchPolicyConflictWithStarterPack) {
+  constexpr char kBookmarksKeyword[] = "@bookmarks";
+  constexpr char16_t kBookmarksKeywordU16[] = u"@bookmarks";
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(omnibox::kSiteSearchSettingsPolicy);
+
+  // Reset the model to ensure an `EnterpriseSiteSearchManager` instance is
+  // created (it depends on `kSiteSearchSettingsPolicy` being enabled).
+  test_util()->ResetModel(/*verify_load=*/true);
+
+  const TemplateURL* bookmarks_entry =
+      model()->GetTemplateURLForKeyword(kBookmarksKeywordU16);
+  ASSERT_TRUE(bookmarks_entry);
+
+  // Set a managed preference that establishes a site search provider
+  // conflicting with pre-defined default search engine not customized by the
+  // user.
+  EnterpriseSiteSearchManager::OwnedTemplateURLDataVector site_search_engines;
+  site_search_engines.push_back(CreateTestSiteSearchEntry(
+      kBookmarksKeyword, /*featured_by_policy=*/true));
+
+  SetManagedSiteSearchSettingsPreference(site_search_engines,
+                                         test_util()->profile());
+
+  // Override bookmarks for keyword search because the site search engine is
+  // featured.
+  ExpectSimilar(
+      site_search_engines[0].get(),
+      &model()->GetTemplateURLForKeyword(kBookmarksKeywordU16)->data());
+
+  // Reset the policy.
+  SetManagedSiteSearchSettingsPreference(
+      EnterpriseSiteSearchManager::OwnedTemplateURLDataVector(),
+      test_util()->profile());
+
+  // Go back to the original bookmarks search once the policy is no longer
+  // applied.
+  AssertEquals(bookmarks_entry,
+               model()->GetTemplateURLForKeyword(kBookmarksKeywordU16));
+}
+
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) ||
         // BUILDFLAG(IS_CHROMEOS_ASH)
 
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java
index c1a9fa8..47ffe4d4 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabData.java
@@ -481,6 +481,30 @@
         }
     }
 
+    /**
+     * Acquire {@link ShoppingPersistedTabData} for a {@link Tab}, with an option to skip delayed
+     * initialization and initialize immediately.
+     * @param tab {@link Tab} ShoppingPersistedTabData is acquired for
+     * @param callback {@link Callback} receiving the Tab's {@link ShoppingPersistedTabData}
+     * @param skipDelayedInit whether to skip the delayed initialization of {@link
+     * ShoppingPersistedTabData} and initialize immediately
+     * The result in the callback wil be null for a:
+     * - Custom Tab
+     * - Incognito Tab
+     * - Tab greater than 90 days old
+     * - Tab with a non-shopping related page currently navigated to
+     * - Tab with a shopping related page for which no shopping related data was found
+     * - Uninitialized Tab
+     */
+    static void from(
+            Tab tab, Callback<ShoppingPersistedTabData> callback, boolean skipDelayedInit) {
+        if (skipDelayedInit) {
+            fromWithoutDelayedInit(tab, callback);
+        } else {
+            from(tab, callback);
+        }
+    }
+
     private static void fromWithoutDelayedInit(
             Tab tab, Callback<ShoppingPersistedTabData> callback) {
         // Shopping related data is not available for incognito or Custom Tabs. For example,
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataService.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataService.java
index 702e67b..9d2b2b4f 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataService.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/ShoppingPersistedTabDataService.java
@@ -12,6 +12,7 @@
 import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.shared_preferences.SharedPreferencesManager;
+import org.chromium.chrome.browser.flags.BooleanCachedFieldTrialParameter;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
 import org.chromium.chrome.browser.profiles.Profile;
@@ -31,6 +32,12 @@
  * refactor that will move it out of current folder.
  */
 public class ShoppingPersistedTabDataService {
+    private static final BooleanCachedFieldTrialParameter
+            SKIP_SHOPPING_PERSISTED_TAB_DATA_DELAYED_INITIALIZATION =
+                    new BooleanCachedFieldTrialParameter(
+                            ChromeFeatureList.PRICE_CHANGE_MODULE,
+                            "skip_shopping_persisted_tab_data_delayed_initialization",
+                            true);
     private static ProfileKeyedMap<ShoppingPersistedTabDataService> sProfileToPriceDropService;
     private static ShoppingPersistedTabDataService sServiceForTesting;
 
@@ -182,7 +189,8 @@
                         if (counter.incrementAndGet() == currentTabsWithPriceDrop.size()) {
                             callback.onResult(sortShoppingPersistedTabDataWithPriceDrops(results));
                         }
-                    });
+                    },
+                    SKIP_SHOPPING_PERSISTED_TAB_DATA_DELAYED_INITIALIZATION.getValue());
         }
     }
 
diff --git a/chrome/browser/tpcd/experiment/eligibility_service_factory.cc b/chrome/browser/tpcd/experiment/eligibility_service_factory.cc
index 1e10e5e..de14e8c 100644
--- a/chrome/browser/tpcd/experiment/eligibility_service_factory.cc
+++ b/chrome/browser/tpcd/experiment/eligibility_service_factory.cc
@@ -33,6 +33,7 @@
           ProfileSelections::Builder()
               .WithRegular(ProfileSelection::kOwnInstance)
               .WithGuest(ProfileSelection::kOwnInstance)
+              .WithAshInternals(ProfileSelection::kNone)
               .Build()) {
   DependsOn(TrackingProtectionOnboardingFactory::GetInstance());
 }
diff --git a/chrome/browser/tpcd/experiment/experiment_manager_impl_browsertest.cc b/chrome/browser/tpcd/experiment/experiment_manager_impl_browsertest.cc
index ab281bf..6cab4310 100644
--- a/chrome/browser/tpcd/experiment/experiment_manager_impl_browsertest.cc
+++ b/chrome/browser/tpcd/experiment/experiment_manager_impl_browsertest.cc
@@ -57,24 +57,13 @@
 
 class ExperimentManagerImplBrowserTest : public InProcessBrowserTest {
  public:
-  ExperimentManagerImplBrowserTest(bool force_profiles_eligible_chromeos,
-                                   std::string group_name_override,
+  ExperimentManagerImplBrowserTest(std::string group_name_override,
                                    bool disable_3pcs,
                                    bool need_onboarding,
                                    bool enable_silent_onboarding = false) {
-    // Force profile eligibility on ChromeOS. There is a flaky issue where
-    // `SetClientEligibility` is sometimes called twice, the second time with an
-    // ineligible profile even if the first was eligible.
-#if !BUILDFLAG(IS_CHROMEOS)
-    force_profiles_eligible_chromeos = false;
-#endif  // !BUILDFLAG(IS_ANDROID)
-    std::string force_profiles_eligible_str =
-        force_profiles_eligible_chromeos ? "true" : "false";
-
     feature_list_.InitAndEnableFeatureWithParameters(
         features::kCookieDeprecationFacilitatedTesting,
         {{"label", kEligibleGroupName},
-         {"force_profiles_eligible", force_profiles_eligible_str},
          {"synthetic_trial_group_override", group_name_override},
          {kDisable3PCookiesName, disable_3pcs ? "true" : "false"},
          {kNeedOnboardingForSyntheticTrialName,
@@ -117,12 +106,10 @@
       public testing::WithParamInterface<SyntheticTrialTestCase> {
  public:
   ExperimentManagerImplSyntheticTrialTest()
-      : ExperimentManagerImplBrowserTest(
-            /*force_profiles_eligible_chromeos=*/GetParam().new_state_eligible,
-            GetParam().group_name_override,
-            GetParam().disable_3pcs,
-            GetParam().need_onboarding,
-            GetParam().enable_silent_onboarding) {}
+      : ExperimentManagerImplBrowserTest(GetParam().group_name_override,
+                                         GetParam().disable_3pcs,
+                                         GetParam().need_onboarding,
+                                         GetParam().enable_silent_onboarding) {}
 };
 
 IN_PROC_BROWSER_TEST_P(ExperimentManagerImplSyntheticTrialTest,
@@ -283,7 +270,6 @@
  public:
   ExperimentManagerImplDisable3PCsSyntheticTrialTest()
       : ExperimentManagerImplBrowserTest(
-            /*force_profiles_eligible_chromeos=*/false,
             /*group_name_override=*/"",
             /*disable_3pcs=*/true,
             /*need_onboarding=*/true) {}
@@ -322,7 +308,6 @@
  public:
   ExperimentManagerImplSilentOnboardingSyntheticTrialTest()
       : ExperimentManagerImplBrowserTest(
-            /*force_profiles_eligible_chromeos=*/false,
             /*group_name_override=*/"",
             /*disable_3pcs=*/false,
             /*need_onboarding=*/true,
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 50418a2..15e497e 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2562,6 +2562,10 @@
       "ash/auth/legacy_fingerprint_engine.h",
       "ash/back_gesture_contextual_nudge_delegate.cc",
       "ash/back_gesture_contextual_nudge_delegate.h",
+      "ash/birch/birch_keyed_service.cc",
+      "ash/birch/birch_keyed_service.h",
+      "ash/birch/birch_keyed_service_factory.cc",
+      "ash/birch/birch_keyed_service_factory.h",
       "ash/browser_data_migration_error_dialog.cc",
       "ash/browser_data_migration_error_dialog.h",
       "ash/calendar/calendar_client_impl.cc",
@@ -4823,6 +4827,8 @@
       "commerce/commerce_page_action_controller.h",
       "commerce/commerce_ui_tab_helper.cc",
       "commerce/commerce_ui_tab_helper.h",
+      "commerce/price_tracking_page_action_controller.cc",
+      "commerce/price_tracking_page_action_controller.h",
       "dialogs/outdated_upgrade_bubble.cc",
       "dialogs/outdated_upgrade_bubble.h",
       "plus_addresses/plus_address_creation_controller_desktop.h",
@@ -6054,6 +6060,7 @@
       "views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.h",
       "views/web_apps/isolated_web_apps/isolated_web_app_installer_view_impl.cc",
       "views/web_apps/isolated_web_apps/isolated_web_app_installer_view_impl.h",
+      "views/web_apps/isolated_web_apps/pref_observer.h",
       "views/web_apps/launch_app_user_choice_dialog_view.cc",
       "views/web_apps/launch_app_user_choice_dialog_view.h",
       "views/web_apps/protocol_handler_launch_dialog_view.cc",
@@ -6349,6 +6356,7 @@
         "views/screen_capture_notification_ui_views.cc",
         "views/sharing_hub/sharing_hub_bubble_view_impl.cc",
         "views/sharing_hub/sharing_hub_bubble_view_impl.h",
+        "views/web_apps/isolated_web_apps/pref_observer.cc",
       ]
       deps += [ "//ui/views/window/vector_icons" ]
     }
@@ -6357,10 +6365,15 @@
       sources += [
         "views/apps/app_dialog/shortcut_removal_dialog_view.cc",
         "views/apps/app_dialog/shortcut_removal_dialog_view.h",
+        "views/web_apps/isolated_web_apps/pref_observer_ash.cc",
       ]
       deps += [ "//components/metrics/debug/structured:debug" ]
     }
 
+    if (is_chromeos_lacros) {
+      sources += [ "views/web_apps/isolated_web_apps/pref_observer_lacros.cc" ]
+    }
+
     if (enable_screen_ai_service) {
       deps += [
         "//chrome/browser/screen_ai:screen_ai_install_state",
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
index 30f69ea..7c5c5b7 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -8,7 +8,6 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.graphics.Typeface;
 import android.view.ActionMode;
 import android.view.View;
 
@@ -815,26 +814,25 @@
     }
 
     /**
-     * @see LocationBarMediator#setUrlBarHintTextColor(boolean)
+     * @see LocationBarMediator#updateUrlBarHintTextColor(boolean)
      */
-    public void setUrlBarHintTextColor(boolean isStartOrNtp) {
-        mLocationBarMediator.setUrlBarHintTextColor(isStartOrNtp);
+    public void updateUrlBarHintTextColor(boolean useDefaultUrlBarHintTextColor) {
+        mLocationBarMediator.updateUrlBarHintTextColor(useDefaultUrlBarHintTextColor);
     }
 
     /**
-     * @see LocationBarMediator#setUrlBarTypeface(Typeface)
+     * @see LocationBarMediator#updateUrlBarTypeface(boolean)
      */
-    public void setUrlBarTypeface(Typeface typeface) {
-        mLocationBarMediator.setUrlBarTypeface(typeface);
+    public void updateUrlBarTypeface(boolean useDefaultUrlBarTypeface) {
+        mLocationBarMediator.updateUrlBarTypeface(useDefaultUrlBarTypeface);
     }
 
     /**
-     * Updates the value for the end margin of the url action container in the search box.
-     *
-     * @param endMargin The end margin for the url action container in the search box.
+     * @see LocationBarMediator#updateUrlActionContainerEndMargin(boolean)
      */
-    public void updateUrlActionContainerEndMargin(int endMargin) {
-        mLocationBarMediator.updateUrlActionContainerEndMargin(endMargin);
+    public void updateUrlActionContainerEndMargin(boolean useDefaultUrlActionContainerEndMargin) {
+        mLocationBarMediator.updateUrlActionContainerEndMargin(
+                useDefaultUrlActionContainerEndMargin);
     }
 
     public int getUrlActionContainerEndMarginForTesting() {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
index 4b36079..9dd5f41 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarLayout.java
@@ -559,7 +559,7 @@
         boolean isInSingleUrlBarMode =
                 isNtpOnPhone
                         && mSearchEngineUtils != null
-                        && mSearchEngineUtils.isDefaultSearchEngineGoogle();
+                        && mSearchEngineUtils.doesDefaultSearchEngineHaveLogo();
         if (mIsSurfacePolishEnabled && isInSingleUrlBarMode) {
             translationX +=
                     (getResources().getDimensionPixelSize(R.dimen.fake_search_box_start_padding)
@@ -602,10 +602,18 @@
     /**
      * Updates the value for the end margin of the url action container in the search box.
      *
-     * @param endMargin The end margin for the url action container in the search box.
+     * @param useDefaultUrlActionContainerEndMargin Whether to use the default end margin for the
+     *     url action container in the search box. If not we will use the specific end margin value
+     *     for surface polish.
      */
-    public void updateUrlActionContainerEndMargin(int endMargin) {
-        mUrlActionContainerEndMargin = endMargin;
+    public void updateUrlActionContainerEndMargin(boolean useDefaultUrlActionContainerEndMargin) {
+        mUrlActionContainerEndMargin =
+                useDefaultUrlActionContainerEndMargin
+                        ? getResources()
+                                .getDimensionPixelSize(R.dimen.location_bar_url_action_offset)
+                        : getResources()
+                                .getDimensionPixelSize(
+                                        R.dimen.location_bar_url_action_offset_polish);
     }
 
     int getUrlActionContainerEndMarginForTesting() {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
index 74ea094..d6330f3 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -13,7 +13,6 @@
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.graphics.Typeface;
 import android.text.TextUtils;
 import android.util.FloatProperty;
 import android.view.KeyEvent;
@@ -1595,34 +1594,38 @@
     }
 
     /**
-     * Sets the search box hint text color, depending on whether or not the the current page is
-     * Start Surface or NTP.
+     * Updates the color of the hint text in the search box.
      *
-     * @param isStartOrNtp Whether the current page is Start Surface or NTP. If true, then a
-     *     colorful theme may be applied.
+     * @param useDefaultUrlBarHintTextColor Whether to use the default color for the search text in
+     *     the search box. If not we will use specific color for surface polish.
      */
-    public void setUrlBarHintTextColor(boolean isStartOrNtp) {
-        if (isStartOrNtp) {
+    public void updateUrlBarHintTextColor(boolean useDefaultUrlBarHintTextColor) {
+        if (useDefaultUrlBarHintTextColor) {
+            mUrlCoordinator.setUrlBarHintTextColorForDefault(mBrandedColorScheme);
+        } else {
             mUrlCoordinator.setUrlBarHintTextColorForSurfacePolish(
                     mIsSurfacePolishOmniboxColorEnabled);
-        } else {
-            mUrlCoordinator.setUrlBarHintTextColorForDefault(mBrandedColorScheme);
         }
     }
 
     /**
-     * Sets the typeface and style of the search text in the search box.
+     * Updates the typeface and style of the search text in the search box.
      *
-     * @param typeface The typeface for the search text in the search box.
+     * @param useDefaultUrlBarTypeface Whether to use the default typeface for the search text in
+     *     the search box. If not we will use medium Google sans typeface for surface polish.
      */
-    public void setUrlBarTypeface(Typeface typeface) {
-        mUrlCoordinator.setUrlBarTypeface(typeface);
+    public void updateUrlBarTypeface(boolean useDefaultUrlBarTypeface) {
+        mUrlCoordinator.updateUrlBarTypeface(useDefaultUrlBarTypeface);
     }
 
     /**
-     * @see LocationBarCoordinator#updateUrlActionContainerEndMargin(int)
+     * Updates the value for the end margin of the url action container in the search box.
+     *
+     * @param useDefaultUrlActionContainerEndMargin Whether to use the default end margin for the
+     *     url action container in the search box. If not we will use the specific end margin value
+     *     for surface polish.
      */
-    public void updateUrlActionContainerEndMargin(int endMargin) {
-        mLocationBarLayout.updateUrlActionContainerEndMargin(endMargin);
+    public void updateUrlActionContainerEndMargin(boolean useDefaultUrlActionContainerEndMargin) {
+        mLocationBarLayout.updateUrlActionContainerEndMargin(useDefaultUrlActionContainerEndMargin);
     }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtils.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtils.java
index 1f6e25d9..f2cdb08 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtils.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/SearchEngineUtils.java
@@ -43,7 +43,7 @@
     private final @NonNull FaviconHelper mFaviconHelper;
     private final int mSearchEngineLogoTargetSizePixels;
     private Boolean mNeedToCheckForSearchEnginePromo;
-    private boolean mDefaultSearchEngineIsGoogle;
+    private boolean mDoesDefaultSearchEngineHaveLogo;
     private @Nullable StatusIconResource mSearchEngineLogo;
 
     /**
@@ -105,9 +105,9 @@
 
     @Override
     public void onTemplateURLServiceChanged() {
-        mDefaultSearchEngineIsGoogle = mTemplateUrlService.isDefaultSearchEngineGoogle();
+        mDoesDefaultSearchEngineHaveLogo = mTemplateUrlService.doesDefaultSearchEngineHaveLogo();
 
-        if (mDefaultSearchEngineIsGoogle) {
+        if (mTemplateUrlService.isDefaultSearchEngineGoogle()) {
             mSearchEngineLogo = new StatusIconResource(R.drawable.ic_logo_googleg_20dp, 0);
         } else {
             mSearchEngineLogo = null;
@@ -227,9 +227,9 @@
     }
 
     /*
-     * Returns whether the current search provider is Google.
+     * Returns whether the current search provider has Logo.
      */
-    boolean isDefaultSearchEngineGoogle() {
-        return mDefaultSearchEngineIsGoogle;
+    boolean doesDefaultSearchEngineHaveLogo() {
+        return mDoesDefaultSearchEngineHaveLogo;
     }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
index 90fcee3..a48477b 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarCoordinator.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.omnibox;
 
 import android.content.Context;
-import android.graphics.Typeface;
 import android.view.ActionMode;
 import android.view.inputmethod.InputMethodManager;
 
@@ -329,9 +328,9 @@
     }
 
     /**
-     * @see UrlBarMediator#setUrlBarTypeface(Typeface)
+     * @see UrlBarMediator#updateUrlBarTypeface(boolean)
      */
-    public void setUrlBarTypeface(Typeface typeface) {
-        mMediator.setUrlBarTypeface(typeface);
+    public void updateUrlBarTypeface(boolean useDefaultUrlBarTypeface) {
+        mMediator.updateUrlBarTypeface(useDefaultUrlBarTypeface);
     }
 }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
index 2b794f1..b22285b 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarMediator.java
@@ -400,11 +400,17 @@
     }
 
     /**
-     * Sets the typeface and style of the search text in the search box.
+     * Updates the typeface and style of the search text in the search box.
      *
-     * @param typeface The typeface for the search text in the search box.
+     * @param useDefaultUrlBarTypeface Whether to use the default typeface for the search text in
+     *     the search box. If not we will use medium Google sans typeface for surface polish.
      */
-    void setUrlBarTypeface(Typeface typeface) {
+    void updateUrlBarTypeface(boolean useDefaultUrlBarTypeface) {
+        // TODO(crbug.com/1487760): Use TextAppearance style instead.
+        Typeface typeface =
+                useDefaultUrlBarTypeface
+                        ? Typeface.defaultFromStyle(Typeface.NORMAL)
+                        : Typeface.create("google-sans-medium", Typeface.NORMAL);
         mModel.set(UrlBarProperties.TYPEFACE, typeface);
     }
 }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuCoordinatorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuCoordinatorTest.java
index 63eb5d0..8f585ca 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuCoordinatorTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveButtonActionMenuCoordinatorTest.java
@@ -32,7 +32,6 @@
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.toolbar.R;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.ui.listmenu.ListMenuButton;
 import org.chromium.ui.widget.AnchoredPopupWindow;
@@ -55,7 +54,6 @@
 
     @Test
     @SmallTest
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
     public void testCreateOnLongClickListener() {
         AdaptiveButtonActionMenuCoordinator coordinator = new AdaptiveButtonActionMenuCoordinator();
@@ -82,7 +80,6 @@
 
     @Test
     @SmallTest
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
     public void testCreateOnLongClickListener_clickHandlerIsNotModified() {
         AdaptiveButtonActionMenuCoordinator coordinator = new AdaptiveButtonActionMenuCoordinator();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonControllerTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonControllerTest.java
index 9650500..5ec96c1 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonControllerTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarButtonControllerTest.java
@@ -53,7 +53,6 @@
 import org.chromium.chrome.browser.toolbar.R;
 import org.chromium.chrome.browser.toolbar.adaptive.settings.AdaptiveToolbarSettingsFragment;
 import org.chromium.chrome.test.util.browser.Features;
-import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
 import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.browser_ui.settings.SettingsLauncher;
 import org.chromium.ui.permissions.AndroidPermissionDelegate;
@@ -107,7 +106,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testCustomization_newTab() {
         AdaptiveToolbarPrefs.saveToolbarSettingsToggleState(true);
         AdaptiveToolbarStatePredictor.setSegmentationResultsForTesting(
@@ -130,7 +128,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testCustomization_share() {
         AdaptiveToolbarPrefs.saveToolbarSettingsToggleState(true);
         AdaptiveToolbarStatePredictor.setSegmentationResultsForTesting(
@@ -153,7 +150,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testCustomization_voice() {
         AdaptiveToolbarPrefs.saveToolbarSettingsToggleState(true);
         AdaptiveToolbarStatePredictor.setSegmentationResultsForTesting(
@@ -176,7 +172,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testCustomization_prefChangeTriggersButtonChange() {
         AdaptiveToolbarPrefs.saveToolbarSettingsToggleState(true);
         AdaptiveToolbarStatePredictor.setSegmentationResultsForTesting(
@@ -209,7 +204,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testLongPress() {
         AdaptiveToolbarPrefs.saveToolbarSettingsToggleState(true);
         AdaptiveToolbarStatePredictor.setSegmentationResultsForTesting(
@@ -262,7 +256,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testShowDynamicAction() {
         Activity activity = Robolectric.setupActivity(Activity.class);
         SettingsLauncher settingsLauncher = mock(SettingsLauncher.class);
@@ -327,7 +320,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testButtonOnLargeScreens() {
         // Screen is wide enough to fit the button, it should appear.
         mConfiguration.screenWidthDp = 450;
@@ -356,7 +348,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testButtonNotShownOnSmallScreens() {
         // Screen too narrow, button shouldn't appear.
         mConfiguration.screenWidthDp = 320;
@@ -385,7 +376,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testButtonVisibilityChangeOnConfigurationChange() {
         // Screen too narrow, button shouldn't appear.
         mConfiguration.screenWidthDp = 320;
@@ -423,7 +413,6 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
-    @DisableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR)
     public void testConfigurationChangeIgnoredWhenNativeNotReady() {
         AdaptiveToolbarPrefs.saveToolbarSettingsToggleState(true);
         AdaptiveToolbarStatePredictor.setSegmentationResultsForTesting(
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictorTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictorTest.java
index 2e6b77b..f72670e 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictorTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/AdaptiveToolbarStatePredictorTest.java
@@ -33,9 +33,6 @@
 /** Unit tests for the {@code AdaptiveToolbarStatePredictor} */
 @Config(manifest = Config.NONE)
 @RunWith(BaseRobolectricTestRunner.class)
-@DisableFeatures({
-    ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR,
-})
 @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
 public class AdaptiveToolbarStatePredictorTest {
     @Rule public TestRule mProcessor = new Features.JUnitProcessor();
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarSettingsFragmentTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarSettingsFragmentTest.java
index bf2e722..3323f8c 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarSettingsFragmentTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/adaptive/settings/AdaptiveToolbarSettingsFragmentTest.java
@@ -56,7 +56,6 @@
 @Config(manifest = Config.NONE)
 @EnableFeatures(ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_CUSTOMIZATION_V2)
 @DisableFeatures({
-    ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR,
     ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_TRANSLATE,
     ChromeFeatureList.ADAPTIVE_BUTTON_IN_TOP_TOOLBAR_ADD_TO_BOOKMARKS,
     ChromeFeatureList.READALOUD
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
index 64ab84eb..91531f1 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarPhone.java
@@ -17,7 +17,6 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
@@ -2139,7 +2138,7 @@
                     updateToNtpBackground();
                 }
             } else {
-                // Update the location bar background when entering the search results page or other
+                // Update the location bar background when entering the focus state or other
                 // non-NTP tabs from the Start Surface.
                 mActiveLocationBarBackground = mLocationBarBackground;
             }
@@ -2682,15 +2681,15 @@
 
     /**
      * Update the appearance (logo background, search text's color and style) of the location bar
-     * based on the state of the current page.
-     * For Start Surface and NTP, while not on the search results page, the real search box's search
-     * text has a particular color and style. The style would be "google-sans-medium" and the color
-     * would be colorOnSurface or colorOnPrimaryContainer based on whether the variant
-     * SURFACE_POLISH_OMNIBOX_COLOR is enabled or not. When being in light mode, there is also a
-     * round white background for the G logo. For other situations such as browser tabs, and search
-     * result pages, the real search box will stay the same.
+     * based on the state of the current page. For Start Surface and NTP, while not on the focus
+     * state, the real search box's search text has a particular color and style. The style would be
+     * "google-sans-medium" and the color would be colorOnSurface or colorOnPrimaryContainer based
+     * on whether the variant SURFACE_POLISH_OMNIBOX_COLOR is enabled or not. When being in light
+     * mode, there is also a round white background for the G logo. For other situations such as
+     * browser tabs, and search result pages, the real search box will stay the same.
+     *
      * @param visualState The Visual State of the current page.
-     * @param hasFocus True if the current page is search results page.
+     * @param hasFocus True if the current page is the focus state.
      */
     @VisibleForTesting
     void updateLocationBarForSurfacePolish(@VisualState int visualState, boolean hasFocus) {
@@ -2700,42 +2699,54 @@
 
         // Detect whether state has changed and update only when that happens.
         boolean prevIsStartOrNtpWithSurfacePolish = mIsStartOrNtpWithSurfacePolish;
-        // Check whether the current page is NTP (the search results page is not included) and the
+
+        // Check whether the current page is NTP (the focus state is not included) and the
         // real omnibox is pinned on the top of the screen. We need to make sure the real omnibox is
         // visible here to forbid the situation that the background of the G logo shows and then
-        // vanishes during the un-focus animation(from search results page to NTP). This situation
+        // vanishes during the un-focus animation(from focus state to NTP). This situation
         // won't happen in Start Surface, so we don't need to check the scroll fraction for Start
         // Surface.
         boolean isNtpShowingWithRealOmnibox =
                 visualState == VisualState.NEW_TAB_NORMAL && mNtpSearchBoxScrollFraction > 0;
         mIsStartOrNtpWithSurfacePolish =
                 (isNtpShowingWithRealOmnibox || mIsShowingStartSurfaceHomepage) && !hasFocus;
+
         if (mIsStartOrNtpWithSurfacePolish == prevIsStartOrNtpWithSurfacePolish) {
             return;
         }
 
-        // TODO(crbug.com/1487760): Use TextAppearance style instead.
-        Typeface typeface;
-        int urlActionContainerEndMargin;
         if (mIsStartOrNtpWithSurfacePolish) {
-            boolean isNightMode = ColorUtils.inNightMode(getContext());
-            mLocationBar.setStatusIconBackgroundVisibility(!isNightMode);
-            typeface = Typeface.create("google-sans-medium", Typeface.NORMAL);
-            urlActionContainerEndMargin =
-                    getResources()
-                            .getDimensionPixelOffset(R.dimen.location_bar_url_action_offset_polish);
+            updateLocationBarForSurfacePolishImpl(
+                    !ColorUtils.inNightMode(getContext()),
+                    /* useDefaultUrlBarAndUrlActionContainerAppearance= */ false);
         } else {
             // Restore the appearance of the real search box when transitioning from Start Surface
-            // or NTP to the search results page.
-            mLocationBar.setStatusIconBackgroundVisibility(false);
-            typeface = Typeface.defaultFromStyle(Typeface.NORMAL);
-            urlActionContainerEndMargin =
-                    getResources().getDimensionPixelOffset(R.dimen.location_bar_url_action_offset);
+            // or NTP to other pages.
+            updateLocationBarForSurfacePolishImpl(
+                    /* statusIconBackgroundVisibility= */ false,
+                    /* useDefaultUrlBarAndUrlActionContainerAppearance= */ true);
         }
-        mLocationBar.setUrlBarTypeface(typeface);
-        mLocationBar.setUrlBarHintTextColor(mIsStartOrNtpWithSurfacePolish);
+    }
+
+    /**
+     * Update the appearance (logo background, search text's color and style) of the location bar
+     * based on the value provided.
+     *
+     * @param statusIconBackgroundVisibility The visibility of the status icon background.
+     * @param useDefaultUrlBarAndUrlActionContainerAppearance Whether to use the default typeface
+     *     and color for the search text in the search box and use the default end margin for the
+     *     url action container in the search box. If not we will use specific settings for surface
+     *     polish.
+     */
+    private void updateLocationBarForSurfacePolishImpl(
+            boolean statusIconBackgroundVisibility,
+            boolean useDefaultUrlBarAndUrlActionContainerAppearance) {
+        mLocationBar.setStatusIconBackgroundVisibility(statusIconBackgroundVisibility);
+        mLocationBar.updateUrlBarTypeface(useDefaultUrlBarAndUrlActionContainerAppearance);
+        mLocationBar.updateUrlBarHintTextColor(useDefaultUrlBarAndUrlActionContainerAppearance);
+        mLocationBar.updateUrlActionContainerEndMargin(
+                useDefaultUrlBarAndUrlActionContainerAppearance);
         mLocationBar.updateButtonTints();
-        mLocationBar.updateUrlActionContainerEndMargin(urlActionContainerEndMargin);
     }
 
     private void updateVisualsForLocationBarState() {
diff --git a/chrome/browser/ui/ash/birch/OWNERS b/chrome/browser/ui/ash/birch/OWNERS
new file mode 100644
index 0000000..0ade3fd
--- /dev/null
+++ b/chrome/browser/ui/ash/birch/OWNERS
@@ -0,0 +1,2 @@
+mmourgos@chromium.org
+tbarzic@chromium.org
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/birch/birch_keyed_service.cc b/chrome/browser/ui/ash/birch/birch_keyed_service.cc
new file mode 100644
index 0000000..a413200f
--- /dev/null
+++ b/chrome/browser/ui/ash/birch/birch_keyed_service.cc
@@ -0,0 +1,89 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/birch/birch_keyed_service.h"
+
+#include "base/files/file.h"
+#include "chrome/browser/ash/file_suggest/file_suggest_keyed_service_factory.h"
+#include "chrome/browser/ash/file_suggest/file_suggest_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace ash {
+
+namespace {
+
+// Gets a list of `BirchFileSuggestion` given a list of `FileSuggestData`.
+// Performs some asynchronous file IO, so should not be run on the UI thread.
+std::vector<BirchFileSuggestion> GetFileSuggestionInfo(
+    std::vector<FileSuggestData> file_suggestions) {
+  std::vector<BirchFileSuggestion> results;
+  for (const auto& suggestion : file_suggestions) {
+    base::File::Info info;
+    if (base::GetFileInfo(suggestion.file_path, &info)) {
+      results.emplace_back(suggestion.file_path, info.last_modified,
+                           info.last_accessed);
+    }
+  }
+  return results;
+}
+
+}  // namespace
+
+BirchFileSuggestion::BirchFileSuggestion(
+    base::FilePath new_file_path,
+    const absl::optional<base::Time> new_last_modified,
+    const absl::optional<base::Time> new_last_accessed)
+    : file_path(new_file_path),
+      last_modified(new_last_modified),
+      last_accessed(new_last_accessed) {}
+
+BirchFileSuggestion::BirchFileSuggestion(BirchFileSuggestion&&) = default;
+
+BirchFileSuggestion::BirchFileSuggestion(const BirchFileSuggestion&) = default;
+
+BirchFileSuggestion& BirchFileSuggestion::operator=(
+    const BirchFileSuggestion&) = default;
+
+BirchFileSuggestion::~BirchFileSuggestion() = default;
+
+BirchKeyedService::BirchKeyedService(Profile* profile)
+    : file_suggest_service_(
+          FileSuggestKeyedServiceFactory::GetInstance()->GetService(profile)) {
+  file_suggest_service_observation_.Observe(file_suggest_service_);
+}
+
+BirchKeyedService::~BirchKeyedService() = default;
+
+void BirchKeyedService::OnFileSuggestionUpdated(FileSuggestionType type) {
+  weak_factory_.InvalidateWeakPtrs();
+
+  if (type == FileSuggestionType::kDriveFile) {
+    file_suggest_service_->GetSuggestFileData(
+        type, base::BindOnce(&BirchKeyedService::OnSuggestedFileDataUpdated,
+                             weak_factory_.GetWeakPtr()));
+  }
+}
+
+void BirchKeyedService::OnSuggestedFileDataUpdated(
+    const absl::optional<std::vector<FileSuggestData>>& suggest_results) {
+  if (suggest_results) {
+    // Convert each `FileSuggestData` into a `BirchFileSuggestion`.
+    base::ThreadPool::PostTaskAndReplyWithResult(
+        FROM_HERE,
+        {base::MayBlock(), base::TaskPriority::USER_BLOCKING,
+         base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
+        base::BindOnce(&GetFileSuggestionInfo, suggest_results.value()),
+        base::BindOnce(&BirchKeyedService::OnFileInfoRetrieved,
+                       weak_factory_.GetWeakPtr()));
+  }
+}
+
+void BirchKeyedService::OnFileInfoRetrieved(
+    std::vector<BirchFileSuggestion> file_suggestions) {
+  // TODO(b/304289452): Pass `file_suggestions` to the birch model.
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/ash/birch/birch_keyed_service.h b/chrome/browser/ui/ash/birch/birch_keyed_service.h
new file mode 100644
index 0000000..81685e5f
--- /dev/null
+++ b/chrome/browser/ui/ash/birch/birch_keyed_service.h
@@ -0,0 +1,65 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ASH_BIRCH_BIRCH_KEYED_SERVICE_H_
+#define CHROME_BROWSER_UI_ASH_BIRCH_BIRCH_KEYED_SERVICE_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "base/time/time.h"
+#include "chrome/browser/ash/file_suggest/file_suggest_keyed_service.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+class Profile;
+
+namespace ash {
+
+struct BirchFileSuggestion {
+  BirchFileSuggestion(base::FilePath new_file_path,
+                      const absl::optional<base::Time> new_last_modified,
+                      const absl::optional<base::Time> new_last_accessed);
+  BirchFileSuggestion(BirchFileSuggestion&&);
+  BirchFileSuggestion(const BirchFileSuggestion&);
+  BirchFileSuggestion& operator=(const BirchFileSuggestion&);
+  ~BirchFileSuggestion();
+
+  base::FilePath file_path;
+  absl::optional<base::Time> last_modified;
+  absl::optional<base::Time> last_accessed;
+};
+
+// A keyed service which is used to manage data providers for the birch feature.
+// Fetched data will be sent to the `BirchModel` to be stored.
+class BirchKeyedService : public KeyedService,
+                          public FileSuggestKeyedService::Observer {
+ public:
+  explicit BirchKeyedService(Profile* profile);
+  BirchKeyedService(const BirchKeyedService&) = delete;
+  BirchKeyedService& operator=(const BirchKeyedService&) = delete;
+  ~BirchKeyedService() override;
+
+  // FileSuggestKeyedService::Observer:
+  void OnFileSuggestionUpdated(FileSuggestionType type) override;
+
+  void OnSuggestedFileDataUpdated(
+      const absl::optional<std::vector<FileSuggestData>>& suggest_results);
+
+ private:
+  void OnFileInfoRetrieved(std::vector<BirchFileSuggestion> file_suggestions);
+
+  const raw_ptr<FileSuggestKeyedService, ExperimentalAsh> file_suggest_service_;
+
+  base::ScopedObservation<FileSuggestKeyedService,
+                          FileSuggestKeyedService::Observer>
+      file_suggest_service_observation_{this};
+
+  base::WeakPtrFactory<BirchKeyedService> weak_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_ASH_BIRCH_BIRCH_KEYED_SERVICE_H_
diff --git a/chrome/browser/ui/ash/birch/birch_keyed_service_factory.cc b/chrome/browser/ui/ash/birch/birch_keyed_service_factory.cc
new file mode 100644
index 0000000..fbb14dc6
--- /dev/null
+++ b/chrome/browser/ui/ash/birch/birch_keyed_service_factory.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/ui/ash/birch/birch_keyed_service_factory.h"
+
+#include <memory>
+
+#include "ash/constants/ash_features.h"
+#include "ash/constants/ash_switches.h"
+#include "base/no_destructor.h"
+#include "chrome/browser/ash/file_suggest/file_suggest_keyed_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_selections.h"
+#include "chrome/browser/ui/ash/birch/birch_keyed_service.h"
+#include "content/public/browser/browser_context.h"
+
+namespace ash {
+
+// static
+BirchKeyedServiceFactory* BirchKeyedServiceFactory::GetInstance() {
+  static base::NoDestructor<BirchKeyedServiceFactory> factory;
+  return factory.get();
+}
+
+BirchKeyedServiceFactory::BirchKeyedServiceFactory()
+    : ProfileKeyedServiceFactory("BirchKeyedService",
+                                 ProfileSelections::BuildForRegularProfile()) {
+  DependsOn(FileSuggestKeyedServiceFactory::GetInstance());
+}
+
+BirchKeyedService* BirchKeyedServiceFactory::GetService(
+    content::BrowserContext* context) {
+  return static_cast<BirchKeyedService*>(
+      GetInstance()->GetServiceForBrowserContext(
+          context, /*create=*/features::IsBirchFeatureEnabled() &&
+                       switches::IsBirchSecretKeyMatched()));
+}
+
+std::unique_ptr<KeyedService>
+BirchKeyedServiceFactory::BuildServiceInstanceForBrowserContext(
+    content::BrowserContext* context) const {
+  return std::make_unique<BirchKeyedService>(
+      Profile::FromBrowserContext(context));
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/ash/birch/birch_keyed_service_factory.h b/chrome/browser/ui/ash/birch/birch_keyed_service_factory.h
new file mode 100644
index 0000000..ca545ef
--- /dev/null
+++ b/chrome/browser/ui/ash/birch/birch_keyed_service_factory.h
@@ -0,0 +1,44 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_ASH_BIRCH_BIRCH_KEYED_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_UI_ASH_BIRCH_BIRCH_KEYED_SERVICE_FACTORY_H_
+
+#include <memory>
+
+#include "base/no_destructor.h"
+#include "chrome/browser/profiles/profile_keyed_service_factory.h"
+
+namespace content {
+class BrowserContext;
+}  // namespace content
+
+namespace ash {
+
+class BirchKeyedService;
+
+// Factory class for `BirchKeyedService`. Creates instances of that
+// service for regular profiles only.
+class BirchKeyedServiceFactory : public ProfileKeyedServiceFactory {
+ public:
+  BirchKeyedServiceFactory(const BirchKeyedServiceFactory&) = delete;
+  BirchKeyedServiceFactory& operator=(const BirchKeyedServiceFactory&) = delete;
+
+  static BirchKeyedServiceFactory* GetInstance();
+
+  BirchKeyedService* GetService(content::BrowserContext* context);
+
+ protected:
+  // BrowserContextKeyedServiceFactory:
+  std::unique_ptr<KeyedService> BuildServiceInstanceForBrowserContext(
+      content::BrowserContext* context) const override;
+
+ private:
+  friend base::NoDestructor<BirchKeyedServiceFactory>;
+  BirchKeyedServiceFactory();
+};
+
+}  // namespace ash
+
+#endif  // CHROME_BROWSER_UI_ASH_BIRCH_BIRCH_KEYED_SERVICE_FACTORY_H_
diff --git a/chrome/browser/ui/ash/birch/birch_keyed_service_factory_unittest.cc b/chrome/browser/ui/ash/birch/birch_keyed_service_factory_unittest.cc
new file mode 100644
index 0000000..e892847e
--- /dev/null
+++ b/chrome/browser/ui/ash/birch/birch_keyed_service_factory_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/ash/birch/birch_keyed_service_factory.h"
+
+#include <memory>
+#include <string>
+
+#include "ash/constants/ash_features.h"
+#include "ash/constants/ash_switches.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
+#include "chrome/browser/ui/ash/birch/birch_keyed_service.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/user_manager/scoped_user_manager.h"
+#include "components/user_manager/user_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+
+class BirchKeyedServiceFactoryTest : public BrowserWithTestWindowTest {
+ public:
+  void SetUp() override {
+    BrowserWithTestWindowTest::SetUp();
+    switches::SetIgnoreBirchSecretKeyForTest(true);
+  }
+
+  void TearDown() override {
+    switches::SetIgnoreBirchSecretKeyForTest(false);
+    BrowserWithTestWindowTest::TearDown();
+  }
+
+ protected:
+  base::test::ScopedFeatureList feature_list_{features::kBirchFeature};
+};
+
+TEST_F(BirchKeyedServiceFactoryTest, SupportWhenFeatureIsEnabled) {
+  EXPECT_TRUE(
+      BirchKeyedServiceFactory::GetInstance()->GetService(GetProfile()));
+}
+
+TEST_F(BirchKeyedServiceFactoryTest, NoSupportWhenFeatureIsDisabled) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures({}, {features::kBirchFeature});
+
+  EXPECT_FALSE(
+      BirchKeyedServiceFactory::GetInstance()->GetService(GetProfile()));
+}
+
+TEST_F(BirchKeyedServiceFactoryTest, NoSupportForGuestProfile) {
+  std::unique_ptr<TestingProfile> guest_profile =
+      TestingProfile::Builder()
+          .SetGuestSession()
+          .SetProfileName("guest_profile")
+          .AddTestingFactories({})
+          .Build();
+  ASSERT_TRUE(guest_profile);
+
+  auto* service =
+      BirchKeyedServiceFactory::GetInstance()->GetService(guest_profile.get());
+  EXPECT_FALSE(service);
+}
+
+TEST_F(BirchKeyedServiceFactoryTest, NoSupportForOffTheRecordProfile) {
+  auto* service_for_primary_profile =
+      BirchKeyedServiceFactory::GetInstance()->GetService(GetProfile());
+  EXPECT_TRUE(service_for_primary_profile);
+
+  TestingProfile* incognito_profile =
+      TestingProfile::Builder()
+          .SetProfileName(GetProfile()->GetProfileUserName())
+          .BuildIncognito(GetProfile());
+  ASSERT_TRUE(incognito_profile);
+  ASSERT_TRUE(incognito_profile->IsOffTheRecord());
+
+  auto* service_for_incognito_profile =
+      BirchKeyedServiceFactory::GetInstance()->GetService(incognito_profile);
+  EXPECT_FALSE(service_for_incognito_profile);
+}
+
+}  // namespace ash
diff --git a/chrome/browser/ui/commerce/commerce_page_action_controller.h b/chrome/browser/ui/commerce/commerce_page_action_controller.h
index 0db96df..ed2a479 100644
--- a/chrome/browser/ui/commerce/commerce_page_action_controller.h
+++ b/chrome/browser/ui/commerce/commerce_page_action_controller.h
@@ -13,6 +13,25 @@
 
 namespace commerce {
 
+// The possible ways a suggested save location can be handled. These must be
+// kept in sync with the values in enums.xml.
+enum class PageActionIconInteractionState {
+  // The icon was shown and the user clicked it.
+  kClicked = 0,
+
+  // The icon was shown and expanded before the user clicked on it.
+  kClickedExpanded = 1,
+
+  // The icon was shown but the user did not interact with it.
+  kNotClicked = 2,
+
+  // The icon was shown and expanded but the user did not interact with it.
+  kNotClickedExpanded = 3,
+
+  // This enum must be last and is only used for histograms.
+  kMaxValue = kNotClickedExpanded
+};
+
 class CommercePageActionController {
  public:
   explicit CommercePageActionController(
diff --git a/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc b/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc
index 602b186..7024cd1 100644
--- a/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc
+++ b/chrome/browser/ui/commerce/commerce_ui_tab_helper.cc
@@ -15,6 +15,8 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/commerce/commerce_page_action_controller.h"
+#include "chrome/browser/ui/commerce/price_tracking_page_action_controller.h"
 #include "chrome/browser/ui/side_panel/side_panel_ui.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
 #include "chrome/browser/ui/views/commerce/price_insights_icon_view.h"
@@ -56,36 +58,6 @@
 namespace commerce {
 
 namespace {
-constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
-    net::DefineNetworkTrafficAnnotation("shopping_list_ui_image_fetcher",
-                                        R"(
-        semantics {
-          sender: "Product image fetcher for the shopping list feature."
-          description:
-            "Retrieves the image for a product that is displayed on the active "
-            "web page. This will be shown to the user as part of the "
-            "bookmarking or price tracking action."
-          trigger:
-            "On navigation, if the URL of the page is determined to be a "
-            "product that can be price tracked, we will attempt to fetch the "
-            "image for it."
-          data:
-            "An image of a product that can be price tracked."
-          destination: WEBSITE
-        }
-        policy {
-          cookies_allowed: NO
-          setting:
-            "This fetch is enabled for any user with the 'Shopping List' "
-            "feature enabled."
-          chrome_policy {
-            ShoppingListEnabled {
-              policy_options {mode: MANDATORY}
-              ShoppingListEnabled: true
-            }
-          }
-        })");
-
 constexpr char kImageFetcherUmaClient[] = "ShoppingList";
 
 // price tracking chip (assuming price insights isn't expanded).
@@ -342,7 +314,7 @@
       info.value().image_url,
       base::BindOnce(&CommerceUiTabHelper::HandleImageFetcherResponse,
                      weak_ptr_factory_.GetWeakPtr(), info.value().image_url),
-      image_fetcher::ImageFetcherParams(kTrafficAnnotation,
+      image_fetcher::ImageFetcherParams(kShoppingListTrafficAnnotation,
                                         kImageFetcherUmaClient));
 }
 
diff --git a/chrome/browser/ui/commerce/commerce_ui_tab_helper.h b/chrome/browser/ui/commerce/commerce_ui_tab_helper.h
index 2244e50..f67a01c 100644
--- a/chrome/browser/ui/commerce/commerce_ui_tab_helper.h
+++ b/chrome/browser/ui/commerce/commerce_ui_tab_helper.h
@@ -41,25 +41,6 @@
 
 struct CommerceSubscription;
 
-// The possible ways a suggested save location can be handled. These must be
-// kept in sync with the values in enums.xml.
-enum class PageActionIconInteractionState {
-  // The icon was shown and the user clicked it.
-  kClicked = 0,
-
-  // The icon was shown and expanded before the user clicked on it.
-  kClickedExpanded = 1,
-
-  // The icon was shown but the user did not interact with it.
-  kNotClicked = 2,
-
-  // The icon was shown and expanded but the user did not interact with it.
-  kNotClickedExpanded = 3,
-
-  // This enum must be last and is only used for histograms.
-  kMaxValue = kNotClickedExpanded
-};
-
 // This tab helper is used to update and maintain the state of UI for commerce
 // features.
 class CommerceUiTabHelper
diff --git a/chrome/browser/ui/commerce/price_tracking_page_action_controller.cc b/chrome/browser/ui/commerce/price_tracking_page_action_controller.cc
new file mode 100644
index 0000000..c26810fa
--- /dev/null
+++ b/chrome/browser/ui/commerce/price_tracking_page_action_controller.cc
@@ -0,0 +1,300 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/commerce/price_tracking_page_action_controller.h"
+
+#include "base/check_is_test.h"
+#include "base/metrics/histogram_functions.h"
+#include "components/commerce/core/price_tracking_utils.h"
+#include "components/commerce/core/shopping_service.h"
+#include "components/feature_engagement/public/tracker.h"
+#include "components/image_fetcher/core/image_fetcher.h"
+
+namespace commerce {
+
+namespace {
+constexpr char kImageFetcherUmaClient[] = "ShoppingList";
+
+// The minimum price that the price tracking UI always wants to expand at.
+constexpr int64_t kAlwaysExpandChipPriceMicros = 100000000L;
+
+}  // namespace
+
+PriceTrackingPageActionController::PriceTrackingPageActionController(
+    base::RepeatingCallback<void()> notify_callback,
+    ShoppingService* shopping_service,
+    image_fetcher::ImageFetcher* image_fetcher,
+    feature_engagement::Tracker* tracker)
+    : CommercePageActionController(std::move(notify_callback)),
+      shopping_service_(shopping_service),
+      image_fetcher_(image_fetcher),
+      tracker_(tracker) {
+  if (shopping_service_) {
+    scoped_observation_.Observe(shopping_service_);
+    shopping_service_->WaitForReady(base::BindOnce(
+        [](base::WeakPtr<PriceTrackingPageActionController> controller,
+           ShoppingService* service) {
+          if (!controller || !service) {
+            return;
+          }
+          if (service->IsShoppingListEligible()) {
+            // Fetching the image may have been blocked by the eligibility
+            // check, retry.
+            controller->MaybeDoProductImageFetch(
+                controller->product_info_for_page_);
+            controller->NotifyHost();
+          }
+        },
+        weak_ptr_factory_.GetWeakPtr()));
+  } else {
+    CHECK_IS_TEST();
+  }
+}
+
+PriceTrackingPageActionController::~PriceTrackingPageActionController() {
+  // Recording this in the destructor corresponds to the tab being closed or the
+  // browser shutting down.
+  MaybeRecordPriceTrackingIconMetrics(/*from_icon_use=*/false);
+}
+
+std::optional<bool>
+PriceTrackingPageActionController::ShouldShowForNavigation() {
+  // If the user isn't eligible for the feature, don't block.
+  if (!shopping_service_ || !shopping_service_->IsShoppingListEligible()) {
+    return false;
+  }
+
+  // If we got a response from the shopping service but the response was empty,
+  // we don't need to wait for the image or subscription status.
+  if (got_product_response_for_page_ && !product_info_for_page_.has_value()) {
+    return false;
+  }
+
+  // If the page is determined to be a product page, we're "undecided" until we
+  // can show the current subscription state and have an image for the UI.
+  if (!got_initial_subscription_status_for_page_ ||
+      !got_image_response_for_page_) {
+    return std::nullopt;
+  }
+
+  return !last_fetched_image_.IsEmpty();
+}
+
+bool PriceTrackingPageActionController::WantsExpandedUi() {
+  if (!product_info_for_page_.has_value()) {
+    return false;
+  }
+  CommerceSubscription sub(
+      SubscriptionType::kPriceTrack, IdentifierType::kProductClusterId,
+      base::NumberToString(product_info_for_page_->product_cluster_id.value()),
+      ManagementType::kUserManaged);
+  bool already_subscribed = shopping_service_->IsSubscribedFromCache(sub);
+
+  // Don't expand the chip if the user is already subscribed to the product.
+  if (!already_subscribed) {
+    if (tracker_ &&
+        tracker_->ShouldTriggerHelpUI(
+            feature_engagement::kIPHPriceTrackingPageActionIconLabelFeature)) {
+      tracker_->Dismissed(
+          feature_engagement::kIPHPriceTrackingPageActionIconLabelFeature);
+      expanded_ui_for_page_ = true;
+      return true;
+    }
+
+    // If none of the above cases expanded a chip, expand the price tracking
+    // chip if the product price is > $100.
+    if (product_info_for_page_->amount_micros > kAlwaysExpandChipPriceMicros &&
+        product_info_for_page_->product_cluster_id.has_value()) {
+      expanded_ui_for_page_ = true;
+      return true;
+    }
+  }
+
+  return false;
+}
+
+void PriceTrackingPageActionController::ResetForNewNavigation(const GURL& url) {
+  if (!shopping_service_->IsShoppingListEligible()) {
+    return;
+  }
+
+  // The page action icon may not have been used for the last page load. If
+  // that's the case, make sure we record it.
+  MaybeRecordPriceTrackingIconMetrics(/*from_icon_use=*/false);
+
+  // Cancel any pending callbacks.
+  weak_ptr_factory_.InvalidateWeakPtrs();
+
+  is_cluster_id_tracked_by_user_ = false;
+  last_fetched_image_ = gfx::Image();
+  last_fetched_image_url_ = GURL();
+  current_url_ = url;
+  got_product_response_for_page_ = false;
+  got_image_response_for_page_ = false;
+  got_initial_subscription_status_for_page_ = false;
+  expanded_ui_for_page_ = false;
+  icon_use_recorded_for_page_ = false;
+
+  shopping_service_->GetProductInfoForUrl(
+      url, base::BindOnce(
+               &PriceTrackingPageActionController::HandleProductInfoResponse,
+               weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PriceTrackingPageActionController::OnIconClicked() {
+  MaybeRecordPriceTrackingIconMetrics(/*from_icon_use=*/true);
+}
+
+void PriceTrackingPageActionController::MaybeRecordPriceTrackingIconMetrics(
+    bool from_icon_use) {
+  // Ignore cases where these is no cluster ID or the metric was already
+  // recorded for the page.
+  if (!cluster_id_for_page_.has_value() || icon_use_recorded_for_page_) {
+    return;
+  }
+
+  icon_use_recorded_for_page_ = true;
+
+  // Ignore any instances where the product is already tracked. This will not
+  // stop cases where the icon is being used to newly track a product since
+  // this logic will run prior to subscriptions updating.
+  if (is_cluster_id_tracked_by_user_) {
+    return;
+  }
+
+  std::string histogram_name = "Commerce.PriceTracking.IconInteractionState";
+
+  // Clicking the icon for a product that is already tracked does not
+  // immediately untrack the product. If we made it this far, we can assume the
+  // interaction was to track a product, otherwise we would have been blocked
+  // above.
+  if (from_icon_use) {
+    if (expanded_ui_for_page_) {
+      base::UmaHistogramEnumeration(
+          histogram_name, PageActionIconInteractionState::kClickedExpanded);
+    } else {
+      base::UmaHistogramEnumeration(histogram_name,
+                                    PageActionIconInteractionState::kClicked);
+    }
+  } else {
+    if (expanded_ui_for_page_) {
+      base::UmaHistogramEnumeration(
+          histogram_name, PageActionIconInteractionState::kNotClickedExpanded);
+    } else {
+      base::UmaHistogramEnumeration(
+          histogram_name, PageActionIconInteractionState::kNotClicked);
+    }
+  }
+}
+
+void PriceTrackingPageActionController::HandleProductInfoResponse(
+    const GURL& url,
+    const std::optional<const ProductInfo>& info) {
+  if (url != current_url_ || !info.has_value()) {
+    got_product_response_for_page_ = true;
+    NotifyHost();
+    return;
+  }
+
+  product_info_for_page_ = info;
+  got_product_response_for_page_ = true;
+
+  if (shopping_service_->IsShoppingListEligible() && CanTrackPrice(info) &&
+      !info->image_url.is_empty()) {
+    cluster_id_for_page_.emplace(info->product_cluster_id.value());
+    UpdatePriceTrackingStateFromSubscriptions();
+
+    MaybeDoProductImageFetch(info);
+  }
+}
+
+void PriceTrackingPageActionController::MaybeDoProductImageFetch(
+    const std::optional<ProductInfo>& info) {
+  if (!shopping_service_->IsShoppingListEligible() || !CanTrackPrice(info) ||
+      info->image_url.is_empty() || !this->last_fetched_image_.IsEmpty()) {
+    return;
+  }
+
+  // TODO(1360850): Delay this fetch by possibly waiting until page load has
+  //                finished.
+  image_fetcher_->FetchImage(
+      info.value().image_url,
+      base::BindOnce(
+          &PriceTrackingPageActionController::HandleImageFetcherResponse,
+          weak_ptr_factory_.GetWeakPtr(), info.value().image_url),
+      image_fetcher::ImageFetcherParams(kShoppingListTrafficAnnotation,
+                                        kImageFetcherUmaClient));
+}
+
+void PriceTrackingPageActionController::
+    UpdatePriceTrackingStateFromSubscriptions() {
+  if (!cluster_id_for_page_.has_value()) {
+    return;
+  }
+
+  shopping_service_->IsSubscribed(
+      BuildUserSubscriptionForClusterId(cluster_id_for_page_.value()),
+      base::BindOnce(
+          [](base::WeakPtr<PriceTrackingPageActionController> controller,
+             bool is_tracked) {
+            if (!controller) {
+              return;
+            }
+
+            controller->is_cluster_id_tracked_by_user_ = is_tracked;
+            controller->got_initial_subscription_status_for_page_ = true;
+            controller->NotifyHost();
+          },
+          weak_ptr_factory_.GetWeakPtr()));
+}
+
+void PriceTrackingPageActionController::OnSubscribe(
+    const CommerceSubscription& subscription,
+    bool succeeded) {
+  HandleSubscriptionChange(subscription);
+}
+
+void PriceTrackingPageActionController::OnUnsubscribe(
+    const CommerceSubscription& subscription,
+    bool succeeded) {
+  HandleSubscriptionChange(subscription);
+}
+
+void PriceTrackingPageActionController::HandleSubscriptionChange(
+    const CommerceSubscription& sub) {
+  if (sub.id_type == IdentifierType::kProductClusterId &&
+      sub.id == base::NumberToString(
+                    cluster_id_for_page_.value_or(kInvalidSubscriptionId))) {
+    UpdatePriceTrackingStateFromSubscriptions();
+    NotifyHost();
+  }
+}
+
+void PriceTrackingPageActionController::HandleImageFetcherResponse(
+    const GURL image_url,
+    const gfx::Image& image,
+    const image_fetcher::RequestMetadata& request_metadata) {
+  got_image_response_for_page_ = true;
+
+  if (!image.IsEmpty()) {
+    last_fetched_image_url_ = image_url;
+    last_fetched_image_ = image;
+  }
+
+  NotifyHost();
+}
+
+const gfx::Image& PriceTrackingPageActionController::GetLastFetchedImage() {
+  return last_fetched_image_;
+}
+
+const GURL& PriceTrackingPageActionController::GetLastFetchedImageUrl() {
+  return last_fetched_image_url_;
+}
+
+bool PriceTrackingPageActionController::IsPriceTrackingCurrentProduct() {
+  return is_cluster_id_tracked_by_user_;
+}
+
+}  // namespace commerce
diff --git a/chrome/browser/ui/commerce/price_tracking_page_action_controller.h b/chrome/browser/ui/commerce/price_tracking_page_action_controller.h
new file mode 100644
index 0000000..b3532c0
--- /dev/null
+++ b/chrome/browser/ui/commerce/price_tracking_page_action_controller.h
@@ -0,0 +1,167 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COMMERCE_PRICE_TRACKING_PAGE_ACTION_CONTROLLER_H_
+#define CHROME_BROWSER_UI_COMMERCE_PRICE_TRACKING_PAGE_ACTION_CONTROLLER_H_
+
+#include <optional>
+
+#include "base/functional/callback.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/scoped_observation.h"
+#include "chrome/browser/ui/commerce/commerce_page_action_controller.h"
+#include "components/commerce/core/commerce_types.h"
+#include "components/commerce/core/subscriptions/subscriptions_observer.h"
+#include "components/image_fetcher/core/request_metadata.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "ui/gfx/image/image.h"
+
+class GURL;
+
+namespace feature_engagement {
+class Tracker;
+}
+
+namespace image_fetcher {
+class ImageFetcher;
+}  // namespace image_fetcher
+
+namespace commerce {
+
+struct CommerceSubscription;
+class ShoppingService;
+
+// TODO(b:301440117): Once the tab helper uses this class, move this annotation
+//                    to an anonymous namespace.
+inline constexpr net::NetworkTrafficAnnotationTag
+    kShoppingListTrafficAnnotation =
+        net::DefineNetworkTrafficAnnotation("shopping_list_ui_image_fetcher",
+                                            R"(
+        semantics {
+          sender: "Product image fetcher for the shopping list feature."
+          description:
+            "Retrieves the image for a product that is displayed on the active "
+            "web page. This will be shown to the user as part of the "
+            "bookmarking or price tracking action."
+          trigger:
+            "On navigation, if the URL of the page is determined to be a "
+            "product that can be price tracked, we will attempt to fetch the "
+            "image for it."
+          data:
+            "An image of a product that can be price tracked."
+          destination: WEBSITE
+        }
+        policy {
+          cookies_allowed: NO
+          setting:
+            "This fetch is enabled for any user with the 'Shopping List' "
+            "feature enabled."
+          chrome_policy {
+            ShoppingListEnabled {
+              policy_options {mode: MANDATORY}
+              ShoppingListEnabled: true
+            }
+          }
+        })");
+
+class PriceTrackingPageActionController : public CommercePageActionController,
+                                          public SubscriptionsObserver {
+ public:
+  PriceTrackingPageActionController(
+      base::RepeatingCallback<void()> notify_callback,
+      ShoppingService* shopping_service,
+      image_fetcher::ImageFetcher* image_fetcher,
+      feature_engagement::Tracker* tracker);
+  PriceTrackingPageActionController(const PriceTrackingPageActionController&) =
+      delete;
+  PriceTrackingPageActionController& operator=(
+      const PriceTrackingPageActionController&) = delete;
+  ~PriceTrackingPageActionController() override;
+
+  // CommercePageActionController impl:
+  std::optional<bool> ShouldShowForNavigation() override;
+  bool WantsExpandedUi() override;
+  void ResetForNewNavigation(const GURL& url) override;
+
+  void OnIconClicked();
+
+  // SubscriptionsObserver impl:
+  void OnSubscribe(const CommerceSubscription& subscription,
+                   bool succeeded) override;
+  void OnUnsubscribe(const CommerceSubscription& subscription,
+                     bool succeeded) override;
+
+  // Accessors for the UI.
+  const gfx::Image& GetLastFetchedImage();
+  const GURL& GetLastFetchedImageUrl();
+  bool IsPriceTrackingCurrentProduct();
+
+ private:
+  void HandleProductInfoResponse(const GURL& url,
+                                 const std::optional<const ProductInfo>& info);
+
+  void MaybeDoProductImageFetch(const std::optional<ProductInfo>& info);
+
+  // Update the flag tracking the price tracking state of the product from
+  // subscriptions.
+  void UpdatePriceTrackingStateFromSubscriptions();
+
+  void HandleSubscriptionChange(const CommerceSubscription& sub);
+
+  void HandleImageFetcherResponse(
+      const GURL image_url,
+      const gfx::Image& image,
+      const image_fetcher::RequestMetadata& request_metadata);
+
+  void MaybeRecordPriceTrackingIconMetrics(bool from_icon_use);
+
+  // The URL for the most recent navigation.
+  GURL current_url_;
+
+  bool got_product_response_for_page_{false};
+  bool got_image_response_for_page_{false};
+  bool got_initial_subscription_status_for_page_{false};
+
+  // Whether the product shown on the current page is tracked by the user.
+  bool is_cluster_id_tracked_by_user_{false};
+
+  // The cluster ID for the current page, if applicable.
+  std::optional<uint64_t> cluster_id_for_page_;
+
+  // The product info available for the current page if available.
+  std::optional<ProductInfo> product_info_for_page_;
+
+  // The shopping service is tied to the lifetime of the browser context
+  // which will always outlive this tab helper.
+  raw_ptr<ShoppingService> shopping_service_;
+
+  raw_ptr<image_fetcher::ImageFetcher> image_fetcher_;
+
+  raw_ptr<feature_engagement::Tracker> tracker_;
+
+  // The URL of the last product image that was fetched.
+  GURL last_fetched_image_url_;
+
+  // The last image that was fetched. See |last_image_fetched_url_| for the
+  // URL that was used.
+  gfx::Image last_fetched_image_;
+
+  // Whether the page action icon was expanded for the current page load.
+  bool expanded_ui_for_page_{false};
+
+  // Whether the price tracking icon was recorded for the current page. This
+  // will only record "track" events.
+  bool icon_use_recorded_for_page_{false};
+
+  base::ScopedObservation<ShoppingService, SubscriptionsObserver>
+      scoped_observation_{this};
+
+  base::WeakPtrFactory<PriceTrackingPageActionController> weak_ptr_factory_{
+      this};
+};
+
+}  // namespace commerce
+
+#endif  // CHROME_BROWSER_UI_COMMERCE_PRICE_TRACKING_PAGE_ACTION_CONTROLLER_H_
diff --git a/chrome/browser/ui/commerce/price_tracking_page_action_controller_unittest.cc b/chrome/browser/ui/commerce/price_tracking_page_action_controller_unittest.cc
new file mode 100644
index 0000000..005bb5b1
--- /dev/null
+++ b/chrome/browser/ui/commerce/price_tracking_page_action_controller_unittest.cc
@@ -0,0 +1,409 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/mock_callback.h"
+#include "base/test/task_environment.h"
+#include "chrome/browser/ui/commerce/price_tracking_page_action_controller.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/commerce/core/commerce_feature_list.h"
+#include "components/commerce/core/mock_shopping_service.h"
+#include "components/commerce/core/shopping_service.h"
+#include "components/commerce/core/subscriptions/commerce_subscription.h"
+#include "components/commerce/core/test_utils.h"
+#include "components/feature_engagement/test/mock_tracker.h"
+#include "components/image_fetcher/core/mock_image_fetcher.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+#include "url/gurl.h"
+
+namespace commerce {
+
+namespace {
+
+// Build a basic ProductInfo object.
+std::optional<ProductInfo> CreateProductInfo(uint64_t cluster_id,
+                                             const GURL& image_url = GURL()) {
+  std::optional<ProductInfo> info;
+  info.emplace();
+  info->product_cluster_id = cluster_id;
+  if (!image_url.is_empty()) {
+    info->image_url = image_url;
+  }
+  return info;
+}
+
+}  // namespace
+
+class PriceTrackingPageActionControllerUnittest : public testing::Test {
+ public:
+  PriceTrackingPageActionControllerUnittest()
+      : shopping_service_(std::make_unique<MockShoppingService>()),
+        image_fetcher_(std::make_unique<image_fetcher::MockImageFetcher>()),
+        tracker_(std::make_unique<feature_engagement::test::MockTracker>()) {}
+
+  PriceTrackingPageActionControllerUnittest(
+      const PriceTrackingPageActionControllerUnittest&) = delete;
+  PriceTrackingPageActionControllerUnittest operator=(
+      const PriceTrackingPageActionControllerUnittest&) = delete;
+  ~PriceTrackingPageActionControllerUnittest() override = default;
+
+  void TestBody() override {}
+
+  void SetupImageFetcherForSimpleImage(bool valid_image) {
+    ON_CALL(*image_fetcher_, FetchImageAndData_)
+        .WillByDefault(
+            [valid_image = valid_image](
+                const GURL& image_url,
+                image_fetcher::ImageDataFetcherCallback* image_data_callback,
+                image_fetcher::ImageFetcherCallback* image_callback,
+                image_fetcher::ImageFetcherParams params) {
+              if (valid_image) {
+                SkBitmap bitmap;
+                bitmap.allocN32Pixels(1, 1);
+                gfx::Image image =
+                    gfx::Image(gfx::ImageSkia::CreateFrom1xBitmap(bitmap));
+                std::move(*image_callback)
+                    .Run(std::move(image), image_fetcher::RequestMetadata());
+              } else {
+                std::move(*image_callback)
+                    .Run(gfx::Image(), image_fetcher::RequestMetadata());
+              }
+            });
+  }
+
+ protected:
+  base::MockRepeatingCallback<void()> notify_host_callback_;
+  std::unique_ptr<MockShoppingService> shopping_service_;
+  std::unique_ptr<image_fetcher::MockImageFetcher> image_fetcher_;
+  std::unique_ptr<feature_engagement::test::MockTracker> tracker_;
+
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+};
+
+TEST_F(PriceTrackingPageActionControllerUnittest, IconShown) {
+  shopping_service_->SetIsShoppingListEligible(true);
+  shopping_service_->SetIsSubscribedCallbackValue(false);
+  SetupImageFetcherForSimpleImage(/*valid_image=*/true);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  // Before a navigation, the controller should be in an "undecided" state.
+  ASSERT_FALSE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_FALSE(controller.WantsExpandedUi());
+
+  uint64_t cluster_id = 12345L;
+  EXPECT_CALL(notify_host_callback_, Run()).Times(testing::AtLeast(1));
+  EXPECT_CALL(*shopping_service_,
+              IsSubscribed(SubscriptionWithId(base::NumberToString(cluster_id)),
+                           testing::_))
+      .Times(1);
+  shopping_service_->SetResponseForGetProductInfoForUrl(
+      CreateProductInfo(cluster_id, GURL("http://example.com/image.png")));
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_TRUE(controller.ShouldShowForNavigation().value());
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest, IconNotShown_NoImage) {
+  shopping_service_->SetIsShoppingListEligible(true);
+  shopping_service_->SetIsSubscribedCallbackValue(false);
+  SetupImageFetcherForSimpleImage(/*valid_image=*/false);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  // Before a navigation, the controller should be in an "undecided" state.
+  ASSERT_FALSE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_FALSE(controller.WantsExpandedUi());
+
+  uint64_t cluster_id = 12345L;
+  EXPECT_CALL(notify_host_callback_, Run()).Times(testing::AtLeast(1));
+  EXPECT_CALL(*shopping_service_,
+              IsSubscribed(SubscriptionWithId(base::NumberToString(cluster_id)),
+                           testing::_))
+      .Times(1);
+  shopping_service_->SetResponseForGetProductInfoForUrl(
+      CreateProductInfo(cluster_id, GURL("http://example.com/image.png")));
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_FALSE(controller.ShouldShowForNavigation().value());
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest, IconNotShown_NoProductInfo) {
+  shopping_service_->SetIsShoppingListEligible(true);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  // Before a navigation, the controller should be in an "undecided" state.
+  ASSERT_FALSE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_FALSE(controller.WantsExpandedUi());
+
+  EXPECT_CALL(notify_host_callback_, Run()).Times(1);
+  EXPECT_CALL(*shopping_service_, IsSubscribed(testing::_, testing::_))
+      .Times(0);
+  shopping_service_->SetResponseForGetProductInfoForUrl(std::nullopt);
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_FALSE(controller.ShouldShowForNavigation().value());
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest, IconNotShown_NotEligible) {
+  shopping_service_->SetIsShoppingListEligible(false);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_FALSE(controller.ShouldShowForNavigation().value());
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest,
+       SubscriptionEventsUpdateState) {
+  shopping_service_->SetIsShoppingListEligible(true);
+  shopping_service_->SetIsSubscribedCallbackValue(false);
+  SetupImageFetcherForSimpleImage(/*valid_image=*/true);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  // Before a navigation, the controller should be in an "undecided" state.
+  ASSERT_FALSE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_FALSE(controller.WantsExpandedUi());
+
+  shopping_service_->SetResponseForGetProductInfoForUrl(
+      CreateProductInfo(12345L, GURL("http://example.com/image.png")));
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  CommerceSubscription sub(SubscriptionType::kPriceTrack,
+                           IdentifierType::kProductClusterId, "12345",
+                           ManagementType::kUserManaged);
+
+  EXPECT_CALL(notify_host_callback_, Run()).Times(1);
+  shopping_service_->SetIsSubscribedCallbackValue(true);
+  controller.OnSubscribe(sub, true);
+
+  EXPECT_CALL(notify_host_callback_, Run()).Times(1);
+  shopping_service_->SetIsSubscribedCallbackValue(false);
+  controller.OnUnsubscribe(sub, true);
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_TRUE(controller.ShouldShowForNavigation().value());
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest, WantsExpandedUi_HighPrice) {
+  shopping_service_->SetIsShoppingListEligible(true);
+  SetupImageFetcherForSimpleImage(/*valid_image=*/true);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  std::optional<ProductInfo> info =
+      CreateProductInfo(12345L, GURL("http://example.com/image.png"));
+  info->amount_micros = 150000000L;  // $150 in micros
+  shopping_service_->SetResponseForGetProductInfoForUrl(info);
+
+  // First run the case where the user isn't subscribed.
+  shopping_service_->SetIsSubscribedCallbackValue(false);
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_TRUE(controller.ShouldShowForNavigation().value());
+  ASSERT_TRUE(controller.WantsExpandedUi());
+
+  // If the user is already subscribed, we shouldn't expand regardless of the
+  // price.
+  shopping_service_->SetIsSubscribedCallbackValue(true);
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_TRUE(controller.ShouldShowForNavigation().value());
+  ASSERT_FALSE(controller.WantsExpandedUi());
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest, WantsExpandedUi_Tracker) {
+  shopping_service_->SetIsShoppingListEligible(true);
+  SetupImageFetcherForSimpleImage(/*valid_image=*/true);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  shopping_service_->SetResponseForGetProductInfoForUrl(
+      CreateProductInfo(12345L, GURL("http://example.com/image.png")));
+
+  ON_CALL(*tracker_,
+          ShouldTriggerHelpUI(testing::Ref(
+              feature_engagement::kIPHPriceTrackingPageActionIconLabelFeature)))
+      .WillByDefault(testing::Return(true));
+
+  // First run the case where the user isn't subscribed.
+  shopping_service_->SetIsSubscribedCallbackValue(false);
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_TRUE(controller.ShouldShowForNavigation().value());
+  ASSERT_TRUE(controller.WantsExpandedUi());
+
+  // If the user is already subscribed, we shouldn't access the tracker or
+  // expand.
+  shopping_service_->SetIsSubscribedCallbackValue(true);
+  controller.ResetForNewNavigation(GURL("http://example.com"));
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_TRUE(controller.ShouldShowForNavigation().has_value());
+  ASSERT_TRUE(controller.ShouldShowForNavigation().value());
+  ASSERT_FALSE(controller.WantsExpandedUi());
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest,
+       IconInteractionStateMetrics_Expanded) {
+  shopping_service_->SetIsShoppingListEligible(true);
+  shopping_service_->SetIsSubscribedCallbackValue(false);
+  SetupImageFetcherForSimpleImage(/*valid_image=*/true);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  std::optional<ProductInfo> info =
+      CreateProductInfo(12345L, GURL("http://example.com/image.png"));
+  info->amount_micros = 150000000L;  // $150 in micros
+  shopping_service_->SetResponseForGetProductInfoForUrl(info);
+
+  std::string histogram_name = "Commerce.PriceTracking.IconInteractionState";
+  base::HistogramTester histogram_tester;
+
+  // First, simulate a click on the expanded chip.
+  controller.ResetForNewNavigation(GURL("http://example.com/1"));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(controller.WantsExpandedUi());
+  controller.OnIconClicked();
+
+  histogram_tester.ExpectTotalCount(histogram_name, 1);
+  histogram_tester.ExpectBucketCount(
+      histogram_name, PageActionIconInteractionState::kClickedExpanded, 1);
+
+  // Simulate no click on an expanded chip.
+  controller.ResetForNewNavigation(GURL("http://example.com/2"));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(controller.WantsExpandedUi());
+  // Leave the page before a click happens.
+  controller.ResetForNewNavigation(GURL("http://example.com/3"));
+  base::RunLoop().RunUntilIdle();
+
+  histogram_tester.ExpectTotalCount(histogram_name, 2);
+  histogram_tester.ExpectBucketCount(
+      histogram_name, PageActionIconInteractionState::kNotClickedExpanded, 1);
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest,
+       IconInteractionStateMetrics_NotExpanded) {
+  shopping_service_->SetIsShoppingListEligible(true);
+
+  // Simulate a product not being tracked.
+  shopping_service_->SetIsSubscribedCallbackValue(false);
+
+  SetupImageFetcherForSimpleImage(/*valid_image=*/true);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  shopping_service_->SetResponseForGetProductInfoForUrl(
+      CreateProductInfo(12345L, GURL("http://example.com/image.png")));
+
+  std::string histogram_name = "Commerce.PriceTracking.IconInteractionState";
+  base::HistogramTester histogram_tester;
+
+  // Click on an unexpanded chip.
+  controller.ResetForNewNavigation(GURL("http://example.com/1"));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(controller.WantsExpandedUi());
+  controller.OnIconClicked();
+
+  histogram_tester.ExpectTotalCount(histogram_name, 1);
+  histogram_tester.ExpectBucketCount(
+      histogram_name, PageActionIconInteractionState::kClicked, 1);
+
+  // No click on an unexpanded chip.
+  controller.ResetForNewNavigation(GURL("http://example.com/2"));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(controller.WantsExpandedUi());
+  // Leave the page before a click happens.
+  controller.ResetForNewNavigation(GURL("http://example.com/3"));
+  base::RunLoop().RunUntilIdle();
+
+  histogram_tester.ExpectTotalCount(histogram_name, 2);
+  histogram_tester.ExpectBucketCount(
+      histogram_name, PageActionIconInteractionState::kNotClicked, 1);
+}
+
+TEST_F(PriceTrackingPageActionControllerUnittest,
+       IconInteractionStateMetrics_AlreadyTracked) {
+  shopping_service_->SetIsShoppingListEligible(true);
+
+  // Simulate a product being tracked.
+  shopping_service_->SetIsSubscribedCallbackValue(true);
+
+  SetupImageFetcherForSimpleImage(/*valid_image=*/true);
+  base::RepeatingCallback<void()> callback = notify_host_callback_.Get();
+  PriceTrackingPageActionController controller(
+      std::move(callback), shopping_service_.get(), image_fetcher_.get(),
+      tracker_.get());
+
+  shopping_service_->SetResponseForGetProductInfoForUrl(
+      CreateProductInfo(12345L, GURL("http://example.com/image.png")));
+
+  std::string histogram_name = "Commerce.PriceTracking.IconInteractionState";
+  base::HistogramTester histogram_tester;
+
+  // First, simulate a click on the expanded chip.
+  controller.ResetForNewNavigation(GURL("http://example.com/1"));
+  base::RunLoop().RunUntilIdle();
+  controller.OnIconClicked();
+
+  // Simulate no click on the chip.
+  controller.ResetForNewNavigation(GURL("http://example.com/2"));
+  base::RunLoop().RunUntilIdle();
+  // Leave the page before a click happens.
+  controller.ResetForNewNavigation(GURL("http://example.com/3"));
+  base::RunLoop().RunUntilIdle();
+
+  // Since the product is already tracked, we shouldn't have collected any
+  // samples.
+  histogram_tester.ExpectTotalCount(histogram_name, 0);
+}
+
+}  // namespace commerce
diff --git a/chrome/browser/ui/extensions/application_launch.cc b/chrome/browser/ui/extensions/application_launch.cc
index 656d789..54f8bc9 100644
--- a/chrome/browser/ui/extensions/application_launch.cc
+++ b/chrome/browser/ui/extensions/application_launch.cc
@@ -224,7 +224,6 @@
 
   extensions::LaunchType launch_type =
       extensions::GetLaunchType(ExtensionPrefs::Get(profile), extension);
-  UMA_HISTOGRAM_ENUMERATION("Extensions.AppTabLaunchType", launch_type, 100);
 
   int add_type = AddTabTypes::ADD_ACTIVE;
   if (launch_type == extensions::LAUNCH_TYPE_PINNED)
diff --git a/chrome/browser/ui/quick_answers/ui/quick_answers_util.cc b/chrome/browser/ui/quick_answers/ui/quick_answers_util.cc
index b1bee2b..6d57e0a9 100644
--- a/chrome/browser/ui/quick_answers/ui/quick_answers_util.cc
+++ b/chrome/browser/ui/quick_answers/ui/quick_answers_util.cc
@@ -20,6 +20,7 @@
 #include "ui/views/controls/separator.h"
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/layout/flex_layout.h"
+#include "ui/views/layout/flex_layout_view.h"
 
 namespace {
 
@@ -136,26 +137,21 @@
   return labels_container;
 }
 
-View* AddHorizontalViews(View* container,
-                         std::vector<std::unique_ptr<views::View>>& views) {
-  auto* views_container = container->AddChildView(std::make_unique<View>());
-  auto* layout =
-      views_container->SetLayoutManager(std::make_unique<views::FlexLayout>());
-  layout->SetOrientation(views::LayoutOrientation::kHorizontal)
-      .SetDefault(views::kMarginsKey, kViewSpacingMargins);
-
-  for (auto& view : views) {
-    views_container->AddChildView(std::move(view));
-  }
-
-  return views_container;
-}
-
 View* AddFillLayoutChildView(View* container,
                              std::unique_ptr<views::View> view) {
   View* child_view = container->AddChildView(std::move(view));
   child_view->SetLayoutManager(std::make_unique<views::FillLayout>());
-  child_view->SetProperty(views::kMarginsKey, kUnderLineIndentation);
+  child_view->SetProperty(views::kMarginsKey, kViewVerticalSpacingMargins);
+
+  return child_view;
+}
+
+std::unique_ptr<views::FlexLayoutView> CreateHorizontalLayoutView() {
+  std::unique_ptr<views::FlexLayoutView> child_view =
+      views::Builder<views::FlexLayoutView>()
+          .SetOrientation(views::LayoutOrientation::kHorizontal)
+          .Build();
+  child_view->SetDefault(views::kMarginsKey, kViewHorizontalSpacingMargins);
 
   return child_view;
 }
diff --git a/chrome/browser/ui/quick_answers/ui/quick_answers_util.h b/chrome/browser/ui/quick_answers/ui/quick_answers_util.h
index 6de6eb2a..ec6f95e 100644
--- a/chrome/browser/ui/quick_answers/ui/quick_answers_util.h
+++ b/chrome/browser/ui/quick_answers/ui/quick_answers_util.h
@@ -12,20 +12,21 @@
 #include "ui/gfx/font_list.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/separator.h"
+#include "ui/views/layout/flex_layout_view.h"
 #include "ui/views/view.h"
 
 namespace quick_answers {
 
 // Size constants.
-inline constexpr int kContentHeaderWidth = 252;
+inline constexpr int kContentHeaderWidth = 248;
 inline constexpr int kContentTextWidth = 280;
 
 // Spacing constants.
 inline constexpr int kContentSingleSpacing = 8;
 inline constexpr int kContentDoubleSpacing = 16;
-inline constexpr gfx::Insets kUnderLineIndentation =
+inline constexpr gfx::Insets kViewVerticalSpacingMargins =
     gfx::Insets::TLBR(0, 0, kContentSingleSpacing, 0);
-inline constexpr gfx::Insets kViewSpacingMargins =
+inline constexpr gfx::Insets kViewHorizontalSpacingMargins =
     gfx::Insets::TLBR(0, 0, 0, kContentSingleSpacing);
 
 // View constants.
@@ -72,12 +73,6 @@
     views::View* container,
     const std::vector<std::unique_ptr<QuickAnswerUiElement>>& elements);
 
-// Adds the list of |Views| horizontally to the container.
-// Returns the resulting container view.
-views::View* AddHorizontalViews(
-    views::View* container,
-    std::vector<std::unique_ptr<views::View>>& views);
-
 // Creates a child view using FillLayout in the container. Uses |view| as the
 // child view if it's specified, otherwise creates a new view.
 // Returns the child view.
@@ -85,6 +80,9 @@
     views::View* container,
     std::unique_ptr<views::View> view = std::make_unique<views::View>());
 
+// Creates a horizontal FlexLayoutView with |kViewSpacingMargins| spacing.
+std::unique_ptr<views::FlexLayoutView> CreateHorizontalLayoutView();
+
 // Creates a separator view with |kContentDoubleSpacing| vertical margins.
 std::unique_ptr<views::Separator> CreateSeparatorView();
 
diff --git a/chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.cc b/chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.cc
index ff6cbda8..2c1eec9 100644
--- a/chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.cc
+++ b/chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.cc
@@ -5,14 +5,32 @@
 #include "chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.h"
 
 #include "base/functional/bind.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/quick_answers/quick_answers_ui_controller.h"
 #include "chrome/browser/ui/quick_answers/ui/quick_answers_text_label.h"
 #include "chrome/browser/ui/quick_answers/ui/quick_answers_util.h"
 #include "chromeos/components/quick_answers/quick_answers_model.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/views/controls/label.h"
+#include "ui/views/controls/webview/webview.h"
+#include "ui/views/layout/box_layout_view.h"
 #include "ui/views/layout/flex_layout_view.h"
 
+namespace {
+
+// The space available to show text in the definition header view.
+// The width of the phonetics audio button needs to be subtracted
+// from the total header width.
+constexpr int kDefinitionHeaderTextWidth =
+    quick_answers::kContentHeaderWidth -
+    (quick_answers::kContentSingleSpacing +
+     quick_answers::kRichAnswersIconContainerRadius);
+
+}  // namespace
+
 namespace quick_answers {
 
 // RichAnswersDefinitionView
@@ -42,35 +60,87 @@
 }
 
 void RichAnswersDefinitionView::AddHeaderViews() {
-  auto* header_view = AddFillLayoutChildView(content_view_);
-  std::unique_ptr<views::View> word_label =
-      QuickAnswersTextLabel::CreateLabelWithStyle(
-          definition_result_.word, GetFontList(TypographyToken::kCrosTitle1),
-          kContentHeaderWidth,
-          /*is_multi_line=*/false);
-  std::unique_ptr<views::View> phonetics_label =
-      QuickAnswersTextLabel::CreateLabelWithStyle(
-          "/" + definition_result_.phonetics_info.text + "/",
-          GetFontList(TypographyToken::kCrosBody2), kContentTextWidth,
-          /*is_multi_line=*/true);
+  views::View* container_view = AddFillLayoutChildView(content_view_);
+  views::BoxLayoutView* box_layout_view =
+      container_view->AddChildView(std::make_unique<views::BoxLayoutView>());
+  views::FlexLayoutView* header_view =
+      box_layout_view->AddChildView(CreateHorizontalLayoutView());
 
-  // Display the word and phonetics in a single line heading if possible,
-  // otherwise show the phonetics as a subheading below the word heading.
-  if (word_label->width() + phonetics_label->width() <= kContentHeaderWidth) {
-    std::vector<std::unique_ptr<views::View>> header_labels;
-    header_labels.push_back(std::move(word_label));
-    header_labels.push_back(std::move(phonetics_label));
-    AddHorizontalViews(header_view, header_labels);
-  } else {
-    auto* header_text = header_view->AddChildView(
-        views::Builder<views::FlexLayoutView>()
-            .SetOrientation(views::LayoutOrientation::kHorizontal)
-            .Build());
-    header_text->AddChildView(std::move(word_label));
-    AddFillLayoutChildView(content_view_, std::move(phonetics_label));
+  QuickAnswersTextLabel* word_label =
+      header_view->AddChildView(QuickAnswersTextLabel::CreateLabelWithStyle(
+          definition_result_.word, GetFontList(TypographyToken::kCrosTitle1),
+          kDefinitionHeaderTextWidth,
+          /*is_multi_line=*/false));
+
+  // The phonetics text label is an optional child view in the header.
+  // Check that the phonetics text is not empty.
+  if (!definition_result_.phonetics_info.text.empty()) {
+    std::unique_ptr<QuickAnswersTextLabel> phonetics_label =
+        QuickAnswersTextLabel::CreateLabelWithStyle(
+            "/" + definition_result_.phonetics_info.text + "/",
+            GetFontList(TypographyToken::kCrosBody2), kContentTextWidth,
+            /*is_multi_line=*/false);
+
+    // Display the phonetics label in the header view if it fits, otherwise show
+    // it in a subheader view below.
+    int header_space_available = kDefinitionHeaderTextWidth -
+                                 word_label->CalculatePreferredSize().width();
+    int phonetics_label_width =
+        phonetics_label->CalculatePreferredSize().width() +
+        kContentSingleSpacing;
+    if (phonetics_label_width <= header_space_available) {
+      header_view->AddChildView(std::move(phonetics_label));
+    } else {
+      AddFillLayoutChildView(content_view_, std::move(phonetics_label));
+    }
   }
 
-  AddSettingsButtonTo(header_view);
+  AddPhoneticsAudioButtonTo(header_view);
+  views::View* settings_button_view = AddSettingsButtonTo(box_layout_view);
+
+  // Set flex weights so that the BoxLayoutView children views don't overlap.
+  // Delegate all remaining space to the `header_view`.
+  box_layout_view->SetFlexForView(header_view,
+                                  /*flex=*/1);
+  box_layout_view->SetFlexForView(settings_button_view,
+                                  /*flex=*/0);
+}
+
+void RichAnswersDefinitionView::AddPhoneticsAudioButtonTo(
+    views::View* container_view) {
+  // Setup an invisible web view to play TTS audio.
+  tts_audio_web_view_ = container_view->AddChildView(
+      std::make_unique<views::WebView>(ProfileManager::GetActiveUserProfile()));
+  tts_audio_web_view_->SetVisible(false);
+
+  base::RepeatingClosure phonetics_audio_button_closure = base::BindRepeating(
+      &RichAnswersDefinitionView::OnPhoneticsAudioButtonPressed,
+      weak_factory_.GetWeakPtr());
+  ui::ImageModel phonetics_audio_button_closure_image_model =
+      ui::ImageModel::FromVectorIcon(vector_icons::kVolumeUpIcon,
+                                     cros_tokens::kCrosSysOnSurface,
+                                     kRichAnswersIconSizeDip);
+  views::ImageButton* button_view =
+      container_view->AddChildView(CreateImageButtonView(
+          phonetics_audio_button_closure,
+          phonetics_audio_button_closure_image_model,
+          cros_tokens::kCrosSysHoverOnSubtle,
+          l10n_util::GetStringUTF16(
+              IDS_QUICK_ANSWERS_PHONETICS_BUTTON_TOOLTIP_TEXT)));
+  button_view->SetMinimumImageSize(
+      gfx::Size(kRichAnswersIconSizeDip, kRichAnswersIconSizeDip));
+}
+
+void RichAnswersDefinitionView::OnPhoneticsAudioButtonPressed() {
+  PhoneticsInfo phonetics_info = definition_result_.phonetics_info;
+  // Use the phonetics audio URL if provided.
+  if (!phonetics_info.phonetics_audio.is_empty()) {
+    tts_audio_web_view_->LoadInitialURL(phonetics_info.phonetics_audio);
+    return;
+  }
+
+  GenerateTTSAudio(tts_audio_web_view_->GetBrowserContext(),
+                   phonetics_info.query_text, phonetics_info.locale);
 }
 
 BEGIN_METADATA(RichAnswersDefinitionView, RichAnswersView)
diff --git a/chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.h b/chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.h
index 6dff6f1..c3a331a 100644
--- a/chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.h
+++ b/chrome/browser/ui/quick_answers/ui/rich_answers_definition_view.h
@@ -9,6 +9,7 @@
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/ui/quick_answers/ui/rich_answers_view.h"
 #include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/views/controls/webview/webview.h"
 #include "ui/views/view.h"
 
 namespace quick_answers {
@@ -31,8 +32,11 @@
  private:
   void InitLayout();
   void AddHeaderViews();
+  void AddPhoneticsAudioButtonTo(views::View* container_view);
+  void OnPhoneticsAudioButtonPressed();
 
   raw_ptr<views::View> content_view_ = nullptr;
+  raw_ptr<views::WebView> tts_audio_web_view_ = nullptr;
 
   DefinitionResult definition_result_;
 
diff --git a/chrome/browser/ui/quick_answers/ui/rich_answers_translation_view.cc b/chrome/browser/ui/quick_answers/ui/rich_answers_translation_view.cc
index 0e978bd..e7f4f48 100644
--- a/chrome/browser/ui/quick_answers/ui/rich_answers_translation_view.cc
+++ b/chrome/browser/ui/quick_answers/ui/rich_answers_translation_view.cc
@@ -49,7 +49,6 @@
 RichAnswersTranslationView::~RichAnswersTranslationView() = default;
 
 void RichAnswersTranslationView::InitLayout() {
-  // TODO (b/265258270): Populate translation view contents.
   content_view_ = GetContentView();
 
   // Source language.
@@ -81,13 +80,12 @@
   if (is_header_view) {
     AddHeaderViewsTo(content_view_, base::UTF16ToUTF8(locale_name));
   } else {
-    auto* second_header_view = AddFillLayoutChildView(
+    AddFillLayoutChildView(
         content_view_,
         QuickAnswersTextLabel::CreateLabelWithStyle(
             base::UTF16ToUTF8(locale_name),
             GetFontList(TypographyToken::kCrosButton2), kContentHeaderWidth,
             /*is_multi_line=*/false, cros_tokens::kCrosSysSecondary));
-    second_header_view->SetProperty(views::kMarginsKey, kUnderLineIndentation);
   }
 }
 
@@ -124,17 +122,15 @@
   // view as the translated text. Otherwise, add the buttons on a separate line
   // underneath.
   if (should_append_buttons_) {
-    translated_text_view->SetDefault(views::kMarginsKey, kViewSpacingMargins);
+    translated_text_view->SetDefault(views::kMarginsKey,
+                                     kViewHorizontalSpacingMargins);
     container_view = translated_text_view;
   } else {
     container_view = AddFillLayoutChildView(content_view_);
   }
 
-  auto* button_views = container_view->AddChildView(
-      views::Builder<views::FlexLayoutView>()
-          .SetOrientation(views::LayoutOrientation::kHorizontal)
-          .Build());
-  button_views->SetDefault(views::kMarginsKey, kViewSpacingMargins);
+  auto* button_views =
+      container_view->AddChildView(CreateHorizontalLayoutView());
 
   // Setup an invisible web view to play TTS audio.
   tts_audio_web_view_ = container_view->AddChildView(
@@ -143,8 +139,9 @@
 
   // Read button.
   base::RepeatingClosure read_closure = base::BindRepeating(
-      &RichAnswersTranslationView::OnReadButtonPressed, base::Unretained(this),
-      translation_result_.translated_text, translation_result_.target_locale);
+      &RichAnswersTranslationView::OnReadButtonPressed,
+      weak_factory_.GetWeakPtr(), translation_result_.translated_text,
+      translation_result_.target_locale);
   ui::ImageModel read_image_model = ui::ImageModel::FromVectorIcon(
       vector_icons::kVolumeUpIcon, cros_tokens::kCrosSysOnSurface,
       /*icon_size=*/kRichAnswersIconSizeDip);
@@ -155,8 +152,8 @@
 
   // Copy button.
   base::RepeatingClosure copy_closure = base::BindRepeating(
-      &RichAnswersTranslationView::OnCopyButtonPressed, base::Unretained(this),
-      translation_result_.translated_text);
+      &RichAnswersTranslationView::OnCopyButtonPressed,
+      weak_factory_.GetWeakPtr(), translation_result_.translated_text);
   ui::ImageModel copy_image_model = ui::ImageModel::FromVectorIcon(
       vector_icons::kContentCopyIcon, cros_tokens::kCrosSysOnSurface,
       /*icon_size=*/kRichAnswersIconSizeDip);
diff --git a/chrome/browser/ui/quick_answers/ui/rich_answers_view.cc b/chrome/browser/ui/quick_answers/ui/rich_answers_view.cc
index 0cbd680..9b46286 100644
--- a/chrome/browser/ui/quick_answers/ui/rich_answers_view.cc
+++ b/chrome/browser/ui/quick_answers/ui/rich_answers_view.cc
@@ -63,7 +63,7 @@
 constexpr auto kContentViewInsets = gfx::Insets::TLBR(0, 16, 0, 0);
 
 // Buttons view.
-constexpr auto kSettingsButtonInsets = gfx::Insets::TLBR(0, 8, 8, 0);
+constexpr auto kSettingsButtonInsets = gfx::Insets::TLBR(0, 8, 0, 0);
 constexpr int kSettingsButtonSizeDip = 20;
 
 // Border corner radius.
@@ -247,14 +247,13 @@
       /*icon_size=*/kRichAnswersIconSizeDip));
 }
 
-void RichAnswersView::AddSettingsButtonTo(views::View* container_view) {
+views::View* RichAnswersView::AddSettingsButtonTo(views::View* container_view) {
   CHECK(container_view);
 
   auto* buttons_view = container_view->AddChildView(
       views::Builder<views::FlexLayoutView>()
           .SetOrientation(views::LayoutOrientation::kHorizontal)
           .SetMainAxisAlignment(views::LayoutAlignment::kEnd)
-          .SetCrossAxisAlignment(views::LayoutAlignment::kStart)
           .SetInteriorMargin(kSettingsButtonInsets)
           .Build());
 
@@ -268,6 +267,8 @@
                                      /*icon_size=*/kSettingsButtonSizeDip));
   settings_button_->SetTooltipText(l10n_util::GetStringUTF16(
       IDS_QUICK_ANSWERS_SETTINGS_BUTTON_TOOLTIP_TEXT));
+
+  return buttons_view;
 }
 
 void RichAnswersView::AddHeaderViewsTo(views::View* container_view,
diff --git a/chrome/browser/ui/quick_answers/ui/rich_answers_view.h b/chrome/browser/ui/quick_answers/ui/rich_answers_view.h
index 17a4666..f6893c4 100644
--- a/chrome/browser/ui/quick_answers/ui/rich_answers_view.h
+++ b/chrome/browser/ui/quick_answers/ui/rich_answers_view.h
@@ -74,7 +74,7 @@
                   base::WeakPtr<QuickAnswersUiController> controller,
                   const ResultType result_type);
 
-  void AddSettingsButtonTo(views::View* container_view);
+  views::View* AddSettingsButtonTo(views::View* container_view);
 
   void AddHeaderViewsTo(views::View* container_view,
                         const std::string& header_text);
diff --git a/chrome/browser/ui/tab_helpers.cc b/chrome/browser/ui/tab_helpers.cc
index bdf33f24..09d8842 100644
--- a/chrome/browser/ui/tab_helpers.cc
+++ b/chrome/browser/ui/tab_helpers.cc
@@ -246,10 +246,6 @@
 #include "components/captive_portal/content/captive_portal_tab_helper.h"
 #endif
 
-#if BUILDFLAG(ENABLE_COMPOSE)
-#include "chrome/browser/compose/chrome_compose_client.h"
-#endif
-
 #if BUILDFLAG(ENABLE_EXTENSIONS)
 #include "chrome/browser/extensions/api/web_navigation/web_navigation_api.h"
 #include "chrome/browser/extensions/navigation_extension_enabler.h"
@@ -290,6 +286,7 @@
 
 #if BUILDFLAG(ENABLE_COMPOSE)
 #include "chrome/browser/compose/chrome_compose_client.h"
+#include "chrome/browser/compose/compose_enabling.h"
 #include "components/compose/buildflags.h"
 #include "components/compose/core/browser/compose_features.h"
 #endif
@@ -626,7 +623,7 @@
 #endif  // BUILDFLAG(IS_ANDROID)
 
 #if BUILDFLAG(ENABLE_COMPOSE)
-  if (base::FeatureList::IsEnabled(compose::features::kEnableCompose) &&
+  if ((ComposeEnabling::IsEnabledForProfile(profile)) &&
       !profile->IsOffTheRecord()) {
     ChromeComposeClient::CreateForWebContents(web_contents);
   }
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc
index 14092ec6..bdc90f6d 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.cc
+++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -863,9 +863,17 @@
 
 void AppMenuModel::LogSafetyHubInteractionMetrics(
     std::optional<safety_hub::SafetyHubModuleType> expected_module) {
-  auto const* safety_hub_menu_notification_service =
+  // TODO(crbug.com/1443466): Remove when the service is only created when the
+  // feature is enabled.
+  if (!base::FeatureList::IsEnabled(features::kSafetyHub)) {
+    return;
+  }
+  auto* const safety_hub_menu_notification_service =
       SafetyHubMenuNotificationServiceFactory::GetForProfile(
           browser_->profile());
+  if (!safety_hub_menu_notification_service) {
+    return;
+  }
   std::optional<safety_hub::SafetyHubModuleType> sh_module =
       safety_hub_menu_notification_service->GetModuleOfActiveNotification();
   if (sh_module.has_value() && (!expected_module.has_value() ||
@@ -1557,37 +1565,9 @@
 #endif
   }
 
-  if (base::FeatureList::IsEnabled(features::kSafetyHub) &&
-      !browser_->profile()->IsGuestSession() &&
-      !browser_->profile()->IsIncognitoProfile()) {
-    auto* safety_hub_menu_notification_service =
-        SafetyHubMenuNotificationServiceFactory::GetForProfile(
-            browser_->profile());
-    std::optional<MenuNotificationEntry> notification =
-        safety_hub_menu_notification_service->GetNotificationToShow();
-    if (notification.has_value()) {
-      base::UmaHistogramEnumeration(
-          "Settings.SafetyHub.Impression",
-          safety_hub::SafetyHubSurfaces::kThreeDotMenu);
-      base::UmaHistogramEnumeration(
-          "Settings.SafetyHub.EntryPointImpression",
-          safety_hub::SafetyHubEntryPoint::kMenuNotifications);
-      std::optional<safety_hub::SafetyHubModuleType> sh_module =
-          safety_hub_menu_notification_service->GetModuleOfActiveNotification();
-      if (sh_module.has_value()) {
-        base::UmaHistogramEnumeration(
-            "Settings.SafetyHub.MenuNotificationImpression", sh_module.value());
-      }
-      const auto safety_hub_icon = ui::ImageModel::FromVectorIcon(
-          kSecurityIcon, ui::kColorMenuIcon, kDefaultIconSize);
-      AddItemWithIcon(notification->command, notification->label,
-                      safety_hub_icon);
-      need_separator = true;
-    }
-  }
-
-  if (AddGlobalErrorMenuItems() || need_separator)
+  if (AddSafetyHubMenuItem() || AddGlobalErrorMenuItems() || need_separator) {
     AddSeparator(ui::NORMAL_SEPARATOR);
+  }
 
   AddItemWithStringId(IDC_NEW_TAB,
                       browser_->profile()->IsIncognitoProfile() &&
@@ -1941,6 +1921,40 @@
   return menu_items_added;
 }
 
+bool AppMenuModel::AddSafetyHubMenuItem() {
+  // TODO(crbug.com/1443466): Remove when the service is only created when the
+  // feature is enabled.
+  if (!base::FeatureList::IsEnabled(features::kSafetyHub)) {
+    return false;
+  }
+  auto* const safety_hub_menu_notification_service =
+      SafetyHubMenuNotificationServiceFactory::GetForProfile(
+          browser_->profile());
+  if (!safety_hub_menu_notification_service) {
+    return false;
+  }
+  std::optional<MenuNotificationEntry> notification =
+      safety_hub_menu_notification_service->GetNotificationToShow();
+  if (!notification.has_value()) {
+    return false;
+  }
+  base::UmaHistogramEnumeration("Settings.SafetyHub.Impression",
+                                safety_hub::SafetyHubSurfaces::kThreeDotMenu);
+  base::UmaHistogramEnumeration(
+      "Settings.SafetyHub.EntryPointImpression",
+      safety_hub::SafetyHubEntryPoint::kMenuNotifications);
+  std::optional<safety_hub::SafetyHubModuleType> sh_module =
+      safety_hub_menu_notification_service->GetModuleOfActiveNotification();
+  if (sh_module.has_value()) {
+    base::UmaHistogramEnumeration(
+        "Settings.SafetyHub.MenuNotificationImpression", sh_module.value());
+  }
+  const auto safety_hub_icon = ui::ImageModel::FromVectorIcon(
+      kSecurityIcon, ui::kColorMenuIcon, kDefaultIconSize);
+  AddItemWithIcon(notification->command, notification->label, safety_hub_icon);
+  return true;
+}
+
 #if BUILDFLAG(IS_CHROMEOS)
 void AppMenuModel::UpdateSettingsItemState() {
   bool is_disabled =
diff --git a/chrome/browser/ui/toolbar/app_menu_model.h b/chrome/browser/ui/toolbar/app_menu_model.h
index 0e3b6a7..11344e8 100644
--- a/chrome/browser/ui/toolbar/app_menu_model.h
+++ b/chrome/browser/ui/toolbar/app_menu_model.h
@@ -262,6 +262,10 @@
   // Returns a boolean indicating whether any menu items were added.
   bool AddGlobalErrorMenuItems();
 
+  // Adds the Safety Hub menu notifications to the menu. Returns a boolean
+  // indicating whether any menu items were added.
+  [[nodiscard]] bool AddSafetyHubMenuItem();
+
 #if BUILDFLAG(IS_CHROMEOS)
   // Disables/Enables the settings item based on kSystemFeaturesDisableList
   // pref.
diff --git a/chrome/browser/ui/views/extensions/chooser_dialog_view.cc b/chrome/browser/ui/views/extensions/chooser_dialog_view.cc
index 6ec4f065..c626204 100644
--- a/chrome/browser/ui/views/extensions/chooser_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/chooser_dialog_view.cc
@@ -95,7 +95,7 @@
   DialogModelChanged();
 }
 
-BEGIN_METADATA(ChooserDialogView, views::DialogDelegateView)
+BEGIN_METADATA(ChooserDialogView)
 END_METADATA
 
 void ShowConstrainedDeviceChooserDialog(
diff --git a/chrome/browser/ui/views/extensions/chooser_dialog_view.h b/chrome/browser/ui/views/extensions/chooser_dialog_view.h
index 781de10..70cca99 100644
--- a/chrome/browser/ui/views/extensions/chooser_dialog_view.h
+++ b/chrome/browser/ui/views/extensions/chooser_dialog_view.h
@@ -22,8 +22,9 @@
 // to the window/tab displaying the given web contents.
 class ChooserDialogView : public views::DialogDelegateView,
                           public views::TableViewObserver {
+  METADATA_HEADER(ChooserDialogView, views::DialogDelegateView)
+
  public:
-  METADATA_HEADER(ChooserDialogView);
   explicit ChooserDialogView(
       std::unique_ptr<permissions::ChooserController> chooser_controller);
   ChooserDialogView(const ChooserDialogView&) = delete;
diff --git a/chrome/browser/ui/views/extensions/extension_install_friction_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_install_friction_dialog_view.cc
index 411ab19d..33bd8d6a 100644
--- a/chrome/browser/ui/views/extensions/extension_install_friction_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_friction_dialog_view.cc
@@ -208,6 +208,5 @@
   OnLearnMoreLinkClicked();
 }
 
-BEGIN_METADATA(ExtensionInstallFrictionDialogView,
-               views::BubbleDialogDelegateView)
+BEGIN_METADATA(ExtensionInstallFrictionDialogView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extension_install_friction_dialog_view.h b/chrome/browser/ui/views/extensions/extension_install_friction_dialog_view.h
index 00da7bb..a40c7a35 100644
--- a/chrome/browser/ui/views/extensions/extension_install_friction_dialog_view.h
+++ b/chrome/browser/ui/views/extensions/extension_install_friction_dialog_view.h
@@ -26,9 +26,10 @@
 // allowlist.
 class ExtensionInstallFrictionDialogView
     : public views::BubbleDialogDelegateView {
- public:
-  METADATA_HEADER(ExtensionInstallFrictionDialogView);
+  METADATA_HEADER(ExtensionInstallFrictionDialogView,
+                  views::BubbleDialogDelegateView)
 
+ public:
   // `web_contents` ownership is not passed, `callback` will be invoked with
   // `true` if the user accepts or `false` if the user cancels.
   ExtensionInstallFrictionDialogView(content::WebContents* web_contents,
diff --git a/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc b/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
index c0045d8..de854433 100644
--- a/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_installed_bubble_view.cc
@@ -105,8 +105,9 @@
 //                      specify a default icon.
 class ExtensionInstalledBubbleView : public BubbleSyncPromoDelegate,
                                      public views::BubbleDialogDelegateView {
+  METADATA_HEADER(ExtensionInstalledBubbleView, views::BubbleDialogDelegateView)
+
  public:
-  METADATA_HEADER(ExtensionInstalledBubbleView);
   ExtensionInstalledBubbleView(
       Browser* browser,
       std::unique_ptr<ExtensionInstalledBubbleModel> model);
@@ -258,7 +259,7 @@
   GetWidget()->Close();
 }
 
-BEGIN_METADATA(ExtensionInstalledBubbleView, views::BubbleDialogDelegateView)
+BEGIN_METADATA(ExtensionInstalledBubbleView)
 END_METADATA
 
 void ShowUiOnToolbarMenu(scoped_refptr<const extensions::Extension> extension,
diff --git a/chrome/browser/ui/views/extensions/extension_permissions_view.cc b/chrome/browser/ui/views/extensions/extension_permissions_view.cc
index 9571fc7..52e3976 100644
--- a/chrome/browser/ui/views/extensions/extension_permissions_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_permissions_view.cc
@@ -49,5 +49,5 @@
   PreferredSizeChanged();
 }
 
-BEGIN_METADATA(ExtensionPermissionsView, views::View)
+BEGIN_METADATA(ExtensionPermissionsView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extension_permissions_view.h b/chrome/browser/ui/views/extensions/extension_permissions_view.h
index c36078c..fd5ce09 100644
--- a/chrome/browser/ui/views/extensions/extension_permissions_view.h
+++ b/chrome/browser/ui/views/extensions/extension_permissions_view.h
@@ -18,8 +18,9 @@
 // the labels for each permission and the views for their associated details, if
 // there are any.
 class ExtensionPermissionsView : public views::View {
+  METADATA_HEADER(ExtensionPermissionsView, views::View)
+
  public:
-  METADATA_HEADER(ExtensionPermissionsView);
   explicit ExtensionPermissionsView(int available_width);
   ExtensionPermissionsView(const ExtensionPermissionsView&) = delete;
   ExtensionPermissionsView& operator=(const ExtensionPermissionsView&) = delete;
diff --git a/chrome/browser/ui/views/extensions/extension_popup.cc b/chrome/browser/ui/views/extensions/extension_popup.cc
index c121ca4..cf9c933 100644
--- a/chrome/browser/ui/views/extensions/extension_popup.cc
+++ b/chrome/browser/ui/views/extensions/extension_popup.cc
@@ -413,5 +413,5 @@
   CloseDeferredIfNecessary();
 }
 
-BEGIN_METADATA(ExtensionPopup, views::BubbleDialogDelegateView)
+BEGIN_METADATA(ExtensionPopup)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extension_popup.h b/chrome/browser/ui/views/extensions/extension_popup.h
index f25e5f1b..5009d04 100644
--- a/chrome/browser/ui/views/extensions/extension_popup.h
+++ b/chrome/browser/ui/views/extensions/extension_popup.h
@@ -48,9 +48,9 @@
                        public content::WebContentsObserver,
                        public TabStripModelObserver,
                        public content::DevToolsAgentHostObserver {
- public:
-  METADATA_HEADER(ExtensionPopup);
+  METADATA_HEADER(ExtensionPopup, views::BubbleDialogDelegateView)
 
+ public:
   // The min/max height of popups.
   // The minimum is just a little larger than the size of the button itself.
   // The maximum is an arbitrary number and should be smaller than most screens.
diff --git a/chrome/browser/ui/views/extensions/extension_view_views.cc b/chrome/browser/ui/views/extensions/extension_view_views.cc
index ded7d0c6..95fe0d8d 100644
--- a/chrome/browser/ui/views/extensions/extension_view_views.cc
+++ b/chrome/browser/ui/views/extensions/extension_view_views.cc
@@ -163,7 +163,7 @@
   SetVisible(false);
 }
 
-BEGIN_METADATA(ExtensionViewViews, views::WebView)
+BEGIN_METADATA(ExtensionViewViews)
 ADD_PROPERTY_METADATA(gfx::Size, MinimumSize)
 ADD_PROPERTY_METADATA(ExtensionViewViews::Container*, Container)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extension_view_views.h b/chrome/browser/ui/views/extensions/extension_view_views.h
index 671c008..77ccc545 100644
--- a/chrome/browser/ui/views/extensions/extension_view_views.h
+++ b/chrome/browser/ui/views/extensions/extension_view_views.h
@@ -25,8 +25,9 @@
 // This handles the display portion of an ExtensionHost.
 class ExtensionViewViews : public views::WebView,
                            public extensions::ExtensionView {
+  METADATA_HEADER(ExtensionViewViews, views::WebView)
+
  public:
-  METADATA_HEADER(ExtensionViewViews);
   // A class that represents the container that this view is in.
   // (bottom shelf, side bar, etc.)
   class Container {
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_button.cc b/chrome/browser/ui/views/extensions/extensions_menu_button.cc
index 42a68b1d..8eaf944 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_button.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_button.cc
@@ -97,5 +97,5 @@
       ToolbarActionViewController::InvocationSource::kMenuEntry);
 }
 
-BEGIN_METADATA(ExtensionsMenuButton, views::LabelButton)
+BEGIN_METADATA(ExtensionsMenuButton)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_button.h b/chrome/browser/ui/views/extensions/extensions_menu_button.h
index a5cedfa..131709d 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_button.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_button.h
@@ -25,8 +25,9 @@
 // the extension action.
 class ExtensionsMenuButton : public HoverButton,
                              public ToolbarActionViewDelegateViews {
+  METADATA_HEADER(ExtensionsMenuButton, HoverButton)
+
  public:
-  METADATA_HEADER(ExtensionsMenuButton);
   ExtensionsMenuButton(Browser* browser,
                        ToolbarActionViewController* controller);
   ExtensionsMenuButton(const ExtensionsMenuButton&) = delete;
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_item_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_item_view.cc
index 02a586f..108c3200f 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_item_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_item_view.cc
@@ -502,5 +502,5 @@
   return context_menu_controller_->IsMenuRunning();
 }
 
-BEGIN_METADATA(ExtensionMenuItemView, views::View)
+BEGIN_METADATA(ExtensionMenuItemView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_item_view.h b/chrome/browser/ui/views/extensions/extensions_menu_item_view.h
index 453a5db..708ac03 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_item_view.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_item_view.h
@@ -30,9 +30,9 @@
 // information about the extension, a button to pin the extension to the toolbar
 // and a button for accessing the associated context menu.
 class ExtensionMenuItemView : public views::FlexLayoutView {
- public:
-  METADATA_HEADER(ExtensionMenuItemView);
+  METADATA_HEADER(ExtensionMenuItemView, views::FlexLayoutView)
 
+ public:
   enum class SiteAccessToggleState {
     // Button is not visible.
     kHidden,
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
index 5983b06f..7d3bf72 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.cc
@@ -780,5 +780,5 @@
   return browser_->tab_strip_model()->GetActiveWebContents();
 }
 
-BEGIN_METADATA(ExtensionsMenuMainPageView, views::View)
+BEGIN_METADATA(ExtensionsMenuMainPageView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
index e467e9a4de..732a880 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_main_page_view.h
@@ -34,9 +34,9 @@
 
 // The main view of the extensions menu.
 class ExtensionsMenuMainPageView : public views::View {
- public:
-  METADATA_HEADER(ExtensionsMenuMainPageView);
+  METADATA_HEADER(ExtensionsMenuMainPageView, views::View)
 
+ public:
   enum class MessageSectionState {
     // Site is restricted to all extensions.
     kRestrictedAccess,
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.cc
index 4d5b519..aff7e68 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.cc
@@ -433,5 +433,5 @@
   return site_access_buttons[GetSiteAccessButtonIndex(site_access)];
 }
 
-BEGIN_METADATA(ExtensionsMenuSitePermissionsPageView, views::View)
+BEGIN_METADATA(ExtensionsMenuSitePermissionsPageView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h b/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h
index f42d7a4..f96c3c4 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_site_permissions_page_view.h
@@ -25,9 +25,9 @@
 class ExtensionsMenuHandler;
 
 class ExtensionsMenuSitePermissionsPageView : public views::View {
- public:
-  METADATA_HEADER(ExtensionsMenuSitePermissionsPageView);
+  METADATA_HEADER(ExtensionsMenuSitePermissionsPageView, views::View)
 
+ public:
   explicit ExtensionsMenuSitePermissionsPageView(
       Browser* browser,
       extensions::ExtensionId extension_id,
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
index b5a8035..b4992cc 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
@@ -538,5 +538,5 @@
   return menu_item_views;
 }
 
-BEGIN_METADATA(ExtensionsMenuView, views::BubbleDialogDelegateView)
+BEGIN_METADATA(ExtensionsMenuView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.h b/chrome/browser/ui/views/extensions/extensions_menu_view.h
index 4118f3a..346d362 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.h
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.h
@@ -36,8 +36,9 @@
 class ExtensionsMenuView : public views::BubbleDialogDelegateView,
                            public TabStripModelObserver,
                            public ToolbarActionsModel::Observer {
+  METADATA_HEADER(ExtensionsMenuView, views::BubbleDialogDelegateView)
+
  public:
-  METADATA_HEADER(ExtensionsMenuView);
   ExtensionsMenuView(views::View* anchor_view,
                      Browser* browser,
                      ExtensionsContainer* extensions_container);
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index 1692aace..6a2fd6f7 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -1143,5 +1143,5 @@
       is_right_aligned ? kRightPanelCloseIcon : kLeftPanelCloseIcon);
 }
 
-BEGIN_METADATA(ExtensionsToolbarContainer, ToolbarIconContainerView)
+BEGIN_METADATA(ExtensionsToolbarContainer)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index 75ad017..c17a2f812 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -44,9 +44,9 @@
       public ToolbarActionView::Delegate,
       public views::WidgetObserver,
       public extensions::PermissionsManager::Observer {
- public:
-  METADATA_HEADER(ExtensionsToolbarContainer);
+  METADATA_HEADER(ExtensionsToolbarContainer, ToolbarIconContainerView)
 
+ public:
   using ToolbarIcons =
       std::map<ToolbarActionsModel::ActionId, ToolbarActionView*>;
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
index 822ed5d90..17df125c 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.cc
@@ -124,5 +124,5 @@
   return request_access_button_->IsShowingConfirmationFor(origin);
 }
 
-BEGIN_METADATA(ExtensionsToolbarControls, ToolbarIconContainerView)
+BEGIN_METADATA(ExtensionsToolbarControls)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
index 8e5a8e0..be9b7757 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_controls.h
@@ -21,9 +21,9 @@
 class ToolbarActionViewController;
 
 class ExtensionsToolbarControls : public ToolbarIconContainerView {
- public:
-  METADATA_HEADER(ExtensionsToolbarControls);
+  METADATA_HEADER(ExtensionsToolbarControls, ToolbarIconContainerView)
 
+ public:
   explicit ExtensionsToolbarControls(
       std::unique_ptr<ExtensionsToolbarButton> extensions_button,
       std::unique_ptr<ExtensionsRequestAccessButton> request_button);
diff --git a/chrome/browser/ui/views/extensions/media_gallery_checkbox_view.cc b/chrome/browser/ui/views/extensions/media_gallery_checkbox_view.cc
index af91251..d8760db 100644
--- a/chrome/browser/ui/views/extensions/media_gallery_checkbox_view.cc
+++ b/chrome/browser/ui/views/extensions/media_gallery_checkbox_view.cc
@@ -82,5 +82,5 @@
   }
 }
 
-BEGIN_METADATA(MediaGalleryCheckboxView, views::BoxLayoutView)
+BEGIN_METADATA(MediaGalleryCheckboxView)
 END_METADATA
diff --git a/chrome/browser/ui/views/extensions/media_gallery_checkbox_view.h b/chrome/browser/ui/views/extensions/media_gallery_checkbox_view.h
index f5195b9..3b9821eb 100644
--- a/chrome/browser/ui/views/extensions/media_gallery_checkbox_view.h
+++ b/chrome/browser/ui/views/extensions/media_gallery_checkbox_view.h
@@ -24,8 +24,9 @@
 // text that will elide to its parent's width. Used by
 // MediaGalleriesDialogViews.
 class MediaGalleryCheckboxView : public views::BoxLayoutView {
+  METADATA_HEADER(MediaGalleryCheckboxView, views::BoxLayoutView)
+
  public:
-  METADATA_HEADER(MediaGalleryCheckboxView);
   MediaGalleryCheckboxView(const MediaGalleryPrefInfo& pref_info,
                            int trailing_vertical_space,
                            views::ContextMenuController* menu_controller);
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
index 2710091..b3bc2b91 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.cc
@@ -78,12 +78,15 @@
 }
 
 void ImmersiveModeControllerChromeos::SetEnabled(bool enabled) {
-  if (controller_.IsEnabled() == enabled) {
-    // TODO(crbug.com/1505996): Remove this comments when the performance check
-    // has completed.
-    LOG(WARNING) << "Sending immersive again while the state is the same: "
-                 << (enabled ? "enabled." : "disabled.");
+  // If `enabled` is same as the state that has requested previously, do not
+  // request the state change again. Note that we should compare this against
+  // the previously requested state instead of the current state since the state
+  // change happesn asynchronously on Lacros so that the current state might not
+  // yet synchronized to the latest request.
+  if (previous_request_enabled_ == enabled) {
+    return;
   }
+  previous_request_enabled_ = enabled;
 
   if (!fullscreen_observer_.IsObserving()) {
     fullscreen_observer_.Observe(browser_view_->browser()
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h
index 53345cc..5e40a7b 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h
@@ -81,6 +81,11 @@
   // an empty rect if the find bar is not visible.
   gfx::Rect find_bar_visible_bounds_in_screen_;
 
+  // Records the previous immersive state requested from SetEnabled. This state
+  // is not always the same as `controller_.IsEnabled()` since the state
+  // transition happesn asynchronously on Lacros.
+  bool previous_request_enabled_ = false;
+
   // The fraction of the TopContainerView's height which is visible. Zero when
   // the top-of-window views are not revealed.
   double visible_fraction_ = 1.0;
diff --git a/chrome/browser/ui/views/toolbar/app_menu_browsertest.cc b/chrome/browser/ui/views/toolbar/app_menu_browsertest.cc
index af04796..6d694a1 100644
--- a/chrome/browser/ui/views/toolbar/app_menu_browsertest.cc
+++ b/chrome/browser/ui/views/toolbar/app_menu_browsertest.cc
@@ -93,21 +93,24 @@
   }
 
   constexpr auto kSubmenus = base::MakeFixedFlatMap<base::StringPiece, int>({
-    // Submenus present in all versions.
-    {"history", IDC_RECENT_TABS_MENU}, {"bookmarks", IDC_BOOKMARKS_MENU},
-        {"more_tools", IDC_MORE_TOOLS_MENU},
+      // Submenus present in all versions.
+      {"history", IDC_RECENT_TABS_MENU},
+      {"bookmarks", IDC_BOOKMARKS_MENU},
+      {"more_tools", IDC_MORE_TOOLS_MENU},
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-        {"help", IDC_HELP_MENU},
+      {"help", IDC_HELP_MENU},
 #endif
 
-        // Submenus only present after Chrome Refresh.
-        {"passwords_and_autofill", IDC_PASSWORDS_AND_AUTOFILL_MENU},
-        {"reading_list", IDC_READING_LIST_MENU},  // Inside the bookmarks menu.
-        {"extensions", IDC_EXTENSIONS_SUBMENU},
-        {"find_and_edit", IDC_FIND_AND_EDIT_MENU},
-        {"save_and_share", IDC_SAVE_AND_SHARE_MENU},
-        {"profile_menu_in_app", IDC_PROFILE_MENU_IN_APP_MENU},
-        {"signin_not_allowed", IDC_PROFILE_MENU_IN_APP_MENU},
+      // Submenus only present after Chrome Refresh.
+      {"passwords_and_autofill", IDC_PASSWORDS_AND_AUTOFILL_MENU},
+      {"reading_list", IDC_READING_LIST_MENU},  // Inside the bookmarks menu.
+      {"extensions", IDC_EXTENSIONS_SUBMENU},
+      {"find_and_edit", IDC_FIND_AND_EDIT_MENU},
+      {"save_and_share", IDC_SAVE_AND_SHARE_MENU},
+      {"profile_menu_in_app_menu_signed_out", IDC_PROFILE_MENU_IN_APP_MENU},
+      {"profile_menu_in_app_menu_signed_in", IDC_PROFILE_MENU_IN_APP_MENU},
+      {"profile_menu_in_app_menu_signin_not_allowed",
+       IDC_PROFILE_MENU_IN_APP_MENU},
   });
   const auto* const id_entry = kSubmenus.find(name);
   if (id_entry == kSubmenus.end()) {
@@ -292,14 +295,18 @@
 }
 
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
+
 IN_PROC_BROWSER_TEST_F(AppMenuBrowserTestRefreshOnly,
-                       InvokeUi_profile_menu_in_app) {
+                       InvokeUi_main_profile_signed_in) {
   signin::IdentityManager* identity_manager =
       IdentityManagerFactory::GetForProfile(browser()->profile());
-  signin::SetPrimaryAccount(identity_manager, "user@example.com",
-                            signin::ConsentLevel::kSignin);
+  signin::MakePrimaryAccountAvailable(identity_manager, "user@example.com",
+                                      signin::ConsentLevel::kSignin);
+  ShowAndVerifyUi();
+}
 
-  // Create an additional profile.
+IN_PROC_BROWSER_TEST_F(AppMenuBrowserTestRefreshOnly,
+                       InvokeUi_profile_menu_in_app_menu_signed_out) {
   ProfileManager* profile_manager = g_browser_process->profile_manager();
   base::FilePath new_path = profile_manager->GenerateNextProfileDirectoryPath();
   profiles::testing::CreateProfileSync(profile_manager, new_path);
@@ -307,7 +314,16 @@
 }
 
 IN_PROC_BROWSER_TEST_F(AppMenuBrowserTestRefreshOnly,
-                       InvokeUi_signin_not_allowed) {
+                       InvokeUi_profile_menu_in_app_menu_signed_in) {
+  signin::IdentityManager* identity_manager =
+      IdentityManagerFactory::GetForProfile(browser()->profile());
+  signin::MakePrimaryAccountAvailable(identity_manager, "user@example.com",
+                                      signin::ConsentLevel::kSignin);
+  ShowAndVerifyUi();
+}
+
+IN_PROC_BROWSER_TEST_F(AppMenuBrowserTestRefreshOnly,
+                       InvokeUi_profile_menu_in_app_menu_signin_not_allowed) {
   browser()->profile()->GetPrefs()->SetBoolean(prefs::kSigninAllowed, false);
   ShowAndVerifyUi();
 }
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_coordinator.cc b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_coordinator.cc
index 366a03c6..2fe1057 100644
--- a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_coordinator.cc
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_coordinator.cc
@@ -8,6 +8,7 @@
 #include <optional>
 
 #include "base/files/file_path.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/memory/ptr_util.h"
@@ -45,8 +46,9 @@
 
 void IsolatedWebAppInstallerCoordinator::Show(
     base::OnceCallback<void(std::optional<webapps::AppId>)> callback) {
-  controller_->Start();
-  controller_->Show(
+  controller_->Start(
+      base::BindOnce(&IsolatedWebAppInstallerViewController::Show,
+                     base::Unretained(controller_.get())),
       base::BindOnce(&IsolatedWebAppInstallerCoordinator::OnDialogClosed,
                      base::Unretained(this), std::move(callback)));
 }
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_browsertest.cc b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_browsertest.cc
index 4b9536c..dfb31a4 100644
--- a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_browsertest.cc
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/files/file_path.h"
 #include "base/functional/callback_helpers.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "base/version.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -79,13 +80,18 @@
   // `TestBrowserDialog`:
   void ShowUi(const std::string& name) override {
     IsolatedWebAppInstallerModel model{base::FilePath()};
-    model.SetStep(GetParam().step);
-    model.SetSignedWebBundleMetadata(CreateTestMetadata());
 
     Profile* profile = browser()->profile();
     IsolatedWebAppInstallerViewController controller{
         profile, WebAppProvider::GetForWebApps(profile), &model};
-    controller.Show(base::DoNothing());
+
+    base::test::TestFuture<void> future;
+    controller.Start(future.GetCallback(), base::DoNothing());
+    ASSERT_TRUE(future.Wait());
+
+    model.SetStep(GetParam().step);
+    model.SetSignedWebBundleMetadata(CreateTestMetadata());
+    controller.Show();
   }
 
  private:
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc
index 714585d5..654b3436 100644
--- a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <optional>
 
+#include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/time/time.h"
 #include "chrome/browser/profiles/profile.h"
@@ -14,6 +15,7 @@
 #include "chrome/browser/ui/views/web_apps/isolated_web_apps/callback_delayer.h"
 #include "chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_model.h"
 #include "chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view.h"
+#include "chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.h"
 #include "chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.h"
 #include "chrome/browser/web_applications/isolated_web_apps/signed_web_bundle_metadata.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
@@ -125,22 +127,45 @@
       web_app_provider_(web_app_provider),
       model_(model),
       view_(nullptr),
-      dialog_delegate_(nullptr) {}
+      dialog_delegate_(nullptr) {
+  CHECK(profile_);
+  CHECK(model_);
+  CHECK(web_app_provider_);
+}
 
 IsolatedWebAppInstallerViewController::
     ~IsolatedWebAppInstallerViewController() = default;
 
-void IsolatedWebAppInstallerViewController::Start() {
-  // TODO(crbug.com/1479140): Check if Feature is enabled
-  OnPrefChanged(true);
+void IsolatedWebAppInstallerViewController::Start(
+    base::OnceClosure initialized_callback,
+    base::OnceClosure completion_callback) {
+  CHECK(initialized_callback);
+  initialized_callback_ = std::move(initialized_callback);
+
+  CHECK(completion_callback);
+  completion_callback_ = std::move(completion_callback);
+
+  CHECK(!pref_observer_);
+  // Upon creation, the observer will invoke callback with initial pref value.
+  pref_observer_ = IsolatedWebAppsEnabledPrefObserver::
+      CreateIsolatedWebAppsEnabledPrefObserver(
+          profile_, base::BindRepeating(
+                        &IsolatedWebAppInstallerViewController::OnPrefChanged,
+                        weak_ptr_factory_.GetWeakPtr()));
 }
 
-void IsolatedWebAppInstallerViewController::Show(base::OnceClosure callback) {
-  CHECK(!callback_);
-  callback_ = std::move(callback);
+void IsolatedWebAppInstallerViewController::SetViewForTesting(
+    IsolatedWebAppInstallerView* view) {
+  view_ = view;
+}
+
+void IsolatedWebAppInstallerViewController::Show() {
+  CHECK(is_initialized_) << "Show() is being called before initialized.";
+  CHECK(!view_) << "Show() should not be called twice";
 
   auto view = IsolatedWebAppInstallerView::Create(this);
   view_ = view.get();
+
   std::unique_ptr<views::DialogDelegate> dialog_delegate =
       CreateDialogDelegate(std::move(view));
   dialog_delegate_ = dialog_delegate.get();
@@ -153,11 +178,6 @@
       ->Show();
 }
 
-void IsolatedWebAppInstallerViewController::SetViewForTesting(
-    IsolatedWebAppInstallerView* view) {
-  view_ = view;
-}
-
 // static
 bool IsolatedWebAppInstallerViewController::OnAcceptWrapper(
     base::WeakPtr<IsolatedWebAppInstallerViewController> controller) {
@@ -211,7 +231,7 @@
 void IsolatedWebAppInstallerViewController::OnComplete() {
   view_ = nullptr;
   dialog_delegate_ = nullptr;
-  std::move(callback_).Run();
+  std::move(completion_callback_).Run();
 }
 
 void IsolatedWebAppInstallerViewController::Close() {
@@ -242,11 +262,15 @@
     // complete and blocks the IWA from launching.
     if (model_->step() < IsolatedWebAppInstallerModel::Step::kInstall) {
       model_->SetStep(IsolatedWebAppInstallerModel::Step::kDisabled);
-      model_->SetDialogContent(absl::nullopt);
+      model_->SetDialogContent(std::nullopt);
       installability_checker_.reset();
     }
   }
   OnModelChanged();
+  if (!is_initialized_) {
+    is_initialized_ = true;
+    std::move(initialized_callback_).Run();
+  }
 }
 
 void IsolatedWebAppInstallerViewController::OnGetMetadataProgressUpdated(
@@ -341,7 +365,9 @@
       // A child dialog on the install screen means the installation failed.
       // Accepting the dialog corresponds to the Retry button.
       model_->SetDialogContent(std::nullopt);
-      Start();
+      installability_checker_.reset();
+      pref_observer_.reset();
+      Start(base::DoNothing(), std::move(completion_callback_));
       break;
 
     default:
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.h b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.h
index cb5f48b33..3b594e2 100644
--- a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.h
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller.h
@@ -5,8 +5,6 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_WEB_APPS_ISOLATED_WEB_APPS_ISOLATED_WEB_APP_INSTALLER_VIEW_CONTROLLER_H_
 #define CHROME_BROWSER_UI_VIEWS_WEB_APPS_ISOLATED_WEB_APPS_ISOLATED_WEB_APP_INSTALLER_VIEW_CONTROLLER_H_
 
-#include <string>
-
 #include "base/functional/callback_forward.h"
 #include "base/gtest_prod_util.h"
 #include "base/memory/raw_ptr.h"
@@ -16,6 +14,7 @@
 #include "chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view.h"
 #include "chrome/browser/web_applications/isolated_web_apps/install_isolated_web_app_command.h"
 
+class IsolatedWebAppsEnabledPrefObserver;
 class Profile;
 
 namespace views {
@@ -37,9 +36,14 @@
                                         IsolatedWebAppInstallerModel* model);
   virtual ~IsolatedWebAppInstallerViewController();
 
-  void Start();
+  // Starts the installer state transition. |initialized_callback| will be
+  // called once the dialog is initialized and ready for display.
+  // |complete_callback| will be called when the dialog is being closed.
+  void Start(base::OnceClosure initialized_callback,
+             base::OnceClosure completion_callback);
 
-  void Show(base::OnceClosure callback);
+  // Present the installer when it is initialized.
+  void Show();
 
   void SetViewForTesting(IsolatedWebAppInstallerView* view);
 
@@ -71,6 +75,7 @@
   void OnGetMetadataProgressUpdated(double progress);
   void OnInstallabilityChecked(InstallabilityChecker::Result result);
   void OnInstallProgressUpdated(double progress);
+
   void OnInstallComplete(
       base::expected<InstallIsolatedWebAppCommandSuccess,
                      InstallIsolatedWebAppCommandError> result);
@@ -96,9 +101,12 @@
   raw_ptr<views::DialogDelegate> dialog_delegate_;
 
   std::unique_ptr<CallbackDelayer> callback_delayer_;
+  std::unique_ptr<IsolatedWebAppsEnabledPrefObserver> pref_observer_;
   std::unique_ptr<InstallabilityChecker> installability_checker_;
+  bool is_initialized_ = false;
 
-  base::OnceClosure callback_;
+  base::OnceClosure initialized_callback_;
+  base::OnceClosure completion_callback_;
   base::WeakPtrFactory<IsolatedWebAppInstallerViewController> weak_ptr_factory_{
       this};
 };
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller_unittest.cc b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller_unittest.cc
index 07eaeed5..d83ac8c 100644
--- a/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller_unittest.cc
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view_controller_unittest.cc
@@ -10,11 +10,13 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
+#include "base/functional/callback_helpers.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
 #include "base/version.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_model.h"
 #include "chrome/browser/ui/views/web_apps/isolated_web_apps/isolated_web_app_installer_view.h"
@@ -41,6 +43,12 @@
 #include "third_party/blink/public/common/manifest/manifest.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "ash/constants/ash_pref_names.h"
+#include "base/values.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "chrome/browser/ash/app_restore/full_restore_service_factory.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -49,6 +57,8 @@
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
 #include "chrome/browser/extensions/extension_keeplist_chromeos.h"
 #include "chrome/browser/web_applications/app_service/test/loopback_crosapi_app_service_proxy.h"
+#include "chromeos/crosapi/mojom/prefs.mojom.h"
+#include "chromeos/lacros/lacros_service.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
 
 namespace web_app {
@@ -175,6 +185,7 @@
     profile_builder.SetIsMainProfile(true);
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
     profile_ = profile_builder.Build();
+    SetIsolatedWebAppsEnabledPref(true);
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     ash::full_restore::FullRestoreServiceFactory::GetInstance()
@@ -203,6 +214,14 @@
         base::FilePath::FromASCII(bundle_filename));
   }
 
+  void SetIsolatedWebAppsEnabledPref(bool value) {
+#if BUILDFLAG(IS_CHROMEOS)
+    sync_preferences::TestingPrefServiceSyncable* pref =
+        profile()->GetTestingPrefService();
+    pref->SetUserPref(ash::prefs::kIsolatedWebAppsEnabled, base::Value(value));
+#endif  //  BUILDFLAG(IS_CHROMEOS)
+  }
+
   void MockIconAndPageState(const IsolatedWebAppUrlInfo& url_info,
                             const std::string& version = "7.7.7") {
     GURL iwa_url = url_info.origin().GetURL();
@@ -259,7 +278,7 @@
                                             u"test app name", "7.7.7")))
       .WillOnce(Invoke(&callback, &base::test::TestFuture<void>::SetValue));
 
-  controller.Start();
+  controller.Start(base::DoNothing(), base::DoNothing());
 
   EXPECT_TRUE(callback.Wait());
   EXPECT_EQ(model.step(), IsolatedWebAppInstallerModel::Step::kShowMetadata);
@@ -291,7 +310,7 @@
                   IDS_IWA_INSTALLER_VERIFICATION_ERROR_SUBTITLE)))
       .WillOnce(Invoke(&callback, &base::test::TestFuture<void>::SetValue));
 
-  controller.Start();
+  controller.Start(base::DoNothing(), base::DoNothing());
 
   EXPECT_TRUE(callback.Wait());
   EXPECT_EQ(model.step(), IsolatedWebAppInstallerModel::Step::kGetMetadata);
@@ -449,9 +468,12 @@
   IsolatedWebAppInstallerModel model(CreateBundlePath("test_bundle.swbn"));
   IsolatedWebAppInstallerViewController controller(profile(), fake_provider(),
                                                    &model);
+
   testing::StrictMock<MockView> view;
   controller.SetViewForTesting(&view);
 
+  controller.completion_callback_ = base::DoNothing();
+
   SignedWebBundleMetadata metadata = CreateMetadata(u"Test App", "0.0.1");
   model.SetSignedWebBundleMetadata(metadata);
   model.SetStep(IsolatedWebAppInstallerModel::Step::kInstall);
@@ -466,4 +488,96 @@
   EXPECT_TRUE(callback.Wait());
 }
 
+// TODO(crbug/1508716): Enable the test for Lacros.
+#if BUILDFLAG(IS_CHROMEOS)
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#define MAYBE_ChangingPrefToFalseDisablesInstaller \
+  DISABLED_ChangingPrefToFalseDisablesInstaller
+#else
+#define MAYBE_ChangingPrefToFalseDisablesInstaller \
+  ChangingPrefToFalseDisablesInstaller
+#endif
+TEST_F(IsolatedWebAppInstallerViewControllerTest,
+       MAYBE_ChangingPrefToFalseDisablesInstaller) {
+  base::FilePath bundle_path = CreateBundlePath("test_bundle.swbn");
+  IsolatedWebAppUrlInfo url_info = CreateAndWriteTestBundle(bundle_path, "1.0");
+  MockIconAndPageState(url_info);
+
+  IsolatedWebAppInstallerModel model(bundle_path);
+  IsolatedWebAppInstallerViewController controller(profile(), fake_provider(),
+                                                   &model);
+  testing::StrictMock<MockView> view;
+  controller.SetViewForTesting(&view);
+
+  base::test::TestFuture<void> callback;
+  EXPECT_CALL(view, UpdateGetMetadataProgress(_)).Times(AnyNumber());
+  EXPECT_CALL(view, ShowGetMetadataScreen());
+  EXPECT_CALL(
+      view, ShowMetadataScreen(WithMetadata("hoealecpbefphiclhampllbdbdpfmfpi",
+                                            u"test app name", "7.7.7")))
+      .WillOnce(Invoke(&callback, &base::test::TestFuture<void>::SetValue));
+
+  controller.Start(base::DoNothing(), base::DoNothing());
+  ASSERT_TRUE(callback.Wait());
+
+  ASSERT_EQ(model.step(), IsolatedWebAppInstallerModel::Step::kShowMetadata);
+
+  callback.Clear();
+
+  EXPECT_CALL(view, ShowDisabledScreen())
+      .WillOnce(Invoke(&callback, &base::test::TestFuture<void>::SetValue));
+
+  SetIsolatedWebAppsEnabledPref(false);
+  EXPECT_TRUE(callback.Wait());
+
+  EXPECT_EQ(model.step(), IsolatedWebAppInstallerModel::Step::kDisabled);
+}
+
+// TODO(crbug/1508716): Enable the test for Lacros.
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+#define MAYBE_ChangingPrefToTrueRestartsInstaller \
+  DISABLED_ChangingPrefToTrueRestartsInstaller
+#else
+#define MAYBE_ChangingPrefToTrueRestartsInstaller \
+  ChangingPrefToTrueRestartsInstaller
+#endif
+TEST_F(IsolatedWebAppInstallerViewControllerTest,
+       MAYBE_ChangingPrefToTrueRestartsInstaller) {
+  base::FilePath bundle_path = CreateBundlePath("test_bundle.swbn");
+  IsolatedWebAppUrlInfo url_info = CreateAndWriteTestBundle(bundle_path, "1.0");
+  MockIconAndPageState(url_info);
+
+  IsolatedWebAppInstallerModel model(bundle_path);
+  IsolatedWebAppInstallerViewController controller(profile(), fake_provider(),
+                                                   &model);
+  testing::StrictMock<MockView> view;
+  controller.SetViewForTesting(&view);
+
+  SetIsolatedWebAppsEnabledPref(false);
+
+  base::test::TestFuture<void> callback;
+  EXPECT_CALL(view, ShowDisabledScreen())
+      .WillOnce(Invoke(&callback, &base::test::TestFuture<void>::SetValue));
+
+  controller.Start(base::DoNothing(), base::DoNothing());
+  ASSERT_TRUE(callback.Wait());
+
+  ASSERT_EQ(model.step(), IsolatedWebAppInstallerModel::Step::kDisabled);
+
+  callback.Clear();
+
+  EXPECT_CALL(view, UpdateGetMetadataProgress(_)).Times(AnyNumber());
+  EXPECT_CALL(view, ShowGetMetadataScreen());
+  EXPECT_CALL(
+      view, ShowMetadataScreen(WithMetadata("hoealecpbefphiclhampllbdbdpfmfpi",
+                                            u"test app name", "7.7.7")))
+      .WillOnce(Invoke(&callback, &base::test::TestFuture<void>::SetValue));
+
+  SetIsolatedWebAppsEnabledPref(true);
+  EXPECT_TRUE(callback.Wait());
+
+  EXPECT_EQ(model.step(), IsolatedWebAppInstallerModel::Step::kShowMetadata);
+}
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 }  // namespace web_app
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.cc b/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.cc
new file mode 100644
index 0000000..1f2d01bd
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.cc
@@ -0,0 +1,23 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.h"
+
+#include <memory>
+
+#include "base/functional/callback.h"
+#include "base/task/sequenced_task_runner.h"
+
+// static
+std::unique_ptr<IsolatedWebAppsEnabledPrefObserver>
+IsolatedWebAppsEnabledPrefObserver::CreateIsolatedWebAppsEnabledPrefObserver(
+    Profile* profile,
+    IsolatedWebAppsEnabledPrefObserver::PrefChangedCallback callback) {
+  // The pref value is for ChromeOS only, therefore just runs callback
+  // asynchronously with default value of true.
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(callback, true));
+
+  return std::make_unique<IsolatedWebAppsEnabledPrefObserver>();
+}
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.h b/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.h
new file mode 100644
index 0000000..3b37a48
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_WEB_APPS_ISOLATED_WEB_APPS_PREF_OBSERVER_H_
+#define CHROME_BROWSER_UI_VIEWS_WEB_APPS_ISOLATED_WEB_APPS_PREF_OBSERVER_H_
+
+#include <memory>
+
+#include "base/functional/callback.h"
+
+class Profile;
+
+// Calls the provided callback when the value of the pref controlling Isolated
+// Web App availability changes, and once on class creation with its initial
+// value. On platforms without an Isolated Web App availability pref, the
+// callback will be run once with a value of true.
+class IsolatedWebAppsEnabledPrefObserver {
+ public:
+  using PrefChangedCallback = base::RepeatingCallback<void(bool)>;
+
+  static std::unique_ptr<IsolatedWebAppsEnabledPrefObserver>
+  CreateIsolatedWebAppsEnabledPrefObserver(
+      Profile* profile,
+      IsolatedWebAppsEnabledPrefObserver::PrefChangedCallback callback);
+
+  virtual ~IsolatedWebAppsEnabledPrefObserver() = default;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_WEB_APPS_ISOLATED_WEB_APPS_PREF_OBSERVER_H_
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer_ash.cc b/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer_ash.cc
new file mode 100644
index 0000000..fae8045
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer_ash.cc
@@ -0,0 +1,65 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.h"
+
+#include "ash/constants/ash_pref_names.h"
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task/sequenced_task_runner.h"
+#include "chrome/browser/profiles/profile.h"
+#include "components/prefs/pref_change_registrar.h"
+#include "components/prefs/pref_service.h"
+
+class IsolatedWebAppsEnabledPrefObserverAsh
+    : public IsolatedWebAppsEnabledPrefObserver {
+ public:
+  IsolatedWebAppsEnabledPrefObserverAsh(Profile* profile,
+                                        PrefChangedCallback callback)
+      : profile_(profile), callback_(callback) {
+    CHECK(profile_);
+
+    pref_change_registrar_.Init(pref_service());
+    base::RepeatingClosure registrar_closure =
+        base::BindRepeating(&IsolatedWebAppsEnabledPrefObserverAsh::RunCallback,
+                            weak_ptr_factory_.GetWeakPtr());
+    pref_change_registrar_.Add(ash::prefs::kIsolatedWebAppsEnabled,
+                               registrar_closure);
+
+    // Runs callback once asynchronously to match the Lacros behavior.
+    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&IsolatedWebAppsEnabledPrefObserverAsh::RunCallback,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  ~IsolatedWebAppsEnabledPrefObserverAsh() override = default;
+
+ private:
+  void RunCallback() {
+    bool enabled =
+        pref_service()->GetBoolean(ash::prefs::kIsolatedWebAppsEnabled);
+    callback_.Run(enabled);
+  }
+
+  PrefService* pref_service() const { return profile_->GetPrefs(); }
+
+  PrefChangeRegistrar pref_change_registrar_;
+
+  const raw_ptr<Profile> profile_;
+
+  PrefChangedCallback callback_;
+
+  base::WeakPtrFactory<IsolatedWebAppsEnabledPrefObserverAsh> weak_ptr_factory_{
+      this};
+};
+
+// static
+std::unique_ptr<IsolatedWebAppsEnabledPrefObserver>
+IsolatedWebAppsEnabledPrefObserver::CreateIsolatedWebAppsEnabledPrefObserver(
+    Profile* profile,
+    IsolatedWebAppsEnabledPrefObserver::PrefChangedCallback callback) {
+  return std::make_unique<IsolatedWebAppsEnabledPrefObserverAsh>(profile,
+                                                                 callback);
+}
diff --git a/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer_lacros.cc b/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer_lacros.cc
new file mode 100644
index 0000000..1ff2a44
--- /dev/null
+++ b/chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer_lacros.cc
@@ -0,0 +1,77 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/notreached.h"
+#include "chrome/browser/ui/views/web_apps/isolated_web_apps/pref_observer.h"
+
+#include <memory>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chromeos/crosapi/mojom/prefs.mojom.h"
+#include "chromeos/lacros/crosapi_pref_observer.h"
+#include "chromeos/lacros/lacros_service.h"
+
+class IsolatedWebAppsEnabledPrefObserverLacros
+    : public IsolatedWebAppsEnabledPrefObserver {
+ public:
+  explicit IsolatedWebAppsEnabledPrefObserverLacros(
+      PrefChangedCallback callback)
+      : callback_(callback) {
+    chromeos::LacrosService* service = chromeos::LacrosService::Get();
+
+    // TODO(crbug/1508716): This workaround is needed for tests without the
+    // mojom::Prefs service, remove it once we have fake mojom::Pref for tests.
+    if (!service || !service->IsAvailable<crosapi::mojom::Prefs>()) {
+      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE,
+          base::BindOnce(&IsolatedWebAppsEnabledPrefObserverLacros::RunCallback,
+                         weak_ptr_factory_.GetWeakPtr()));
+      return;
+    }
+
+    crosapi_observer_ = std::make_unique<CrosapiPrefObserver>(
+        crosapi::mojom::PrefPath::kIsolatedWebAppsEnabled,
+        base::BindRepeating(
+            &IsolatedWebAppsEnabledPrefObserverLacros::CallbackWrapper,
+            weak_ptr_factory_.GetWeakPtr()));
+  }
+
+  ~IsolatedWebAppsEnabledPrefObserverLacros() override = default;
+
+ private:
+  // TODO(crbug/1508716): Remove |RunCallback()| once we have fake mojom::Pref
+  // for tests.
+  void RunCallback() {
+    chromeos::LacrosService* service = chromeos::LacrosService::Get();
+    if (!service || !service->IsAvailable<crosapi::mojom::Prefs>()) {
+      // For Lacros without the crosapi::mojom::Prefs interface, the value is
+      // assumed to be true.
+      callback_.Run(true);
+      return;
+    }
+    NOTREACHED_NORETURN();
+  }
+
+  void CallbackWrapper(base::Value value) {
+    CHECK(value.is_bool());
+    callback_.Run(value.GetBool());
+  }
+
+  PrefChangedCallback callback_;
+  std::unique_ptr<CrosapiPrefObserver> crosapi_observer_;
+
+  base::WeakPtrFactory<IsolatedWebAppsEnabledPrefObserverLacros>
+      weak_ptr_factory_{this};
+};
+
+// static
+std::unique_ptr<IsolatedWebAppsEnabledPrefObserver>
+IsolatedWebAppsEnabledPrefObserver::CreateIsolatedWebAppsEnabledPrefObserver(
+    Profile* profile,
+    IsolatedWebAppsEnabledPrefObserver::PrefChangedCallback callback) {
+  return std::make_unique<IsolatedWebAppsEnabledPrefObserverLacros>(callback);
+}
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.cc b/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.cc
index 9155fa60..d32272e 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.cc
@@ -7,6 +7,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/accelerator_actions.h"
 #include "ash/public/cpp/input_device_settings_controller.h"
+#include "ash/public/mojom/input_device_settings.mojom-forward.h"
 #include "ash/public/mojom/input_device_settings.mojom.h"
 #include "base/containers/flat_set.h"
 #include "base/ranges/algorithm.h"
@@ -16,6 +17,7 @@
 #include "mojo/public/cpp/bindings/clone_traits.h"
 #include "mojo/public/cpp/bindings/struct_ptr.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/events/ash/keyboard_capability.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash::settings {
@@ -567,4 +569,21 @@
   std::move(callback).Run(std::move(choices));
 }
 
+void InputDeviceSettingsProvider::HasLauncherButton(
+    HasLauncherButtonCallback callback) {
+  DCHECK(features::IsInputDeviceSettingsSplitEnabled());
+  DCHECK(InputDeviceSettingsController::Get());
+
+  auto keyboards =
+      InputDeviceSettingsController::Get()->GetConnectedKeyboards();
+  for (const ::ash::mojom::KeyboardPtr& keyboard : keyboards) {
+    if (keyboard->meta_key == ::ash::mojom::MetaKey::kLauncher) {
+      std::move(callback).Run(true);
+      return;
+    }
+  }
+
+  std::move(callback).Run(/*has_launcher_button=*/false);
+}
+
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.h b/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.h
index b334fd2..6095f668 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.h
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.h
@@ -117,6 +117,7 @@
   void OnWidgetDestroyed(views::Widget* widget) override;
 
   void SetWidgetForTesting(views::Widget* widget);
+  void HasLauncherButton(HasLauncherButtonCallback callback) override;
 
  private:
   void NotifyKeyboardsUpdated();
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.mojom b/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.mojom
index ec9a225..eefdca5 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.mojom
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.mojom
@@ -132,4 +132,10 @@
   // customization.
   GetActionsForGraphicsTabletButtonCustomization() =>
       (array<ActionChoice> options);
+
+  // Returns whether any of the keyboards has launcher button.
+  // True if the keyboard has launcher button as meta key and we display the
+  // launcher icon in settings. False if the keyboard has other buttons as meta
+  // key and we display the search icon in settings.
+  HasLauncherButton() => (bool has_launcher_button);
 };
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider_unittest.cc b/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider_unittest.cc
index 02b804f..2a1e8769 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider_unittest.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
 #include "chrome/browser/ui/webui/ash/settings/pages/device/input_device_settings/input_device_settings_provider.mojom.h"
 #include "mojo/public/cpp/bindings/clone_traits.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -1033,4 +1034,22 @@
   EXPECT_EQ(*expected_button, fake_observer.last_pressed_button());
 }
 
+TEST_F(InputDeviceSettingsProviderTest, HasLauncherButton) {
+  base::test::TestFuture<bool> future;
+
+  controller_->AddKeyboard(kKeyboard2.Clone());
+  controller_->AddKeyboard(kKeyboard3.Clone());
+
+  provider_->HasLauncherButton(future.GetCallback());
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_FALSE(future.Get<0>());
+  future.Clear();
+  controller_->AddKeyboard(kKeyboard1.Clone());
+  provider_->HasLauncherButton(future.GetCallback());
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(future.Get<0>());
+}
+
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index e1fb8c46..050ca967 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -7,7 +7,6 @@
 #include <stddef.h>
 #include <memory>
 #include <utility>
-
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
@@ -299,6 +298,7 @@
 #endif  // BUILDFLAG(PLATFORM_CFM)
 
 #if BUILDFLAG(ENABLE_COMPOSE)
+#include "chrome/browser/compose/compose_enabling.h"
 #include "chrome/browser/ui/webui/compose/compose_ui.h"
 #include "components/compose/core/browser/compose_features.h"
 #endif
@@ -757,7 +757,7 @@
   }
 #if BUILDFLAG(ENABLE_COMPOSE)
   if (url.host_piece() == chrome::kChromeUIComposeHost &&
-      base::FeatureList::IsEnabled(compose::features::kEnableCompose)) {
+      ComposeEnabling::IsEnabledForProfile(profile)) {
     return &NewWebUI<ComposeUI>;
   }
 #endif
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index 3e7a637..861c08c 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -16,6 +16,7 @@
 #include "base/strings/stringprintf.h"
 #include "base/time/time.h"
 #include "base/values.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_features.h"
 #include "chrome/browser/buildflags.h"
 #include "chrome/browser/cart/cart_handler.h"
@@ -681,13 +682,16 @@
 
   web_ui->AddRequestableScheme(content::kChromeUIUntrustedScheme);
 
-  // Give OGB 3P Cookie Permissions.
+// Give OGB 3P Cookie Permissions. Only necessary on non-Ash builds. Granting
+// 3P cookies on Ash causes b/314326552.
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
   WebUIAllowlist::GetOrCreate(profile_)->RegisterAutoGrantedThirdPartyCookies(
       url::Origin::Create(GURL(chrome::kChromeUIUntrustedNewTabPageUrl)),
       {
           ContentSettingsPattern::FromURL(GURL("https://ogs.google.com")),
           ContentSettingsPattern::FromURL(GURL("https://corp.google.com")),
       });
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
   pref_change_registrar_.Init(profile_->GetPrefs());
   pref_change_registrar_.Add(
diff --git a/chrome/browser/ui/webui/settings/accessibility_main_handler.cc b/chrome/browser/ui/webui/settings/accessibility_main_handler.cc
index 1a66a22..ad0e14d3 100644
--- a/chrome/browser/ui/webui/settings/accessibility_main_handler.cc
+++ b/chrome/browser/ui/webui/settings/accessibility_main_handler.cc
@@ -39,6 +39,13 @@
       base::BindRepeating(
           &AccessibilityMainHandler::HandleCheckAccessibilityImageLabels,
           base::Unretained(this)));
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+  web_ui()->RegisterMessageCallback(
+      "getScreenAiInstallState",
+      base::BindRepeating(
+          &AccessibilityMainHandler::HandleGetScreenAIInstallState,
+          base::Unretained(this)));
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
 }
 
 void AccessibilityMainHandler::OnJavascriptAllowed() {
@@ -84,6 +91,18 @@
   base::Value state_value = base::Value(static_cast<int>(state));
   FireWebUIListener("pdf-ocr-state-changed", state_value);
 }
+
+void AccessibilityMainHandler::HandleGetScreenAIInstallState(
+    const base::Value::List& args) {
+  CHECK_EQ(1U, args.size());
+  const base::Value& callback_id = args[0];
+  AllowJavascript();
+  // Get the current install state and send it back to a UI listener.
+  screen_ai::ScreenAIInstallState::State current_install_state =
+      screen_ai::ScreenAIInstallState::GetInstance()->get_state();
+  ResolveJavascriptCallback(
+      callback_id, base::Value(static_cast<int>(current_install_state)));
+}
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
 
 void AccessibilityMainHandler::HandleA11yPageReady(
diff --git a/chrome/browser/ui/webui/settings/accessibility_main_handler.h b/chrome/browser/ui/webui/settings/accessibility_main_handler.h
index b2e2a95..e0c48b53 100644
--- a/chrome/browser/ui/webui/settings/accessibility_main_handler.h
+++ b/chrome/browser/ui/webui/settings/accessibility_main_handler.h
@@ -46,10 +46,14 @@
   void StateChanged(screen_ai::ScreenAIInstallState::State state) override;
 #endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
 
+ private:
   void HandleA11yPageReady(const base::Value::List& args);
   void HandleCheckAccessibilityImageLabels(const base::Value::List& args);
 
- private:
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+  void HandleGetScreenAIInstallState(const base::Value::List& args);
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+
   void SendScreenReaderStateChanged();
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/webui/settings/accessibility_main_handler_unittest.cc b/chrome/browser/ui/webui/settings/accessibility_main_handler_unittest.cc
index 791f10d..ff2bf206 100644
--- a/chrome/browser/ui/webui/settings/accessibility_main_handler_unittest.cc
+++ b/chrome/browser/ui/webui/settings/accessibility_main_handler_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/webui/settings/accessibility_main_handler.h"
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
@@ -15,16 +16,21 @@
 #include "content/public/test/test_web_ui.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/accessibility/accessibility_features.h"
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
 
 namespace settings {
 
 namespace {
 
-const char kA11yPageReadyCallback[] = "a11yPageReady";
+// TODO(crbug.com/1499996): Uncomment and use the callback string below when
+// adding test cases for a11y_page.ts.
+// const char kA11yPageReadyCallback[] = "a11yPageReady";
+
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+const char kWebUIListenerCall[] = "cr.webUIListenerCallback";
 const char kPdfOcrDownloadingProgressChangedEventName[] =
     "pdf-ocr-downloading-progress-changed";
 const char kPdfOcrStateChangedEventName[] = "pdf-ocr-state-changed";
-const char kWebUIListenerCall[] = "cr.webUIListenerCallback";
 
 class TestScreenAIInstallState : public screen_ai::ScreenAIInstallState {
  public:
@@ -59,18 +65,21 @@
                           screen_ai::ScreenAIInstallState::Observer>
       component_ready_observer_{this};
 };
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
 
 }  // namespace
 
-class AccessibilityMainHandlerTest : public testing::Test {
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+class AccessibilityMainHandlerPdfOcrTest : public testing::Test {
  public:
-  AccessibilityMainHandlerTest() : features_({features::kPdfOcr}) {}
+  AccessibilityMainHandlerPdfOcrTest() : features_({features::kPdfOcr}) {}
 
-  AccessibilityMainHandlerTest(const AccessibilityMainHandlerTest&) = delete;
-  AccessibilityMainHandlerTest& operator=(const AccessibilityMainHandlerTest&) =
-      delete;
+  AccessibilityMainHandlerPdfOcrTest(
+      const AccessibilityMainHandlerPdfOcrTest&) = delete;
+  AccessibilityMainHandlerPdfOcrTest& operator=(
+      const AccessibilityMainHandlerPdfOcrTest&) = delete;
 
-  ~AccessibilityMainHandlerTest() override = default;
+  ~AccessibilityMainHandlerPdfOcrTest() override = default;
 
   // testing::Test:
   void SetUp() override {
@@ -92,9 +101,6 @@
     handler_->AllowJavascript();
     ASSERT_TRUE(handler_->IsJavascriptAllowed());
 
-    base::Value::List empty_args;
-    test_web_ui()->HandleReceivedMessage(kA11yPageReadyCallback, empty_args);
-
     // Run until idle so the handler picks up initial screen ai install state,
     // which is screen_ai::ScreenAIInstallState::State::kNotDownloaded.
     browser_task_environment_.RunUntilIdle();
@@ -137,7 +143,7 @@
   std::unique_ptr<content::WebContents> web_contents_;
 };
 
-TEST_F(AccessibilityMainHandlerTest, MessageForScreenAIDownloadingState) {
+TEST_F(AccessibilityMainHandlerPdfOcrTest, MessageForScreenAIDownloadingState) {
   size_t call_data_count_before_call = test_web_ui()->call_data().size();
 
   screen_ai::ScreenAIInstallState::State state =
@@ -148,7 +154,8 @@
                     /*call_count=*/call_data_count_before_call + 1u);
 }
 
-TEST_F(AccessibilityMainHandlerTest, MessageForScreenAIDownloadingProgress) {
+TEST_F(AccessibilityMainHandlerPdfOcrTest,
+       MessageForScreenAIDownloadingProgress) {
   // State needs to be `kDownloading` before updating the download progress.
   size_t call_data_count_before_call = test_web_ui()->call_data().size();
 
@@ -169,7 +176,7 @@
                     /*call_count=*/call_data_count_before_call + 1u);
 }
 
-TEST_F(AccessibilityMainHandlerTest, MessageForScreenAIDownloadedState) {
+TEST_F(AccessibilityMainHandlerPdfOcrTest, MessageForScreenAIDownloadedState) {
   size_t call_data_count_before_call = test_web_ui()->call_data().size();
 
   screen_ai::ScreenAIInstallState::State state =
@@ -180,7 +187,8 @@
                     /*call_count=*/call_data_count_before_call + 1u);
 }
 
-TEST_F(AccessibilityMainHandlerTest, MessageForScreenAIDownloadFailedState) {
+TEST_F(AccessibilityMainHandlerPdfOcrTest,
+       MessageForScreenAIDownloadFailedState) {
   size_t call_data_count_before_call = test_web_ui()->call_data().size();
 
   screen_ai::ScreenAIInstallState::State state =
@@ -191,7 +199,7 @@
                     /*call_count=*/call_data_count_before_call + 1u);
 }
 
-TEST_F(AccessibilityMainHandlerTest, MessageForScreenAIReadyState) {
+TEST_F(AccessibilityMainHandlerPdfOcrTest, MessageForScreenAIReadyState) {
   size_t call_data_count_before_call = test_web_ui()->call_data().size();
 
   screen_ai::ScreenAIInstallState::State state =
@@ -202,7 +210,8 @@
                     /*call_count=*/call_data_count_before_call + 1u);
 }
 
-TEST_F(AccessibilityMainHandlerTest, MessageForScreenAINotDownloadedState) {
+TEST_F(AccessibilityMainHandlerPdfOcrTest,
+       MessageForScreenAINotDownloadedState) {
   size_t call_data_count_before_call = test_web_ui()->call_data().size();
 
   // Either `kReady` or `kFailed` needs to be set for testing `kNotDownloaded`.
@@ -219,5 +228,6 @@
                     /*expected_arg=*/static_cast<int>(state),
                     /*call_count=*/call_data_count_before_call + 1u);
 }
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
 
 }  // namespace settings
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index e2d58fc..96f9a7d 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -186,6 +186,10 @@
 #include "chrome/browser/ui/webui/settings/mac_system_settings_handler.h"
 #endif
 
+#if BUILDFLAG(ENABLE_COMPOSE)
+#include "chrome/browser/compose/compose_enabling.h"
+#endif
+
 namespace settings {
 
 // static
@@ -393,9 +397,8 @@
               companion::features::kCompanionEnablePageContent));
 
 #if BUILDFLAG(ENABLE_COMPOSE)
-  html_source->AddBoolean(
-      "enableComposeSetting",
-      base::FeatureList::IsEnabled(compose::features::kEnableCompose));
+  html_source->AddBoolean("enableComposeSetting",
+                          ComposeEnabling::IsEnabledForProfile(profile));
 #endif
 
   html_source->AddBoolean(
diff --git a/chrome/browser/ui/webui/webui_util.cc b/chrome/browser/ui/webui/webui_util.cc
index 303f928..9e0acc0 100644
--- a/chrome/browser/ui/webui/webui_util.cc
+++ b/chrome/browser/ui/webui/webui_util.cc
@@ -77,8 +77,8 @@
       "print-preview-plugin-loader "
       // Add TrustedTypes policies necessary for using Polymer.
       "polymer-html-literal polymer-template-event-attribute-policy "
-      // Add TrustedTypes policies necessary for using Lit.
-      "lit-html;");
+      // Add TrustedTypes policies necessary for using Desktop's Lit bundle.
+      "lit-html-desktop;");
 }
 
 void AddLocalizedString(content::WebUIDataSource* source,
diff --git a/chrome/browser/wallet/android/boarding_pass_detector.cc b/chrome/browser/wallet/android/boarding_pass_detector.cc
index 052a1676..49e58a0 100644
--- a/chrome/browser/wallet/android/boarding_pass_detector.cc
+++ b/chrome/browser/wallet/android/boarding_pass_detector.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/wallet/android/boarding_pass_detector.h"
 
 #include "base/feature_list.h"
+#include "base/no_destructor.h"
 #include "base/strings/string_split.h"
 #include "chrome/common/chrome_features.h"
 #include "content/public/browser/render_frame_host.h"
@@ -20,17 +21,21 @@
       remote.BindNewPipeAndPassReceiver());
   return remote;
 }
+
+const std::vector<std::string>& GetAllowlist() {
+  static base::NoDestructor<std::vector<std::string>> allowed_urls([] {
+    std::string param_val = base::GetFieldTrialParamValueByFeature(
+        features::kBoardingPassDetector,
+        features::kBoardingPassDetectorUrlParam.name);
+    return base::SplitString(std::move(param_val), ",", base::TRIM_WHITESPACE,
+                             base::SPLIT_WANT_NONEMPTY);
+  }());
+  return *allowed_urls;
+}
 }  // namespace
 
 bool BoardingPassDetector::ShouldDetect(const std::string& url) {
-  std::string param_val = base::GetFieldTrialParamValueByFeature(
-      features::kBoardingPassDetector,
-      features::kBoardingPassDetectorUrlParam.name);
-
-  std::vector<std::string> allowed_urls =
-      base::SplitString(std::move(param_val), ",", base::TRIM_WHITESPACE,
-                        base::SPLIT_WANT_NONEMPTY);
-  for (const auto& allowed_url : allowed_urls) {
+  for (const auto& allowed_url : GetAllowlist()) {
     if (url.starts_with(allowed_url)) {
       return true;
     }
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 14d4220d..71c71d52 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1702569590-a95a72413f14566fb193623655d539dfc205c70f.profdata
+chrome-mac-arm-main-1702936733-e22c65059657a9d5898e430eda02afdeb3cd9963.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 5f5f1ad..205de180 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1702565862-a2f2b250f345dd1dcf530831bd5ead479f94bf8a.profdata
+chrome-win64-main-1702933089-95bda8e015b301e2b0b631fe6b8c0e7a9c936e8e.profdata
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.cc b/chrome/renderer/accessibility/read_anything_app_controller.cc
index b3c47a9..df98a610 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller.cc
@@ -1362,41 +1362,147 @@
     return next_nodes;
   }
 
-  // Look at the current node and the text position within it. If we're at
-  // the end of the node, move to the next node.
-  ui::AXNode* anchor_node = GetNodeFromCurrentPosition();
+  std::u16string current_text;
 
-  std::u16string text = anchor_node->GetTextContentUTF16();
-  int prev_index = current_text_index_;
+  // Loop through the tree in order to group nodes together into the same
+  // granularity segment until there are no more pieces that can be added
+  // to the current segment or we've reached the end of the tree.
+  // e.g. if the following two nodes are next to one another in the tree:
+  //  AXNode: id=1, text = "This is a "
+  //  AXNode: id=2, text = "link. "
+  // both AXNodes should be added to next_nodes, as the combined text across
+  // the two nodes forms a complete sentence.
+  // This allows text to be spoken smoothly across nodes with broken sentences,
+  // such as links and formatted text.
+  // TODO(crbug.com/1474951): Investigate how much of this can be pulled into
+  // AXPosition to simplify Read Aloud-specific code and allow improvements
+  // to be used by other places where AXPosition is used.
+  while (!ax_position_->IsNullPosition() && !ax_position_->AtEndOfAXTree()) {
+    ui::AXNode* anchor_node = GetNodeFromCurrentPosition();
+    std::u16string text = anchor_node->GetTextContentUTF16();
+    std::u16string text_substr = text.substr(current_text_index_);
+    int prev_index = current_text_index_;
+    // Gets the starting index for the next sentence in the current node.
+    int next_sentence_index =
+        GetNextSentence(text_substr, max_text_length) + prev_index;
+    // If our current index within the current node is greater than that node's
+    // text, look at the next node. If the starting index of the next sentence
+    // in the node is the same the current index within the node, this means
+    // that we've reached the end of all possible sentences within the current
+    // node, and should move to the next node.
+    if ((size_t)current_text_index_ >= text.size() ||
+        (current_text_index_ == next_sentence_index)) {
+      // Move the AXPosition to the next node.
+      ax_position_ = GetNextValidPositionFromCurrentPosition();
+      // Reset the current text index within the current node since we just
+      // moved to a new node.
+      current_text_index_ = 0;
+      // If we've reached the end of the content, go ahead and return the
+      // current list of nodes because there are no more nodes to look through.
+      if (ax_position_->IsNullPosition() || ax_position_->AtEndOfAXTree() ||
+          !ax_position_->GetAnchor()) {
+        return next_nodes;
+      }
 
-  std::u16string text_substr = text.substr(current_text_index_);
-  int nextSentenceIndex =
-      GetNextSentence(text_substr, max_text_length) + prev_index;
+      std::u16string base_text =
+          GetNodeFromCurrentPosition()->GetTextContentUTF16();
 
-  // If the current text index is greater than the current size or the
-  // remaining text in the node, move to the next node.
-  if (((size_t)current_text_index_ >= text.size()) ||
-      (current_text_index_ == nextSentenceIndex)) {
-    ax_position_ = GetNextValidPositionFromCurrentPosition();
-    if (ax_position_->IsNullPosition()) {
-      return next_nodes;
+      // TODO(crbug.com/1474951): With this implementation, sometimes headers
+      // are combined with standard text. In addition to checking if the text
+      // is too long, also check that we're not crossing paragraphs.
+
+      // Look at the text of the items we've already added to the
+      // current sentence (current_text) combined with the text of the next
+      // node (base_text).
+      const std::u16string& combined_text = current_text + base_text;
+      // Get the index of the next sentence if we're looking at the combined
+      // previous and current node text.
+      int combined_sentence_index =
+          GetNextSentence(combined_text, max_text_length);
+      // If the combined_sentence_index is the same as the current_text length,
+      // the new node should not be considered part of the current sentence.
+      // If these values differ, add the current node's text to the list of
+      // nodes in the current sentence.
+      // Consider these two examples:
+      // Example 1:
+      //  current text: Hello
+      //  current node's text: , how are you?
+      //    The current text length is 5, but the index of the next sentence of
+      //    the combined text is 19, so the current node should be added to
+      //    the current sentence.
+      // Example 2:
+      //  current text: Hello.
+      //  current node: Goodbye.
+      //    The current text length is 6, and the next sentence index of
+      //    "Hello. Goodbye." is still 6, so the current node's text shouldn't
+      //    be added to the current sentence.
+      if ((int)current_text.length() != combined_sentence_index) {
+        anchor_node = GetNodeFromCurrentPosition();
+        previously_spoken_ids_.push_back(anchor_node->id());
+        // Calculate the new sentence index.
+        int index_in_new_node = combined_sentence_index - current_text.length();
+        // Add the current node to the list of nodes to be returned, with a
+        // text range from 0 to the start of the next sentence
+        // (index_in_new_node);
+        next_nodes.push_back({anchor_node->id(), 0, index_in_new_node});
+        current_text +=
+            anchor_node->GetTextContentUTF16().substr(0, index_in_new_node);
+        current_text_index_ = index_in_new_node;
+        if (current_text_index_ != (int)base_text.length()) {
+          // If we're in the middle of the node, there's no need to attempt
+          // to find another segment, as we're at the end of the current
+          // segment.
+          return next_nodes;
+        }
+        continue;
+      } else if (next_nodes.size() > 0) {
+        // If nothing has been added to the list of current nodes, we should
+        // look at the next sentence within the current node. However, if
+        // there have already been nodes added to the list of nodes to return
+        // and we determine that the next node shouldn't be added to the
+        // current sentence, we've completed the current sentence, so we can
+        // return the current list.
+        return next_nodes;
+      }
     }
 
-    // Since we've updated the position, update our state in order to calculate
-    // the next sentence.
-    current_text_index_ = 0;
+    // Add the next granularity piece within the current node.
     anchor_node = GetNodeFromCurrentPosition();
     text = anchor_node->GetTextContentUTF16();
     prev_index = current_text_index_;
     text_substr = text.substr(current_text_index_);
+    // Find the next sentence within the current node.
+    int new_current_text_index =
+        GetNextSentence(text_substr, max_text_length) + prev_index;
+    // If adding the next piece of the sentence from the current node doesn't
+    // make the returned text too long, add it to the list of nodes.
+    if ((current_text.length() + new_current_text_index - prev_index) <
+        (size_t)max_text_length) {
+      int start_index = current_text_index_;
+      current_text_index_ = new_current_text_index;
+      // Add the current node to the list of nodes to be returned, with a
+      // text range from the starting index (the end of the previous piece of
+      // the sentence) to the start of the next sentence.
+      next_nodes.push_back(
+          {anchor_node->id(), start_index, current_text_index_});
+      current_text += anchor_node->GetTextContentUTF16().substr(
+          start_index, current_text_index_ - start_index);
+      previously_spoken_ids_.push_back(anchor_node->id());
+    } else {
+      // If adding the next segment to the list of nodes is greater than the
+      // maximum text length, return the current nodes.
+      // TODO(crbug.com/1474951): Find a better way of segmenting granularities
+      // that are too long.
+      return next_nodes;
+    }
+
+    // After adding the most recent granularity segment, if we're not at the
+    //  end of the node, the current nodes can be returned, as we know there's
+    // no further segments remaining.
+    if ((size_t)current_text_index_ != text.length()) {
+      return next_nodes;
+    }
   }
-
-  int new_current_text_index =
-      GetNextSentence(text_substr, max_text_length) + prev_index;
-  current_text_index_ = new_current_text_index;
-  next_nodes.push_back({anchor_node->id(), prev_index, current_text_index_});
-  previously_spoken_ids_.push_back(anchor_node->id());
-
   return next_nodes;
 }
 
@@ -1463,6 +1569,12 @@
         new_position->CreateNextSentenceStartPosition(movement_options);
     anchor_node = possible_new_position->GetAnchor();
     if (!anchor_node) {
+      if (was_previously_spoken) {
+        // If the previous position we were looking at was previously spoken,
+        // go ahead and return the null position to avoid duplicate nodes
+        // being added.
+        return possible_new_position;
+      }
       return new_position;
     }
 
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.h b/chrome/renderer/accessibility/read_anything_app_controller.h
index fcb7218..bd3a2e3 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller.h
+++ b/chrome/renderer/accessibility/read_anything_app_controller.h
@@ -111,8 +111,6 @@
   void ScreenAIServiceReady() override;
 #endif
 
-  // Read Aloud Helper methods.
-
   // Returns the next valid AXNodePosition.
   ui::AXNodePosition::AXPositionInstance
   GetNextValidPositionFromCurrentPosition();
diff --git a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
index f0243d2..4c14cf0 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
@@ -2174,9 +2174,11 @@
 }
 
 TEST_F(ReadAnythingAppControllerTest, GetNextText_ReturnsExpectedNodes) {
-  std::u16string sentence1 = u"This is a sentence.";
-  std::u16string sentence2 = u"This is another sentence.";
-  std::u16string sentence3 = u"And this is yet another sentence.";
+  // TODO(crbug.com/1474951): Investigate if we can improve in scenarios when
+  // there's not a space between sentences.
+  std::u16string sentence1 = u"This is a sentence. ";
+  std::u16string sentence2 = u"This is another sentence. ";
+  std::u16string sentence3 = u"And this is yet another sentence. ";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
   ui::AXNodeData staticText1;
@@ -2230,8 +2232,8 @@
 }
 
 TEST_F(ReadAnythingAppControllerTest, GetNextText_AfterAXTreeRefresh) {
-  std::u16string sentence1 = u"This is a sentence.";
-  std::u16string sentence2 = u"This is another sentence.";
+  std::u16string sentence1 = u"This is a sentence. ";
+  std::u16string sentence2 = u"This is another sentence. ";
   std::u16string sentence3 = u"And this is yet another sentence.";
   ui::AXTreeUpdate update;
   SetUpdateTreeID(&update);
@@ -2263,9 +2265,9 @@
 
   // Simulate updating the page text.
   std::u16string new_sentence_1 =
-      u"And so I read a book or maybe two or three.";
+      u"And so I read a book or maybe two or three. ";
   std::u16string new_sentence_2 =
-      u"I will add a few new paitings to my gallery.";
+      u"I will add a few new paitings to my gallery. ";
   std::u16string new_sentence_3 =
       u"I will play guitar and knit and cook and basically wonder when will my "
       u"life begin.";
@@ -2326,3 +2328,202 @@
   next_node_ids = GetNextText();
   EXPECT_EQ((int)next_node_ids.size(), 0);
 }
+
+TEST_F(ReadAnythingAppControllerTest,
+       GetNextText_SentenceSplitAcrossMultipleNodes) {
+  std::u16string sentence1 = u"The wind is howling like this ";
+  std::u16string sentence2 = u"swirling storm ";
+  std::u16string sentence3 = u"inside.";
+  ui::AXTreeUpdate update;
+  SetUpdateTreeID(&update);
+  ui::AXNodeData staticText1;
+  staticText1.id = 2;
+  staticText1.role = ax::mojom::Role::kStaticText;
+  staticText1.SetNameChecked(sentence1);
+
+  ui::AXNodeData staticText2;
+  staticText2.id = 3;
+  staticText2.role = ax::mojom::Role::kStaticText;
+  staticText2.SetNameChecked(sentence2);
+
+  ui::AXNodeData staticText3;
+  staticText3.id = 4;
+  staticText3.role = ax::mojom::Role::kStaticText;
+  staticText3.SetNameChecked(sentence3);
+  update.nodes = {staticText1, staticText2, staticText3};
+  AccessibilityEventReceived({update});
+  OnAXTreeDistilled({staticText1.id, staticText2.id, staticText3.id});
+  InitAXPosition(update.nodes[0].id);
+
+  std::vector<std::vector<int>> next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 3);
+
+  // The first segment was returned correctly.
+  EXPECT_EQ((int)next_node_ids[0].size(), 3);
+  EXPECT_EQ(next_node_ids[0][0], staticText1.id);
+  EXPECT_EQ(next_node_ids[0][1], 0);
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence1.length());
+
+  // The second segment was returned correctly.
+  EXPECT_EQ((int)next_node_ids[1].size(), 3);
+  EXPECT_EQ(next_node_ids[1][0], staticText2.id);
+  EXPECT_EQ(next_node_ids[1][1], 0);
+  EXPECT_EQ(next_node_ids[1][2], (int)sentence2.length());
+
+  // The third segment was returned correctly.
+  EXPECT_EQ((int)next_node_ids[2].size(), 3);
+  EXPECT_EQ(next_node_ids[2][0], staticText3.id);
+  EXPECT_EQ(next_node_ids[2][1], 0);
+  EXPECT_EQ(next_node_ids[2][2], (int)sentence3.length());
+
+  // Nodes are empty at the end of the new tree.
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 0);
+}
+
+TEST_F(ReadAnythingAppControllerTest, GetNextText_SentenceSplitAcrossTwoNodes) {
+  std::u16string sentence1 = u"And I am almost ";
+  std::u16string sentence2 = u"there. ";
+  std::u16string sentence3 = u"I am almost there.";
+  ui::AXTreeUpdate update;
+  SetUpdateTreeID(&update);
+  ui::AXNodeData staticText1;
+  staticText1.id = 2;
+  staticText1.role = ax::mojom::Role::kStaticText;
+  staticText1.SetNameChecked(sentence1);
+
+  ui::AXNodeData staticText2;
+  staticText2.id = 3;
+  staticText2.role = ax::mojom::Role::kStaticText;
+  staticText2.SetNameChecked(sentence2);
+
+  ui::AXNodeData staticText3;
+  staticText3.id = 4;
+  staticText3.role = ax::mojom::Role::kStaticText;
+  staticText3.SetNameChecked(sentence3);
+  update.nodes = {staticText1, staticText2, staticText3};
+  AccessibilityEventReceived({update});
+  OnAXTreeDistilled({staticText1.id, staticText2.id, staticText3.id});
+  InitAXPosition(update.nodes[0].id);
+
+  std::vector<std::vector<int>> next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 2);
+  EXPECT_EQ((int)next_node_ids[0].size(), 3);
+
+  // The first segment was returned correctly.
+  EXPECT_EQ(next_node_ids[0][0], staticText1.id);
+  EXPECT_EQ(next_node_ids[0][1], 0);
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence1.length());
+
+  // The second segment was returned correctly.
+  EXPECT_EQ(next_node_ids[1][0], staticText2.id);
+  EXPECT_EQ(next_node_ids[1][1], 0);
+  EXPECT_EQ(next_node_ids[1][2], (int)sentence2.length());
+
+  // The third segment was returned correctly after getting the next text.
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+  EXPECT_EQ(next_node_ids[0][0], staticText3.id);
+  EXPECT_EQ(next_node_ids[0][1], 0);
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence3.length());
+
+  // Nodes are empty at the end of the new tree.
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 0);
+}
+
+TEST_F(ReadAnythingAppControllerTest, GetNextText_MultipleSentencesInSameNode) {
+  std::u16string sentence1 = u"But from up here. The ";
+  std::u16string sentence2 = u"world ";
+  std::u16string sentence3 =
+      u"looks so small. And suddenly life seems so clear. And from up here. "
+      u"You coast past it all. The obstacles just disappear.";
+  ui::AXTreeUpdate update;
+  SetUpdateTreeID(&update);
+  ui::AXNodeData staticText1;
+  staticText1.id = 2;
+  staticText1.role = ax::mojom::Role::kStaticText;
+  staticText1.SetNameChecked(sentence1);
+
+  ui::AXNodeData staticText2;
+  staticText2.id = 3;
+  staticText2.role = ax::mojom::Role::kStaticText;
+  staticText2.SetNameChecked(sentence2);
+
+  ui::AXNodeData staticText3;
+  staticText3.id = 4;
+  staticText3.role = ax::mojom::Role::kStaticText;
+  staticText3.SetNameChecked(sentence3);
+  update.nodes = {staticText1, staticText2, staticText3};
+  AccessibilityEventReceived({update});
+  OnAXTreeDistilled({staticText1.id, staticText2.id, staticText3.id});
+  InitAXPosition(update.nodes[0].id);
+
+  std::vector<std::vector<int>> next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+  EXPECT_EQ((int)next_node_ids[0].size(), 3);
+
+  // The first segment was returned correctly.
+  EXPECT_EQ(next_node_ids[0][0], staticText1.id);
+  EXPECT_EQ(next_node_ids[0][1], 0);
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence1.find(u"The"));
+
+  // The second segment was returned correctly, across 3 nodes.
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 3);
+
+  EXPECT_EQ((int)next_node_ids[0].size(), 3);
+  EXPECT_EQ(next_node_ids[0][0], staticText1.id);
+  EXPECT_EQ(next_node_ids[0][1], (int)sentence1.find(u"The"));
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence1.length());
+
+  EXPECT_EQ((int)next_node_ids[1].size(), 3);
+  EXPECT_EQ(next_node_ids[1][0], staticText2.id);
+  EXPECT_EQ(next_node_ids[1][1], 0);
+  EXPECT_EQ(next_node_ids[1][2], (int)sentence2.length());
+
+  EXPECT_EQ((int)next_node_ids[2].size(), 3);
+  EXPECT_EQ(next_node_ids[2][0], staticText3.id);
+  EXPECT_EQ(next_node_ids[2][1], 0);
+  EXPECT_EQ(next_node_ids[2][2], (int)sentence3.find(u"And"));
+
+  // The next sentence "And suddenly life seems so clear" was returned correctly
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+  EXPECT_EQ((int)next_node_ids[0].size(), 3);
+
+  EXPECT_EQ(next_node_ids[0][0], staticText3.id);
+  EXPECT_EQ(next_node_ids[0][1], (int)sentence3.find(u"And"));
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence3.find(u"And from"));
+
+  // The next sentence "And from up here" was returned correctly
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+  EXPECT_EQ((int)next_node_ids[0].size(), 3);
+
+  EXPECT_EQ(next_node_ids[0][0], staticText3.id);
+  EXPECT_EQ(next_node_ids[0][1], (int)sentence3.find(u"And from"));
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence3.find(u"You"));
+
+  // The next sentence "You coast past it all" was returned correctly
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+  EXPECT_EQ((int)next_node_ids[0].size(), 3);
+
+  EXPECT_EQ(next_node_ids[0][0], staticText3.id);
+  EXPECT_EQ(next_node_ids[0][1], (int)sentence3.find(u"You"));
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence3.find(u"The"));
+
+  // The next sentence "The obstacles just disappear" was returned correctly
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+  EXPECT_EQ((int)next_node_ids[0].size(), 3);
+
+  EXPECT_EQ(next_node_ids[0][0], staticText3.id);
+  EXPECT_EQ(next_node_ids[0][1], (int)sentence3.find(u"The"));
+  EXPECT_EQ(next_node_ids[0][2], (int)sentence3.length());
+
+  // Nodes are empty at the end of the new tree.
+  next_node_ids = GetNextText();
+  EXPECT_EQ((int)next_node_ids.size(), 0);
+}
diff --git a/chrome/renderer/wallet/boarding_pass_extractor.cc b/chrome/renderer/wallet/boarding_pass_extractor.cc
index ee4bdc15..d45e638 100644
--- a/chrome/renderer/wallet/boarding_pass_extractor.cc
+++ b/chrome/renderer/wallet/boarding_pass_extractor.cc
@@ -6,6 +6,8 @@
 
 #include "chrome/common/chrome_isolated_world_ids.h"
 #include "content/public/renderer/render_frame.h"
+#include "content/public/renderer/render_thread.h"
+#include "services/metrics/public/cpp/ukm_builders.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/platform/web_string.h"
 #include "third_party/blink/public/web/web_local_frame.h"
@@ -108,6 +110,11 @@
   // RenderFrame.
   registry->AddInterface(base::BindRepeating(
       &BoardingPassExtractor::BindReceiver, base::Unretained(this)));
+
+  mojo::Remote<ukm::mojom::UkmRecorderFactory> factory;
+  content::RenderThread::Get()->BindHostReceiver(
+      factory.BindNewPipeAndPassReceiver());
+  ukm_recorder_ = ukm::MojoUkmRecorder::Create(*factory);
 }
 
 BoardingPassExtractor::~BoardingPassExtractor() = default;
@@ -152,11 +159,17 @@
     ExtractBoardingPassCallback callback,
     absl::optional<base::Value> results,
     base::TimeTicks start_time) {
+  std::vector<std::string> boarding_passes;
   if (results.has_value()) {
-    std::move(callback).Run(ConvertResultsToStrings(*results));
-  } else {
-    std::move(callback).Run(std::vector<std::string>());
+    boarding_passes = ConvertResultsToStrings(*results);
   }
+
+  ukm::builders::Wallet_BoardingPassDetect(
+      render_frame()->GetWebFrame()->GetDocument().GetUkmSourceId())
+      .SetDetected(!boarding_passes.empty())
+      .Record(ukm_recorder_.get());
+
+  std::move(callback).Run(std::move(boarding_passes));
 }
 
 }  // namespace wallet
diff --git a/chrome/renderer/wallet/boarding_pass_extractor.h b/chrome/renderer/wallet/boarding_pass_extractor.h
index 0a19be6..df27225 100644
--- a/chrome/renderer/wallet/boarding_pass_extractor.h
+++ b/chrome/renderer/wallet/boarding_pass_extractor.h
@@ -9,6 +9,7 @@
 #include "chrome/common/wallet/boarding_pass_extractor.mojom.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 
 namespace wallet {
@@ -40,6 +41,8 @@
                                base::TimeTicks start_time);
 
   mojo::Receiver<mojom::BoardingPassExtractor> receiver_{this};
+
+  std::unique_ptr<ukm::MojoUkmRecorder> ukm_recorder_;
 };
 }  // namespace wallet
 
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index edd73dd..09cf5b9 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2480,7 +2480,6 @@
       "../browser/performance_manager/background_tab_loading_policy_browsertest.cc",
       "../browser/performance_manager/frame_node_impl_browsertest.cc",
       "../browser/performance_manager/mechanisms/page_discarder_browsertest.cc",
-      "../browser/performance_manager/metrics/page_resource_monitor_browsertest.cc",
       "../browser/performance_manager/observers/page_load_metrics_observer_browsertest.cc",
       "../browser/performance_manager/page_load_tracker_decorator_browsertest.cc",
       "../browser/performance_manager/page_node_browsertest.cc",
@@ -7824,6 +7823,7 @@
         "../browser/ui/commander/entity_match_unittest.cc",
         "../browser/ui/commander/fuzzy_finder_unittest.cc",
         "../browser/ui/commerce/commerce_ui_tab_helper_unittest.cc",
+        "../browser/ui/commerce/price_tracking_page_action_controller_unittest.cc",
         "../browser/ui/content_settings/content_setting_bubble_model_unittest.cc",
         "../browser/ui/content_settings/content_setting_image_model_unittest.cc",
         "../browser/ui/exclusive_access/fullscreen_controller_state_unittest.cc",
@@ -8483,6 +8483,7 @@
         "../browser/ui/ash/assistant/assistant_state_client_unittest.cc",
         "../browser/ui/ash/assistant/device_actions_unittest.cc",
         "../browser/ui/ash/assistant/search_and_assistant_enabled_checker_unittest.cc",
+        "../browser/ui/ash/birch/birch_keyed_service_factory_unittest.cc",
         "../browser/ui/ash/calendar/calendar_keyed_service_unittest.cc",
         "../browser/ui/ash/desks/chrome_saved_desk_delegate_unittest.cc",
         "../browser/ui/ash/device_scheduled_reboot/reboot_notification_controller_unittest.cc",
diff --git a/chrome/test/base/chromeos/ash_browser_test_starter.cc b/chrome/test/base/chromeos/ash_browser_test_starter.cc
index 04aaf5d..31287529 100644
--- a/chrome/test/base/chromeos/ash_browser_test_starter.cc
+++ b/chrome/test/base/chromeos/ash_browser_test_starter.cc
@@ -208,8 +208,7 @@
   // Disable gpu sandbox in Lacros since it fails in Linux emulator environment.
   // See details in crbug/1483530.
   lacros_args.emplace_back("--disable-gpu-sandbox");
-  command_line->AppendSwitchASCII(ash::switches::kLacrosChromeAdditionalArgs,
-                                  base::JoinString(lacros_args, "####"));
+  crosapi::BrowserLauncher::AddLacrosArgumentsForTest(lacros_args);
 
   return true;
 }
diff --git a/chrome/test/base/chromeos/crosier/ash_integration_test.cc b/chrome/test/base/chromeos/crosier/ash_integration_test.cc
index 9003dbb..c4699cad 100644
--- a/chrome/test/base/chromeos/crosier/ash_integration_test.cc
+++ b/chrome/test/base/chromeos/crosier/ash_integration_test.cc
@@ -15,6 +15,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
+#include "chrome/browser/ash/crosapi/browser_launcher.h"
 #include "chrome/browser/ui/ash/chrome_browser_main_extra_parts_ash.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/chromeos/crosier/chromeos_integration_login_mixin.h"
@@ -116,6 +117,5 @@
   // crbug.com/1371655.
   lacros_args.emplace_back(base::StringPrintf(
       "--%s=%s", switches::kGaiaUrl, https_server_->base_url().spec().c_str()));
-  command_line->AppendSwitchASCII(ash::switches::kLacrosChromeAdditionalArgs,
-                                  base::JoinString(lacros_args, "####"));
+  crosapi::BrowserLauncher::AddLacrosArgumentsForTest(lacros_args);
 }
diff --git a/chrome/test/data/webui/compose/compose_browsertest.cc b/chrome/test/data/webui/compose/compose_browsertest.cc
index a30f58f2..181a557 100644
--- a/chrome/test/data/webui/compose/compose_browsertest.cc
+++ b/chrome/test/data/webui/compose/compose_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "chrome/browser/compose/compose_enabling.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/test/base/web_ui_mocha_browser_test.h"
 #include "components/compose/core/browser/compose_features.h"
@@ -9,7 +10,10 @@
 
 class ComposeTest : public WebUIMochaBrowserTest {
  protected:
-  ComposeTest() { set_test_loader_host(chrome::kChromeUIComposeHost); }
+  ComposeTest() {
+    set_test_loader_host(chrome::kChromeUIComposeHost);
+    ComposeEnabling::SetEnabledForTesting(true);
+  }
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_{
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index f52f4e4..3a472a1 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -167,7 +167,6 @@
 
   if (!is_chromeos) {
     files += [
-      "a11y_page_test.ts",
       "live_caption_section_test.ts",
       "live_translate_section_test.ts",
       "test_captions_browser_proxy.ts",
@@ -189,6 +188,13 @@
     files += [ "passkeys_subpage_test.ts" ]
   }
 
+  if (is_win || is_mac || is_linux) {
+    files += [
+      "a11y_page_test.ts",
+      "pdf_ocr_toggle_test.ts",
+    ]
+  }
+
   if (is_win && is_chrome_branded) {
     files += [ "incompatible_applications_page_test.ts" ]
   }
diff --git a/chrome/test/data/webui/settings/a11y_page_test.ts b/chrome/test/data/webui/settings/a11y_page_test.ts
index 8234cd50..fa23014a 100644
--- a/chrome/test/data/webui/settings/a11y_page_test.ts
+++ b/chrome/test/data/webui/settings/a11y_page_test.ts
@@ -7,9 +7,8 @@
 // clang-format off
 // <if expr="is_win or is_linux or is_macosx">
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
-import {ScreenAiInstallStatus} from 'chrome://settings/lazy_load.js';
-import {SettingsToggleButtonElement} from 'chrome://settings/settings.js';
-import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {ScreenAiInstallStatus, SettingsPdfOcrToggleElement} from 'chrome://settings/lazy_load.js';
+import {assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 // </if>
@@ -22,11 +21,18 @@
 
 class TestAccessibilityBrowserProxy extends TestBrowserProxy implements
     AccessibilityBrowserProxy {
+  private pdfOcrState_: ScreenAiInstallStatus;
+
   constructor() {
     super([
       'openTrackpadGesturesSettings',
       'recordOverscrollHistoryNavigationChanged',
+      // <if expr="is_win or is_linux or is_macosx">
+      'getScreenAiInstallState',
+      // </if>
     ]);
+
+    this.pdfOcrState_ = ScreenAiInstallStatus.NOT_DOWNLOADED;
   }
 
   openTrackpadGesturesSettings() {
@@ -36,6 +42,13 @@
   recordOverscrollHistoryNavigationChanged(enabled: boolean) {
     this.methodCalled('recordOverscrollHistoryNavigationChanged', enabled);
   }
+
+  // <if expr="is_win or is_linux or is_macosx">
+  getScreenAiInstallState() {
+    this.methodCalled('getScreenAiInstallState');
+    return Promise.resolve(this.pdfOcrState_);
+  }
+  // </if>
 }
 
 suite('A11yPage', () => {
@@ -70,86 +83,27 @@
   test('check pdf ocr toggle visibility', async () => {
     assertTrue(loadTimeData.getBoolean('pdfOcrEnabled'));
 
-    // Simulate disabling a screen reader to hide the PDF OCR toggle.
+    // Simulate disabling a screen reader to exclude the PDF OCR toggle in a
+    // DOM.
     webUIListenerCallback('screen-reader-state-changed', false);
 
-    const pdfOcrToggle =
-        a11yPage.shadowRoot!.querySelector<SettingsToggleButtonElement>(
-            '#pdfOcrToggle');
-    assertTrue(!!pdfOcrToggle);
     await flushTasks();
-    assertFalse(isVisible(pdfOcrToggle));
+    let pdfOcrToggle =
+        a11yPage.shadowRoot!.querySelector<SettingsPdfOcrToggleElement>(
+            '#pdfOcrToggle');
+    assertFalse(!!pdfOcrToggle);
 
-    // Simulate enabling a screen reader to show the PDF OCR toggle.
+    // Simulate enabling a screen reader to include the PDF OCR toggle in a
+    // DOM.
     webUIListenerCallback('screen-reader-state-changed', true);
 
     await flushTasks();
+    pdfOcrToggle =
+        a11yPage.shadowRoot!.querySelector<SettingsPdfOcrToggleElement>(
+            '#pdfOcrToggle');
+    assertTrue(!!pdfOcrToggle);
     assertTrue(isVisible(pdfOcrToggle));
   });
-
-  test('test pdf ocr toggle and pref', async () => {
-    assertTrue(loadTimeData.getBoolean('pdfOcrEnabled'));
-    // Simulate enabling a screen reader to show the PDF OCR toggle.
-    webUIListenerCallback('screen-reader-state-changed', true);
-
-    const pdfOcrToggle =
-        a11yPage.shadowRoot!.querySelector<SettingsToggleButtonElement>(
-            '#pdfOcrToggle');
-    assertTrue(!!pdfOcrToggle);
-    await flushTasks();
-
-    // The PDF OCR pref is on by default, so the button should be toggled on.
-    assertTrue(
-        a11yPage.getPref('settings.a11y.pdf_ocr_always_active').value,
-        'pdf ocr pref should be on by default');
-    assertTrue(pdfOcrToggle.checked);
-
-    pdfOcrToggle.click();
-    await flushTasks();
-    assertFalse(
-        a11yPage.getPref('settings.a11y.pdf_ocr_always_active').value,
-        'pdf ocr pref should be off');
-    assertFalse(pdfOcrToggle.checked);
-  });
-
-  test('test pdf ocr toggle subtitle', async () => {
-    assertTrue(loadTimeData.getBoolean('pdfOcrEnabled'));
-    // Simulate enabling a screen reader to show the PDF OCR toggle.
-    webUIListenerCallback('screen-reader-state-changed', true);
-
-    const pdfOcrToggle =
-        a11yPage.shadowRoot!.querySelector<SettingsToggleButtonElement>(
-            '#pdfOcrToggle');
-    assertTrue(!!pdfOcrToggle);
-    await flushTasks();
-
-    webUIListenerCallback(
-        'pdf-ocr-state-changed', ScreenAiInstallStatus.NOT_DOWNLOADED);
-    assertEquals(a11yPage.i18n('pdfOcrSubtitle'), pdfOcrToggle.subLabel);
-
-    webUIListenerCallback(
-        'pdf-ocr-state-changed', ScreenAiInstallStatus.FAILED);
-    assertEquals(
-        a11yPage.i18n('pdfOcrDownloadErrorLabel'), pdfOcrToggle.subLabel);
-
-    webUIListenerCallback(
-        'pdf-ocr-state-changed', ScreenAiInstallStatus.DOWNLOADING);
-    assertEquals(
-        a11yPage.i18n('pdfOcrDownloadingLabel'), pdfOcrToggle.subLabel);
-
-    webUIListenerCallback('pdf-ocr-downloading-progress-changed', 50);
-    assertEquals(
-        a11yPage.i18n('pdfOcrDownloadProgressLabel', 50),
-        pdfOcrToggle.subLabel);
-
-    webUIListenerCallback(
-        'pdf-ocr-state-changed', ScreenAiInstallStatus.DOWNLOADED);
-    assertEquals(
-        a11yPage.i18n('pdfOcrDownloadCompleteLabel'), pdfOcrToggle.subLabel);
-
-    webUIListenerCallback('pdf-ocr-state-changed', ScreenAiInstallStatus.READY);
-    assertEquals(a11yPage.i18n('pdfOcrSubtitle'), pdfOcrToggle.subLabel);
-  });
   // </if>
 
   // TODO(crbug.com/1499996): Add more test cases to improve code coverage.
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 4efb1b2..5613475 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -74,7 +74,6 @@
     "crostini_page/crostini_settings_card_test.ts",
     "crostini_page/crostini_shared_usb_devices_test.ts",
     "crostini_page/crostini_subpage_test.ts",
-    "crostini_page/guest_os_shared_paths_for_crostini_test.ts",
     "crostini_page/test_crostini_browser_proxy.ts",
 
     "date_time_page/date_time_settings_card_test.ts",
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_arc_adb_test.ts b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_arc_adb_test.ts
index a280d9c..73aed526 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_arc_adb_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_arc_adb_test.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://os-settings/lazy_load.js';
 
-import {CrostiniPortSetting, SettingsCrostiniArcAdbElement, SettingsCrostiniPageElement} from 'chrome://os-settings/lazy_load.js';
+import {CrostiniPortSetting, SettingsCrostiniArcAdbElement} from 'chrome://os-settings/lazy_load.js';
 import {Router, routes, settingMojom} from 'chrome://os-settings/os_settings.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {getDeepActiveElement} from 'chrome://resources/js/util.js';
@@ -12,8 +12,8 @@
 import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {disableAnimationsAndTransitions} from 'chrome://webui-test/test_api.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
 
-let crostiniPage: SettingsCrostiniPageElement;
 let subpage: SettingsCrostiniArcAdbElement;
 
 interface PrefParams {
@@ -31,7 +31,7 @@
   arcEnabled = false,
   bruschettaInstalled = false,
 }: PrefParams = {}): void {
-  crostiniPage.prefs = {
+  subpage.prefs = {
     arc: {
       enabled: {value: arcEnabled},
     },
@@ -53,35 +53,26 @@
 }
 
 suite('<settings-crostini-arc-adb>', () => {
+  suiteSetup(() => {
+    disableAnimationsAndTransitions();
+  });
+
   setup(async () => {
     loadTimeData.overrideValues({
+      arcAdbSideloadingSupported: true,
       isCrostiniAllowed: true,
       isCrostiniSupported: true,
     });
 
-    crostiniPage = document.createElement('settings-crostini-page');
-    document.body.appendChild(crostiniPage);
-    flush();
-
-    disableAnimationsAndTransitions();
-
-    setCrostiniPrefs(true, {arcEnabled: true});
-    loadTimeData.overrideValues({
-      arcAdbSideloadingSupported: true,
-    });
-
-    await flushTasks();
     Router.getInstance().navigateTo(routes.CROSTINI_ANDROID_ADB);
-
+    subpage = document.createElement('settings-crostini-arc-adb');
+    document.body.appendChild(subpage);
+    setCrostiniPrefs(true, {arcEnabled: true});
     await flushTasks();
-    const subpageElement =
-        crostiniPage.shadowRoot!.querySelector('settings-crostini-arc-adb');
-    assertTrue(!!subpageElement);
-    subpage = subpageElement;
   });
 
   teardown(() => {
-    crostiniPage.remove();
+    subpage.remove();
     Router.getInstance().resetRouteForTesting();
   });
 
@@ -98,6 +89,8 @@
         subpage.shadowRoot!.querySelector<HTMLButtonElement>(
             '#arcAdbEnabledButton');
     assertTrue(!!deepLinkElement);
+    assertTrue(isVisible(deepLinkElement));
+
     await waitAfterNextRender(deepLinkElement);
     assertEquals(
         deepLinkElement, getDeepActiveElement(),
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_export_import_test.ts b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_export_import_test.ts
index c1e0dde..01a908f 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_export_import_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_export_import_test.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://os-settings/lazy_load.js';
 
-import {ContainerInfo, ContainerSelectElement, CrostiniBrowserProxyImpl, CrostiniPortSetting, GuestOsBrowserProxyImpl, SettingsCrostiniExportImportElement, SettingsCrostiniPageElement} from 'chrome://os-settings/lazy_load.js';
+import {ContainerInfo, ContainerSelectElement, CrostiniBrowserProxyImpl, CrostiniPortSetting, GuestOsBrowserProxyImpl, SettingsCrostiniExportImportElement} from 'chrome://os-settings/lazy_load.js';
 import {Router, routes, settingMojom} from 'chrome://os-settings/os_settings.js';
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
@@ -13,12 +13,12 @@
 import {assertEquals, assertFalse, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {disableAnimationsAndTransitions} from 'chrome://webui-test/test_api.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
 
 import {TestGuestOsBrowserProxy} from '../guest_os/test_guest_os_browser_proxy.js';
 
 import {TestCrostiniBrowserProxy} from './test_crostini_browser_proxy.js';
 
-let crostiniPage: SettingsCrostiniPageElement;
 let subpage: SettingsCrostiniExportImportElement;
 let guestOsBrowserProxy: TestGuestOsBrowserProxy;
 let crostiniBrowserProxy: TestCrostiniBrowserProxy;
@@ -66,7 +66,7 @@
   arcEnabled = false,
   bruschettaInstalled = false,
 }: PrefParams = {}): void {
-  crostiniPage.prefs = {
+  subpage.prefs = {
     arc: {
       enabled: {value: arcEnabled},
     },
@@ -98,27 +98,14 @@
 }
 
 suite('<settings-crostini-export-import>', () => {
+  suiteSetup(() => {
+    disableAnimationsAndTransitions();
+  });
+
   setup(async () => {
     loadTimeData.overrideValues({
       isCrostiniAllowed: true,
       isCrostiniSupported: true,
-    });
-    crostiniBrowserProxy = new TestCrostiniBrowserProxy();
-    CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
-    guestOsBrowserProxy = new TestGuestOsBrowserProxy();
-    GuestOsBrowserProxyImpl.setInstanceForTesting(guestOsBrowserProxy);
-
-    crostiniPage = document.createElement('settings-crostini-page');
-    document.body.appendChild(crostiniPage);
-    flush();
-
-    disableAnimationsAndTransitions();
-
-    setCrostiniPrefs(true, {arcEnabled: true});
-    const requestInstallerStatusCallCount =
-        crostiniBrowserProxy.getCallCount('requestCrostiniInstallerStatus');
-
-    loadTimeData.overrideValues({
       showCrostiniExportImport: true,
       showCrostiniContainerUpgrade: true,
       showCrostiniPortForwarding: true,
@@ -126,29 +113,33 @@
       arcAdbSideloadingSupported: true,
       showCrostiniExtraContainers: true,
     });
+
+    crostiniBrowserProxy = new TestCrostiniBrowserProxy();
     crostiniBrowserProxy.containerInfo = singleContainer;
-    await flushTasks();
+    CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
+    guestOsBrowserProxy = new TestGuestOsBrowserProxy();
+    GuestOsBrowserProxyImpl.setInstanceForTesting(guestOsBrowserProxy);
 
     Router.getInstance().navigateTo(routes.CROSTINI_EXPORT_IMPORT);
-
+    subpage = document.createElement('settings-crostini-export-import');
+    document.body.appendChild(subpage);
+    setCrostiniPrefs(true, {arcEnabled: true});
     await flushTasks();
-    const subpageElement = crostiniPage.shadowRoot!.querySelector(
-        'settings-crostini-export-import');
-    assertTrue(!!subpageElement);
-    subpage = subpageElement;
+
     assertEquals(
         1,
         crostiniBrowserProxy.getCallCount(
             'requestCrostiniExportImportOperationStatus'));
     assertEquals(
-        requestInstallerStatusCallCount + 1,
-        crostiniBrowserProxy.getCallCount('requestCrostiniInstallerStatus'));
+        1, crostiniBrowserProxy.getCallCount('requestCrostiniInstallerStatus'));
     assertEquals(1, crostiniBrowserProxy.getCallCount('requestContainerInfo'));
   });
 
   teardown(() => {
-    crostiniPage.remove();
+    subpage.remove();
     Router.getInstance().resetRouteForTesting();
+    crostiniBrowserProxy.reset();
+    guestOsBrowserProxy.reset();
   });
 
   test('Deep link to backup linux', async () => {
@@ -157,17 +148,13 @@
     const params = new URLSearchParams();
     params.append('settingId', BACKUP_LINUX_APPS_AND_FILES_SETTING);
     Router.getInstance().navigateTo(routes.CROSTINI_EXPORT_IMPORT, params);
-
     flush();
-    const subpageElement = crostiniPage.shadowRoot!.querySelector(
-        'settings-crostini-export-import');
-    assertTrue(!!subpageElement);
-    subpage = subpageElement;
 
     const deepLinkElement =
         subpage.shadowRoot!.querySelector<HTMLButtonElement>(
             '#export cr-button');
     assertTrue(!!deepLinkElement);
+    assertTrue(isVisible(subpage));
     await waitAfterNextRender(deepLinkElement);
     assertEquals(
         deepLinkElement, getDeepActiveElement(),
@@ -281,12 +268,8 @@
     assertFalse(importBtn.disabled);
     webUIListenerCallback(
         'crostini-export-import-operation-status-changed', true);
-
     await flushTasks();
-    let subpageElement = crostiniPage.shadowRoot!.querySelector(
-        'settings-crostini-export-import');
-    assertTrue(!!subpageElement);
-    subpage = subpageElement;
+
     exportBtn = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
         '#export cr-button');
     assertTrue(!!exportBtn);
@@ -297,12 +280,8 @@
     assertTrue(importBtn.disabled);
     webUIListenerCallback(
         'crostini-export-import-operation-status-changed', false);
-
     await flushTasks();
-    subpageElement = crostiniPage.shadowRoot!.querySelector(
-        'settings-crostini-export-import');
-    assertTrue(!!subpageElement);
-    subpage = subpageElement;
+
     exportBtn = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
         '#export cr-button');
     assertTrue(!!exportBtn);
@@ -326,12 +305,8 @@
         assertFalse(exportBtn.disabled);
         assertFalse(importBtn.disabled);
         webUIListenerCallback('crostini-installer-status-changed', true);
-
         await flushTasks();
-        let subpageElement = crostiniPage.shadowRoot!.querySelector(
-            'settings-crostini-export-import');
-        assertTrue(!!subpageElement);
-        subpage = subpageElement;
+
         exportBtn = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
             '#export cr-button');
         assertTrue(!!exportBtn);
@@ -342,12 +317,8 @@
         assertTrue(exportBtn.disabled);
         assertTrue(importBtn.disabled);
         webUIListenerCallback('crostini-installer-status-changed', false);
-
         await flushTasks();
-        subpageElement = crostiniPage.shadowRoot!.querySelector(
-            'settings-crostini-export-import');
-        assertTrue(!!subpageElement);
-        subpage = subpageElement;
+
         exportBtn = subpage.shadowRoot!.querySelector<HTMLButtonElement>(
             '#export cr-button');
         assertTrue(!!exportBtn);
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_page_test.ts b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_page_test.ts
index 3c2ecc5..dfcac79c 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_page_test.ts
@@ -5,11 +5,11 @@
 import 'chrome://os-settings/lazy_load.js';
 
 import {CrostiniBrowserProxyImpl, GuestOsBrowserProxyImpl, SettingsCrostiniConfirmationDialogElement, SettingsCrostiniPageElement} from 'chrome://os-settings/lazy_load.js';
-import {Router} from 'chrome://os-settings/os_settings.js';
+import {Router, routes} from 'chrome://os-settings/os_settings.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {disableAnimationsAndTransitions} from 'chrome://webui-test/test_api.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 
 import {TestGuestOsBrowserProxy} from '../guest_os/test_guest_os_browser_proxy.js';
 
@@ -20,6 +20,34 @@
 let crostiniBrowserProxy: TestCrostiniBrowserProxy;
 
 suite('<settings-crostini-page>', () => {
+  function setCrostiniPrefs(enabled: boolean, {
+    sharedPaths = {},
+    forwardedPorts = [],
+    micAllowed = false,
+    arcEnabled = false,
+    bruschettaInstalled = false,
+  } = {}): void {
+    crostiniPage.prefs = {
+      arc: {
+        enabled: {value: arcEnabled},
+      },
+      bruschetta: {
+        installed: {
+          value: bruschettaInstalled,
+        },
+      },
+      crostini: {
+        enabled: {value: enabled},
+        mic_allowed: {value: micAllowed},
+        port_forwarding: {ports: {value: forwardedPorts}},
+      },
+      guest_os: {
+        paths_shared_to_vms: {value: sharedPaths},
+      },
+    };
+    flush();
+  }
+
   setup(() => {
     loadTimeData.overrideValues({
       isCrostiniAllowed: true,
@@ -30,11 +58,10 @@
     guestOsBrowserProxy = new TestGuestOsBrowserProxy();
     GuestOsBrowserProxyImpl.setInstanceForTesting(guestOsBrowserProxy);
 
+    Router.getInstance().navigateTo(routes.CROSTINI);
     crostiniPage = document.createElement('settings-crostini-page');
     document.body.appendChild(crostiniPage);
     flush();
-
-    disableAnimationsAndTransitions();
   });
 
   teardown(() => {
@@ -104,4 +131,24 @@
       assertFalse(crDialogElement.open);
     });
   });
+
+  // Functionality is already tested in OSSettingsGuestOsSharedPathsTest,
+  // so just check that we correctly set up the page for our 'termina' VM.
+  test(
+      '<settings-guest-os-shared-paths> is correctly set up for termina VM',
+      async () => {
+        setCrostiniPrefs(
+            true,
+            {sharedPaths: {path1: ['termina'], path2: ['some-other-vm']}});
+        await flushTasks();
+
+        Router.getInstance().navigateTo(routes.CROSTINI_SHARED_PATHS);
+        await flushTasks();
+
+        const subpage = crostiniPage.shadowRoot!.querySelector(
+            'settings-guest-os-shared-paths');
+        assertTrue(!!subpage);
+        assertEquals(
+            1, subpage.shadowRoot!.querySelectorAll('.list-item').length);
+      });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_port_forwarding_test.ts b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_port_forwarding_test.ts
index 283f4824..5e3e48a4 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_port_forwarding_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_port_forwarding_test.ts
@@ -4,18 +4,17 @@
 
 import 'chrome://os-settings/lazy_load.js';
 
-import {ContainerInfo, ContainerSelectElement, CrostiniBrowserProxyImpl, CrostiniPortForwardingElement, CrostiniPortSetting, SettingsCrostiniPageElement} from 'chrome://os-settings/lazy_load.js';
+import {ContainerInfo, ContainerSelectElement, CrostiniBrowserProxyImpl, CrostiniPortForwardingElement, CrostiniPortSetting} from 'chrome://os-settings/lazy_load.js';
 import {CrInputElement, CrToastElement, CrToggleElement, Router, routes} from 'chrome://os-settings/os_settings.js';
 import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
-import {disableAnimationsAndTransitions} from 'chrome://webui-test/test_api.js';
+import {isVisible} from 'chrome://webui-test/test_util.js';
 
 import {TestCrostiniBrowserProxy} from './test_crostini_browser_proxy.js';
 
-let crostiniPage: SettingsCrostiniPageElement;
 let subpage: CrostiniPortForwardingElement;
 let crostiniBrowserProxy: TestCrostiniBrowserProxy;
 
@@ -34,7 +33,7 @@
   arcEnabled = false,
   bruschettaInstalled = false,
 }: PrefParams = {}): void {
-  crostiniPage.prefs = {
+  subpage.prefs = {
     arc: {
       enabled: {value: arcEnabled},
     },
@@ -89,17 +88,15 @@
       isCrostiniAllowed: true,
       isCrostiniSupported: true,
     });
+
     crostiniBrowserProxy = new TestCrostiniBrowserProxy();
-    CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
-
-    crostiniPage = document.createElement('settings-crostini-page');
-    document.body.appendChild(crostiniPage);
-    flush();
-
-    disableAnimationsAndTransitions();
-
     crostiniBrowserProxy.portOperationSuccess = true;
     crostiniBrowserProxy.containerInfo = allContainers;
+    CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
+
+    Router.getInstance().navigateTo(routes.CROSTINI_PORT_FORWARDING);
+    subpage = document.createElement('settings-crostini-port-forwarding');
+    document.body.appendChild(subpage);
     setCrostiniPrefs(true, {
       forwardedPorts: [
         {
@@ -128,22 +125,15 @@
         },
       ],
     });
-
     await flushTasks();
-    Router.getInstance().navigateTo(routes.CROSTINI_PORT_FORWARDING);
 
-    await flushTasks();
-    const subpageElement = crostiniPage.shadowRoot!.querySelector(
-        'settings-crostini-port-forwarding');
-    assertTrue(!!subpageElement);
-    subpage = subpageElement;
-    assertTrue(!!subpage);
     assertEquals(1, crostiniBrowserProxy.getCallCount('requestContainerInfo'));
   });
 
   teardown(() => {
-    crostiniPage.remove();
+    subpage.remove();
     Router.getInstance().resetRouteForTesting();
+    crostiniBrowserProxy.reset();
   });
 
   test('Display ports', () => {
@@ -264,10 +254,9 @@
             'cr-dialog cr-button[id="cancel"]');
     assertTrue(!!cancelBtn);
     cancelBtn.click();
+    flush();
 
-    await flushTasks();
-    assertTrue(!!crostiniPage.shadowRoot!.querySelector(
-        'settings-crostini-port-forwarding'));
+    assertFalse(isVisible(addPortDialogElement));
   });
 
   test('Remove all ports', async () => {
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_shared_usb_devices_test.ts b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_shared_usb_devices_test.ts
index cc70a88..d385372 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_shared_usb_devices_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/crostini_page/crostini_shared_usb_devices_test.ts
@@ -4,10 +4,9 @@
 
 import 'chrome://os-settings/lazy_load.js';
 
-import {ContainerInfo, CrostiniBrowserProxyImpl, CrostiniPortSetting, CrostiniSharedUsbDevicesElement, GuestOsBrowserProxyImpl, SettingsCrostiniPageElement} from 'chrome://os-settings/lazy_load.js';
+import {ContainerInfo, CrostiniBrowserProxyImpl, CrostiniSharedUsbDevicesElement, GuestOsBrowserProxyImpl} from 'chrome://os-settings/lazy_load.js';
 import {CrToggleElement, Router, routes} from 'chrome://os-settings/os_settings.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {disableAnimationsAndTransitions} from 'chrome://webui-test/test_api.js';
@@ -16,7 +15,6 @@
 
 import {TestCrostiniBrowserProxy} from './test_crostini_browser_proxy.js';
 
-let crostiniPage: SettingsCrostiniPageElement;
 let subpage: CrostiniSharedUsbDevicesElement;
 let guestOsBrowserProxy: TestGuestOsBrowserProxy;
 let crostiniBrowserProxy: TestCrostiniBrowserProxy;
@@ -39,70 +37,42 @@
   },
 ];
 
-interface PrefParams {
-  sharedPaths?: {[key: string]: string[]};
-  forwardedPorts?: CrostiniPortSetting[];
-  micAllowed?: boolean;
-  arcEnabled?: boolean;
-  bruschettaInstalled?: boolean;
-}
-
-function setCrostiniPrefs(enabled: boolean, {
-  sharedPaths = {},
-  forwardedPorts = [],
-  micAllowed = false,
-  arcEnabled = false,
-  bruschettaInstalled = false,
-}: PrefParams = {}): void {
-  crostiniPage.prefs = {
-    arc: {
-      enabled: {value: arcEnabled},
-    },
-    bruschetta: {
-      installed: {
-        value: bruschettaInstalled,
-      },
-    },
-    crostini: {
-      enabled: {value: enabled},
-      mic_allowed: {value: micAllowed},
-      port_forwarding: {ports: {value: forwardedPorts}},
-    },
-    guest_os: {
-      paths_shared_to_vms: {value: sharedPaths},
-    },
-  };
-  flush();
-}
-
 suite('<settings-crostini-shared-usb-devices>', () => {
+  async function initSubpage(): Promise<void> {
+    subpage = document.createElement('settings-crostini-shared-usb-devices');
+    document.body.appendChild(subpage);
+    await flushTasks();
+  }
+
+  suiteSetup(() => {
+    disableAnimationsAndTransitions();
+
+    crostiniBrowserProxy = new TestCrostiniBrowserProxy();
+    CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
+    guestOsBrowserProxy = new TestGuestOsBrowserProxy();
+    GuestOsBrowserProxyImpl.setInstanceForTesting(guestOsBrowserProxy);
+  });
+
   setup(() => {
     loadTimeData.overrideValues({
       isCrostiniAllowed: true,
       isCrostiniSupported: true,
     });
-    crostiniBrowserProxy = new TestCrostiniBrowserProxy();
-    CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
-    guestOsBrowserProxy = new TestGuestOsBrowserProxy();
-    GuestOsBrowserProxyImpl.setInstanceForTesting(guestOsBrowserProxy);
 
-    crostiniPage = document.createElement('settings-crostini-page');
-    document.body.appendChild(crostiniPage);
-    flush();
-
-    disableAnimationsAndTransitions();
+    Router.getInstance().navigateTo(routes.CROSTINI_SHARED_USB_DEVICES);
   });
 
   teardown(() => {
-    crostiniPage.remove();
+    subpage.remove();
     Router.getInstance().resetRouteForTesting();
+    crostiniBrowserProxy.reset();
+    guestOsBrowserProxy.reset();
   });
 
   // Functionality is already tested in OSSettingsGuestOsSharedUsbDevicesTest,
   // so just check that we correctly set up the page for our 'termina' VM.
   suite('Subpage shared Usb devices', () => {
     setup(async () => {
-      setCrostiniPrefs(true);
       loadTimeData.overrideValues({
         showCrostiniExtraContainers: false,
       });
@@ -131,14 +101,7 @@
         },
       ];
 
-      await flushTasks();
-      Router.getInstance().navigateTo(routes.CROSTINI_SHARED_USB_DEVICES);
-
-      await flushTasks();
-      const subpageElement = crostiniPage.shadowRoot!.querySelector(
-          'settings-crostini-shared-usb-devices');
-      assertTrue(!!subpageElement);
-      subpage = subpageElement;
+      await initSubpage();
     });
 
     test('USB devices are shown', () => {
@@ -154,7 +117,6 @@
   // so just check that we correctly set up the page.
   suite('Subpage shared Usb devices multi container', () => {
     setup(async () => {
-      setCrostiniPrefs(true);
       loadTimeData.overrideValues({
         showCrostiniExtraContainers: true,
       });
@@ -195,14 +157,7 @@
         },
       ];
 
-      await flushTasks();
-      Router.getInstance().navigateTo(routes.CROSTINI_SHARED_USB_DEVICES);
-
-      await flushTasks();
-      const subpageElement = crostiniPage.shadowRoot!.querySelector(
-          'settings-crostini-shared-usb-devices');
-      assertTrue(!!subpageElement);
-      subpage = subpageElement;
+      await initSubpage();
     });
 
     test('USB devices are shown', () => {
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_page/guest_os_shared_paths_for_crostini_test.ts b/chrome/test/data/webui/settings/chromeos/crostini_page/guest_os_shared_paths_for_crostini_test.ts
deleted file mode 100644
index 888aa75..0000000
--- a/chrome/test/data/webui/settings/chromeos/crostini_page/guest_os_shared_paths_for_crostini_test.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'chrome://os-settings/lazy_load.js';
-
-import {CrostiniBrowserProxyImpl, CrostiniPortSetting, GuestOsBrowserProxyImpl, SettingsCrostiniPageElement, SettingsGuestOsSharedPathsElement} from 'chrome://os-settings/lazy_load.js';
-import {Router, routes} from 'chrome://os-settings/os_settings.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
-import {disableAnimationsAndTransitions} from 'chrome://webui-test/test_api.js';
-
-import {TestGuestOsBrowserProxy} from '../guest_os/test_guest_os_browser_proxy.js';
-
-import {TestCrostiniBrowserProxy} from './test_crostini_browser_proxy.js';
-
-let crostiniPage: SettingsCrostiniPageElement;
-let subpage: SettingsGuestOsSharedPathsElement;
-let guestOsBrowserProxy: TestGuestOsBrowserProxy;
-let crostiniBrowserProxy: TestCrostiniBrowserProxy;
-
-interface PrefParams {
-  sharedPaths?: {[key: string]: string[]};
-  forwardedPorts?: CrostiniPortSetting[];
-  micAllowed?: boolean;
-  arcEnabled?: boolean;
-  bruschettaInstalled?: boolean;
-}
-
-function setCrostiniPrefs(enabled: boolean, {
-  sharedPaths = {},
-  forwardedPorts = [],
-  micAllowed = false,
-  arcEnabled = false,
-  bruschettaInstalled = false,
-}: PrefParams = {}): void {
-  crostiniPage.prefs = {
-    arc: {
-      enabled: {value: arcEnabled},
-    },
-    bruschetta: {
-      installed: {
-        value: bruschettaInstalled,
-      },
-    },
-    crostini: {
-      enabled: {value: enabled},
-      mic_allowed: {value: micAllowed},
-      port_forwarding: {ports: {value: forwardedPorts}},
-    },
-    guest_os: {
-      paths_shared_to_vms: {value: sharedPaths},
-    },
-  };
-  flush();
-}
-
-// Functionality is already tested in OSSettingsGuestOsSharedPathsTest,
-// so just check that we correctly set up the page for our 'termina' VM.
-suite('Subpage shared paths', () => {
-  setup(async () => {
-    loadTimeData.overrideValues({
-      isCrostiniAllowed: true,
-      isCrostiniSupported: true,
-    });
-    crostiniBrowserProxy = new TestCrostiniBrowserProxy();
-    CrostiniBrowserProxyImpl.setInstanceForTesting(crostiniBrowserProxy);
-    guestOsBrowserProxy = new TestGuestOsBrowserProxy();
-    GuestOsBrowserProxyImpl.setInstanceForTesting(guestOsBrowserProxy);
-
-    crostiniPage = document.createElement('settings-crostini-page');
-    document.body.appendChild(crostiniPage);
-    flush();
-
-    disableAnimationsAndTransitions();
-
-    setCrostiniPrefs(
-        true, {sharedPaths: {path1: ['termina'], path2: ['some-other-vm']}});
-
-    await flushTasks();
-    Router.getInstance().navigateTo(routes.CROSTINI_SHARED_PATHS);
-
-    await flushTasks();
-    const subpageElement = crostiniPage.shadowRoot!.querySelector(
-        'settings-guest-os-shared-paths');
-    assertTrue(!!subpageElement);
-    subpage = subpageElement;
-    await flushTasks();
-  });
-
-  test('Basic', () => {
-    assertEquals(1, subpage.shadowRoot!.querySelectorAll('.list-item').length);
-  });
-});
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 ba2c953..49a8601 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
@@ -75,6 +75,11 @@
     assertDeepEquals(buttonActionList, expectedActionList);
   });
 
+  test('hasLauncherButton fetched from provider', async () => {
+    const expectedHasLauncherButton =
+        (await provider.hasLauncherButton())?.hasLauncherButton;
+    assertEquals(page.get('hasLauncherButton_'), expectedHasLauncherButton);
+  });
 
   test('button name change triggers settings update', async () => {
     const provider = page.get('inputDeviceSettingsProvider_');
diff --git a/chrome/test/data/webui/settings/chromeos/device_page/customize_pen_buttons_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/device_page/customize_pen_buttons_subpage_test.ts
index 51d131d3..18c71a2 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page/customize_pen_buttons_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/device_page/customize_pen_buttons_subpage_test.ts
@@ -76,6 +76,11 @@
     assertDeepEquals(buttonActionList, expectedActionList);
   });
 
+  test('hasLauncherButton fetched from provider', async () => {
+    const expectedHasLauncherButton =
+        (await provider.hasLauncherButton())?.hasLauncherButton;
+    assertEquals(page.get('hasLauncherButton_'), expectedHasLauncherButton);
+  });
 
   test('button name change triggers settings update', async () => {
     const provider = page.get('inputDeviceSettingsProvider_');
diff --git a/chrome/test/data/webui/settings/chromeos/device_page/customize_tablet_buttons_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/device_page/customize_tablet_buttons_subpage_test.ts
index 3b0d7a5d2..da24e224 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page/customize_tablet_buttons_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/device_page/customize_tablet_buttons_subpage_test.ts
@@ -72,6 +72,12 @@
     assertDeepEquals(buttonActionList, expectedActionList);
   });
 
+  test('hasLauncherButton fetched from provider', async () => {
+    const expectedHasLauncherButton =
+        (await provider.hasLauncherButton())?.hasLauncherButton;
+    assertEquals(page.get('hasLauncherButton_'), expectedHasLauncherButton);
+  });
+
   test('button name change triggers settings update', async () => {
     const provider = page.get('inputDeviceSettingsProvider_');
     assertTrue(!!provider);
diff --git a/chrome/test/data/webui/settings/chromeos/device_page/fake_input_device_settings_provider_test.ts b/chrome/test/data/webui/settings/chromeos/device_page/fake_input_device_settings_provider_test.ts
index 27cbafa..99407c1 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page/fake_input_device_settings_provider_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/device_page/fake_input_device_settings_provider_test.ts
@@ -163,4 +163,14 @@
     assertDeepEquals(
         graphicsTabletActions.options, fakeGraphicsTabletButtonActions);
   });
+
+  test('hasLauncherButton', async () => {
+    provider.setFakeHasLauncherButton(true);
+    let hasLauncherButton = await provider.hasLauncherButton();
+    assertDeepEquals(hasLauncherButton, {hasLauncherButton: true});
+
+    provider.setFakeHasLauncherButton(false);
+    hasLauncherButton = await provider.hasLauncherButton();
+    assertDeepEquals(hasLauncherButton, {hasLauncherButton: false});
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index 07bc3e3d..1f61f1b 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -32,6 +32,7 @@
       enabled: [
         'ash::features::kEnableHostnameSetting',
       ],
+      disabled: [],
     };
   }
 };
@@ -204,6 +205,22 @@
   mocha.run();
 });
 
+var OSSettingsCrostiniPageCrostiniArcAdbRevampTest =
+    class extends OSSettingsCrostiniPageCrostiniArcAdbTest {
+  /** @override */
+  get featureList() {
+    return {
+      enabled: super.featureList.enabled.concat([
+        'ash::features::kOsSettingsRevampWayfinding',
+      ]),
+    };
+  }
+};
+
+TEST_F('OSSettingsCrostiniPageCrostiniArcAdbRevampTest', 'AllJsTests', () => {
+  mocha.run();
+});
+
 var OSSettingsCrostiniPageCrostiniExportImportTest =
     class extends OSSettingsBrowserTest {
   /** @override */
@@ -221,6 +238,24 @@
   mocha.run();
 });
 
+var OSSettingsCrostiniPageCrostiniExportImportRevampTest =
+    class extends OSSettingsCrostiniPageCrostiniExportImportTest {
+  /** @override */
+  get featureList() {
+    return {
+      enabled: super.featureList.enabled.concat([
+        'ash::features::kOsSettingsRevampWayfinding',
+      ]),
+    };
+  }
+};
+
+TEST_F(
+    'OSSettingsCrostiniPageCrostiniExportImportRevampTest', 'AllJsTests',
+    () => {
+      mocha.run();
+    });
+
 var OSSettingsCrostiniPageCrostiniExtraContainersSubpageTest =
     class extends OSSettingsBrowserTest {
   /** @override */
@@ -268,6 +303,16 @@
   testGenPreamble() {
     return crostiniTestGenPreamble();
   }
+
+  /** @override */
+  get featureList() {
+    return {
+      ...super.featureList.enabled,
+      disabled: super.featureList.disabled.concat([
+        'ash::features::kOsSettingsRevampWayfinding',
+      ]),
+    };
+  }
 };
 
 TEST_F('OSSettingsCrostiniPageTest', 'AllJsTests', () => {
@@ -291,6 +336,24 @@
   mocha.run();
 });
 
+var OSSettingsCrostiniPageCrostiniPortForwardingRevampTest =
+    class extends OSSettingsCrostiniPageCrostiniPortForwardingTest {
+  /** @override */
+  get featureList() {
+    return {
+      enabled: super.featureList.enabled.concat([
+        'ash::features::kOsSettingsRevampWayfinding',
+      ]),
+    };
+  }
+};
+
+TEST_F(
+    'OSSettingsCrostiniPageCrostiniPortForwardingRevampTest', 'AllJsTests',
+    () => {
+      mocha.run();
+    });
+
 var OSSettingsCrostiniPageCrostiniSettingsCardTest =
     class extends OSSettingsBrowserTest {
   /** @override */
@@ -343,6 +406,24 @@
       mocha.run();
     });
 
+var OSSettingsCrostiniPageCrostiniSharedUsbDevicesRevampTest =
+    class extends OSSettingsCrostiniPageCrostiniSharedUsbDevicesTest {
+  /** @override */
+  get featureList() {
+    return {
+      enabled: super.featureList.enabled.concat([
+        'ash::features::kOsSettingsRevampWayfinding',
+      ]),
+    };
+  }
+};
+
+TEST_F(
+    'OSSettingsCrostiniPageCrostiniSharedUsbDevicesRevampTest', 'AllJsTests',
+    () => {
+      mocha.run();
+    });
+
 var OSSettingsCrostiniPageCrostiniSubpageTest =
     class extends OSSettingsBrowserTest {
   /** @override */
@@ -360,25 +441,6 @@
   mocha.run();
 });
 
-var OSSettingsCrostiniPageGuestOsSharedPathsForCrostiniTest =
-    class extends OSSettingsBrowserTest {
-  /** @override */
-  get browsePreload() {
-    return 'chrome://os-settings/test_loader.html?module=settings/chromeos/crostini_page/guest_os_shared_paths_for_crostini_test.js';
-  }
-
-  /** @override */
-  testGenPreamble() {
-    return crostiniTestGenPreamble();
-  }
-};
-
-TEST_F(
-    'OSSettingsCrostiniPageGuestOsSharedPathsForCrostiniTest', 'AllJsTests',
-    () => {
-      mocha.run();
-    });
-
 [['AboutPage', 'os_about_page_tests.js'],
  ['ApnDetailDialog', 'apn_detail_dialog_test.js'],
  [
diff --git a/chrome/test/data/webui/settings/pdf_ocr_toggle_test.ts b/chrome/test/data/webui/settings/pdf_ocr_toggle_test.ts
new file mode 100644
index 0000000..78d2c1a
--- /dev/null
+++ b/chrome/test/data/webui/settings/pdf_ocr_toggle_test.ts
@@ -0,0 +1,90 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://settings/lazy_load.js';
+
+import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {ScreenAiInstallStatus, SettingsPdfOcrToggleElement} from 'chrome://settings/lazy_load.js';
+import {CrSettingsPrefs, loadTimeData, SettingsPrefsElement} from 'chrome://settings/settings.js';
+import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
+
+suite('SettingsPdfOcrToggleTest', () => {
+  let testElement: SettingsPdfOcrToggleElement;
+  let settingsPrefs: SettingsPrefsElement;
+
+  suiteSetup(function() {
+    loadTimeData.overrideValues({
+      pdfOcrEnabled: true,
+    });
+  });
+
+  setup(async function() {
+    document.body.innerHTML = window.trustedTypes!.emptyHTML;
+    testElement = document.createElement('settings-pdf-ocr-toggle');
+    settingsPrefs = document.createElement('settings-prefs');
+    document.body.appendChild(settingsPrefs);
+    await CrSettingsPrefs.initialized;
+
+    testElement.prefs = settingsPrefs.prefs;
+    document.body.appendChild(testElement);
+    flush();
+  });
+
+  test('test pdf ocr toggle and pref', async () => {
+    assertTrue(loadTimeData.getBoolean('pdfOcrEnabled'));
+    // Simulate enabling a screen reader to show the PDF OCR toggle.
+    webUIListenerCallback('screen-reader-state-changed', true);
+
+    const toggle = testElement.$.toggle;
+    await flushTasks();
+
+    // The PDF OCR pref is on by default, so the button should be toggled on.
+    assertTrue(
+        testElement.getPref('settings.a11y.pdf_ocr_always_active').value,
+        'pdf ocr pref should be on by default');
+    assertTrue(toggle.checked);
+
+    toggle.click();
+    await flushTasks();
+    assertFalse(
+        testElement.getPref('settings.a11y.pdf_ocr_always_active').value,
+        'pdf ocr pref should be off');
+    assertFalse(toggle.checked);
+  });
+
+  test('test pdf ocr toggle subtitle', async () => {
+    assertTrue(loadTimeData.getBoolean('pdfOcrEnabled'));
+    // Simulate enabling a screen reader to show the PDF OCR toggle.
+    webUIListenerCallback('screen-reader-state-changed', true);
+
+    const toggle = testElement.$.toggle;
+    await flushTasks();
+
+    webUIListenerCallback(
+        'pdf-ocr-state-changed', ScreenAiInstallStatus.NOT_DOWNLOADED);
+    assertEquals(testElement.i18n('pdfOcrSubtitle'), toggle.subLabel);
+
+    webUIListenerCallback(
+        'pdf-ocr-state-changed', ScreenAiInstallStatus.FAILED);
+    assertEquals(testElement.i18n('pdfOcrDownloadErrorLabel'), toggle.subLabel);
+
+    webUIListenerCallback(
+        'pdf-ocr-state-changed', ScreenAiInstallStatus.DOWNLOADING);
+    assertEquals(testElement.i18n('pdfOcrDownloadingLabel'), toggle.subLabel);
+
+    webUIListenerCallback('pdf-ocr-downloading-progress-changed', 50);
+    assertEquals(
+        testElement.i18n('pdfOcrDownloadProgressLabel', 50), toggle.subLabel);
+
+    webUIListenerCallback(
+        'pdf-ocr-state-changed', ScreenAiInstallStatus.DOWNLOADED);
+    assertEquals(
+        testElement.i18n('pdfOcrDownloadCompleteLabel'), toggle.subLabel);
+
+    webUIListenerCallback('pdf-ocr-state-changed', ScreenAiInstallStatus.READY);
+    assertEquals(testElement.i18n('pdfOcrSubtitle'), toggle.subLabel);
+  });
+});
diff --git a/chrome/test/data/webui/settings/settings_browsertest.cc b/chrome/test/data/webui/settings/settings_browsertest.cc
index 2c66c895..a6e4899 100644
--- a/chrome/test/data/webui/settings/settings_browsertest.cc
+++ b/chrome/test/data/webui/settings/settings_browsertest.cc
@@ -235,6 +235,12 @@
   RunTest("settings/payments_section_iban_test.js", "mocha.run()");
 }
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+IN_PROC_BROWSER_TEST_F(SettingsTest, PdfOcrToggle) {
+  RunTest("settings/pdf_ocr_toggle_test.js", "mocha.run()");
+}
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC)
+
 IN_PROC_BROWSER_TEST_F(SettingsTest, PeoplePage) {
   RunTest("settings/people_page_test.js", "mocha.run()");
 }
diff --git a/chrome/test/data/webui/side_panel/read_anything/highlight_callback_toggles_highlight.js b/chrome/test/data/webui/side_panel/read_anything/highlight_callback_toggles_highlight.js
index 611eb21..54cce278 100644
--- a/chrome/test/data/webui/side_panel/read_anything/highlight_callback_toggles_highlight.js
+++ b/chrome/test/data/webui/side_panel/read_anything/highlight_callback_toggles_highlight.js
@@ -14,7 +14,7 @@
       readAnythingApp.shadowRoot.querySelector('read-anything-toolbar')
           .shadowRoot;
   const highlightButton = toolbar.getElementById('highlight');
-  const sentence1 = 'Big wheel keep on turning.';
+  const sentence1 = 'Big wheel keep on turning. ';
   const sentence2 = 'Proud Mary keep on burning.';
   const axTree = {
     rootId: 1,
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlight_while_reading.js b/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlight_while_reading.js
index 309fcf6c..e86ab06 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlight_while_reading.js
+++ b/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlight_while_reading.js
@@ -14,7 +14,7 @@
   const readAnythingApp = document.querySelector('read-anything-app');
   const container = readAnythingApp.shadowRoot.getElementById('container');
 
-  const sentence1 = 'Only need the light when it\'s burning low.';
+  const sentence1 = 'Only need the light when it\'s burning low. ';
   const sentence2 = 'Only miss the sun when it starts to snow.';
   const axTree = {
     rootId: 1,
diff --git a/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlight_with_granularity_changes.js b/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlight_with_granularity_changes.js
index 93b4342..8362f79 100644
--- a/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlight_with_granularity_changes.js
+++ b/chrome/test/data/webui/side_panel/read_anything/read_aloud_highlight_with_granularity_changes.js
@@ -15,9 +15,9 @@
   const readAnythingApp = document.querySelector('read-anything-app');
   const container = readAnythingApp.shadowRoot.getElementById('container');
 
-  const sentence1 = 'The snow glows white on the mountain tonight.';
-  const sentence2 = 'Not a footprint to be seen';
-  const sentence3 = 'A kingdom of isolation.';
+  const sentence1 = 'The snow glows white on the mountain tonight. ';
+  const sentence2 = 'Not a footprint to be seen. ';
+  const sentence3 = 'A kingdom of isolation. ';
   const sentence4 = 'And it looks like I\'m the queen.';
   const axTree = {
     rootId: 1,
diff --git a/chrome/updater/certificate_tag.cc b/chrome/updater/certificate_tag.cc
index dd19570..f3a02bf4 100644
--- a/chrome/updater/certificate_tag.cc
+++ b/chrome/updater/certificate_tag.cc
@@ -38,8 +38,7 @@
 PEBinary::~PEBinary() = default;
 
 // static
-std::unique_ptr<BinaryInterface> PEBinary::Parse(
-    base::span<const uint8_t> binary) {
+std::unique_ptr<PEBinary> PEBinary::Parse(base::span<const uint8_t> binary) {
   // Parse establishes some offsets into |binary| for structures that |GetTag|
   // and |SetTag| will both need.
 
@@ -169,7 +168,7 @@
   return ret;
 }
 
-std::optional<std::vector<const uint8_t>> PEBinary::tag() const {
+std::optional<std::vector<uint8_t>> PEBinary::tag() const {
   return tag_;
 }
 
@@ -497,7 +496,7 @@
 bool PEBinary::ParseTag() {
   const auto [success, tag] = ParseTagImpl(content_info_);
   if (tag) {
-    tag_ = std::vector<const uint8_t>(tag->begin(), tag->end());
+    tag_ = std::vector<uint8_t>(tag->begin(), tag->end());
   }
   return success;
 }
@@ -790,7 +789,7 @@
 MSIBinary::~MSIBinary() = default;
 
 // static
-std::unique_ptr<BinaryInterface> MSIBinary::Parse(
+std::unique_ptr<MSIBinary> MSIBinary::Parse(
     base::span<const uint8_t> file_contents) {
   if (file_contents.size() < kNumHeaderTotalBytes) {
     // MSI file is too short to contain header.
@@ -867,6 +866,8 @@
   MSIDirEntry new_sig_dir_entry = sig_dir_entry_;
   new_sig_dir_entry.stream_first_sector = first_signed_data_sector;
   new_sig_dir_entry.stream_size = signed_data.size();
+  const size_t signed_data_offset =
+      first_signed_data_sector * sector_format_.size;
 
   // Write out the...
   // ...header,
@@ -879,9 +880,11 @@
 
   // ...content,
   // Make a copy of the content bytes, since new data will be overlaid on it.
-  std::vector<uint8_t> new_contents(sector_format_.size *
-                                    FirstFreeFatEntry(new_fat_entries));
-  std::memcpy(&new_contents[0], &contents_[0], contents_.size());
+  const size_t new_contents_size =
+      sector_format_.size * FirstFreeFatEntry(new_fat_entries);
+  CHECK_GT(new_contents_size, signed_data_offset + signed_data.size());
+  std::vector<uint8_t> new_contents(new_contents_size);
+  std::memcpy(&new_contents[0], &contents_[0], signed_data_offset);
 
   // ...signedData directory entry from local modified copy,
   std::memcpy(&new_contents[sig_dir_offset_], &new_sig_dir_entry,
@@ -916,8 +919,8 @@
   // ...signedData
   // `new_contents` is zero-initialized, so no need to add padding to end of
   // sector. The sectors allocated for signedData are guaranteed contiguous.
-  std::memcpy(&new_contents[first_signed_data_sector * sector_format_.size],
-              &signed_data[0], signed_data.size());
+  std::memcpy(&new_contents[signed_data_offset], &signed_data[0],
+              signed_data.size());
 
   // ...finally, build and return the new binary.
   std::vector<uint8_t> binary(header_sector_bytes.size() + new_contents.size());
@@ -941,12 +944,12 @@
 bool MSIBinary::ParseTag() {
   const auto [success, tag] = ParseTagImpl(signed_data_bytes_);
   if (tag) {
-    tag_ = std::vector<const uint8_t>(tag->begin(), tag->end());
+    tag_ = std::vector<uint8_t>(tag->begin(), tag->end());
   }
   return success;
 }
 
-std::optional<std::vector<const uint8_t>> MSIBinary::tag() const {
+std::optional<std::vector<uint8_t>> MSIBinary::tag() const {
   return tag_;
 }
 
diff --git a/chrome/updater/certificate_tag.h b/chrome/updater/certificate_tag.h
index 35b437f..687fbbfe 100644
--- a/chrome/updater/certificate_tag.h
+++ b/chrome/updater/certificate_tag.h
@@ -27,7 +27,7 @@
 class BinaryInterface {
  public:
   // tag returns the embedded tag, if any.
-  virtual std::optional<std::vector<const uint8_t>> tag() const = 0;
+  virtual std::optional<std::vector<uint8_t>> tag() const = 0;
 
   // Returns an updated version of the binary with the provided `tag`, or
   // `nullopt` on error. If the binary already contains a tag then it will be
diff --git a/chrome/updater/certificate_tag_internal.h b/chrome/updater/certificate_tag_internal.h
index db27a71d..a3808aa 100644
--- a/chrome/updater/certificate_tag_internal.h
+++ b/chrome/updater/certificate_tag_internal.h
@@ -7,6 +7,7 @@
 
 #include <cstdint>
 #include <cstring>
+#include <functional>
 #include <memory>
 #include <optional>
 #include <string>
@@ -31,11 +32,10 @@
 
   // Parse a signed, Windows PE binary. Note that the returned structure
   // contains pointers into the given data.
-  static std::unique_ptr<BinaryInterface> Parse(
-      base::span<const uint8_t> binary);
+  static std::unique_ptr<PEBinary> Parse(base::span<const uint8_t> binary);
 
   // Returns the embedded tag, if any.
-  std::optional<std::vector<const uint8_t>> tag() const override;
+  std::optional<std::vector<uint8_t>> tag() const override;
 
   // SetTag returns an updated version of the PE binary image that contains the
   // given tag, or `nullopt` on error. If the binary already contains a tag then
@@ -55,7 +55,7 @@
   base::span<const uint8_t> content_info_;
 
   // tag_ contains the embedded tag, or `nullopt` if there isn't one.
-  std::optional<std::vector<const uint8_t>> tag_;
+  std::optional<std::vector<uint8_t>> tag_;
 
   // attr_cert_offset_ is the offset in the file where the `WIN_CERTIFICATE`
   // structure appears. (This is the last structure in the file.)
@@ -141,11 +141,11 @@
   // Parses the MSI header, the directory entry for the SignedData, and the
   // SignedData itself. If successful, returns a `BinaryInterface` to the
   // `MSIBinary` object.
-  static std::unique_ptr<BinaryInterface> Parse(
+  static std::unique_ptr<MSIBinary> Parse(
       base::span<const uint8_t> file_contents);
 
   // Returns the embedded tag, if any.
-  std::optional<std::vector<const uint8_t>> tag() const override;
+  std::optional<std::vector<uint8_t>> tag() const override;
 
   // Returns a complete MSI binary image based on bin, but where the superfluous
   // certificate contains the given tag data.
@@ -233,7 +233,7 @@
   std::vector<uint32_t> difat_sectors_;
 
   // The parsed tag, if any.
-  std::optional<std::vector<const uint8_t>> tag_;
+  std::optional<std::vector<uint8_t>> tag_;
 
   FRIEND_TEST_ALL_PREFIXES(CertificateTagMsiFirstFreeFatEntryTest, TestCases);
   FRIEND_TEST_ALL_PREFIXES(CertificateTagMsiEnsureFreeDifatEntryTest,
@@ -243,6 +243,9 @@
   FRIEND_TEST_ALL_PREFIXES(CertificateTagMsiAssignDifatEntryTest, TestCases);
   friend MSIBinary GetMsiBinary(const std::vector<uint32_t>& fat_entries,
                                 const std::vector<uint32_t>& difat_entries);
+  friend void Validate(
+      const MSIBinary& bin,
+      std::optional<std::reference_wrapper<const MSIBinary>> other);
 };
 
 // CBS is a structure from BoringSSL used for parsing binary and ASN.1-based
diff --git a/chrome/updater/certificate_tag_unittest.cc b/chrome/updater/certificate_tag_unittest.cc
index ce915e3..867cf70 100644
--- a/chrome/updater/certificate_tag_unittest.cc
+++ b/chrome/updater/certificate_tag_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <cstdint>
 #include <cstring>
+#include <functional>
 #include <memory>
 #include <optional>
 #include <vector>
@@ -13,6 +14,7 @@
 #include "base/containers/span.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
 #include "chrome/updater/certificate_tag_internal.h"
 #include "chrome/updater/util/unit_test_util.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -32,7 +34,7 @@
   ASSERT_TRUE(bin);
 
   // Binary should be untagged on disk.
-  std::optional<std::vector<const uint8_t>> orig_tag(bin->tag());
+  std::optional<std::vector<uint8_t>> orig_tag(bin->tag());
   EXPECT_FALSE(orig_tag);
 
   constexpr uint8_t kTag[] = {1, 2, 3, 4, 5};
@@ -41,7 +43,7 @@
 
   std::unique_ptr<BinaryInterface> bin2(CreatePEBinary(*updated_exe));
   ASSERT_TRUE(bin2);
-  std::optional<std::vector<const uint8_t>> parsed_tag(bin2->tag());
+  std::optional<std::vector<uint8_t>> parsed_tag(bin2->tag());
   ASSERT_TRUE(parsed_tag);
   ASSERT_EQ(parsed_tag->size(), sizeof(kTag));
   EXPECT_TRUE(memcmp(kTag, parsed_tag->data(), sizeof(kTag)) == 0);
@@ -53,7 +55,7 @@
 
   std::unique_ptr<BinaryInterface> bin3(CreatePEBinary(*updated_again_exe));
   ASSERT_TRUE(bin3);
-  std::optional<std::vector<const uint8_t>> parsed_tag2(bin3->tag());
+  std::optional<std::vector<uint8_t>> parsed_tag2(bin3->tag());
   ASSERT_TRUE(parsed_tag2);
   ASSERT_EQ(parsed_tag2->size(), sizeof(kTag2));
   EXPECT_TRUE(memcmp(kTag2, parsed_tag2->data(), sizeof(kTag2)) == 0);
@@ -406,12 +408,12 @@
 }
 
 struct CertificateTagMsiAssignDifatEntryTestCase {
-  int difat_sectors;
-  int num_free_difat_entries;
-  size_t difat_entries_assigned_index;
-  uint32_t difat_entry_assigned_value;
-  int num_fat_sectors;
-  int num_free_fat_entries;
+  const int difat_sectors;
+  const int num_free_difat_entries;
+  const size_t difat_entries_assigned_index;
+  const uint32_t difat_entry_assigned_value;
+  const int num_fat_sectors;
+  const int num_free_fat_entries;
 };
 
 class CertificateTagMsiAssignDifatEntryTest
@@ -442,6 +444,158 @@
             GetParam().difat_entry_assigned_value);
 }
 
+// Checks the provided `bin` MSIBinary for internal consistency. Additionally,
+// if `other` MSIBinary is provided, checks that the data streams in `other` are
+// bitwise identical to `bin`.
+void Validate(const MSIBinary& bin,
+              std::optional<std::reference_wrapper<const MSIBinary>> other) {
+  // Check the fat sector entries.
+  uint64_t i = 0;
+  for (const auto s : bin.difat_entries_) {
+    ASSERT_TRUE(s == kFatFreeSector || IsLastInSector(bin.sector_format_, i) ||
+                bin.fat_entries_[s] == kFatFatSector);
+    ++i;
+  }
+
+  // Check the difat sector entries.
+  i = kNumDifatHeaderEntries - 1;
+  uint32_t num = 0;
+  for (uint32_t s = bin.header_.first_difat_sector; s != kFatEndOfChain;
+       ++num) {
+    ASSERT_EQ(bin.fat_entries_[s], kFatDifSector);
+    i += bin.sector_format_.ints;
+    s = bin.difat_entries_[i];
+  }
+  ASSERT_EQ(num, bin.header_.num_difat_sectors);
+
+  // Enumerate the directory entries.
+  // * Validate streams in the fat: Walk the chain, validate the stream length,
+  //   and mark sectors in a copy of the fat so we can tell if any sectors are
+  //   re-used.
+  // * Compare bytes in the data streams, to validate none of them changed.
+  //   We could match stream names, but the directory entries are not reordered
+  //   and the streams are not moved.
+  std::vector<uint32_t> fat_entries = bin.fat_entries_;
+  uint32_t dir_sector = bin.header_.first_dir_sector;
+  MSIDirEntry entry;
+  do {
+    // Fixed `kNumDirEntryBytes` directory entry size.
+    for (i = 0; i < bin.sector_format_.size / kNumDirEntryBytes; ++i) {
+      uint64_t offset =
+          dir_sector * bin.sector_format_.size + i * kNumDirEntryBytes;
+      std::memcpy(&entry, &bin.contents_[offset], sizeof(MSIDirEntry));
+
+      // Skip the mini stream and signature entries.
+      if (entry.stream_size < kMiniStreamCutoffSize ||
+          std::equal(entry.name, entry.name + entry.num_name_bytes,
+                     std::begin(kSignatureName))) {
+        continue;
+      }
+      uint64_t allocated_size = 0;
+      uint32_t sector = entry.stream_first_sector;
+      uint32_t next = kFatEndOfChain;
+      do {
+        allocated_size += bin.sector_format_.size;
+        ASSERT_TRUE(fat_entries[sector] == kFatEndOfChain ||
+                    fat_entries[sector] < kFatReserved);
+        if (other) {
+          offset = sector * bin.sector_format_.size;
+          ASSERT_TRUE(std::equal(
+              bin.contents_.begin() + offset,
+              bin.contents_.begin() + offset + bin.sector_format_.size,
+              other->get().contents_.begin() + offset));
+        }
+        next = fat_entries[sector];
+
+        // Overwrite the fat entry and detect if the entry is re-used.
+        fat_entries[sector] = kFatReserved;
+        sector = next;
+      } while (next != kFatEndOfChain);
+      ASSERT_GE(allocated_size, entry.stream_size);
+    }
+
+    // Go to the next directory sector.
+    dir_sector = bin.fat_entries_[dir_sector];
+  } while (dir_sector != kFatEndOfChain);
+}
+
+struct CertificateTagMsiValidateTestCase {
+  const std::string infile;
+  const std::vector<uint8_t> expected_tag;
+  const std::vector<uint8_t> new_tag;
+};
+
+class CertificateTagMsiValidateTest
+    : public ::testing::TestWithParam<CertificateTagMsiValidateTestCase> {};
+
+INSTANTIATE_TEST_SUITE_P(
+    CertificateTagMsiValidateTestCases,
+    CertificateTagMsiValidateTest,
+    ::testing::ValuesIn(std::vector<CertificateTagMsiValidateTestCase>{
+        {"GUH-untagged.msi", {}, {1, 2, 3, 4, 5}},
+        {"GUH-brand-only.msi",
+         [] {
+           std::vector<uint8_t> expected_tag = {
+               'G', 'a', 'c', 't',  '2',  '.', '0', 'O', 'm',
+               'a', 'h', 'a', '\0', '\n', 'b', 'r', 'a', 'n',
+               'd', '=', 'Q', 'A',  'Q',  'A', '\0'};
+           expected_tag.resize(8240);
+           return expected_tag;
+         }(),
+         {1, 2, 3, 4, 5}},
+        {"GUH-multiple.msi",
+         [] {
+           std::vector<uint8_t> expected_tag(8632);
+           constexpr char magic[] = "Gact2.0Omaha";
+           std::memcpy(&expected_tag[0], magic, sizeof(magic));
+           constexpr char tag[] =
+               "appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&iid={2D8C18E9-"
+               "8D3A-4EFC-6D61-AE23E3530EA2}&lang=en&browser=4&usagestats=0&"
+               "appname=Google%20Chrome&needsadmin=prefers&brand=CHMB&"
+               "installdataindex=defaultbrowser";
+           expected_tag[sizeof(magic)] = sizeof(tag) - 1;
+           std::memcpy(&expected_tag[sizeof(magic) + 1], tag, sizeof(tag));
+           return expected_tag;
+         }(),
+         [] {
+           std::vector<uint8_t> new_tag = {'G', 'a', 'c', 't', '2', '.',  '0',
+                                           'O', 'm', 'a', 'h', 'a', '\0', '\n',
+                                           'b', 'r', 'a', 'n', 'd', '=',  'Q',
+                                           'A', 'Q', 'A', '\0'};
+           new_tag.resize(8206);
+           return new_tag;
+         }()},
+    }));
+
+TEST_P(CertificateTagMsiValidateTest, TestCases) {
+  base::MemoryMappedFile mapped_file;
+  ASSERT_TRUE(mapped_file.Initialize(
+      test::GetTestFilePath("tagged_msi").AppendASCII(GetParam().infile)));
+  const std::unique_ptr<MSIBinary> bin = MSIBinary::Parse(mapped_file.bytes());
+  ASSERT_TRUE(bin);
+
+  if (GetParam().expected_tag.empty()) {
+    ASSERT_FALSE(bin->tag());
+  } else {
+    ASSERT_EQ(*bin->tag(), GetParam().expected_tag);
+  }
+  ASSERT_NO_FATAL_FAILURE(Validate(*bin, {}));
+
+  // Set a new tag on `bin`.
+  const std::optional<std::vector<uint8_t>> tagged_bytes =
+      bin->SetTag(GetParam().new_tag);
+  ASSERT_TRUE(tagged_bytes);
+  const std::unique_ptr<MSIBinary> tagged_bin = MSIBinary::Parse(*tagged_bytes);
+  ASSERT_TRUE(tagged_bin);
+
+  if (GetParam().new_tag.empty()) {
+    ASSERT_FALSE(tagged_bin->tag());
+  } else {
+    ASSERT_EQ(*tagged_bin->tag(), GetParam().new_tag);
+  }
+  ASSERT_NO_FATAL_FAILURE(Validate(*bin, *tagged_bin));
+}
+
 }  // namespace internal
 
 }  // namespace updater::tagging
diff --git a/chrome/updater/tag.cc b/chrome/updater/tag.cc
index d5a58837..7819b6f 100644
--- a/chrome/updater/tag.cc
+++ b/chrome/updater/tag.cc
@@ -798,13 +798,13 @@
     return {};
   }
 
-  std::optional<std::vector<const uint8_t>> tag = bin->tag();
+  std::optional<std::vector<uint8_t>> tag = bin->tag();
   if (!tag) {
     LOG(ERROR) << __func__ << ": No superfluous certificate in file: " << file;
     return {};
   }
 
-  const std::vector<const uint8_t> tag_data = {tag->begin(), tag->end()};
+  const std::vector<uint8_t> tag_data = {tag->begin(), tag->end()};
   const std::string tag_string = ReadTag(tag_data.begin(), tag_data.end());
   if (tag_string.empty()) {
     LOG(ERROR) << __func__ << ": file is untagged: " << file;
diff --git a/chromeos/ash/components/dbus/fwupd/BUILD.gn b/chromeos/ash/components/dbus/fwupd/BUILD.gn
index a1e90bdf..58c71df 100644
--- a/chromeos/ash/components/dbus/fwupd/BUILD.gn
+++ b/chromeos/ash/components/dbus/fwupd/BUILD.gn
@@ -28,6 +28,8 @@
     "fwupd_device.h",
     "fwupd_properties.cc",
     "fwupd_properties.h",
+    "fwupd_request.cc",
+    "fwupd_request.h",
     "fwupd_update.cc",
     "fwupd_update.h",
   ]
diff --git a/chromeos/ash/components/dbus/fwupd/fwupd_client.h b/chromeos/ash/components/dbus/fwupd/fwupd_client.h
index 72313d8..15da595 100644
--- a/chromeos/ash/components/dbus/fwupd/fwupd_client.h
+++ b/chromeos/ash/components/dbus/fwupd/fwupd_client.h
@@ -14,6 +14,7 @@
 #include "base/observer_list.h"
 #include "chromeos/ash/components/dbus/fwupd/fwupd_device.h"
 #include "chromeos/ash/components/dbus/fwupd/fwupd_properties.h"
+#include "chromeos/ash/components/dbus/fwupd/fwupd_request.h"
 #include "chromeos/ash/components/dbus/fwupd/fwupd_update.h"
 #include "chromeos/dbus/common/dbus_client.h"
 
@@ -32,6 +33,7 @@
                                       FwupdUpdateList* updates) = 0;
     virtual void OnInstallResponse(bool success) = 0;
     virtual void OnPropertiesChangedResponse(FwupdProperties* properties) = 0;
+    virtual void OnDeviceRequestResponse(FwupdRequest* request) = 0;
   };
 
   void AddObserver(Observer* observer);
diff --git a/chromeos/ash/components/dbus/fwupd/fwupd_client_unittest.cc b/chromeos/ash/components/dbus/fwupd/fwupd_client_unittest.cc
index 85284a7..7e6f58b5 100644
--- a/chromeos/ash/components/dbus/fwupd/fwupd_client_unittest.cc
+++ b/chromeos/ash/components/dbus/fwupd/fwupd_client_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "chromeos/ash/components/dbus/fwupd/fwupd_properties.h"
+#include "chromeos/ash/components/dbus/fwupd/fwupd_request.h"
 #include "dbus/message.h"
 #include "dbus/mock_bus.h"
 #include "dbus/mock_object_proxy.h"
@@ -67,6 +68,10 @@
               OnPropertiesChangedResponse,
               (ash::FwupdProperties * properties),
               (override));
+  MOCK_METHOD(void,
+              OnDeviceRequestResponse,
+              (ash::FwupdRequest * request),
+              (override));
 };
 
 }  // namespace
diff --git a/chromeos/ash/components/dbus/fwupd/fwupd_request.cc b/chromeos/ash/components/dbus/fwupd/fwupd_request.cc
new file mode 100644
index 0000000..4a08c88
--- /dev/null
+++ b/chromeos/ash/components/dbus/fwupd/fwupd_request.cc
@@ -0,0 +1,16 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ash/components/dbus/fwupd/fwupd_request.h"
+
+namespace ash {
+
+FwupdRequest::FwupdRequest() = default;
+
+FwupdRequest::FwupdRequest(uint32_t id, uint32_t kind) : id(id), kind(kind) {}
+
+FwupdRequest::FwupdRequest(const FwupdRequest& other) = default;
+FwupdRequest::~FwupdRequest() = default;
+
+}  // namespace ash
diff --git a/chromeos/ash/components/dbus/fwupd/fwupd_request.h b/chromeos/ash/components/dbus/fwupd/fwupd_request.h
new file mode 100644
index 0000000..181be43
--- /dev/null
+++ b/chromeos/ash/components/dbus/fwupd/fwupd_request.h
@@ -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.
+
+#ifndef CHROMEOS_ASH_COMPONENTS_DBUS_FWUPD_FWUPD_REQUEST_H_
+#define CHROMEOS_ASH_COMPONENTS_DBUS_FWUPD_FWUPD_REQUEST_H_
+
+#include <stdint.h>
+#include "base/component_export.h"
+
+namespace ash {
+
+// Structure to hold FwupdRequest data received from fwupd.
+struct COMPONENT_EXPORT(ASH_DBUS_FWUPD) FwupdRequest {
+  FwupdRequest();
+  FwupdRequest(uint32_t id, uint32_t kind);
+  FwupdRequest(const FwupdRequest& other);
+  ~FwupdRequest();
+
+  // The ID of the Request; corresponds to the enum values found in
+  // https://github.com/fwupd/fwupd/blob/main/libfwupd/fwupd-request.h
+  uint32_t id;
+  // The Kind of the Request; corresponds to the enum values found in
+  // https://github.com/fwupd/fwupd/blob/main/libfwupd/fwupd-request.h
+  uint32_t kind;
+};
+
+}  // namespace ash
+
+#endif  // CHROMEOS_ASH_COMPONENTS_DBUS_FWUPD_FWUPD_REQUEST_H_
diff --git a/chromeos/ash/components/fwupd/firmware_update_manager.cc b/chromeos/ash/components/fwupd/firmware_update_manager.cc
index 7424e1f..3599903 100644
--- a/chromeos/ash/components/fwupd/firmware_update_manager.cc
+++ b/chromeos/ash/components/fwupd/firmware_update_manager.cc
@@ -254,6 +254,14 @@
                                  kAllowedFilepathChars);
 }
 
+// Converts a FwupdRequest into a mojom DeviceRequest.
+firmware_update::mojom::DeviceRequestPtr GetDeviceRequest(
+    FwupdRequest request) {
+  return firmware_update::mojom::DeviceRequest::New(
+      static_cast<firmware_update::mojom::DeviceRequestId>(request.id),
+      static_cast<firmware_update::mojom::DeviceRequestKind>(request.kind));
+}
+
 }  // namespace
 
 FirmwareUpdateManager::FirmwareUpdateManager()
@@ -701,6 +709,17 @@
   receiver_.Bind(std::move(pending_receiver));
 }
 
+void FirmwareUpdateManager::OnDeviceRequestResponse(FwupdRequest* request) {
+  if (!request || !device_request_observer_.is_bound()) {
+    LOG(ERROR) << "OnDeviceRequestResponse triggered with unbound observer or "
+                  "nullptr request.";
+    return;
+  }
+  // Convert the FwupdRequest into a mojom DeviceRequest, then pass that
+  // request to observers.
+  device_request_observer_->OnDeviceRequest(GetDeviceRequest(*request));
+}
+
 void FirmwareUpdateManager::OnPropertiesChangedResponse(
     FwupdProperties* properties) {
   if (!properties || !update_progress_observer_.is_bound()) {
diff --git a/chromeos/ash/components/fwupd/firmware_update_manager.h b/chromeos/ash/components/fwupd/firmware_update_manager.h
index e0127897..e53cb5d 100644
--- a/chromeos/ash/components/fwupd/firmware_update_manager.h
+++ b/chromeos/ash/components/fwupd/firmware_update_manager.h
@@ -22,6 +22,7 @@
 #include "chromeos/ash/components/dbus/fwupd/fwupd_client.h"
 #include "chromeos/ash/components/dbus/fwupd/fwupd_device.h"
 #include "chromeos/ash/components/dbus/fwupd/fwupd_properties.h"
+#include "chromeos/ash/components/dbus/fwupd/fwupd_request.h"
 #include "chromeos/ash/components/dbus/fwupd/fwupd_update.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -93,6 +94,8 @@
   // it calls this function and passes the response.
   void OnDeviceListResponse(FwupdDeviceList* devices) override;
 
+  void OnDeviceRequestResponse(FwupdRequest* request) override;
+
   // When the fwupd DBus client gets a response with updates from fwupd,
   // it calls this function and passes the response.
   void OnUpdateListResponse(const std::string& device_id,
diff --git a/chromeos/ash/components/fwupd/firmware_update_manager_unittest.cc b/chromeos/ash/components/fwupd/firmware_update_manager_unittest.cc
index f84436ce..dca1cbb 100644
--- a/chromeos/ash/components/fwupd/firmware_update_manager_unittest.cc
+++ b/chromeos/ash/components/fwupd/firmware_update_manager_unittest.cc
@@ -22,6 +22,7 @@
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
 #include "chromeos/ash/components/dbus/fwupd/fwupd_client.h"
+#include "chromeos/ash/components/dbus/fwupd/fwupd_request.h"
 #include "chromeos/ash/components/fwupd/fake_fwupd_download_client.h"
 #include "chromeos/ash/components/fwupd/histogram_util.h"
 #include "dbus/message.h"
@@ -237,6 +238,11 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void TriggerOnDeviceRequestResponse(FwupdRequest* request) {
+    firmware_update_manager_->OnDeviceRequestResponse(request);
+    base::RunLoop().RunUntilIdle();
+  }
+
   void SetFakeUrlForTesting(const std::string& fake_url) {
     firmware_update_manager_->SetFakeUrlForTesting(fake_url);
   }
@@ -1082,4 +1088,35 @@
   SetupDeviceRequestObserver(&device_request_observer);
 }
 
+TEST_F(FirmwareUpdateManagerTest, DeviceRequestObserver) {
+  EXPECT_TRUE(PrepareForUpdate(std::string(kFakeDeviceIdForTesting)));
+  FakeDeviceRequestObserver device_request_observer;
+  SetupDeviceRequestObserver(&device_request_observer);
+
+  // For each combination of DeviceRequestId and DeviceRequestKind, call
+  // OnDeviceRequestResponse on firmware_update_manager and then verify that the
+  // observer received the correct DeviceRequest.
+  int device_request_id_size =
+      static_cast<int>(firmware_update::mojom::DeviceRequestId::kMaxValue) + 1;
+  int device_request_kind_size =
+      static_cast<int>(firmware_update::mojom::DeviceRequestKind::kMaxValue) +
+      1;
+
+  for (int id_index = 0; id_index < device_request_id_size; id_index++) {
+    for (int kind_index = 0; kind_index < device_request_kind_size;
+         kind_index++) {
+      firmware_update::mojom::DeviceRequestId id =
+          static_cast<firmware_update::mojom::DeviceRequestId>(id_index);
+      firmware_update::mojom::DeviceRequestKind kind =
+          static_cast<firmware_update::mojom::DeviceRequestKind>(kind_index);
+
+      TriggerOnDeviceRequestResponse(new FwupdRequest(
+          static_cast<uint32_t>(id), static_cast<uint32_t>(kind)));
+
+      EXPECT_EQ(id, device_request_observer.GetLatestRequest()->id);
+      EXPECT_EQ(kind, device_request_observer.GetLatestRequest()->kind);
+    }
+  }
+}
+
 }  // namespace ash
diff --git a/chromeos/ash/services/bluetooth_config/device_name_manager_impl.cc b/chromeos/ash/services/bluetooth_config/device_name_manager_impl.cc
index 9dac678f..080cbf2 100644
--- a/chromeos/ash/services/bluetooth_config/device_name_manager_impl.cc
+++ b/chromeos/ash/services/bluetooth_config/device_name_manager_impl.cc
@@ -168,15 +168,6 @@
       continue;
     }
 
-    // Avoid overwriting an existing entry with a BlueZ nickname. This allows us
-    // to guarantee that Floss nicknames are migrated and outdate BlueZ
-    // nicknames are effectively forgotten.
-    if (local_state_->GetDict(kDeviceIdToNicknameMapPrefName).contains(id)) {
-      BLUETOOTH_LOG(EVENT)
-          << "Device ID format already matches Floss format; skipping entry";
-      continue;
-    }
-
     // Since BlueZ uses the DBus object path of the device as the ID, which
     // includes the parts of the address of the device, we are able to extract
     // these parts and use them to generate an ID for the device that follows
@@ -200,10 +191,20 @@
       continue;
     }
 
+    auto floss_id =
+        base::JoinString(base::make_span(parts.end() - 6, parts.end()), ":");
+
+    // Avoid overwriting an existing entry with a BlueZ nickname. This allows us
+    // to guarantee that Floss nicknames are migrated and outdate BlueZ
+    // nicknames are effectively forgotten.
+    if (local_state_->GetDict(kDeviceIdToNicknameMapPrefName)
+            .contains(floss_id)) {
+      BLUETOOTH_LOG(EVENT) << "Device ID already exists; skipping entry";
+      continue;
+    }
+
     ScopedDictPrefUpdate update(local_state_, kDeviceIdToNicknameMapPrefName);
-    update->Set(
-        base::JoinString(base::make_span(parts.end() - 6, parts.end()), ":"),
-        nickname.Clone());
+    update->Set(floss_id, nickname.Clone());
 
     BLUETOOTH_LOG(EVENT) << "Successfully migrated Bluetooth nickname pref";
   }
diff --git a/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc b/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc
index 4a950f7..4e379da4 100644
--- a/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc
+++ b/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc
@@ -169,8 +169,14 @@
     PairDeviceCallback callback) {
   BLUETOOTH_LOG(USER) << "Attempting to pair with device " << device_id;
 
-  // There should only be one PairDevice request at a time.
-  CHECK(current_pairing_device_id_.empty());
+  // In certain situations, such as when the pairing dialog is initiated from
+  // both the system tray and ChromeOS settings, an active pairing request
+  // might be underway when the pairDevice function is called. In such
+  // instances, cancel the second pairing request. (See b/311813249)
+  if (!current_pairing_device_id_.empty()) {
+    std::move(callback).Run(mojom::PairingResult::kNonAuthFailure);
+    return;
+  }
 
   pairing_start_timestamp_ = base::Time::Now();
   pair_device_callback_ = std::move(callback);
diff --git a/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl_unittest.cc b/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl_unittest.cc
index 2e2feb7..a5125c9 100644
--- a/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl_unittest.cc
+++ b/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl_unittest.cc
@@ -216,7 +216,6 @@
                        base::Unretained(this)));
     base::RunLoop().RunUntilIdle();
 
-    EXPECT_TRUE(delegate->IsMojoPipeConnected());
     return delegate;
   }
 
@@ -405,7 +404,8 @@
   // Disable Bluetooth before attempting to pair.
   SetBluetoothSystemState(mojom::BluetoothSystemState::kDisabled);
 
-  PairDevice(device_id);
+  std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
 
   // Pairing should immediately fail.
   EXPECT_FALSE(HasPendingConnectCallback());
@@ -449,7 +449,8 @@
   std::string device_id;
   AddDevice(&device_id, AuthType::kNone);
 
-  PairDevice(device_id);
+  std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_TRUE(HasPendingConnectCallback());
   DestroyHandler();
 
@@ -494,6 +495,7 @@
   AddDevice(&device_id, AuthType::kDisplayPinCode);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate1 = PairDevice(device_id);
+  EXPECT_TRUE(delegate1->IsMojoPipeConnected());
   EXPECT_TRUE(HasPendingConnectCallback());
   EXPECT_EQ(delegate1->displayed_pin_code(), kDefaultPinCode);
   EXPECT_TRUE(delegate1->key_entered_handler()->IsMojoPipeConnected());
@@ -524,6 +526,7 @@
 
   // Attempt to pair with the device again.
   std::unique_ptr<FakeDevicePairingDelegate> delegate2 = PairDevice(device_id);
+  EXPECT_TRUE(delegate2->IsMojoPipeConnected());
   EXPECT_TRUE(HasPendingConnectCallback());
   InvokePendingConnectCallback(ConnectErrorCode::ERROR_AUTH_FAILED);
 
@@ -552,6 +555,7 @@
   AddDevice(&device_id, AuthType::kNone);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_TRUE(HasPendingConnectCallback());
   ClearDevices();
 
@@ -578,6 +582,7 @@
   AddDevice(&device_id, AuthType::kNone);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_TRUE(HasPendingConnectCallback());
   InvokePendingConnectCallback(ConnectErrorCode::ERROR_FAILED);
   FastForwardOperation(GetPairingFailureDelay());
@@ -597,8 +602,47 @@
                                 /*transport_name=*/"Classic");
 }
 
+TEST_F(DevicePairingHandlerImplTest, PairingFailsWhenThereIsAPendingPairing) {
+  std::string device_id1;
+  AddDevice(&device_id1, AuthType::kNone);
+
+  std::string device_id2;
+  AddDevice(&device_id2, AuthType::kNone);
+
+  std::unique_ptr<FakeDevicePairingDelegate> delegate1 = PairDevice(device_id1);
+  EXPECT_TRUE(HasPendingConnectCallback());
+  EXPECT_TRUE(delegate1->IsMojoPipeConnected());
+
+  std::unique_ptr<FakeDevicePairingDelegate> delegate2 = PairDevice(device_id1);
+  EXPECT_FALSE(delegate2->IsMojoPipeConnected());
+
+  // Pending callback should still exist, this would be the first device pairing
+  // pending callback.
+  EXPECT_TRUE(HasPendingConnectCallback());
+
+  // We expect the second pairing request to fail.
+  EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure);
+
+  // Check that first pairing was successful.
+  FastForwardOperation(kTestDuration);
+
+  // We expect no failure histogram recorded since we returned early in
+  // PairDevice.
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/0, /*failure_count=*/0,
+                         /*success_count=*/0);
+
+  InvokePendingConnectCallback(/*error_code=*/std::nullopt);
+  EXPECT_EQ(pairing_result(), mojom::PairingResult::kSuccess);
+
+  CheckPairingHistograms(device::BluetoothTransportType::kClassic,
+                         /*type_count=*/1, /*failure_count=*/0,
+                         /*success_count=*/1);
+}
+
 TEST_F(DevicePairingHandlerImplTest, PairDeviceNotFound) {
-  PairDevice("device_id");
+  std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice("device_id");
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
 
   EXPECT_FALSE(HasPendingConnectCallback());
   EXPECT_EQ(pairing_result(), mojom::PairingResult::kNonAuthFailure);
@@ -629,6 +673,7 @@
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
   EXPECT_TRUE(HasPendingConnectCallback());
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
 
   // Update device to be connected.
   ChangeDeviceIsConnected(device_id, /*is_connected=*/true);
@@ -652,6 +697,7 @@
   AddDevice(&device_id, AuthType::kRequestPinCode);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_TRUE(delegate->HasPendingRequestPinCodeCallback());
 
   delegate->InvokePendingRequestPinCodeCallback(kDefaultPinCode);
@@ -676,6 +722,7 @@
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
   EXPECT_TRUE(delegate->HasPendingRequestPinCodeCallback());
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   FastForwardOperation(kTestDuration);
   // Simulate device no longer being available.
   ClearDevices();
@@ -701,6 +748,7 @@
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
   EXPECT_TRUE(delegate->HasPendingRequestPasskeyCallback());
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   delegate->InvokePendingRequestPasskeyCallback(kDefaultPinCode);
   EXPECT_EQ(received_passkey(), kDefaultPinCodeNum);
 
@@ -723,6 +771,7 @@
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
   EXPECT_TRUE(delegate->HasPendingRequestPasskeyCallback());
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
 
   FastForwardOperation(kTestDuration);
   // Simulate device no longer being available.
@@ -749,6 +798,7 @@
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
   EXPECT_TRUE(delegate->HasPendingRequestPasskeyCallback());
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
 
   FastForwardOperation(kTestDuration);
   delegate->InvokePendingRequestPasskeyCallback("hello_world");
@@ -786,6 +836,7 @@
   AddDevice(&device_id, AuthType::kDisplayPinCode);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_EQ(delegate->displayed_pin_code(), kDefaultPinCode);
   EXPECT_TRUE(delegate->key_entered_handler()->IsMojoPipeConnected());
 
@@ -819,6 +870,7 @@
   AddDevice(&device_id, AuthType::kDisplayPinCode);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_EQ(delegate->displayed_pin_code(), kDefaultPinCode);
   EXPECT_TRUE(delegate->key_entered_handler()->IsMojoPipeConnected());
 
@@ -846,6 +898,7 @@
   AddDevice(&device_id, AuthType::kDisplayPasskey);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_EQ(delegate->displayed_passkey(), kDefaultPinCode);
   EXPECT_TRUE(delegate->key_entered_handler()->IsMojoPipeConnected());
 
@@ -873,6 +926,7 @@
   AddDevice(&device_id1, AuthType::kDisplayPasskey, /*passkey=*/33333);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate1 = PairDevice(device_id1);
+  EXPECT_TRUE(delegate1->IsMojoPipeConnected());
 
   // Passkey displayed should be a 6-digit number, padded with zeroes if needed.
   EXPECT_EQ(delegate1->displayed_passkey(), "033333");
@@ -895,6 +949,7 @@
   AddDevice(&device_id2, AuthType::kDisplayPasskey, /*passkey=*/0);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate2 = PairDevice(device_id2);
+  EXPECT_TRUE(delegate2->IsMojoPipeConnected());
   FastForwardOperation(GetPairingFailureDelay());
 
   // Passkey displayed should be a 6-digit number, padded with zeroes if needed.
@@ -916,6 +971,7 @@
   AddDevice(&device_id1, AuthType::kConfirmPasskey);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate1 = PairDevice(device_id1);
+  EXPECT_TRUE(delegate1->IsMojoPipeConnected());
   EXPECT_EQ(delegate1->passkey_to_confirm(), kDefaultPinCode);
   EXPECT_TRUE(delegate1->HasPendingConfirmPasskeyCallback());
   EXPECT_EQ(num_confirm_pairing_calls(), 0u);
@@ -941,6 +997,7 @@
   AddDevice(&device_id2, AuthType::kConfirmPasskey, /*passkey=*/0);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate2 = PairDevice(device_id2);
+  EXPECT_TRUE(delegate2->IsMojoPipeConnected());
 
   // Passkey to confirm should be a 6-digit number, padded with zeroes if
   // needed.
@@ -972,6 +1029,7 @@
   AddDevice(&device_id, AuthType::kConfirmPasskey);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_EQ(delegate->passkey_to_confirm(), kDefaultPinCode);
   EXPECT_TRUE(delegate->HasPendingConfirmPasskeyCallback());
 
@@ -1000,6 +1058,7 @@
   AddDevice(&device_id, AuthType::kAuthorizePairing);
 
   std::unique_ptr<FakeDevicePairingDelegate> delegate = PairDevice(device_id);
+  EXPECT_TRUE(delegate->IsMojoPipeConnected());
   EXPECT_TRUE(delegate->HasPendingAuthorizePairingCallback());
 
   // Confirm the pairing.
diff --git a/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom b/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom
index 3940fca9..3cd956f 100644
--- a/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom
+++ b/chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom
@@ -267,6 +267,9 @@
   // |result| is returned when the pairing attempt completes. It is
   // possible that |result| is returned before any delegate function
   // is invoked.
+  // Only one pairing request should be made at a time. If a second request
+  // is made while a request is pending the second request will return with
+  // mojom::PairingResult::kNonAuthFailure.
   PairDevice(
       string device_id,
       pending_remote<DevicePairingDelegate> delegate) =>
diff --git a/chromeos/crosapi/mojom/prefs.mojom b/chromeos/crosapi/mojom/prefs.mojom
index 5c40391..b5aeae8 100644
--- a/chromeos/crosapi/mojom/prefs.mojom
+++ b/chromeos/crosapi/mojom/prefs.mojom
@@ -121,6 +121,8 @@
   [MinVersion=14] kProxy = 42,
   // M121: DefaultSearchManager::kDefaultSearchProviderDataPrefName
   [MinVersion=15] kDefaultSearchProviderDataPrefName = 43,
+  // M121: prefs::kIsolatedWebAppsEnabled
+  [MinVersion=16] kIsolatedWebAppsEnabled = 44,
 };
 
 // Information about who or what is controlling a particular pref. This is used
diff --git a/clank b/clank
index a2fed0c..81557d3 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit a2fed0cd29a5673211c8327dfd847830ebcbcb24
+Subproject commit 81557d351b8541438b80a494040df59f3eec95fd
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index 01b45bb4..20361a3c 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -172,6 +172,9 @@
       return "Skipped: Field type filling limit reached";
     case FieldFillingSkipReason::kFieldDoesNotMatchTargetFieldsSet:
       return "Skipped: The field type does not match the targeted fields.";
+    case FieldFillingSkipReason::kFieldTypeUnrelated:
+      return "Skipped: The field type is not related to the data used for "
+             "filling.";
     case FieldFillingSkipReason::kNotSkipped:
       return "Fillable";
     case FieldFillingSkipReason::kUnknown:
@@ -2170,11 +2173,12 @@
     const FormStructure& form_structure,
     const FormFieldData& trigger_field,
     const Section& filling_section,
-    const CreditCard* optional_credit_card,
     const FieldTypeSet& field_types_to_fill,
     const DenseSet<FieldTypeGroup>* optional_type_groups_originally_filled,
+    FillingProduct filling_product,
     bool skip_unrecognized_autocomplete_fields,
-    bool is_refill) const {
+    bool is_refill,
+    bool is_expired_credit_card) const {
   // Counts the number of times a type was seen in the section to be filled.
   // This is used to limit the maximum number of fills per value.
   base::flat_map<FieldType, size_t> type_count;
@@ -2258,8 +2262,7 @@
     FieldType field_type = autofill_field->Type().GetStorableType();
     // Don't fill expired cards expiration date.
     if (data_util::IsCreditCardExpirationType(field_type) &&
-        (optional_credit_card &&
-         optional_credit_card->IsExpired(AutofillClock::Now()))) {
+        is_expired_credit_card) {
       skip_reasons[i] = FieldFillingSkipReason::kExpiredCards;
       continue;
     }
@@ -2289,6 +2292,21 @@
       continue;
     }
 
+    // Usually, this should not happen because Autofill sectioning logic
+    // separates address fields from credit card fields. However, autofill
+    // respects the HTML `autocomplete` attribute when it is used to specify a
+    // section, and so in some rare cases it might happen that a credit card
+    // field is included in an address field or vice versa.
+    // Note that autofilling using manual fallback does not use this logic flow,
+    // otherwise this wouldn't be true.
+    if ((filling_product == FillingProduct::kAddress &&
+         !IsAddressType(autofill_field->Type().GetStorableType())) ||
+        (filling_product == FillingProduct::kCreditCard &&
+         autofill_field->Type().group() != FieldTypeGroup::kCreditCard)) {
+      skip_reasons[i] = FieldFillingSkipReason::kFieldTypeUnrelated;
+      continue;
+    }
+
     skip_reasons[i] = FieldFillingSkipReason::kNotSkipped;
   }
   return skip_reasons;
@@ -2396,15 +2414,15 @@
 
   std::vector<FieldFillingSkipReason> skip_reasons = GetFieldFillingSkipReasons(
       result, *form_structure, field, autofill_trigger_field->section,
-      absl::holds_alternative<const CreditCard*>(profile_or_credit_card)
-          ? absl::get<const CreditCard*>(profile_or_credit_card)
-          : nullptr,
       trigger_details.field_types_to_fill,
       filling_context ? &filling_context->type_groups_originally_filled
                       : nullptr,
+      is_credit_card ? FillingProduct::kCreditCard : FillingProduct::kAddress,
       /*skip_unrecognized_autocomplete_fields=*/
       trigger_details.trigger_source != AutofillTriggerSource::kManualFallback,
-      is_refill);
+      is_refill,
+      is_credit_card && absl::get<const CreditCard*>(profile_or_credit_card)
+                            ->IsExpired(AutofillClock::Now()));
 
   constexpr DenseSet<FieldFillingSkipReason> pre_ukm_logging_skips{
       FieldFillingSkipReason::kNotInFilledSection,
@@ -2752,16 +2770,16 @@
       form.fields.size() == form_structure.field_count()
           ? GetFieldFillingSkipReasons(
                 form, form_structure, field, autofill_field.section,
-                /*optional_credit_card=*/nullptr,
                 last_address_fields_to_fill_for_section
                     ? GetTargetServerFieldsForTypeAndLastTargetedFields(
                           *last_address_fields_to_fill_for_section,
                           autofill_field.Type().GetStorableType())
                     : kAllFieldTypes,
                 /*optional_type_groups_originally_filled=*/nullptr,
+                FillingProduct::kAddress,
                 /*skip_unrecognized_autocomplete_fields=*/trigger_source !=
                     AutofillSuggestionTriggerSource::kManualFallbackAddress,
-                /*is_refill=*/false)
+                /*is_refill=*/false, /*is_expired_credit_card=*/false)
           : std::vector<FieldFillingSkipReason>(
                 form_structure.field_count(),
                 FieldFillingSkipReason::kNotSkipped);
diff --git a/components/autofill/core/browser/browser_autofill_manager.h b/components/autofill/core/browser/browser_autofill_manager.h
index 16e7fca..f2c47b5 100644
--- a/components/autofill/core/browser/browser_autofill_manager.h
+++ b/components/autofill/core/browser/browser_autofill_manager.h
@@ -29,6 +29,7 @@
 #include "components/autofill/core/browser/autofill_manager.h"
 #include "components/autofill/core/browser/autofill_trigger_details.h"
 #include "components/autofill/core/browser/field_types.h"
+#include "components/autofill/core/browser/filling_product.h"
 #include "components/autofill/core/browser/form_autofill_history.h"
 #include "components/autofill/core/browser/form_structure.h"
 #include "components/autofill/core/browser/form_types.h"
@@ -437,8 +438,6 @@
 
   // Given a `form` (and corresponding `form_structure`) to fill, return a list
   // of skip reasons for the fields.
-  // `optional_credit_card` is the credit card to be filled or nullopt if we're
-  // filling an AutofillProfile.
   // `type_group_originally_filled` denotes, in case of a refill, what groups
   // where filled in the initial filling.
   // It is assumed here that `form` and `form_structure` have the same
@@ -448,6 +447,7 @@
   // filling, and the actual fields filled will be the intersection between
   // `field_types_to_fill` and the classified fields for which we have data
   // stored.
+  // `filling_product` is the type of filling calling this function.
   // TODO(crbug/1275649): Add the case removed in crrev.com/c/4675831 when the
   // experiment resumes.
   // TODO(crbug.com/1481035): Make `optional_type_groups_originally_filled` also
@@ -457,11 +457,12 @@
       const FormStructure& form_structure,
       const FormFieldData& trigger_field,
       const Section& filling_section,
-      const CreditCard* optional_credit_card,
       const FieldTypeSet& field_types_to_fill,
       const DenseSet<FieldTypeGroup>* optional_type_groups_originally_filled,
+      FillingProduct filling_product,
       bool skip_unrecognized_autocomplete_fields,
-      bool is_refill) const;
+      bool is_refill,
+      bool is_expired_credit_card) const;
 
   // When `FillOrPreviewCreditCardForm()` fetches a credit card, this gets
   // called once the fetching has finished. If successful, the `credit_card` is
diff --git a/components/autofill/core/browser/field_filling_address_util.cc b/components/autofill/core/browser/field_filling_address_util.cc
index b687750..594dc60 100644
--- a/components/autofill/core/browser/field_filling_address_util.cc
+++ b/components/autofill/core/browser/field_filling_address_util.cc
@@ -12,6 +12,7 @@
 #include "components/autofill/core/browser/autofill_data_util.h"
 #include "components/autofill/core/browser/data_model/autofill_profile.h"
 #include "components/autofill/core/browser/data_model/data_model_utils.h"
+#include "components/autofill/core/browser/field_type_utils.h"
 #include "components/autofill/core/browser/geo/country_names.h"
 #include "components/autofill/core/browser/geo/state_names.h"
 #include "components/autofill/core/browser/select_control_util.h"
@@ -401,6 +402,7 @@
     const FormFieldData& field_data,
     AddressNormalizer* address_normalizer,
     std::string* failure_to_fill) {
+  CHECK(IsAddressType(field_type.GetStorableType()));
   std::optional<std::u16string> value = GetValueForProfileForInput(
       profile, app_locale, field_type, field_data, failure_to_fill);
 
diff --git a/components/autofill/core/browser/field_filling_payments_util.cc b/components/autofill/core/browser/field_filling_payments_util.cc
index 50cab3e..04acd39 100644
--- a/components/autofill/core/browser/field_filling_payments_util.cc
+++ b/components/autofill/core/browser/field_filling_payments_util.cc
@@ -472,6 +472,7 @@
     mojom::ActionPersistence action_persistence,
     const AutofillField& field,
     std::string* failure_to_fill) {
+  CHECK(field.Type().group() == FieldTypeGroup::kCreditCard);
   std::optional<std::u16string> value =
       credit_card.record_type() == CreditCard::RecordType::kVirtualCard &&
               action_persistence == mojom::ActionPersistence::kPreview
diff --git a/components/autofill/core/browser/metrics/log_event.h b/components/autofill/core/browser/metrics/log_event.h
index f4dca7d3..23b417d 100644
--- a/components/autofill/core/browser/metrics/log_event.h
+++ b/components/autofill/core/browser/metrics/log_event.h
@@ -49,7 +49,8 @@
   kFillingLimitReachedType = 12,
   kUnrecognizedAutocompleteAttribute = 13,
   kFieldDoesNotMatchTargetFieldsSet = 14,
-  kMaxValue = kFieldDoesNotMatchTargetFieldsSet
+  kFieldTypeUnrelated = 15,
+  kMaxValue = kFieldTypeUnrelated
 };
 
 // Enum for different data types filled during autofill filling events,
diff --git a/components/compose/core/browser/compose_manager_impl.cc b/components/compose/core/browser/compose_manager_impl.cc
index 4027630..0d480dcc 100644
--- a/components/compose/core/browser/compose_manager_impl.cc
+++ b/components/compose/core/browser/compose_manager_impl.cc
@@ -56,10 +56,6 @@
   return client_->HasSession(trigger_field_id);
 }
 
-bool ComposeManagerImpl::IsEnabled() const {
-  return base::FeatureList::IsEnabled(features::kEnableCompose);
-}
-
 void ComposeManagerImpl::GetBrowserFormHandler(
     autofill::FieldGlobalId field_id,
     compose::ComposeManagerImpl::UiEntryPoint ui_entry_point,
@@ -102,7 +98,6 @@
     const autofill::FormFieldData& trigger_field,
     std::optional<PopupScreenLocation> popup_screen_location,
     ComposeCallback callback) {
-  CHECK(IsEnabled());
   if (ui_entry_point == UiEntryPoint::kContextMenu) {
     compose::LogComposeContextMenuCtr(
         compose::ComposeContextMenuCtrEvent::kComposeOpened);
diff --git a/components/ml/webnn/BUILD.gn b/components/ml/webnn/BUILD.gn
index 903a05e1..c86e0b1b 100644
--- a/components/ml/webnn/BUILD.gn
+++ b/components/ml/webnn/BUILD.gn
@@ -2,15 +2,12 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-component("features") {
-  defines = [ "IS_WEBNN_FEATURES_IMPL" ]
+import("//mojo/public/tools/bindings/mojom.gni")
 
-  sources = [
-    "features.cc",
-    "features.h",
-  ]
-
-  deps = [ "//base" ]
+mojom_component("features") {
+  sources = [ "features.mojom" ]
+  output_prefix = "components_ml_webnn_features_mojom"
+  macro_prefix = "COMPONENTS_ML_WEBNN_FEATURES_MOJOM"
 }
 
 static_library("webnn") {
diff --git a/components/ml/webnn/OWNERS b/components/ml/webnn/OWNERS
index b141619..d794d1515 100644
--- a/components/ml/webnn/OWNERS
+++ b/components/ml/webnn/OWNERS
@@ -1,3 +1,6 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
+
 qjw@chromium.org
 ningxin.hu@intel.com
 rafael.cintron@microsoft.com
diff --git a/components/ml/webnn/features.cc b/components/ml/webnn/features.cc
deleted file mode 100644
index a82b0d5..0000000
--- a/components/ml/webnn/features.cc
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/ml/webnn/features.h"
-#include "base/feature_list.h"
-
-namespace webnn::features {
-
-BASE_FEATURE(kWebMachineLearningNeuralNetwork,
-             "WebMachineLearningNeuralNetwork",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-}
diff --git a/components/ml/webnn/features.h b/components/ml/webnn/features.h
deleted file mode 100644
index 027ff29..0000000
--- a/components/ml/webnn/features.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_ML_WEBNN_FEATURES_H_
-#define COMPONENTS_ML_WEBNN_FEATURES_H_
-
-#include "base/component_export.h"
-#include "base/feature_list.h"
-
-namespace webnn::features {
-
-// Enables the Web Machine Learning Neural Network API. Explainer:
-// https://github.com/webmachinelearning/webnn/blob/main/explainer.md
-COMPONENT_EXPORT(WEBNN_FEATURES)
-BASE_DECLARE_FEATURE(kWebMachineLearningNeuralNetwork);
-
-}  // namespace webnn::features
-
-#endif  // COMPONENTS_ML_WEBNN_FEATURES_H_
diff --git a/components/ml/webnn/features.mojom b/components/ml/webnn/features.mojom
new file mode 100644
index 0000000..1a201656
--- /dev/null
+++ b/components/ml/webnn/features.mojom
@@ -0,0 +1,12 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module webnn.mojom.features;
+
+// Enables the Web Machine Learning Neural Network API. Explainer:
+// https://github.com/webmachinelearning/webnn/blob/main/explainer.md
+feature kWebMachineLearningNeuralNetwork {
+  const string name = "WebMachineLearningNeuralNetwork";
+  const bool default_state = false;
+};
diff --git a/components/performance_manager/features.cc b/components/performance_manager/features.cc
index 8f0f0a6..f6b5fc6 100644
--- a/components/performance_manager/features.cc
+++ b/components/performance_manager/features.cc
@@ -207,9 +207,6 @@
              "PageTimelineMonitor",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-const base::FeatureParam<base::TimeDelta> kPageTimelineStateIntervalTime{
-    &kPageTimelineMonitor, "time_between_collect_slice", base::Minutes(5)};
-
 const base::FeatureParam<bool> kUseResourceAttributionCPUMonitor{
     &kPageTimelineMonitor, "use_resource_attribution_cpu_monitor", false};
 
diff --git a/components/performance_manager/public/features.h b/components/performance_manager/public/features.h
index bea9ddb..2b1233b8 100644
--- a/components/performance_manager/public/features.h
+++ b/components/performance_manager/public/features.h
@@ -191,14 +191,10 @@
 // Whether tabs are discarded under high memory pressure.
 BASE_DECLARE_FEATURE(kUrgentPageDiscarding);
 
-// Enable PageResourceMonitor timer and by extension, PageTimelineState event
+// Enable PageResourceMonitor timer and by extension, PageResourceUsage event
 // collection.
 BASE_DECLARE_FEATURE(kPageTimelineMonitor);
 
-// Set the interval in seconds between calls of
-// PageResourceMonitor::CollectSlice()
-extern const base::FeatureParam<base::TimeDelta> kPageTimelineStateIntervalTime;
-
 // Whether to use the resource_attribution::CPUMeasurementMonitor for logging
 // UKM.
 extern const base::FeatureParam<bool> kUseResourceAttributionCPUMonitor;
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 6a64a5b..d83a1bf3 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
@@ -167,6 +167,7 @@
   generation_id_ = -1;
   sequence_id_ = -1;
   if (encrypted_record_list != nullptr && !encrypted_record_list->empty()) {
+    record_count_ = encrypted_record_list->size();
     const auto sequence_information_it =
         std::prev(encrypted_record_list->cend());
     const auto* const sequence_information =
@@ -225,6 +226,10 @@
   // Now pick up the state.
   const auto* const state =
       AccessState(priority_, generation_id_, sequence_id_);
+  // If there are no records, allow upload (it will not overload the server).
+  if (record_count_ == 0u) {
+    return base::TimeDelta();  // 0 - allowed right away.
+  }
   // Use and update previously recorded state, base upload decision on it.
   if (state->last_sequence_id > sequence_id_) {
     // Sequence id decreased, the upload is outdated, reject it forever.
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 8608f7e..e5ce866 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
@@ -139,6 +139,7 @@
   ::reporting::Priority priority_;
   int64_t generation_id_{-1};
   int64_t sequence_id_{-1};
+  size_t record_count_{0u};
 };
 
 }  // namespace policy
diff --git a/components/reporting/encryption/decryption.cc b/components/reporting/encryption/decryption.cc
index 2f38482..b4369a0 100644
--- a/components/reporting/encryption/decryption.cc
+++ b/components/reporting/encryption/decryption.cc
@@ -119,7 +119,8 @@
              base::OnceCallback<void(StatusOr<Encryptor::PublicKeyId>)> cb,
              scoped_refptr<Decryptor> decryptor) {
             DCHECK_CALLED_ON_VALID_SEQUENCE(decryptor->keys_sequence_checker_);
-            StatusOr<Encryptor::PublicKeyId> result;
+            StatusOr<Encryptor::PublicKeyId> result =
+                CreateUnknownErrorStatusOr();
             if (key_info.private_key.size() != kKeySize) {
               result = base::unexpected(Status(
                   error::FAILED_PRECONDITION,
diff --git a/components/reporting/encryption/encryption.cc b/components/reporting/encryption/encryption.cc
index e52001f..27f107b 100644
--- a/components/reporting/encryption/encryption.cc
+++ b/components/reporting/encryption/encryption.cc
@@ -158,7 +158,6 @@
              scoped_refptr<Encryptor> encryptor) {
             DCHECK_CALLED_ON_VALID_SEQUENCE(
                 encryptor->asymmetric_key_sequence_checker_);
-            StatusOr<std::pair<std::string, PublicKeyId>> response;
             // Schedule response on regular thread pool.
             base::ThreadPool::PostTask(
                 FROM_HERE,
diff --git a/components/reporting/util/statusor.cc b/components/reporting/util/statusor.cc
index 198addb..c78fc26c 100644
--- a/components/reporting/util/statusor.cc
+++ b/components/reporting/util/statusor.cc
@@ -7,8 +7,11 @@
 #include <utility>
 
 #include "base/check.h"
+#include "base/no_destructor.h"
+#include "base/types/expected.h"
 
-namespace reporting::internal {
+namespace reporting {
+namespace internal {
 ErrorStatus::ErrorStatus(const ErrorStatus&) = default;
 ErrorStatus& ErrorStatus::operator=(const ErrorStatus&) = default;
 ErrorStatus::ErrorStatus(ErrorStatus&&) = default;
@@ -22,4 +25,11 @@
 ErrorStatus::ErrorStatus(Status&& status) : Status(std::move(status)) {
   CHECK(!ok()) << "The status must not be OK";
 }
-}  // namespace reporting::internal
+}  // namespace internal
+
+base::unexpected<Status> CreateUnknownErrorStatusOr() {
+  static base::NoDestructor<base::unexpected<Status>> status_not_initialized(
+      Status(error::UNKNOWN, "Not initialized"));
+  return *status_not_initialized;
+}
+}  // namespace reporting
diff --git a/components/reporting/util/statusor.h b/components/reporting/util/statusor.h
index 832e637..9298c4b 100644
--- a/components/reporting/util/statusor.h
+++ b/components/reporting/util/statusor.h
@@ -53,6 +53,9 @@
 //   std::move(cb).Run(base::unexpected(Status(...)));
 template <typename T>
 using StatusOr = base::expected<T, internal::ErrorStatus>;
+
+// Create a `StatusOr<T>` object with an unknown error.
+base::unexpected<Status> CreateUnknownErrorStatusOr();
 }  // namespace reporting
 
 #endif  // COMPONENTS_REPORTING_UTIL_STATUSOR_H_
diff --git a/components/reporting/util/task_runner_context.h b/components/reporting/util/task_runner_context.h
index 3524a6f..fd4e47a 100644
--- a/components/reporting/util/task_runner_context.h
+++ b/components/reporting/util/task_runner_context.h
@@ -134,7 +134,7 @@
   friend void Start(Args&&... args);
 
   // Hook for execution start. Should be overridden to do non-trivial work.
-  virtual void OnStart() { Response(ResponseType()); }
+  virtual void OnStart() = 0;
 
   // Finalization action before responding and deleting the context.
   // May be overridden, if necessary.
diff --git a/components/services/app_service/public/cpp/app_types.h b/components/services/app_service/public/cpp/app_types.h
index bdd07c0..bd4ca96 100644
--- a/components/services/app_service/public/cpp/app_types.h
+++ b/components/services/app_service/public/cpp/app_types.h
@@ -186,11 +186,13 @@
   // There is no guarantee that this is sorted by any criteria.
   Permissions permissions;
 
-  // Whether the app was installed by sync, policy or as a default app.
+  // The main reason why this app is currently installed on the device (e.g.
+  // because it is required by Policy). This may change over time and is not
+  // necessarily the reason why the app was originally installed.
   InstallReason install_reason = InstallReason::kUnknown;
 
-  // Where the app was installed from, e.g. from Play Store, from Chrome Web
-  // Store, etc.
+  // How installation of the app was triggered on this device. Either a UI
+  // surface (e.g. Play Store), or a system component (e.g. Sync).
   InstallSource install_source = InstallSource::kUnknown;
 
   // IDs used for policy to identify the app.
diff --git a/components/services/app_service/public/cpp/app_update.h b/components/services/app_service/public/cpp/app_update.h
index 3829563..3779d205 100644
--- a/components/services/app_service/public/cpp/app_update.h
+++ b/components/services/app_service/public/cpp/app_update.h
@@ -130,9 +130,14 @@
   apps::Permissions Permissions() const;
   bool PermissionsChanged() const;
 
+  // The main reason why this app is currently installed on the device (e.g.
+  // because it is required by Policy). This may change over time and is not
+  // necessarily the reason why the app was originally installed.
   apps::InstallReason InstallReason() const;
   bool InstallReasonChanged() const;
 
+  // How installation of the app was triggered on this device. Either a UI
+  // surface (e.g. Play Store), or a system component (e.g. Sync).
   apps::InstallSource InstallSource() const;
   bool InstallSourceChanged() const;
 
diff --git a/components/soda/soda_installer_impl_chromeos.cc b/components/soda/soda_installer_impl_chromeos.cc
index 11f90c4f8..84d31aa3 100644
--- a/components/soda/soda_installer_impl_chromeos.cc
+++ b/components/soda/soda_installer_impl_chromeos.cc
@@ -168,8 +168,6 @@
                                                 PrefService* global_prefs) {
   if (never_download_soda_for_testing_)
     return;
-  // TODO(crbug.com/1161569): SODA is only available for en-US right now.
-  DCHECK_EQ(language, kUsEnglishLocale);
   SodaInstaller::RegisterLanguage(language, global_prefs);
   // Clear cached path in case this is a reinstallation (path could
   // change).
diff --git a/components/update_client/background_downloader_mac.mm b/components/update_client/background_downloader_mac.mm
index fbd7727..bfb8a65ed 100644
--- a/components/update_client/background_downloader_mac.mm
+++ b/components/update_client/background_downloader_mac.mm
@@ -39,6 +39,7 @@
 #include "base/thread_annotations.h"
 #include "base/threading/sequence_bound.h"
 #include "base/time/time.h"
+#include "base/timer/timer.h"
 #include "components/update_client/crx_downloader.h"
 #include "components/update_client/task_traits.h"
 #include "components/update_client/update_client_errors.h"
@@ -55,6 +56,10 @@
 // Callback invoked by DownloadDelegate when download metrics are available.
 using DelegateMetricsCollectedCallback =
     base::RepeatingCallback<void(const GURL& url, uint64_t download_time_ms)>;
+
+// Callback invoked by DownloadDelegate when progress has been made on a task.
+using DelegateDownloadProgressCallback =
+    base::RepeatingCallback<void(const GURL&)>;
 using OnDownloadCompleteCallback = update_client::
     BackgroundDownloaderSharedSession::OnDownloadCompleteCallback;
 
@@ -67,6 +72,18 @@
 // The age at which unclaimed downloads should be evicted from the cache.
 constexpr base::TimeDelta kMaxCachedDownloadAge = base::Days(2);
 
+// How often to perform periodic actions on download tasks.
+constexpr base::TimeDelta kTaskPollingInterval = base::Minutes(5);
+
+// The maximum number of tasks the downloader can have active at once.
+constexpr int kMaxTasks = 10;
+
+// How long to tolerate a background task that has not made any progress.
+constexpr base::TimeDelta kNoProgressTimeout = base::Minutes(15);
+
+// The maximum duration a task can exist before giving up.
+constexpr base::TimeDelta kMaxTaskAge = base::Days(3);
+
 // These methods have been copied from //net/base/mac/url_conversions.h to
 // avoid introducing a dependancy on //net.
 NSURL* NSURLWithGURL(const GURL& url) {
@@ -108,18 +125,6 @@
   return GURL();
 }
 
-// Detects and removes old files from the download cache.
-void CleanDownloadCache(const base::FilePath& download_cache) {
-  base::FileEnumerator(download_cache, false, base::FileEnumerator::FILES)
-      .ForEach([](const base::FilePath& download) {
-        base::File::Info info;
-        if (base::GetFileInfo(download, &info) &&
-            base::Time::Now() - info.creation_time > kMaxCachedDownloadAge) {
-          base::DeleteFile(download);
-        }
-      });
-}
-
 }  // namespace
 
 @interface DownloadDelegate : NSObject <NSURLSessionDownloadDelegate>
@@ -127,13 +132,16 @@
              downloadCompleteCallback:
                  (DelegateDownloadCompleteCallback)downloadCompleteCallback
              metricsCollectedCallback:
-                 (DelegateMetricsCollectedCallback)metricsCollectedCallback;
+                 (DelegateMetricsCollectedCallback)metricsCollectedCallback
+                     progressCallback:
+                         (DelegateDownloadProgressCallback)progressCallback;
 @end
 
 @implementation DownloadDelegate {
   base::FilePath _download_cache;
   DelegateDownloadCompleteCallback _download_complete_callback;
   DelegateMetricsCollectedCallback _metrics_collected_callback;
+  DelegateDownloadProgressCallback _progress_callback;
   scoped_refptr<base::SequencedTaskRunner> _callback_runner;
 }
 
@@ -141,11 +149,14 @@
              downloadCompleteCallback:
                  (DelegateDownloadCompleteCallback)downloadCompleteCallback
              metricsCollectedCallback:
-                 (DelegateMetricsCollectedCallback)metricsCollectedCallback {
+                 (DelegateMetricsCollectedCallback)metricsCollectedCallback
+                     progressCallback:
+                         (DelegateDownloadProgressCallback)progressCallback {
   if (self = [super init]) {
     _download_cache = downloadCache;
     _download_complete_callback = downloadCompleteCallback;
     _metrics_collected_callback = metricsCollectedCallback;
+    _progress_callback = progressCallback;
     _callback_runner = base::SequencedTaskRunner::GetCurrentDefault();
   }
   return self;
@@ -198,6 +209,19 @@
          withError:static_cast<int>(update_client::CrxDownloaderError::NONE)];
 }
 
+- (void)URLSession:(NSURLSession*)session
+                 downloadTask:(nonnull NSURLSessionDownloadTask*)downloadTask
+                 didWriteData:(int64_t)bytesWritten
+            totalBytesWritten:(int64_t)totalBytesWritten
+    totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
+  if (bytesWritten > 0) {
+    _callback_runner->PostTask(
+        FROM_HERE,
+        base::BindOnce(_progress_callback,
+                       GURLWithNSURL(downloadTask.originalRequest.URL)));
+  }
+}
+
 #pragma mark - NSURLSessionDelegate
 
 - (void)URLSession:(NSURLSession*)session
@@ -252,14 +276,35 @@
                     weak_this->OnMetricsCollected(url, download_time_ms);
                   }
                 },
-                weak_factory_.GetWeakPtr())];
+                weak_factory_.GetWeakPtr())
+                progressCallback:
+                    base::BindRepeating(
+                        [](base::WeakPtr<BackgroundDownloaderSharedSessionImpl>
+                               weak_this,
+                           const GURL& url) {
+                          if (weak_this) {
+                            weak_this->OnDownloadProgressMade(url);
+                          }
+                        },
+                        weak_factory_.GetWeakPtr())];
 
     NSURLSessionConfiguration* config = [NSURLSessionConfiguration
         backgroundSessionConfigurationWithIdentifier:base::SysUTF8ToNSString(
                                                          session_identifier)];
+    config.timeoutIntervalForResource = kMaxTaskAge.InSeconds();
     session_ = [NSURLSession sessionWithConfiguration:config
                                              delegate:delegate
                                         delegateQueue:nil];
+
+    periodic_task_timer_.Start(
+        FROM_HERE, kTaskPollingInterval,
+        base::BindRepeating(
+            [](base::WeakPtr<BackgroundDownloaderSharedSessionImpl> weak_this) {
+              if (weak_this) {
+                weak_this->StartPeriodicTasks();
+              }
+            },
+            weak_factory_.GetWeakPtr()));
   }
 
   ~BackgroundDownloaderSharedSessionImpl() {
@@ -269,10 +314,6 @@
   void DoStartDownload(const GURL& url, OnDownloadCompleteCallback callback) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
-        FROM_HERE, base::BindRepeating(&CleanDownloadCache, download_cache_),
-        base::Minutes(10));
-
     if (!session_) {
       CrxDownloader::DownloadMetrics metrics = GetDefaultMetrics(url);
       metrics.error =
@@ -299,24 +340,48 @@
       return;
     }
 
-    downloads_.emplace(url, callback);
-    HasOngoingDownload(
+    QueryOngoingDownloads(
         url, base::BindOnce(
                  [](base::WeakPtr<BackgroundDownloaderSharedSessionImpl> impl,
-                    const GURL& url, bool has_download) {
-                   if (!impl) {
-                     return;
-                   }
-                   metrics::RecordBDMStartDownloadOutcome(
-                       has_download ? metrics::BDMStartDownloadOutcome::
-                                          kSessionHasOngoingDownload
-                                    : metrics::BDMStartDownloadOutcome::
-                                          kNewDownloadTaskCreated);
-                   if (!has_download) {
-                     impl->CreateAndResumeDownloadTask(url);
+                    const GURL& url, OnDownloadCompleteCallback callback,
+                    bool has_download, int num_tasks) {
+                   if (impl) {
+                     impl->OnDownloadsQueried(url, std::move(callback),
+                                              has_download, num_tasks);
                    }
                  },
-                 weak_factory_.GetWeakPtr(), url));
+                 weak_factory_.GetWeakPtr(), url, std::move(callback)));
+  }
+
+  void OnDownloadsQueried(const GURL& url,
+                          OnDownloadCompleteCallback callback,
+                          bool has_download,
+                          int num_tasks) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    if (has_download) {
+      metrics::RecordBDMStartDownloadOutcome(
+          metrics::BDMStartDownloadOutcome::kSessionHasOngoingDownload);
+      downloads_.emplace(url, callback);
+    } else if (num_tasks >= kMaxTasks) {
+      CrxDownloader::DownloadMetrics metrics = GetDefaultMetrics(url);
+      metrics.error =
+          static_cast<int>(CrxDownloaderError::MAC_BG_SESSION_TOO_MANY_TASKS);
+      metrics::RecordBDMStartDownloadOutcome(
+          metrics::BDMStartDownloadOutcome::kTooManyTasks);
+      callback.Run(false, {metrics.error, base::FilePath()}, metrics);
+    } else {
+      metrics::RecordBDMStartDownloadOutcome(
+          metrics::BDMStartDownloadOutcome::kNewDownloadTaskCreated);
+      NSMutableURLRequest* urlRequest =
+          [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)];
+      NSURLSessionDownloadTask* downloadTask =
+          [session_ downloadTaskWithRequest:urlRequest];
+      downloadTask.priority = NSURLSessionTaskPriorityHigh;
+
+      [downloadTask resume];
+      downloads_.emplace(url, callback);
+    }
   }
 
   void InvalidateAndCancel() {
@@ -345,17 +410,6 @@
     CrxDownloader::DownloadMetrics download_metrics;
   };
 
-  void CreateAndResumeDownloadTask(const GURL& url) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    NSMutableURLRequest* urlRequest =
-        [[NSMutableURLRequest alloc] initWithURL:NSURLWithGURL(url)];
-    NSURLSessionDownloadTask* downloadTask =
-        [session_ downloadTaskWithRequest:urlRequest];
-    downloadTask.priority = NSURLSessionTaskPriorityHigh;
-
-    [downloadTask resume];
-  }
-
   // Looks for a completed download in the cache. Returns false if the cache
   // does not contain `url`.
   bool HandleDownloadFromCache(const GURL& url,
@@ -388,10 +442,11 @@
     return true;
   }
 
-  // Replies with true if the background session contains a non-canceled
-  // download task for `url`.
-  void HasOngoingDownload(const GURL& url,
-                          base::OnceCallback<void(bool)> callback) {
+  // Queries the tasks owned by the background session to determine if
+  // an existing download exists for a URL and how many jobs are ongoing.
+  void QueryOngoingDownloads(
+      const GURL& url,
+      base::OnceCallback<void(bool hasDownload, int numTasks)> callback) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     CHECK(session_);
     // A copy of the URL is needed so that a reference is not captured by the
@@ -399,7 +454,7 @@
     GURL url_for_block(url);
     scoped_refptr<base::SequencedTaskRunner> reply_sequence =
         base::SequencedTaskRunner::GetCurrentDefault();
-    __block base::OnceCallback<void(bool)> block_scoped_callback =
+    __block base::OnceCallback<void(bool, int)> block_scoped_callback =
         std::move(callback);
     [session_ getAllTasksWithCompletionHandler:^(
                   NSArray<__kindof NSURLSessionTask*>* _Nonnull tasks) {
@@ -419,7 +474,8 @@
         }
       }
       reply_sequence->PostTask(
-          FROM_HERE, base::BindOnce(std::move(block_scoped_callback), has_url));
+          FROM_HERE, base::BindOnce(std::move(block_scoped_callback), has_url,
+                                    tasks.count));
     }];
   }
 
@@ -482,9 +538,84 @@
                              result.download_metrics);
       results_.erase(url);
       downloads_.erase(url);
+      if (last_progress_times_.contains(url)) {
+        last_progress_times_.erase(url);
+      }
     }
   }
 
+  // Called when the delegate has noticed progress being made on a download.
+  void OnDownloadProgressMade(const GURL& url) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    last_progress_times_.insert_or_assign(url, base::Time::Now());
+  }
+
+  void StartPeriodicTasks() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    // Clean the download cache of stale files.
+    base::FileEnumerator(download_cache_, false, base::FileEnumerator::FILES)
+        .ForEach([](const base::FilePath& download) {
+          base::File::Info info;
+          if (base::GetFileInfo(download, &info) &&
+              base::Time::Now() - info.creation_time > kMaxCachedDownloadAge) {
+            base::DeleteFile(download);
+          }
+        });
+
+    if (session_) {
+      __block base::OnceCallback<void(
+          NSArray<__kindof NSURLSessionTask*>* _Nonnull)>
+          callback = base::BindOnce(
+              [](base::WeakPtr<BackgroundDownloaderSharedSessionImpl> weak_this,
+                 NSArray<__kindof NSURLSessionTask*>* _Nonnull tasks) {
+                if (weak_this) {
+                  weak_this->CompletePeriodicTasks(tasks);
+                }
+              },
+              weak_factory_.GetWeakPtr());
+      scoped_refptr<base::SequencedTaskRunner> reply_sequence =
+          base::SequencedTaskRunner::GetCurrentDefault();
+      [session_ getAllTasksWithCompletionHandler:^(
+                    NSArray<__kindof NSURLSessionTask*>* _Nonnull tasks) {
+        reply_sequence->PostTask(FROM_HERE,
+                                 base::BindOnce(std::move(callback), tasks));
+      }];
+    }
+  }
+
+  void CompletePeriodicTasks(
+      NSArray<__kindof NSURLSessionTask*>* _Nonnull tasks) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    if (!session_) {
+      return;
+    }
+
+    base::flat_map<GURL, base::Time> filtered_progresses;
+    const base::Time now = base::Time::Now();
+    for (NSURLSessionTask* task in tasks) {
+      if (task.state != NSURLSessionTaskState::NSURLSessionTaskStateRunning) {
+        continue;
+      }
+
+      const GURL url = GURLWithNSURL([task originalRequest].URL);
+      if (!last_progress_times_.contains(url)) {
+        // If the last progress time is unknown it should be set to now so that
+        // the task can be cleaned even if it fails to send a progress update.
+        filtered_progresses.emplace(url, now);
+      } else {
+        const base::Time& last_progress_time = last_progress_times_.at(url);
+        if (now - last_progress_time > kNoProgressTimeout) {
+          [task cancel];
+        } else {
+          filtered_progresses.emplace(url, last_progress_time);
+        }
+      }
+    }
+
+    last_progress_times_ = std::move(filtered_progresses);
+  }
+
   // Returns a `CrxDownloader::DownloadMetrics` with url and downloader set.
   static CrxDownloader::DownloadMetrics GetDefaultMetrics(const GURL& url) {
     CrxDownloader::DownloadMetrics metrics;
@@ -502,12 +633,17 @@
   base::flat_map<GURL, DownloadResult> results_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
+  // Stores the last time progress was recorded for a download.
+  base::flat_map<GURL, base::Time> last_progress_times_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
   // Tracks which downloads have been requested. This is used to notify the
   // CrxDownloader only of the downloads it has requested in the lifetime of
   // this BackgroundDownloaderSharedSessionImpl, as opposed to downloads which
   // were started by a previous BackgroundDownloaderSharedSessionImpl.
   base::flat_map<GURL, OnDownloadCompleteCallback> downloads_
       GUARDED_BY_CONTEXT(sequence_checker_);
+  base::RepeatingTimer periodic_task_timer_;
   base::WeakPtrFactory<BackgroundDownloaderSharedSessionImpl> weak_factory_
       GUARDED_BY_CONTEXT(sequence_checker_){this};
 };
diff --git a/components/update_client/background_downloader_mac_unittest.cc b/components/update_client/background_downloader_mac_unittest.cc
index 7c2d775f..57c9060 100644
--- a/components/update_client/background_downloader_mac_unittest.cc
+++ b/components/update_client/background_downloader_mac_unittest.cc
@@ -53,6 +53,7 @@
 using net::test_server::HttpRequest;
 using net::test_server::HttpResponse;
 using net::test_server::HttpResponseDelegate;
+using net::test_server::HungResponse;
 
 namespace update_client {
 namespace {
@@ -300,55 +301,49 @@
 }
 
 TEST_F(BackgroundDownloaderTest, DuplicateDownload) {
+  scoped_refptr<base::SequencedTaskRunner> current_task_runner =
+      base::SequencedTaskRunner::GetCurrentDefault();
+  base::RunLoop second_download_run_loop;
   request_handler_ = base::BindLambdaForTesting([&](const HttpRequest&) {
-    std::unique_ptr<BasicHttpResponse> response =
-        std::make_unique<BasicHttpResponse>();
-    response->set_code(net::HTTP_OK);
-    response->set_content(kSmallDownloadData);
-    response->set_content_type("text/plain");
-    return base::WrapUnique<HttpResponse>(response.release());
+    current_task_runner->PostTask(
+        FROM_HERE, base::BindLambdaForTesting([&]() {
+          DoStartDownload(
+              GetURL(),
+              base::BindLambdaForTesting(
+                  [&](bool is_handled, const CrxDownloader::Result& result,
+                      const CrxDownloader::DownloadMetrics& metrics) {
+                    EXPECT_FALSE(is_handled);
+                    EXPECT_EQ(
+                        result.error,
+                        static_cast<int>(
+                            CrxDownloaderError::MAC_BG_DUPLICATE_DOWNLOAD));
+                    ExpectDownloadMetrics(
+                        metrics,
+                        static_cast<int>(
+                            CrxDownloaderError::MAC_BG_DUPLICATE_DOWNLOAD),
+                        0, -1, -1, false);
+                  })
+                  .Then(second_download_run_loop.QuitClosure()));
+        }));
+    return base::WrapUnique<HttpResponse>(new HungResponse());
   });
 
-  base::RunLoop run_loop;
-  base::RepeatingClosure barrier_closure =
-      base::BarrierClosure(2, run_loop.QuitClosure());
+  base::RunLoop first_download_run_loop;
   DoStartDownload(GetURL(),
                   base::BindLambdaForTesting(
                       [&](bool is_handled, const CrxDownloader::Result& result,
                           const CrxDownloader::DownloadMetrics& metrics) {
-                        EXPECT_TRUE(is_handled);
-                        EXPECT_EQ(result.error, 0);
-                        ExpectSmallDownloadContents(result.response);
-                        ExpectDownloadMetrics(
-                            metrics, static_cast<int>(CrxDownloaderError::NONE),
-                            0, std::strlen(kSmallDownloadData),
-                            std::strlen(kSmallDownloadData), true);
-                      })
-                      .Then(base::BindPostTaskToCurrentDefault(barrier_closure,
-                                                               FROM_HERE)));
-  DoStartDownload(
-      GetURL(),
-      base::BindLambdaForTesting([&](bool is_handled,
-                                     const CrxDownloader::Result& result,
-                                     const CrxDownloader::DownloadMetrics&
-                                         metrics) {
-        EXPECT_FALSE(is_handled);
-        EXPECT_EQ(
-            result.error,
-            static_cast<int>(CrxDownloaderError::MAC_BG_DUPLICATE_DOWNLOAD));
-        ExpectDownloadMetrics(
-            metrics,
-            static_cast<int>(CrxDownloaderError::MAC_BG_DUPLICATE_DOWNLOAD), 0,
-            -1, -1, false);
-      }).Then(base::BindPostTaskToCurrentDefault(barrier_closure, FROM_HERE)));
-
-  run_loop.Run();
+                        first_download_run_loop.Quit();
+                      }));
+  second_download_run_loop.Run();
+  shared_session_->InvalidateAndCancel();
+  first_download_run_loop.Run();
 }
 
 // Tests that downloads can complete when using multiple instances of
 // BackgroundDownloader.
 TEST_F(BackgroundDownloaderTest, ConcurrentDownloaders) {
-  request_handler_ = base::BindLambdaForTesting([&](const HttpRequest&) {
+  request_handler_ = base::BindLambdaForTesting([](const HttpRequest&) {
     std::unique_ptr<BasicHttpResponse> response =
         std::make_unique<BasicHttpResponse>();
     response->set_code(net::HTTP_OK);
@@ -388,7 +383,57 @@
   run_loop.Run();
 }
 
-class BackgroundDownloaderCacheCleanTest : public BackgroundDownloaderTest {
+TEST_F(BackgroundDownloaderTest, MaxDownloads) {
+  request_handler_ = base::BindLambdaForTesting([](const HttpRequest& request) {
+    return base::WrapUnique<HttpResponse>(new HungResponse());
+  });
+
+  base::RunLoop all_downloads_complete_run_loop;
+  base::RepeatingClosure barrier_closure =
+      base::BarrierClosure(10, all_downloads_complete_run_loop.QuitClosure());
+  for (int i = 0; i < 10; i++) {
+    DoStartDownload(
+        GetURL(base::NumberToString(i)),
+        base::BindLambdaForTesting(
+            [](bool is_handled, const CrxDownloader::Result& result,
+               const CrxDownloader::DownloadMetrics& metrics) {})
+            .Then(base::BindPostTaskToCurrentDefault(barrier_closure)));
+  }
+
+  base::RunLoop run_loop;
+  // Mac may decide to not make progress on all of the downloads created
+  // immediately. Thus, we have no way of observing when all of the download
+  // tasks above have been created. Delaying the request for the final download
+  // is an alternative to adding intrusive instrumentation to the
+  // implementation.
+  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+      FROM_HERE, base::BindLambdaForTesting([&]() {
+        DoStartDownload(
+            GetURL(),
+            base::BindLambdaForTesting(
+                [&](bool is_handled, const CrxDownloader::Result& result,
+                    const CrxDownloader::DownloadMetrics& metrics) {
+                  EXPECT_FALSE(is_handled);
+                  EXPECT_EQ(
+                      result.error,
+                      static_cast<int>(
+                          CrxDownloaderError::MAC_BG_SESSION_TOO_MANY_TASKS));
+                  ExpectDownloadMetrics(
+                      metrics,
+                      static_cast<int>(
+                          CrxDownloaderError::MAC_BG_SESSION_TOO_MANY_TASKS),
+                      0, -1, -1, false);
+                })
+                .Then(run_loop.QuitClosure()));
+      }),
+      base::Milliseconds(100));
+  run_loop.Run();
+
+  shared_session_->InvalidateAndCancel();
+  all_downloads_complete_run_loop.Run();
+}
+
+class BackgroundDownloaderPeriodicTasksTest : public BackgroundDownloaderTest {
  public:
   std::unique_ptr<base::test::TaskEnvironment> CreateTaskEnvironment()
       override {
@@ -403,8 +448,8 @@
   }
 };
 
-TEST_F(BackgroundDownloaderCacheCleanTest, CleansStaleDownloads) {
-  request_handler_ = base::BindLambdaForTesting([&](const HttpRequest&) {
+TEST_F(BackgroundDownloaderPeriodicTasksTest, CleansStaleDownloads) {
+  request_handler_ = base::BindLambdaForTesting([](const HttpRequest&) {
     std::unique_ptr<BasicHttpResponse> response =
         std::make_unique<BasicHttpResponse>();
     response->set_code(net::HTTP_OK);
@@ -434,6 +479,23 @@
   EXPECT_FALSE(base::PathExists(download_cache_.AppendASCII("file2")));
 }
 
+TEST_F(BackgroundDownloaderPeriodicTasksTest, CancelsTasksWithNoProgress) {
+  request_handler_ = base::BindLambdaForTesting([](const HttpRequest&) {
+    return base::WrapUnique<HttpResponse>(new HungResponse());
+  });
+
+  base::RunLoop run_loop;
+  DoStartDownload(GetURL(),
+                  base::BindLambdaForTesting(
+                      [](bool is_handled, const CrxDownloader::Result& result,
+                         const CrxDownloader::DownloadMetrics& metrics) {
+                        EXPECT_EQ(result.error, -999 /* NSURLErrorCancelled */);
+                      })
+                      .Then(run_loop.QuitClosure()));
+  environment_->FastForwardBy(base::Minutes(30));
+  run_loop.Run();
+}
+
 class BackgroundDownloaderCrashingClientTest : public testing::Test {
  public:
   void SetUp() override {
diff --git a/components/update_client/update_client_errors.h b/components/update_client/update_client_errors.h
index 54fa182..56bb2dd 100644
--- a/components/update_client/update_client_errors.h
+++ b/components/update_client/update_client_errors.h
@@ -50,6 +50,7 @@
   MAC_BG_MISSING_COMPLETION_DATA = 1103,
   MAC_BG_DUPLICATE_DOWNLOAD = 1104,
   MAC_BG_SESSION_INVALIDATED = 1105,
+  MAC_BG_SESSION_TOO_MANY_TASKS = 1106,
   GENERIC_ERROR = -1
 };
 
diff --git a/components/update_client/update_client_metrics.h b/components/update_client/update_client_metrics.h
index 1fa3f23e..3c4b7cc 100644
--- a/components/update_client/update_client_metrics.h
+++ b/components/update_client/update_client_metrics.h
@@ -19,7 +19,8 @@
   kDownloadRecoveredFromCache = 1,
   kSessionHasOngoingDownload = 2,
   kNewDownloadTaskCreated = 3,
-  kMaxValue = kNewDownloadTaskCreated
+  kTooManyTasks = 4,
+  kMaxValue = kTooManyTasks
 };
 
 // These values are persisted to logs. Entries should not be renumbered and
diff --git a/components/viz/host/gpu_client.cc b/components/viz/host/gpu_client.cc
index bb886ce..79f3ef5 100644
--- a/components/viz/host/gpu_client.cc
+++ b/components/viz/host/gpu_client.cc
@@ -22,7 +22,7 @@
 
 #if !BUILDFLAG(IS_CHROMEOS)
 #include "base/feature_list.h"
-#include "components/ml/webnn/features.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
 namespace viz {
@@ -125,9 +125,6 @@
 #if !BUILDFLAG(IS_CHROMEOS)
 void GpuClient::BindWebNNContextProvider(
     mojo::PendingReceiver<webnn::mojom::WebNNContextProvider> receiver) {
-  CHECK(base::FeatureList::IsEnabled(
-      webnn::features::kWebMachineLearningNeuralNetwork));
-
   if (auto* gpu_host = delegate_->EnsureGpuHost()) {
     gpu_host->gpu_service()->BindWebNNContextProvider(std::move(receiver),
                                                       client_id_);
diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc
index 5ac82fa..d6c3080 100644
--- a/components/viz/service/gl/gpu_service_impl.cc
+++ b/components/viz/service/gl/gpu_service_impl.cc
@@ -111,7 +111,7 @@
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if !BUILDFLAG(IS_CHROMEOS)
-#include "components/ml/webnn/features.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #include "services/webnn/webnn_context_provider_impl.h"
 #endif  // !BUILDFLAG(IS_CHROMEOS)
 
@@ -937,8 +937,6 @@
 void GpuServiceImpl::BindWebNNContextProvider(
     mojo::PendingReceiver<webnn::mojom::WebNNContextProvider> pending_receiver,
     int client_id) {
-  CHECK(base::FeatureList::IsEnabled(
-      webnn::features::kWebMachineLearningNeuralNetwork));
   webnn::WebNNContextProviderImpl::Create(std::move(pending_receiver));
 }
 #endif  // !BUILDFLAG(IS_CHROMEOS)
diff --git a/components/zucchini/patch_utils_unittest.cc b/components/zucchini/patch_utils_unittest.cc
index 15b1c93..ba09d2a 100644
--- a/components/zucchini/patch_utils_unittest.cc
+++ b/components/zucchini/patch_utils_unittest.cc
@@ -88,25 +88,25 @@
 TEST(PatchUtilsTest, EncodeDecodeVarUInt32) {
   TestEncodeDecodeVarUInt<uint32_t>({0, 64, 128, 8192, 16384, 1 << 20, 1 << 21,
                                      1 << 22, 1 << 27, 1 << 28, 0x7FFFFFFFU,
-                                     UINT32_MAX});
+                                     UINT32_MAX - 4});
 }
 
 TEST(PatchUtilsTest, EncodeDecodeVarInt32) {
   TestEncodeDecodeVarInt<int32_t>({0, 64, 128, 8192, 16384, 1 << 20, 1 << 21,
-                                   1 << 22, 1 << 27, 1 << 28, -1, INT32_MIN,
-                                   INT32_MAX});
+                                   1 << 22, 1 << 27, 1 << 28, -1, INT32_MIN + 5,
+                                   INT32_MAX - 4});
 }
 
 TEST(PatchUtilsTest, EncodeDecodeVarUInt64) {
   TestEncodeDecodeVarUInt<uint64_t>({0, 64, 128, 8192, 16384, 1 << 20, 1 << 21,
                                      1 << 22, 1ULL << 55, 1ULL << 56,
-                                     0x7FFFFFFFFFFFFFFFULL, UINT64_MAX});
+                                     0x7FFFFFFFFFFFFFFFULL, (UINT64_MAX - 4)});
 }
 
 TEST(PatchUtilsTest, EncodeDecodeVarInt64) {
   TestEncodeDecodeVarInt<int64_t>({0, 64, 128, 8192, 16384, 1 << 20, 1 << 21,
-                                   1 << 22, 1LL << 55, 1LL << 56, -1, INT64_MIN,
-                                   INT64_MAX});
+                                   1 << 22, 1LL << 55, 1LL << 56, -1,
+                                   (INT64_MIN + 5), (INT64_MAX - 4)});
 }
 
 TEST(PatchUtilsTest, DecodeVarUInt32Malformed) {
diff --git a/content/browser/DEPS b/content/browser/DEPS
index a49a4fb..72a3104 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -140,7 +140,7 @@
     "+services/network/test",
   ],
   "browser_interface_binders\.cc": [
-    "+components/ml/webnn/features.h",
+    "+components/ml/webnn/features.mojom-features.h",
   ],
   "browser_main_loop\.cc": [
     # TODO(crbug.com/1049894): Remove.
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index baa9d3ae..dfe5f00 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -237,7 +237,7 @@
 #endif
 
 #if !BUILDFLAG(IS_CHROMEOS)
-#include "components/ml/webnn/features.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #include "components/viz/host/gpu_client.h"
 #include "services/webnn/public/mojom/webnn_context_provider.mojom.h"
 #endif
@@ -902,7 +902,7 @@
 
 #if !BUILDFLAG(IS_CHROMEOS)
   if (base::FeatureList::IsEnabled(
-          webnn::features::kWebMachineLearningNeuralNetwork)) {
+          webnn::mojom::features::kWebMachineLearningNeuralNetwork)) {
     map->Add<webnn::mojom::WebNNContextProvider>(base::BindRepeating(
         &BindWebNNContextProviderForRenderFrame, base::Unretained(host)));
   }
@@ -1282,7 +1282,7 @@
 
 #if !BUILDFLAG(IS_CHROMEOS)
   if (base::FeatureList::IsEnabled(
-          webnn::features::kWebMachineLearningNeuralNetwork)) {
+          webnn::mojom::features::kWebMachineLearningNeuralNetwork)) {
     // base::Unretained(host->GetProcessHost()) is safe because the map is owned
     // by |DedicatedWorkerHost::broker_|.
     map->Add<webnn::mojom::WebNNContextProvider>(base::BindRepeating(
diff --git a/content/browser/private_aggregation/private_aggregation_host.cc b/content/browser/private_aggregation/private_aggregation_host.cc
index 8fc0cbc6..759ed79 100644
--- a/content/browser/private_aggregation/private_aggregation_host.cc
+++ b/content/browser/private_aggregation/private_aggregation_host.cc
@@ -111,7 +111,8 @@
       on_report_request_details_received_(
           std::move(on_report_request_details_received)),
       browser_context_(*browser_context) {
-  DCHECK(!on_report_request_details_received_.is_null());
+  CHECK(browser_context);
+  CHECK(!on_report_request_details_received_.is_null());
 
   // `base::Unretained()` is safe as `receiver_set_` is owned by `this`.
   receiver_set_.set_disconnect_handler(base::BindRepeating(
diff --git a/content/browser/renderer_host/navigation_controller_impl.cc b/content/browser/renderer_host/navigation_controller_impl.cc
index 2e5160e2..2ca9b25 100644
--- a/content/browser/renderer_host/navigation_controller_impl.cc
+++ b/content/browser/renderer_host/navigation_controller_impl.cc
@@ -1381,24 +1381,18 @@
   }
   details->previous_main_frame_url = GetLastCommittedEntry()->GetURL();
   details->previous_entry_index = GetLastCommittedEntryIndex();
-  if (PendingEntryMatchesRequest(navigation_request) &&
-      pending_entry_->GetIsOverridingUserAgent() !=
-          GetLastCommittedEntry()->GetIsOverridingUserAgent()) {
-    overriding_user_agent_changed = true;
-  }
-#if BUILDFLAG(IS_ANDROID)
-  // TODO(crbug.com/1266277): Clean up the logic of setting
-  // |overriding_user_agent_changed| post-launch.
   // Must honor user agent overrides in the |navigation_request|, such as
   // from things like RequestDesktopSiteWebContentsObserverAndroid. As a
   // result, besides comparing |pending_entry_|'s user agent against
   // LastCommittedEntry's, also need to compare |navigation_request|'s user
   // agent against LastCommittedEntry's.
   if (navigation_request->is_overriding_user_agent() !=
-      GetLastCommittedEntry()->GetIsOverridingUserAgent()) {
+          GetLastCommittedEntry()->GetIsOverridingUserAgent() ||
+      (PendingEntryMatchesRequest(navigation_request) &&
+       pending_entry_->GetIsOverridingUserAgent() !=
+           GetLastCommittedEntry()->GetIsOverridingUserAgent())) {
     overriding_user_agent_changed = true;
   }
-#endif  // BUILDFLAG(IS_ANDROID)
 
   bool is_main_frame_navigation = !rfh->GetParent();
 
diff --git a/content/browser/service_worker/service_worker_context_core.cc b/content/browser/service_worker/service_worker_context_core.cc
index fab4f3b..857ba32 100644
--- a/content/browser/service_worker/service_worker_context_core.cc
+++ b/content/browser/service_worker/service_worker_context_core.cc
@@ -915,6 +915,9 @@
   observer_list_->Notify(FROM_HERE,
                          &ServiceWorkerContextCoreObserver::OnNewLiveVersion,
                          version_info);
+  for (auto& observer : test_version_observers_) {
+    observer.OnServiceWorkerVersionCreated(version);
+  }
 }
 
 void ServiceWorkerContextCore::RemoveLiveVersion(int64_t id) {
diff --git a/content/browser/service_worker/service_worker_context_core.h b/content/browser/service_worker/service_worker_context_core.h
index 60ea269..27577c51 100644
--- a/content/browser/service_worker/service_worker_context_core.h
+++ b/content/browser/service_worker/service_worker_context_core.h
@@ -19,6 +19,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list_threadsafe.h"
+#include "base/observer_list_types.h"
 #include "components/services/storage/public/mojom/quota_client.mojom.h"
 #include "components/services/storage/public/mojom/service_worker_storage_control.mojom.h"
 #include "content/browser/service_worker/service_worker_info.h"
@@ -109,6 +110,15 @@
     ContainerHostByClientUUIDMap::iterator container_host_iterator_;
   };
 
+  class TestVersionObserver : public base::CheckedObserver {
+   public:
+    TestVersionObserver() = default;
+
+    // Called when a new `ServiceWorkerVersion` is added to this context.
+    virtual void OnServiceWorkerVersionCreated(
+        ServiceWorkerVersion* service_worker_version) {}
+  };
+
   // This is owned by ServiceWorkerContextWrapper. |observer_list| is created in
   // ServiceWorkerContextWrapper. When Notify() of |observer_list| is called in
   // ServiceWorkerContextCore, the methods of ServiceWorkerContextCoreObserver
@@ -416,6 +426,14 @@
   void BeginProcessingWarmingUp() { is_processing_warming_up_ = true; }
   void EndProcessingWarmingUp() { is_processing_warming_up_ = false; }
 
+  void AddVersionObserverForTest(TestVersionObserver* observer) {
+    test_version_observers_.AddObserver(observer);
+  }
+
+  void RemoveVersionObserverForTest(TestVersionObserver* observer) {
+    test_version_observers_.RemoveObserver(observer);
+  }
+
 #if !BUILDFLAG(IS_ANDROID)
   ServiceWorkerHidDelegateObserver* hid_delegate_observer();
 
@@ -568,6 +586,8 @@
   std::unique_ptr<ServiceWorkerUsbDelegateObserver> usb_delegate_observer_;
 #endif  // !BUILDFLAG(IS_ANDROID)
 
+  base::ObserverList<TestVersionObserver> test_version_observers_;
+
   base::WeakPtrFactory<ServiceWorkerContextCore> weak_factory_{this};
 };
 
diff --git a/content/browser/service_worker/service_worker_context_wrapper.h b/content/browser/service_worker/service_worker_context_wrapper.h
index 1ca97ec..e7e6f197 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.h
+++ b/content/browser/service_worker/service_worker_context_wrapper.h
@@ -414,6 +414,10 @@
   static void SetURLLoaderFactoryInterceptorForTesting(
       const URLLoaderFactoryInterceptor& interceptor);
 
+  ServiceWorkerContextCore* GetContextCoreForTest() {
+    return context_core_.get();
+  }
+
  private:
   friend class BackgroundSyncManagerTest;
   friend class base::DeleteHelper<ServiceWorkerContextWrapper>;
diff --git a/content/browser/webauth/webauth_browsertest.cc b/content/browser/webauth/webauth_browsertest.cc
index 3bbdc935..95dc3be 100644
--- a/content/browser/webauth/webauth_browsertest.cc
+++ b/content/browser/webauth/webauth_browsertest.cc
@@ -66,6 +66,7 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features_generated.h"
 #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
 
 #if BUILDFLAG(IS_WIN)
@@ -128,17 +129,16 @@
 
 constexpr char kAbortReasonMessage[] = "Error";
 
+constexpr char kCreatePermissionsPolicyMissingMessage[] =
+    "NotAllowedError: The 'publickey-credentials-create' feature is "
+    "not enabled in this document. Permissions Policy may be used to delegate "
+    "Web Authentication capabilities to cross-origin child frames.";
+
 constexpr char kGetPermissionsPolicyMissingMessage[] =
     "NotAllowedError: The 'publickey-credentials-get' feature is "
     "not enabled in this document. Permissions Policy may be used to delegate "
     "Web Authentication capabilities to cross-origin child frames.";
 
-constexpr char kCrossOriginAncestorMessage[] =
-    "NotAllowedError: The following credential operations can only "
-    "occur in a document which is same-origin with all of its ancestors: "
-    "storage/retrieval of 'PasswordCredential' and 'FederatedCredential', "
-    "storage of 'PublicKeyCredential'.";
-
 constexpr char kAllowCredentialsRangeErrorMessage[] =
     "RangeError: The `allowCredentials` attribute exceeds the maximum "
     "allowed size (64).";
@@ -958,6 +958,10 @@
       const WebAuthJavascriptClientBrowserTest&) = delete;
 
   ~WebAuthJavascriptClientBrowserTest() override = default;
+
+ private:
+  const base::test::ScopedFeatureList scoped_feature_list{
+      blink::features::kWebAuthAllowCreateInCrossOriginFrame};
 };
 
 constexpr device::ProtocolVersion kAllProtocols[] = {
@@ -1438,6 +1442,7 @@
       {false, true, true, ""},
       {true, false, false, ""},
       {true, false, true, "publickey-credentials-get"},
+      {true, true, false, "publickey-credentials-create"},
   };
 
   for (const auto& test : kTestCases) {
@@ -1472,7 +1477,7 @@
     if (test.create_should_work) {
       EXPECT_EQ(std::string(kOkMessage), result);
     } else {
-      EXPECT_EQ(kCrossOriginAncestorMessage, result);
+      EXPECT_EQ(kCreatePermissionsPolicyMissingMessage, result);
     }
 
     GetParameters get_params;
diff --git a/content/browser/webauth/webauth_request_security_checker.cc b/content/browser/webauth/webauth_request_security_checker.cc
index a7d62e48..be638c85 100644
--- a/content/browser/webauth/webauth_request_security_checker.cc
+++ b/content/browser/webauth/webauth_request_security_checker.cc
@@ -26,6 +26,7 @@
 #include "services/network/public/cpp/simple_url_loader.h"
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/blink/public/common/features_generated.h"
 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
 #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h"
 #include "url/gurl.h"
@@ -280,12 +281,22 @@
 
   // MakeCredential requests do not have an associated permissions policy, but
   // are prohibited in cross-origin subframes.
-  if (!*is_cross_origin && type == RequestType::kMakeCredential) {
+  if (!base::FeatureList::IsEnabled(
+          blink::features::kWebAuthAllowCreateInCrossOriginFrame) &&
+      !*is_cross_origin && type == RequestType::kMakeCredential) {
     return blink::mojom::AuthenticatorStatus::SUCCESS;
   }
 
   // Requests in cross-origin iframes are permitted if enabled via permissions
   // policy and for SPC requests.
+  if (base::FeatureList::IsEnabled(
+          blink::features::kWebAuthAllowCreateInCrossOriginFrame) &&
+      type == RequestType::kMakeCredential &&
+      render_frame_host_->IsFeatureEnabled(
+          blink::mojom::PermissionsPolicyFeature::
+              kPublicKeyCredentialsCreate)) {
+    return blink::mojom::AuthenticatorStatus::SUCCESS;
+  }
   if (type == RequestType::kGetAssertion &&
       render_frame_host_->IsFeatureEnabled(
           blink::mojom::PermissionsPolicyFeature::kPublicKeyCredentialsGet)) {
diff --git a/content/browser/webid/fedcm_metrics.cc b/content/browser/webid/fedcm_metrics.cc
index bc6bc8f..45a108c 100644
--- a/content/browser/webid/fedcm_metrics.cc
+++ b/content/browser/webid/fedcm_metrics.cc
@@ -7,6 +7,7 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/types/pass_key.h"
 #include "content/browser/webid/flags.h"
+#include "content/browser/webid/webid_utils.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_response_headers.h"
 #include "net/http/http_status_code.h"
@@ -15,6 +16,28 @@
 
 namespace content {
 
+namespace {
+
+FedCmRequesterFrameType ComputeRequesterFrameType(const RenderFrameHost& rfh,
+                                                  url::Origin requester,
+                                                  url::Origin embedder) {
+  // Since FedCM methods are not supported in FencedFrames, we can know whether
+  // this is a main frame by calling GetParent().
+  if (!rfh.GetParent()) {
+    return FedCmRequesterFrameType::kMainFrame;
+  }
+  std::string requester_str =
+      webid::FormatUrlWithDomain(requester.GetURL(), /*for_display=*/false);
+  std::string embedder_str = webid::FormatUrlWithDomain(embedder.GetURL(),
+                                                        /*for_display=*/false);
+  if (requester_str == embedder_str) {
+    return FedCmRequesterFrameType::kSameSiteIframe;
+  }
+  return FedCmRequesterFrameType::kCrossSiteIframe;
+}
+
+}  // namespace
+
 FedCmMetrics::FedCmMetrics(const GURL& provider,
                            ukm::SourceId page_source_id,
                            int session_id,
@@ -397,12 +420,18 @@
 
 void FedCmMetrics::RecordDisconnectMetrics(
     FedCmDisconnectStatus status,
-    std::optional<base::TimeDelta> duration) {
+    std::optional<base::TimeDelta> duration,
+    const RenderFrameHost& rfh,
+    url::Origin requester,
+    url::Origin embedder) {
   if (is_disabled_) {
     return;
   }
+  FedCmRequesterFrameType requester_frame_type =
+      ComputeRequesterFrameType(rfh, requester, embedder);
   auto RecordUkm = [&](auto& ukm_builder) {
     ukm_builder.SetStatus_Disconnect(static_cast<int>(status));
+    ukm_builder.SetDisconnect_FrameType(static_cast<int>(requester_frame_type));
     if (duration) {
       ukm_builder.SetTiming_Disconnect(
           ukm::GetSemanticBucketMinForDurationTiming(
@@ -418,6 +447,8 @@
   RecordUkm(fedcm_idp_builder);
 
   base::UmaHistogramEnumeration("Blink.FedCm.Status.Disconnect", status);
+  base::UmaHistogramEnumeration("Blink.FedCm.Disconnect.FrameType",
+                                requester_frame_type);
   if (duration) {
     base::UmaHistogramMediumTimes("Blink.FedCm.Timing.Disconnect", *duration);
   }
@@ -498,9 +529,12 @@
 }
 
 void RecordPreventSilentAccess(RenderFrameHost& rfh,
-                               PreventSilentAccessFrameType frame_type) {
+                               url::Origin requester,
+                               url::Origin embedder) {
+  FedCmRequesterFrameType requester_frame_type =
+      ComputeRequesterFrameType(rfh, requester, embedder);
   base::UmaHistogramEnumeration("Blink.FedCm.PreventSilentAccessFrameType",
-                                frame_type);
+                                requester_frame_type);
 
   // Ensure the lifecycle state as GetPageUkmSourceId doesn't support the
   // prerendering page. As FederatedAithRequest runs behind the
@@ -509,7 +543,8 @@
   CHECK(
       !rfh.IsInLifecycleState(RenderFrameHost::LifecycleState::kPrerendering));
   ukm::builders::Blink_FedCm ukm_builder(rfh.GetPageUkmSourceId());
-  ukm_builder.SetPreventSilentAccessFrameType(static_cast<int>(frame_type));
+  ukm_builder.SetPreventSilentAccessFrameType(
+      static_cast<int>(requester_frame_type));
   ukm_builder.Record(ukm::UkmRecorder::Get());
 }
 
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index 250cf5241..30ce305 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -100,8 +100,8 @@
   kMaxValue = kMismatchWithUnexpectedAccounts
 };
 
-// This enum describes the type of frame that invokes preventSilentAccess.
-enum class PreventSilentAccessFrameType {
+// This enum describes the type of frame that invokes a FedCM API.
+enum class FedCmRequesterFrameType {
   // Do not change the meaning or order of these values since they are being
   // recorded in metrics and in sync with the counterpart in enums.xml.
   kMainFrame,
@@ -268,7 +268,10 @@
   // disconnect fetch request was not sent, in which case we do not log the
   // metric.
   void RecordDisconnectMetrics(FedCmDisconnectStatus status,
-                               std::optional<base::TimeDelta> duration);
+                               std::optional<base::TimeDelta> duration,
+                               const RenderFrameHost& rfh,
+                               url::Origin requester,
+                               url::Origin embedder);
 
   // Records the type of error dialog shown.
   void RecordErrorDialogType(
@@ -310,7 +313,8 @@
 // existing FedCM call. Records metrics associated with a preventSilentAccess()
 // call from the given RenderFrameHost.
 void RecordPreventSilentAccess(RenderFrameHost& rfh,
-                               PreventSilentAccessFrameType frame_type);
+                               url::Origin requester,
+                               url::Origin embedder);
 
 // The following are UMA-only recordings, hence do not need to be in the
 // FedCmMetrics class.
diff --git a/content/browser/webid/federated_auth_disconnect_request.cc b/content/browser/webid/federated_auth_disconnect_request.cc
index bf0d9d5..8adcc69 100644
--- a/content/browser/webid/federated_auth_disconnect_request.cc
+++ b/content/browser/webid/federated_auth_disconnect_request.cc
@@ -249,7 +249,9 @@
       disconnect_request_sent_
           ? std::optional<base::TimeDelta>{base::TimeTicks::Now() - start_time_}
           : std::nullopt;
-  metrics_->RecordDisconnectMetrics(disconnect_status_for_metrics, duration);
+  metrics_->RecordDisconnectMetrics(disconnect_status_for_metrics, duration,
+                                    *render_frame_host_, origin_,
+                                    embedding_origin_);
 
   std::move(callback_).Run(status);
 }
diff --git a/content/browser/webid/federated_auth_disconnect_request_unittest.cc b/content/browser/webid/federated_auth_disconnect_request_unittest.cc
index b106cfb..c996c7c 100644
--- a/content/browser/webid/federated_auth_disconnect_request_unittest.cc
+++ b/content/browser/webid/federated_auth_disconnect_request_unittest.cc
@@ -191,34 +191,13 @@
       const url::Origin& relying_party_embedder,
       const url::Origin& identity_provider,
       const absl::optional<std::string>& account_id) override {
-    url::Origin rp_origin_with_data = url::Origin::Create(GURL(kRpUrl));
-    url::Origin idp_origin_with_data = url::Origin::Create(GURL(kProviderUrl));
-    bool has_granted_permission_per_profile =
-        relying_party_requester == rp_origin_with_data &&
-        relying_party_embedder == rp_origin_with_data &&
-        identity_provider == idp_origin_with_data;
-    return has_granted_permission_per_profile &&
-           (account_id
-                ? accounts_with_sharing_permission_.count(account_id.value())
-                : !accounts_with_sharing_permission_.empty());
+    return true;
   }
 
   absl::optional<bool> GetIdpSigninStatus(
       const url::Origin& idp_origin) override {
     return true;
   }
-
-  void SetConfig(const Config& config) {
-    accounts_with_sharing_permission_.clear();
-    for (const AccountConfig& account_config : config.accounts) {
-      if (account_config.was_granted_sharing_permission) {
-        accounts_with_sharing_permission_.insert(account_config.id);
-      }
-    }
-  }
-
- private:
-  std::set<std::string> accounts_with_sharing_permission_;
 };
 
 }  // namespace
@@ -248,15 +227,18 @@
   }
 
   void RunDisconnectTest(const Config& config,
-                         DisconnectStatus expected_disconnect_status) {
-    permission_delegate_->SetConfig(config);
-
+                         DisconnectStatus expected_disconnect_status,
+                         RenderFrameHost* rfh = nullptr) {
     auto network_manager =
         std::make_unique<TestIdpNetworkRequestManager>(config);
     network_manager_ = network_manager.get();
 
+    if (!rfh) {
+      rfh = static_cast<RenderFrameHost*>(main_test_rfh());
+    }
+
     metrics_ = std::make_unique<FedCmMetrics>(
-        GURL(config.config_url), main_test_rfh()->GetPageUkmSourceId(),
+        GURL(config.config_url), rfh->GetPageUkmSourceId(),
         /*session_id=*/1, /*is_disabled=*/false);
 
     blink::mojom::IdentityCredentialDisconnectOptionsPtr options =
@@ -268,7 +250,7 @@
 
     DisconnectRequestCallbackHelper callback_helper;
     request_ = FederatedAuthDisconnectRequest::Create(
-        std::move(network_manager), permission_delegate_.get(), main_rfh(),
+        std::move(network_manager), permission_delegate_.get(), rfh,
         metrics_.get(), std::move(options));
     request_->SetCallbackAndStart(callback_helper.callback(),
                                   api_permission_delegate_.get());
@@ -277,16 +259,20 @@
     EXPECT_EQ(expected_disconnect_status, callback_helper.status());
   }
 
-  void ExpectDisconnectMetricsAndConsoleError(DisconnectStatusForMetrics status,
-                                              bool should_record_duration) {
+  void ExpectDisconnectMetricsAndConsoleError(
+      DisconnectStatusForMetrics status,
+      FedCmRequesterFrameType requester_frame_type,
+      bool should_record_duration) {
     histogram_tester_.ExpectUniqueSample("Blink.FedCm.Status.Disconnect",
                                          status, 1);
+    histogram_tester_.ExpectUniqueSample("Blink.FedCm.Disconnect.FrameType",
+                                         requester_frame_type, 1);
     histogram_tester_.ExpectTotalCount("Blink.FedCm.Timing.Disconnect",
                                        should_record_duration ? 1 : 0);
-    ExpectDisconnectUKM(status, ukm::builders::Blink_FedCm::kEntryName,
-                        should_record_duration);
-    ExpectDisconnectUKM(status, ukm::builders::Blink_FedCmIdp::kEntryName,
-                        should_record_duration);
+    ExpectDisconnectUKM(ukm::builders::Blink_FedCm::kEntryName, status,
+                        requester_frame_type, should_record_duration);
+    ExpectDisconnectUKM(ukm::builders::Blink_FedCmIdp::kEntryName, status,
+                        requester_frame_type, should_record_duration);
 
     std::vector<std::string> messages =
         RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
@@ -298,8 +284,9 @@
     }
   }
 
-  void ExpectDisconnectUKM(DisconnectStatusForMetrics status,
-                           const char* entry_name,
+  void ExpectDisconnectUKM(const char* entry_name,
+                           DisconnectStatusForMetrics status,
+                           FedCmRequesterFrameType requester_frame_type,
                            bool should_record_duration) {
     auto entries = ukm_recorder()->GetEntriesByName(entry_name);
 
@@ -317,6 +304,13 @@
       const int64_t* metric =
           ukm_recorder()->GetEntryMetric(entry, "Status.Disconnect");
       if (!metric) {
+        EXPECT_FALSE(ukm_recorder()->GetEntryMetric(entry, "Timing.Disconnect"))
+            << "Timing.Disconnect must not be present when Status is not "
+               "present";
+        EXPECT_FALSE(
+            ukm_recorder()->GetEntryMetric(entry, "Disconnect.FrameType"))
+            << "Disconnect.FrameType must not be present when Status is not "
+               "present";
         continue;
       }
       EXPECT_FALSE(metric_found)
@@ -326,6 +320,12 @@
       EXPECT_EQ(static_cast<int>(status), *metric)
           << "Unexpected status recorded in " << entry_name;
 
+      metric = ukm_recorder()->GetEntryMetric(entry, "Disconnect.FrameType");
+      ASSERT_TRUE(metric)
+          << "Disconnect.FrameType must be present when Status is present";
+      EXPECT_EQ(static_cast<int>(requester_frame_type), *metric)
+          << "Unexpected frame type recorded in " << entry_name;
+
       if (should_record_duration) {
         EXPECT_TRUE(ukm_recorder()->GetEntryMetric(entry, "Timing.Disconnect"))
             << "Timing.Disconnect must be present in the same entry as "
@@ -363,6 +363,7 @@
   EXPECT_TRUE(network_manager_->has_fetched_disconnect_);
 
   ExpectDisconnectMetricsAndConsoleError(DisconnectStatusForMetrics::kSuccess,
+                                         FedCmRequesterFrameType::kMainFrame,
                                          /*should_record_duration=*/true);
 }
 
@@ -374,14 +375,12 @@
 
   ExpectDisconnectMetricsAndConsoleError(
       DisconnectStatusForMetrics::kIdpNotPotentiallyTrustworthy,
+      FedCmRequesterFrameType::kMainFrame,
       /*should_record_duration=*/false);
 }
 
 TEST_F(FederatedAuthDisconnectRequestTest,
        NoSharingPermissionButIdpHasThirdPartyCookiesAccessAndClaimsSignin) {
-  base::test::ScopedFeatureList list;
-  list.InitAndEnableFeature(features::kFedCmExemptIdpWithThirdPartyCookies);
-
   const char kAccountId[] = "account";
 
   Config config = kValidConfig;
@@ -400,7 +399,45 @@
   EXPECT_TRUE(network_manager_->has_fetched_disconnect_);
 
   ExpectDisconnectMetricsAndConsoleError(DisconnectStatusForMetrics::kSuccess,
+                                         FedCmRequesterFrameType::kMainFrame,
                                          /*should_record_duration=*/true);
 }
 
+TEST_F(FederatedAuthDisconnectRequestTest, SameSiteIframe) {
+  const char kSameSiteIframeUrl[] = "https://rp.example/iframe.html";
+  RenderFrameHost* same_site_iframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kSameSiteIframeUrl),
+          RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame())
+              ->AppendChild("same_site_iframe"));
+  Config config = kValidConfig;
+  RunDisconnectTest(config, DisconnectStatus::kSuccess, same_site_iframe);
+
+  ExpectDisconnectMetricsAndConsoleError(
+      DisconnectStatusForMetrics::kSuccess,
+      FedCmRequesterFrameType::kSameSiteIframe,
+      /*should_record_duration=*/true);
+}
+
+TEST_F(FederatedAuthDisconnectRequestTest, CrossSiteIframe) {
+  // Use FedCmExemptIdpWithThirdPartyCookies since sharing permission is not set
+  // for the cross-site RP.
+  base::test::ScopedFeatureList list;
+  list.InitAndEnableFeature(features::kFedCmExemptIdpWithThirdPartyCookies);
+
+  const char kCrossSiteIframeUrl[] = "https://otherrp.com";
+  RenderFrameHost* cross_site_iframe =
+      NavigationSimulator::NavigateAndCommitFromDocument(
+          GURL(kCrossSiteIframeUrl),
+          RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame())
+              ->AppendChild("cross_site_iframe"));
+  Config config = kValidConfig;
+  RunDisconnectTest(config, DisconnectStatus::kSuccess, cross_site_iframe);
+
+  ExpectDisconnectMetricsAndConsoleError(
+      DisconnectStatusForMetrics::kSuccess,
+      FedCmRequesterFrameType::kCrossSiteIframe,
+      /*should_record_duration=*/true);
+}
+
 }  // namespace content
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index febb6f6..443024f 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -2772,22 +2772,8 @@
     PreventSilentAccessCallback callback) {
   SetRequiresUserMediation(true);
   if (permission_delegate_->HasSharingPermission(GetEmbeddingOrigin())) {
-    PreventSilentAccessFrameType frame_type =
-        PreventSilentAccessFrameType::kMainFrame;
-    RenderFrameHost* main_rfh = render_frame_host().GetMainFrame();
-    if (main_rfh != &render_frame_host()) {
-      std::string site =
-          webid::FormatUrlWithDomain(origin().GetURL(), /*for_display=*/false);
-      std::string embedder =
-          webid::FormatUrlWithDomain(GetEmbeddingOrigin().GetURL(),
-                                     /*for_display=*/false);
-      if (site == embedder) {
-        frame_type = PreventSilentAccessFrameType::kSameSiteIframe;
-      } else {
-        frame_type = PreventSilentAccessFrameType::kCrossSiteIframe;
-      }
-    }
-    RecordPreventSilentAccess(render_frame_host(), frame_type);
+    RecordPreventSilentAccess(render_frame_host(), origin(),
+                              GetEmbeddingOrigin());
   }
 
   // Send acknowledge response back.
@@ -2822,7 +2808,8 @@
         webid::GetDisconnectConsoleErrorMessage(
             FedCmDisconnectStatus::kTooManyRequests));
     fedcm_metrics_->RecordDisconnectMetrics(
-        FedCmDisconnectStatus::kTooManyRequests, std::nullopt);
+        FedCmDisconnectStatus::kTooManyRequests, std::nullopt,
+        render_frame_host(), origin(), GetEmbeddingOrigin());
     std::move(callback).Run(DisconnectStatus::kErrorTooManyRequests);
     return;
   }
diff --git a/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc b/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
index b2f7af2..1f317263 100644
--- a/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
@@ -512,8 +512,7 @@
         ukm_recorder_->GetEntryMetric(entry, "PreventSilentAccessFrameType");
     if (metric) {
       metric_found = true;
-      EXPECT_EQ(*metric,
-                static_cast<int>(PreventSilentAccessFrameType::kMainFrame));
+      EXPECT_EQ(*metric, static_cast<int>(FedCmRequesterFrameType::kMainFrame));
     }
   }
   EXPECT_TRUE(metric_found);
@@ -558,8 +557,8 @@
         ukm_recorder_->GetEntryMetric(entry, "PreventSilentAccessFrameType");
     if (metric) {
       metric_found = true;
-      EXPECT_EQ(*metric, static_cast<int>(
-                             PreventSilentAccessFrameType::kSameSiteIframe));
+      EXPECT_EQ(*metric,
+                static_cast<int>(FedCmRequesterFrameType::kSameSiteIframe));
     }
   }
   EXPECT_TRUE(metric_found);
@@ -603,8 +602,8 @@
         ukm_recorder_->GetEntryMetric(entry, "PreventSilentAccessFrameType");
     if (metric) {
       metric_found = true;
-      EXPECT_EQ(*metric, static_cast<int>(
-                             PreventSilentAccessFrameType::kCrossSiteIframe));
+      EXPECT_EQ(*metric,
+                static_cast<int>(FedCmRequesterFrameType::kCrossSiteIframe));
     }
   }
   EXPECT_TRUE(metric_found);
diff --git a/content/child/DEPS b/content/child/DEPS
index f8b04514..de0106e 100644
--- a/content/child/DEPS
+++ b/content/child/DEPS
@@ -1,7 +1,7 @@
 include_rules = [
   # Allow inclusion of specific components that we depend on.
   # See comment in content/DEPS for which components are allowed.
-  "+components/ml/webnn/features.h",
+  "+components/ml/webnn/features.mojom-features.h",
   "+components/tracing",
   "+components/webcrypto",
   "+components/services/font/public",
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 5fc3d65..11cd977 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -19,7 +19,7 @@
 #include "build/chromeos_buildflags.h"
 #include "cc/base/features.h"
 #include "components/attribution_reporting/features.h"
-#include "components/ml/webnn/features.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #include "content/common/content_navigation_policy.h"
 #include "content/common/content_switches_internal.h"
 #include "content/common/features.h"
@@ -321,7 +321,7 @@
           {wf::EnableWebIdentityDigitalCredentials,
            raw_ref(features::kWebIdentityDigitalCredentials), kDefault},
           {wf::EnableMachineLearningNeuralNetwork,
-           raw_ref(webnn::features::kWebMachineLearningNeuralNetwork),
+           raw_ref(webnn::mojom::features::kWebMachineLearningNeuralNetwork),
            kDefault},
           {wf::EnableWebOTP, raw_ref(features::kWebOTP), kSetOnlyIfOverridden},
           {wf::EnableWebOTPAssertionFeaturePolicy,
diff --git a/content/public/test/service_worker_test_helpers.cc b/content/public/test/service_worker_test_helpers.cc
index a3eec4bf..5e21d82 100644
--- a/content/public/test/service_worker_test_helpers.cc
+++ b/content/public/test/service_worker_test_helpers.cc
@@ -3,25 +3,50 @@
 // found in the LICENSE file.
 
 #include "content/public/test/service_worker_test_helpers.h"
-#include "base/memory/raw_ptr.h"
 
 #include <memory>
 #include <utility>
 
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
+#include "base/memory/raw_ptr.h"
 #include "base/run_loop.h"
+#include "base/scoped_observation.h"
+#include "base/scoped_observation_traits.h"
 #include "base/time/default_tick_clock.h"
+#include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_core_observer.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/browser/service_worker/service_worker_version.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/service_worker_context.h"
 #include "third_party/blink/public/common/notifications/platform_notification_data.h"
 #include "third_party/blink/public/common/service_worker/embedded_worker_status.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-forward.h"
 #include "url/gurl.h"
 
+// Allow `ServiceWorkerVersionCreatedWatcher` to scoped observe the custom
+// observer add/remove methods on `ServiceWorkerContextCore`.
+namespace base {
+template <>
+struct ScopedObservationTraits<
+    content::ServiceWorkerContextCore,
+    content::ServiceWorkerContextCore::TestVersionObserver> {
+  static void AddObserver(
+      content::ServiceWorkerContextCore* source,
+      content::ServiceWorkerContextCore::TestVersionObserver* observer) {
+    source->AddVersionObserverForTest(observer);
+  }
+  static void RemoveObserver(
+      content::ServiceWorkerContextCore* source,
+      content::ServiceWorkerContextCore::TestVersionObserver* observer) {
+    source->RemoveVersionObserverForTest(observer);
+  }
+};
+}  // namespace base
+
 namespace content {
 
 namespace {
@@ -124,6 +149,99 @@
 
 }  // namespace
 
+// Implementation for `content::ServiceWorkerContextCore::TestVersionObserver`.
+// Observes new versions created and sets a state observer new versions that it
+// observes being created.
+class ServiceWorkerTestHelper::ServiceWorkerVersionCreatedWatcher
+    : public content::ServiceWorkerContextCore::TestVersionObserver {
+ public:
+  ServiceWorkerVersionCreatedWatcher(
+      content::ServiceWorkerContextCore* context_core,
+      ServiceWorkerTestHelper* parent)
+      : parent_(parent) {
+    scoped_observation_.Observe(context_core);
+  }
+
+ private:
+  // content::ServiceWorkerContextCore::TestObserver
+  void OnServiceWorkerVersionCreated(ServiceWorkerVersion* version) override {
+    // Create a `ServiceWorkerVersionStateManager` for this version.
+    parent_->OnServiceWorkerVersionCreated(version);
+  }
+
+  raw_ptr<ServiceWorkerTestHelper> const parent_;
+  base::ScopedObservation<
+      content::ServiceWorkerContextCore,
+      content::ServiceWorkerContextCore::TestVersionObserver>
+      scoped_observation_{this};
+};
+
+// Observes state changes of the `ServiceWorkerVersion` it is observing.
+class ServiceWorkerTestHelper::ServiceWorkerVersionStateManager
+    : public ServiceWorkerVersion::Observer {
+ public:
+  ServiceWorkerVersionStateManager(ServiceWorkerTestHelper* parent,
+                                   ServiceWorkerVersion* version)
+      : parent_(parent), sw_version_(version) {
+    scoped_observation_.Observe(sw_version_);
+  }
+
+ private:
+  // ServiceWorkerVersion::Observer
+  void OnRunningStateChanged(ServiceWorkerVersion* version) override {
+    parent_->OnDidRunningStatusChange(version->running_status(),
+                                      version->version_id());
+  }
+
+  raw_ptr<ServiceWorkerTestHelper> parent_;
+  raw_ptr<ServiceWorkerVersion> sw_version_;
+  base::ScopedObservation<ServiceWorkerVersion, ServiceWorkerVersion::Observer>
+      scoped_observation_{this};
+};
+
+ServiceWorkerTestHelper::ServiceWorkerTestHelper(ServiceWorkerContext* context,
+                                                 int64_t worker_version_id) {
+  DCHECK(context);
+  if (worker_version_id != blink::mojom::kInvalidServiceWorkerVersionId) {
+    RegisterStateObserver(context, worker_version_id);
+  } else {
+    RegisterVersionCreatedObserver(context);
+  }
+}
+
+ServiceWorkerTestHelper::~ServiceWorkerTestHelper() {
+  version_created_watcher_.reset();
+  for (auto& version_state_manager : version_state_managers_) {
+    version_state_manager.reset();
+  }
+  version_state_managers_.clear();
+}
+
+void ServiceWorkerTestHelper::RegisterVersionCreatedObserver(
+    ServiceWorkerContext* context) {
+  scoped_refptr<ServiceWorkerContextWrapper> context_wrapper(
+      static_cast<ServiceWorkerContextWrapper*>(context));
+  version_created_watcher_ =
+      std::make_unique<ServiceWorkerVersionCreatedWatcher>(
+          context_wrapper->GetContextCoreForTest(), this);
+}
+
+void ServiceWorkerTestHelper::RegisterStateObserver(
+    ServiceWorkerContext* context,
+    int64_t worker_version_id) {
+  scoped_refptr<ServiceWorkerContextWrapper> context_wrapper(
+      static_cast<ServiceWorkerContextWrapper*>(context));
+  version_state_managers_.push_back(
+      std::make_unique<ServiceWorkerVersionStateManager>(
+          this, context_wrapper->GetLiveVersion(worker_version_id)));
+}
+
+void ServiceWorkerTestHelper::OnServiceWorkerVersionCreated(
+    ServiceWorkerVersion* version) {
+  version_state_managers_.push_back(
+      std::make_unique<ServiceWorkerVersionStateManager>(this, version));
+}
+
 void StopServiceWorkerForScope(ServiceWorkerContext* context,
                                const GURL& scope,
                                base::OnceClosure completion_callback_ui) {
diff --git a/content/public/test/service_worker_test_helpers.h b/content/public/test/service_worker_test_helpers.h
index 90c3c2a..949b857 100644
--- a/content/public/test/service_worker_test_helpers.h
+++ b/content/public/test/service_worker_test_helpers.h
@@ -5,18 +5,70 @@
 #ifndef CONTENT_PUBLIC_TEST_SERVICE_WORKER_TEST_HELPERS_H_
 #define CONTENT_PUBLIC_TEST_SERVICE_WORKER_TEST_HELPERS_H_
 
+#include <cstdint>
+#include <memory>
+#include <vector>
+
 #include "base/functional/callback_forward.h"
 #include "base/test/simple_test_tick_clock.h"
+#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-forward.h"
 
 class GURL;
 
 namespace blink {
+enum class EmbeddedWorkerStatus;
 struct PlatformNotificationData;
 }  // namespace blink
 
 namespace content {
 
 class ServiceWorkerContext;
+class ServiceWorkerVersion;
+
+// Helper classes.
+
+// A class that assists with observing the state of `ServiceWorkerVersion`s
+// during tests.
+class ServiceWorkerTestHelper {
+ public:
+  // Starts observing every new `ServiceWorkerVersion`'s running status. If a
+  // valid `worker_verion_id` is provided it starts observing the
+  // `ServiceWorkerVersion`'s (for `worker_verion_id`'s) running status.
+  explicit ServiceWorkerTestHelper(
+      ServiceWorkerContext* context,
+      int64_t worker_verion_id = blink::mojom::kInvalidServiceWorkerVersionId);
+
+  ~ServiceWorkerTestHelper();
+  ServiceWorkerTestHelper(const ServiceWorkerTestHelper&) = delete;
+  ServiceWorkerTestHelper& operator=(const ServiceWorkerTestHelper&) = delete;
+
+  // Proxy for ServiceWorkerVersion::Observer::OnDidRunningStatusChange().
+  // Override this to observe changes to running status.
+  virtual void OnDidRunningStatusChange(
+      blink::EmbeddedWorkerStatus running_status,
+      int64_t version_id) {}
+
+ private:
+  class ServiceWorkerVersionCreatedWatcher;
+  class ServiceWorkerVersionStateManager;
+
+  void RegisterVersionCreatedObserver(ServiceWorkerContext* context);
+  void RegisterStateObserver(ServiceWorkerContext* context,
+                             int64_t worker_version_id);
+
+  // When a new version is noticed being created this starts observing it with a
+  // `ServiceWorkerVersionStateManager`.
+  void OnServiceWorkerVersionCreated(ServiceWorkerVersion* version);
+
+  // Watches for any new versions being created.
+  std::unique_ptr<ServiceWorkerVersionCreatedWatcher> version_created_watcher_;
+
+  // Contains observers for all versions encountered by this class.
+  std::vector<std::unique_ptr<ServiceWorkerVersionStateManager>>
+      version_state_managers_;
+};
+
+// Helper methods.
 
 // Stops the active service worker of the registration for the given |scope|,
 // and calls |complete_callback_ui| callback on UI thread when done.
diff --git a/content/test/data/accessibility/aria/input-text-aria-placeholder-expected-blink.txt b/content/test/data/accessibility/aria/input-text-aria-placeholder-expected-blink.txt
index 2a1881a..9892aa7 100644
--- a/content/test/data/accessibility/aria/input-text-aria-placeholder-expected-blink.txt
+++ b/content/test/data/accessibility/aria/input-text-aria-placeholder-expected-blink.txt
@@ -18,4 +18,4 @@
 ++++++++staticText name='aria-description5'
 ++++++++++inlineTextBox name='aria-description5'
 ++++++textField name='title6' placeholder='aria-placeholder6'
-++++++++genericContainer
\ No newline at end of file
+++++++++genericContainer
diff --git a/content/test/data/accessibility/html/input-button-expected-blink.txt b/content/test/data/accessibility/html/input-button-expected-blink.txt
index 40e5817d..23ed173 100644
--- a/content/test/data/accessibility/html/input-button-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-button-expected-blink.txt
@@ -6,4 +6,4 @@
 ++++++++++inlineTextBox name='Button'
 ++++++button description='Description' inputType='button' name='Name' descriptionFrom=buttonLabel
 ++++++++staticText name='Description'
-++++++++++inlineTextBox name='Description'
\ No newline at end of file
+++++++++++inlineTextBox name='Description'
diff --git a/content/test/data/accessibility/html/input-date-disabled-expected-blink.txt b/content/test/data/accessibility/html/input-date-disabled-expected-blink.txt
index d9a6f1f2..17aa165 100644
--- a/content/test/data/accessibility/html/input-date-disabled-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-date-disabled-expected-blink.txt
@@ -2,19 +2,20 @@
 ++genericContainer ignored
 ++++genericContainer
 ++++++date inputType='date' restriction=disabled
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Month' placeholder='mm' restriction=disabled valueForRange=0.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='mm'
-++++++++++++++++inlineTextBox name='mm'
-++++++++++++staticText name='/'
-++++++++++++++inlineTextBox name='/'
-++++++++++++spinButton name='Day' placeholder='dd' restriction=disabled valueForRange=0.00 minValueForRange=1.00 maxValueForRange=31.00
-++++++++++++++staticText name='dd'
-++++++++++++++++inlineTextBox name='dd'
-++++++++++++staticText name='/'
-++++++++++++++inlineTextBox name='/'
-++++++++++++spinButton name='Year' placeholder='yyyy' restriction=disabled valueForRange=0.00 minValueForRange=1.00 maxValueForRange=275760.00
-++++++++++++++staticText name='yyyy'
-++++++++++++++++inlineTextBox name='yyyy'
-++++++++popUpButton ignored invisible
+++++++++++++genericContainer
+++++++++++++++spinButton name='Month' placeholder='mm' restriction=disabled valueForRange=0.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='mm'
+++++++++++++++++++inlineTextBox name='mm'
+++++++++++++++staticText name='/'
+++++++++++++++++inlineTextBox name='/'
+++++++++++++++spinButton name='Day' placeholder='dd' restriction=disabled valueForRange=0.00 minValueForRange=1.00 maxValueForRange=31.00
+++++++++++++++++staticText name='dd'
+++++++++++++++++++inlineTextBox name='dd'
+++++++++++++++staticText name='/'
+++++++++++++++++inlineTextBox name='/'
+++++++++++++++spinButton name='Year' placeholder='yyyy' restriction=disabled valueForRange=0.00 minValueForRange=1.00 maxValueForRange=275760.00
+++++++++++++++++staticText name='yyyy'
+++++++++++++++++++inlineTextBox name='yyyy'
+++++++++++popUpButton ignored invisible
diff --git a/content/test/data/accessibility/html/input-date-expected-auralinux.txt b/content/test/data/accessibility/html/input-date-expected-auralinux.txt
index e3c97df..d516759 100644
--- a/content/test/data/accessibility/html/input-date-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/input-date-expected-auralinux.txt
@@ -17,4 +17,4 @@
 ++++++++++[spin button] name='Day When' current=1.000000 minimum=1.000000 maximum=31.000000
 ++++++++++[static] name='/'
 ++++++++++[spin button] name='Year When' current=2008.000000 minimum=1.000000 maximum=275760.000000
-++++++[push button] name='Show date picker' description='Show date picker' description-from:tooltip
\ No newline at end of file
+++++++[push button] name='Show date picker' description='Show date picker' description-from:tooltip
diff --git a/content/test/data/accessibility/html/input-date-expected-blink.txt b/content/test/data/accessibility/html/input-date-expected-blink.txt
index adb74cc..fed79ef 100644
--- a/content/test/data/accessibility/html/input-date-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-date-expected-blink.txt
@@ -2,36 +2,38 @@
 ++genericContainer ignored
 ++++genericContainer
 ++++++date inputType='date' value='2008-09-01'
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Month' placeholder='mm' value='09' valueForRange=9.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='09'
-++++++++++++++++inlineTextBox name='09'
-++++++++++++staticText name='/'
-++++++++++++++inlineTextBox name='/'
-++++++++++++spinButton name='Day' placeholder='dd' value='01' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=31.00
-++++++++++++++staticText name='01'
-++++++++++++++++inlineTextBox name='01'
-++++++++++++staticText name='/'
-++++++++++++++inlineTextBox name='/'
-++++++++++++spinButton name='Year' placeholder='yyyy' value='2008' valueForRange=2008.00 minValueForRange=1.00 maxValueForRange=275760.00
-++++++++++++++staticText name='2008'
-++++++++++++++++inlineTextBox name='2008'
-++++++++popUpButton description='Show date picker' name='Show date picker' descriptionFrom=title
+++++++++++++genericContainer
+++++++++++++++spinButton name='Month' placeholder='mm' value='09' valueForRange=9.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='09'
+++++++++++++++++++inlineTextBox name='09'
+++++++++++++++staticText name='/'
+++++++++++++++++inlineTextBox name='/'
+++++++++++++++spinButton name='Day' placeholder='dd' value='01' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=31.00
+++++++++++++++++staticText name='01'
+++++++++++++++++++inlineTextBox name='01'
+++++++++++++++staticText name='/'
+++++++++++++++++inlineTextBox name='/'
+++++++++++++++spinButton name='Year' placeholder='yyyy' value='2008' valueForRange=2008.00 minValueForRange=1.00 maxValueForRange=275760.00
+++++++++++++++++staticText name='2008'
+++++++++++++++++++inlineTextBox name='2008'
+++++++++++popUpButton description='Show date picker' name='Show date picker' descriptionFrom=title
 ++++++date inputType='date' name='When' value='2008-09-01'
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Month When' placeholder='mm' value='09' valueForRange=9.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='09'
-++++++++++++++++inlineTextBox name='09'
-++++++++++++staticText name='/'
-++++++++++++++inlineTextBox name='/'
-++++++++++++spinButton name='Day When' placeholder='dd' value='01' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=31.00
-++++++++++++++staticText name='01'
-++++++++++++++++inlineTextBox name='01'
-++++++++++++staticText name='/'
-++++++++++++++inlineTextBox name='/'
-++++++++++++spinButton name='Year When' placeholder='yyyy' value='2008' valueForRange=2008.00 minValueForRange=1.00 maxValueForRange=275760.00
-++++++++++++++staticText name='2008'
-++++++++++++++++inlineTextBox name='2008'
-++++++++popUpButton description='Show date picker' name='Show date picker' descriptionFrom=title
+++++++++++++genericContainer
+++++++++++++++spinButton name='Month When' placeholder='mm' value='09' valueForRange=9.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='09'
+++++++++++++++++++inlineTextBox name='09'
+++++++++++++++staticText name='/'
+++++++++++++++++inlineTextBox name='/'
+++++++++++++++spinButton name='Day When' placeholder='dd' value='01' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=31.00
+++++++++++++++++staticText name='01'
+++++++++++++++++++inlineTextBox name='01'
+++++++++++++++staticText name='/'
+++++++++++++++++inlineTextBox name='/'
+++++++++++++++spinButton name='Year When' placeholder='yyyy' value='2008' valueForRange=2008.00 minValueForRange=1.00 maxValueForRange=275760.00
+++++++++++++++++staticText name='2008'
+++++++++++++++++++inlineTextBox name='2008'
+++++++++++popUpButton description='Show date picker' name='Show date picker' descriptionFrom=title
diff --git a/content/test/data/accessibility/html/input-datetime-local-expected-auralinux.txt b/content/test/data/accessibility/html/input-datetime-local-expected-auralinux.txt
index 72e046d..32a0cf02 100644
--- a/content/test/data/accessibility/html/input-datetime-local-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/input-datetime-local-expected-auralinux.txt
@@ -14,4 +14,4 @@
 ++++++++++[spin button] name='Minutes' current=0.000000 minimum=0.000000 maximum=59.000000
 ++++++++++[static] name=' '
 ++++++++++[spin button] name='AM/PM' current=0.000000 minimum=1.000000 maximum=2.000000
-++++++[push button] name='Show local date and time picker' description='Show local date and time picker' description-from:tooltip haspopup:menu
\ No newline at end of file
+++++++[push button] name='Show local date and time picker' description='Show local date and time picker' description-from:tooltip haspopup:menu
diff --git a/content/test/data/accessibility/html/input-month-expected-auralinux.txt b/content/test/data/accessibility/html/input-month-expected-auralinux.txt
index 8afaf109..0b75a92 100644
--- a/content/test/data/accessibility/html/input-month-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/input-month-expected-auralinux.txt
@@ -6,4 +6,4 @@
 ++++++++++[spin button] name='Month' valuetext:0 current=0.000000 minimum=1.000000 maximum=12.000000
 ++++++++++[static] name=' '
 ++++++++++[spin button] name='Year' valuetext:0 current=0.000000 minimum=1.000000 maximum=275760.000000
-++++++[push button] name='Show month picker' description='Show month picker' description-from:tooltip haspopup:menu
\ No newline at end of file
+++++++[push button] name='Show month picker' description='Show month picker' description-from:tooltip haspopup:menu
diff --git a/content/test/data/accessibility/html/input-month-expected-blink.txt b/content/test/data/accessibility/html/input-month-expected-blink.txt
index 1585ef52..51403ae 100644
--- a/content/test/data/accessibility/html/input-month-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-month-expected-blink.txt
@@ -2,14 +2,15 @@
 ++genericContainer ignored
 ++++genericContainer
 ++++++dateTime inputType='month'
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Month' placeholder='---------' valueForRange=0.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='---------'
-++++++++++++++++inlineTextBox name='---------'
-++++++++++++staticText name=' '
-++++++++++++++inlineTextBox name=' '
-++++++++++++spinButton name='Year' placeholder='----' valueForRange=0.00 minValueForRange=1.00 maxValueForRange=275760.00
-++++++++++++++staticText name='----'
-++++++++++++++++inlineTextBox name='----'
-++++++++popUpButton description='Show month picker' name='Show month picker' descriptionFrom=title haspopup=menu
\ No newline at end of file
+++++++++++++genericContainer
+++++++++++++++spinButton name='Month' placeholder='---------' valueForRange=0.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='---------'
+++++++++++++++++++inlineTextBox name='---------'
+++++++++++++++staticText name=' '
+++++++++++++++++inlineTextBox name=' '
+++++++++++++++spinButton name='Year' placeholder='----' valueForRange=0.00 minValueForRange=1.00 maxValueForRange=275760.00
+++++++++++++++++staticText name='----'
+++++++++++++++++++inlineTextBox name='----'
+++++++++++popUpButton description='Show month picker' name='Show month picker' descriptionFrom=title haspopup=menu
diff --git a/content/test/data/accessibility/html/input-time-expected-auralinux.txt b/content/test/data/accessibility/html/input-time-expected-auralinux.txt
index 9400f0a2..30e1137 100644
--- a/content/test/data/accessibility/html/input-time-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/input-time-expected-auralinux.txt
@@ -17,4 +17,4 @@
 ++++++++++[spin button] name='Minutes Breakfast' xml-roles:spinbutton current=0.000000 minimum=0.000000 maximum=59.000000
 ++++++++++[static] name=' '
 ++++++++++[spin button] name='AM/PM Breakfast' xml-roles:spinbutton current=1.000000 minimum=1.000000 maximum=2.000000
-++++++[push button] name='Show time picker' description='Show time picker' description-from:tooltip xml-roles:button
\ No newline at end of file
+++++++[push button] name='Show time picker' description='Show time picker' description-from:tooltip xml-roles:button
diff --git a/content/test/data/accessibility/html/input-time-expected-blink.txt b/content/test/data/accessibility/html/input-time-expected-blink.txt
index 1d612de..8ecd2fc 100644
--- a/content/test/data/accessibility/html/input-time-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-time-expected-blink.txt
@@ -2,36 +2,38 @@
 ++genericContainer ignored
 ++++genericContainer
 ++++++inputTime inputType='time' value='00:00:00'
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Hours' placeholder='--' value='12' valueForRange=12.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='12'
-++++++++++++++++inlineTextBox name='12'
-++++++++++++staticText name=':'
-++++++++++++++inlineTextBox name=':'
-++++++++++++spinButton name='Minutes' placeholder='--' value='00' valueForRange=0.00 minValueForRange=0.00 maxValueForRange=59.00
-++++++++++++++staticText name='00'
-++++++++++++++++inlineTextBox name='00'
-++++++++++++staticText name=' '
-++++++++++++++inlineTextBox name=' '
-++++++++++++spinButton name='AM/PM' placeholder='--' value='AM' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=2.00
-++++++++++++++staticText name='AM'
-++++++++++++++++inlineTextBox name='AM'
-++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
+++++++++++++genericContainer
+++++++++++++++spinButton name='Hours' placeholder='--' value='12' valueForRange=12.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='12'
+++++++++++++++++++inlineTextBox name='12'
+++++++++++++++staticText name=':'
+++++++++++++++++inlineTextBox name=':'
+++++++++++++++spinButton name='Minutes' placeholder='--' value='00' valueForRange=0.00 minValueForRange=0.00 maxValueForRange=59.00
+++++++++++++++++staticText name='00'
+++++++++++++++++++inlineTextBox name='00'
+++++++++++++++staticText name=' '
+++++++++++++++++inlineTextBox name=' '
+++++++++++++++spinButton name='AM/PM' placeholder='--' value='AM' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=2.00
+++++++++++++++++staticText name='AM'
+++++++++++++++++++inlineTextBox name='AM'
+++++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
 ++++++inputTime inputType='time' name='Breakfast' value='00:00:00'
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Hours Breakfast' placeholder='--' value='12' valueForRange=12.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='12'
-++++++++++++++++inlineTextBox name='12'
-++++++++++++staticText name=':'
-++++++++++++++inlineTextBox name=':'
-++++++++++++spinButton name='Minutes Breakfast' placeholder='--' value='00' valueForRange=0.00 minValueForRange=0.00 maxValueForRange=59.00
-++++++++++++++staticText name='00'
-++++++++++++++++inlineTextBox name='00'
-++++++++++++staticText name=' '
-++++++++++++++inlineTextBox name=' '
-++++++++++++spinButton name='AM/PM Breakfast' placeholder='--' value='AM' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=2.00
-++++++++++++++staticText name='AM'
-++++++++++++++++inlineTextBox name='AM'
-++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
\ No newline at end of file
+++++++++++++genericContainer
+++++++++++++++spinButton name='Hours Breakfast' placeholder='--' value='12' valueForRange=12.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='12'
+++++++++++++++++++inlineTextBox name='12'
+++++++++++++++staticText name=':'
+++++++++++++++++inlineTextBox name=':'
+++++++++++++++spinButton name='Minutes Breakfast' placeholder='--' value='00' valueForRange=0.00 minValueForRange=0.00 maxValueForRange=59.00
+++++++++++++++++staticText name='00'
+++++++++++++++++++inlineTextBox name='00'
+++++++++++++++staticText name=' '
+++++++++++++++++inlineTextBox name=' '
+++++++++++++++spinButton name='AM/PM Breakfast' placeholder='--' value='AM' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=2.00
+++++++++++++++++staticText name='AM'
+++++++++++++++++++inlineTextBox name='AM'
+++++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
diff --git a/content/test/data/accessibility/html/input-time-with-popup-open-expected-blink.txt b/content/test/data/accessibility/html/input-time-with-popup-open-expected-blink.txt
index ab85855..c5184681 100644
--- a/content/test/data/accessibility/html/input-time-with-popup-open-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-time-with-popup-open-expected-blink.txt
@@ -2,32 +2,33 @@
 ++genericContainer ignored
 ++++genericContainer
 ++++++inputTime inputType='time' value='13:50:02.922' controlsIds=group
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Hours' placeholder='--' value='01' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='01'
-++++++++++++++++inlineTextBox name='01'
-++++++++++++staticText name=':'
-++++++++++++++inlineTextBox name=':'
-++++++++++++spinButton name='Minutes' placeholder='--' value='50' valueForRange=50.00 minValueForRange=0.00 maxValueForRange=59.00
-++++++++++++++staticText name='50'
-++++++++++++++++inlineTextBox name='50'
-++++++++++++staticText name=':'
-++++++++++++++inlineTextBox name=':'
-++++++++++++spinButton name='Seconds' placeholder='--' value='02' restriction=disabled valueForRange=2.00 minValueForRange=0.00 maxValueForRange=59.00
-++++++++++++++staticText name='02'
-++++++++++++++++inlineTextBox name='02'
-++++++++++++staticText name='.'
-++++++++++++++inlineTextBox name='.'
-++++++++++++spinButton name='Milliseconds' placeholder='---' value='922' restriction=disabled valueForRange=922.00 minValueForRange=0.00 maxValueForRange=999.00
-++++++++++++++staticText name='922'
-++++++++++++++++inlineTextBox name='922'
-++++++++++++staticText name=' '
-++++++++++++++inlineTextBox name=' '
-++++++++++++spinButton name='AM/PM' placeholder='--' value='PM' valueForRange=2.00 minValueForRange=1.00 maxValueForRange=2.00
-++++++++++++++staticText name='PM'
-++++++++++++++++inlineTextBox name='PM'
-++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
+++++++++++++genericContainer
+++++++++++++++spinButton name='Hours' placeholder='--' value='01' valueForRange=1.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='01'
+++++++++++++++++++inlineTextBox name='01'
+++++++++++++++staticText name=':'
+++++++++++++++++inlineTextBox name=':'
+++++++++++++++spinButton name='Minutes' placeholder='--' value='50' valueForRange=50.00 minValueForRange=0.00 maxValueForRange=59.00
+++++++++++++++++staticText name='50'
+++++++++++++++++++inlineTextBox name='50'
+++++++++++++++staticText name=':'
+++++++++++++++++inlineTextBox name=':'
+++++++++++++++spinButton name='Seconds' placeholder='--' value='02' restriction=disabled valueForRange=2.00 minValueForRange=0.00 maxValueForRange=59.00
+++++++++++++++++staticText name='02'
+++++++++++++++++++inlineTextBox name='02'
+++++++++++++++staticText name='.'
+++++++++++++++++inlineTextBox name='.'
+++++++++++++++spinButton name='Milliseconds' placeholder='---' value='922' restriction=disabled valueForRange=922.00 minValueForRange=0.00 maxValueForRange=999.00
+++++++++++++++++staticText name='922'
+++++++++++++++++++inlineTextBox name='922'
+++++++++++++++staticText name=' '
+++++++++++++++++inlineTextBox name=' '
+++++++++++++++spinButton name='AM/PM' placeholder='--' value='PM' valueForRange=2.00 minValueForRange=1.00 maxValueForRange=2.00
+++++++++++++++++staticText name='PM'
+++++++++++++++++++inlineTextBox name='PM'
+++++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
 ++++++++group
 ++++++++++genericContainer ignored
 ++++++++++++genericContainer ignored
@@ -470,4 +471,4 @@
 ++++++++++++++++++++++++++inlineTextBox name='PM'
 ++++++++++++++++++++++listBoxOption name='AM' selected=false
 ++++++++++++++++++++++++staticText name='AM'
-++++++++++++++++++++++++++inlineTextBox name='AM'
\ No newline at end of file
+++++++++++++++++++++++++++inlineTextBox name='AM'
diff --git a/content/test/data/accessibility/html/input-week-expected-auralinux.txt b/content/test/data/accessibility/html/input-week-expected-auralinux.txt
index 1ac1978..9f8f7f8 100644
--- a/content/test/data/accessibility/html/input-week-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/input-week-expected-auralinux.txt
@@ -7,4 +7,4 @@
 ++++++++++[spin button] name='Week' current=0.000000 minimum=1.000000 maximum=53.000000
 ++++++++++[static] name=', '
 ++++++++++[spin button] name='Year' current=0.000000 minimum=1.000000 maximum=275760.000000
-++++++[push button] name='Show week picker' description='Show week picker' description-from:tooltip haspopup:menu
\ No newline at end of file
+++++++[push button] name='Show week picker' description='Show week picker' description-from:tooltip haspopup:menu
diff --git a/content/test/data/accessibility/html/input-week-expected-blink.txt b/content/test/data/accessibility/html/input-week-expected-blink.txt
index f4b2ca6..70df92f 100644
--- a/content/test/data/accessibility/html/input-week-expected-blink.txt
+++ b/content/test/data/accessibility/html/input-week-expected-blink.txt
@@ -2,16 +2,17 @@
 ++genericContainer ignored
 ++++genericContainer
 ++++++dateTime inputType='week'
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++staticText name='Week '
-++++++++++++++inlineTextBox name='Week '
-++++++++++++spinButton name='Week' placeholder='--' valueForRange=0.00 minValueForRange=1.00 maxValueForRange=53.00
-++++++++++++++staticText name='--'
-++++++++++++++++inlineTextBox name='--'
-++++++++++++staticText name=', '
-++++++++++++++inlineTextBox name=', '
-++++++++++++spinButton name='Year' placeholder='----' valueForRange=0.00 minValueForRange=1.00 maxValueForRange=275760.00
-++++++++++++++staticText name='----'
-++++++++++++++++inlineTextBox name='----'
-++++++++popUpButton description='Show week picker' name='Show week picker' descriptionFrom=title haspopup=menu
\ No newline at end of file
+++++++++++++genericContainer
+++++++++++++++staticText name='Week '
+++++++++++++++++inlineTextBox name='Week '
+++++++++++++++spinButton name='Week' placeholder='--' valueForRange=0.00 minValueForRange=1.00 maxValueForRange=53.00
+++++++++++++++++staticText name='--'
+++++++++++++++++++inlineTextBox name='--'
+++++++++++++++staticText name=', '
+++++++++++++++++inlineTextBox name=', '
+++++++++++++++spinButton name='Year' placeholder='----' valueForRange=0.00 minValueForRange=1.00 maxValueForRange=275760.00
+++++++++++++++++staticText name='----'
+++++++++++++++++++inlineTextBox name='----'
+++++++++++popUpButton description='Show week picker' name='Show week picker' descriptionFrom=title haspopup=menu
diff --git a/content/test/data/accessibility/html/interactive-controls-with-labels-expected-auralinux.txt b/content/test/data/accessibility/html/interactive-controls-with-labels-expected-auralinux.txt
index e638b18..2c8bfe5 100644
--- a/content/test/data/accessibility/html/interactive-controls-with-labels-expected-auralinux.txt
+++ b/content/test/data/accessibility/html/interactive-controls-with-labels-expected-auralinux.txt
@@ -48,4 +48,4 @@
 ++++++++[spin button] name='AM/PM Test label' labelled-by=[label] current=2.000000 minimum=1.000000 maximum=2.000000
 ++++[push button] name='Show time picker' description='Show time picker' description-from:tooltip
 ++[push button] name='aria label'
-++[push button] name='Test label' labelled-by=[label]
\ No newline at end of file
+++[push button] name='Test label' labelled-by=[label]
diff --git a/content/test/data/accessibility/html/interactive-controls-with-labels-expected-blink.txt b/content/test/data/accessibility/html/interactive-controls-with-labels-expected-blink.txt
index d7854468..ce2135f 100644
--- a/content/test/data/accessibility/html/interactive-controls-with-labels-expected-blink.txt
+++ b/content/test/data/accessibility/html/interactive-controls-with-labels-expected-blink.txt
@@ -85,42 +85,44 @@
 ++++++++++staticText name='Option 8'
 ++++++++++++inlineTextBox name='Option 8'
 ++++++inputTime name='aria label' value='12:05'
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Hours aria label' placeholder='--' value='12' valueForRange=12.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='12'
-++++++++++++++++inlineTextBox name='12'
-++++++++++++staticText name=':'
-++++++++++++++inlineTextBox name=':'
-++++++++++++spinButton name='Minutes aria label' placeholder='--' value='05' valueForRange=5.00 minValueForRange=0.00 maxValueForRange=59.00
-++++++++++++++staticText name='05'
-++++++++++++++++inlineTextBox name='05'
-++++++++++++staticText name=' '
-++++++++++++++inlineTextBox name=' '
-++++++++++++spinButton name='AM/PM aria label' placeholder='--' value='PM' valueForRange=2.00 minValueForRange=1.00 maxValueForRange=2.00
-++++++++++++++staticText name='PM'
-++++++++++++++++inlineTextBox name='PM'
-++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
+++++++++++++genericContainer
+++++++++++++++spinButton name='Hours aria label' placeholder='--' value='12' valueForRange=12.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='12'
+++++++++++++++++++inlineTextBox name='12'
+++++++++++++++staticText name=':'
+++++++++++++++++inlineTextBox name=':'
+++++++++++++++spinButton name='Minutes aria label' placeholder='--' value='05' valueForRange=5.00 minValueForRange=0.00 maxValueForRange=59.00
+++++++++++++++++staticText name='05'
+++++++++++++++++++inlineTextBox name='05'
+++++++++++++++staticText name=' '
+++++++++++++++++inlineTextBox name=' '
+++++++++++++++spinButton name='AM/PM aria label' placeholder='--' value='PM' valueForRange=2.00 minValueForRange=1.00 maxValueForRange=2.00
+++++++++++++++++staticText name='PM'
+++++++++++++++++++inlineTextBox name='PM'
+++++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
 ++++++inputTime name='Test label' value='12:05'
-++++++++genericContainer
+++++++++genericContainer ignored
 ++++++++++genericContainer
-++++++++++++spinButton name='Hours Test label' placeholder='--' value='12' valueForRange=12.00 minValueForRange=1.00 maxValueForRange=12.00
-++++++++++++++staticText name='12'
-++++++++++++++++inlineTextBox name='12'
-++++++++++++staticText name=':'
-++++++++++++++inlineTextBox name=':'
-++++++++++++spinButton name='Minutes Test label' placeholder='--' value='05' valueForRange=5.00 minValueForRange=0.00 maxValueForRange=59.00
-++++++++++++++staticText name='05'
-++++++++++++++++inlineTextBox name='05'
-++++++++++++staticText name=' '
-++++++++++++++inlineTextBox name=' '
-++++++++++++spinButton name='AM/PM Test label' placeholder='--' value='PM' valueForRange=2.00 minValueForRange=1.00 maxValueForRange=2.00
-++++++++++++++staticText name='PM'
-++++++++++++++++inlineTextBox name='PM'
-++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
+++++++++++++genericContainer
+++++++++++++++spinButton name='Hours Test label' placeholder='--' value='12' valueForRange=12.00 minValueForRange=1.00 maxValueForRange=12.00
+++++++++++++++++staticText name='12'
+++++++++++++++++++inlineTextBox name='12'
+++++++++++++++staticText name=':'
+++++++++++++++++inlineTextBox name=':'
+++++++++++++++spinButton name='Minutes Test label' placeholder='--' value='05' valueForRange=5.00 minValueForRange=0.00 maxValueForRange=59.00
+++++++++++++++++staticText name='05'
+++++++++++++++++++inlineTextBox name='05'
+++++++++++++++staticText name=' '
+++++++++++++++++inlineTextBox name=' '
+++++++++++++++spinButton name='AM/PM Test label' placeholder='--' value='PM' valueForRange=2.00 minValueForRange=1.00 maxValueForRange=2.00
+++++++++++++++++staticText name='PM'
+++++++++++++++++++inlineTextBox name='PM'
+++++++++++popUpButton description='Show time picker' name='Show time picker' descriptionFrom=title
 ++++++colorWell name='aria label' value='#e4e4e4'
 ++++++++genericContainer ignored
 ++++++++++genericContainer ignored
 ++++++colorWell name='Test label' value='#e4e4e4'
 ++++++++genericContainer ignored
-++++++++++genericContainer ignored
\ No newline at end of file
+++++++++++genericContainer ignored
diff --git a/google_apis/gaia/oauth_multilogin_result.cc b/google_apis/gaia/oauth_multilogin_result.cc
index fae66be..afa655f 100644
--- a/google_apis/gaia/oauth_multilogin_result.cc
+++ b/google_apis/gaia/oauth_multilogin_result.cc
@@ -13,6 +13,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
+#include "net/cookies/cookie_constants.h"
 
 namespace {
 
@@ -112,7 +113,8 @@
     // Alternatly, if we were sure GAIA cookies wouldn't try to expire more
     // than 400 days in the future we wouldn't need this either.
     base::Time expiration = net::CanonicalCookie::ValidateAndAdjustExpiryDate(
-        now + base::Seconds(expiration_delta.value_or(0.0)), now);
+        now + base::Seconds(expiration_delta.value_or(0.0)), now,
+        net::CookieSourceScheme::kSecure);
     std::string cookie_domain = domain ? *domain : "";
     std::string cookie_host = host ? *host : "";
     if (cookie_domain.empty() && !cookie_host.empty() &&
diff --git a/gpu/command_buffer/service/dawn_context_provider.cc b/gpu/command_buffer/service/dawn_context_provider.cc
index 8ea02d5..a8aa8fa 100644
--- a/gpu/command_buffer/service/dawn_context_provider.cc
+++ b/gpu/command_buffer/service/dawn_context_provider.cc
@@ -331,6 +331,7 @@
 
   const wgpu::FeatureName kOptionalFeatures[] = {
       wgpu::FeatureName::BGRA8UnormStorage,
+      wgpu::FeatureName::BufferMapExtendedUsages,
       wgpu::FeatureName::DualSourceBlending,
       wgpu::FeatureName::FramebufferFetch,
       wgpu::FeatureName::MultiPlanarFormatExtendedUsages,
diff --git a/gpu/command_buffer/service/raster_decoder.cc b/gpu/command_buffer/service/raster_decoder.cc
index b588707..9cc96ce7 100644
--- a/gpu/command_buffer/service/raster_decoder.cc
+++ b/gpu/command_buffer/service/raster_decoder.cc
@@ -1253,7 +1253,8 @@
   // Vulkan currently doesn't support single-component cross-thread shared
   // images.
   caps.disable_one_component_textures =
-      disable_legacy_mailbox_ && features::IsUsingVulkan();
+      workarounds().avoid_one_component_egl_images ||
+      (disable_legacy_mailbox_ && features::IsUsingVulkan());
   caps.angle_rgbx_internal_format =
       feature_info()->feature_flags().angle_rgbx_internal_format;
   caps.chromium_gpu_fence = feature_info()->feature_flags().chromium_gpu_fence;
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 95b7e8a..f4f5d2c 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3311,6 +3311,18 @@
       "features": [
         "disable_svc_encoding"
       ]
+    },
+    {
+      "id": 425,
+      "cr_bugs": [1512211],
+      "description": "Single component images fail on some embedded Intel GPUs",
+      "os": {
+        "type": "android"
+      },
+      "gl_renderer": ".*HD Graphics 500.*",
+      "features": [
+        "avoid_one_component_egl_images"
+      ]
     }
   ]
 }
diff --git a/headless/lib/browser/command_line_handler.cc b/headless/lib/browser/command_line_handler.cc
index 8df5e6ca..40d5ebac 100644
--- a/headless/lib/browser/command_line_handler.cc
+++ b/headless/lib/browser/command_line_handler.cc
@@ -75,10 +75,10 @@
 
 void HandleProxyServer(base::CommandLine& command_line,
                        HeadlessBrowser::Options::Builder& builder) {
-  DCHECK(command_line.HasSwitch(::switches::kProxyServer));
+  DCHECK(command_line.HasSwitch(switches::kProxyServer));
 
   std::string proxy_server =
-      command_line.GetSwitchValueASCII(::switches::kProxyServer);
+      command_line.GetSwitchValueASCII(switches::kProxyServer);
   auto proxy_config = std::make_unique<net::ProxyConfig>();
   proxy_config->proxy_rules().ParseFromString(proxy_server);
   if (command_line.HasSwitch(switches::kProxyBypassList)) {
@@ -173,7 +173,7 @@
     builder.EnableDevToolsPipe();
   }
 
-  if (command_line.HasSwitch(::switches::kProxyServer)) {
+  if (command_line.HasSwitch(switches::kProxyServer)) {
     HandleProxyServer(command_line, builder);
   }
 
diff --git a/headless/lib/headless_content_main_delegate.cc b/headless/lib/headless_content_main_delegate.cc
index 8251c75..f36b434 100644
--- a/headless/lib/headless_content_main_delegate.cc
+++ b/headless/lib/headless_content_main_delegate.cc
@@ -339,7 +339,7 @@
       command_line.GetSwitchValueASCII(::switches::kProcessType);
   bool enable_crash_reporter =
       process_type.empty() &&
-      command_line.HasSwitch(::switches::kEnableCrashReporter) &&
+      command_line.HasSwitch(switches::kEnableCrashReporter) &&
       !command_line.HasSwitch(switches::kDisableCrashReporter);
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
diff --git a/headless/lib/switches.cc b/headless/lib/switches.cc
index 26fe18f..5e490a26 100644
--- a/headless/lib/switches.cc
+++ b/headless/lib/switches.cc
@@ -44,6 +44,9 @@
 // (experimental).
 const char kEnableBeginFrameControl[] = "enable-begin-frame-control";
 
+// Enable crash reporter for headless.
+const char kEnableCrashReporter[] = "enable-crash-reporter";
+
 // Enable hardware GPU support.
 // Headless uses swiftshader by default for consistency across headless
 // environments. This flag just turns forcing of swiftshader off and lets
@@ -84,6 +87,10 @@
 // "net/proxy_resolution/proxy_bypass_rules.h" for the format of these rules.
 const char kProxyBypassList[] = "proxy-bypass-list";
 
+// Uses a specified proxy server, overrides system settings. This switch only
+// affects HTTP and HTTPS requests.
+const char kProxyServer[] = "proxy-server";
+
 // Use the given address instead of the default loopback for accepting remote
 // debugging connections. Should be used together with --remote-debugging-port.
 // Note that the remote debugging protocol does not perform any authentication,
diff --git a/headless/public/switches.h b/headless/public/switches.h
index ca0bd70d..9e4c3d24 100644
--- a/headless/public/switches.h
+++ b/headless/public/switches.h
@@ -23,6 +23,7 @@
 HEADLESS_EXPORT extern const char kDisableLazyLoading[];
 HEADLESS_EXPORT extern const char kDiskCacheDir[];
 HEADLESS_EXPORT extern const char kEnableBeginFrameControl[];
+HEADLESS_EXPORT extern const char kEnableCrashReporter[];
 HEADLESS_EXPORT extern const char kEnableGPU[];
 HEADLESS_EXPORT extern const char kExplicitlyAllowedPorts[];
 HEADLESS_EXPORT extern const char kFontRenderHinting[];
@@ -30,6 +31,7 @@
 HEADLESS_EXPORT extern const char kNoSystemProxyConfigService[];
 HEADLESS_EXPORT extern const char kPasswordStore[];
 HEADLESS_EXPORT extern const char kProxyBypassList[];
+HEADLESS_EXPORT extern const char kProxyServer[];
 HEADLESS_EXPORT extern const char kRemoteDebuggingAddress[];
 HEADLESS_EXPORT extern const char kUserAgent[];
 HEADLESS_EXPORT extern const char kUserDataDir[];
diff --git a/headless/test/headless_browser_browsertest.cc b/headless/test/headless_browser_browsertest.cc
index 9da206a..3bf7ec6 100644
--- a/headless/test/headless_browser_browsertest.cc
+++ b/headless/test/headless_browser_browsertest.cc
@@ -7,7 +7,6 @@
 #include <tuple>
 #include <vector>
 
-#include "base/base_switches.h"
 #include "base/command_line.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
@@ -430,7 +429,7 @@
   void SetUpCommandLine(base::CommandLine* command_line) override {
     base::CreateNewTempDirectory(FILE_PATH_LITERAL("CrashReporterTest"),
                                  &crash_dumps_dir_);
-    command_line->AppendSwitch(::switches::kEnableCrashReporter);
+    command_line->AppendSwitch(switches::kEnableCrashReporter);
     command_line->AppendSwitchPath(switches::kCrashDumpsDir, crash_dumps_dir_);
     HeadlessBrowserTest::SetUpCommandLine(command_line);
   }
diff --git a/internal b/internal
index 2f4e35e..db44998 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 2f4e35e9c81b8c737b0a03b3d28d374831d66aff
+Subproject commit db449982a6f6dda8b8aa3d44acaf100805040349
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
index 022742c..197ca41 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
@@ -1665,6 +1665,11 @@
   CGPoint offset = _magicStackScrollView.contentOffset;
   offset.x = _magicStackPage * (moduleWidth + kMagicStackSpacing) -
              [self peekOffsetForMagicStackPage:_magicStackPage];
+  // Do not allow scrolling beyond the end of content, which also ensures that
+  // the "edit menu" page doesn't end up left-aligned after a rotation.
+  CGFloat maxOffset = MAX(0, _magicStackScrollView.contentSize.width -
+                                 _magicStackScrollView.bounds.size.width);
+  offset.x = MIN(offset.x, maxOffset);
   _magicStackScrollView.contentOffset = offset;
 }
 
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/BUILD.gn b/ios/chrome/browser/ui/ntp/feed_top_section/BUILD.gn
index 8772eab..5b8f053 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/BUILD.gn
@@ -30,6 +30,10 @@
     "//components/sync/base:features",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/discover_feed/model:constants",
+    "//ios/chrome/browser/push_notification/model:alert_presenter",
+    "//ios/chrome/browser/push_notification/model:push_notification_client",
+    "//ios/chrome/browser/push_notification/model:push_notification_service",
+    "//ios/chrome/browser/shared/coordinator/alert",
     "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/browser",
@@ -60,9 +64,11 @@
   ]
   deps = [
     ":constants",
+    "resources:notifications_promo_icon",
     "//build:branding_buildflags",
     "//ios/chrome/app/strings:ios_strings_grit",
     "//ios/chrome/browser/shared/public/features",
+    "//ios/chrome/browser/shared/ui/symbols:symbols",
     "//ios/chrome/browser/shared/ui/util:util",
     "//ios/chrome/common:button_config",
     "//ios/chrome/common/ui/colors",
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_coordinator.h b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_coordinator.h
index 2071630..1b765695 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_coordinator.h
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_coordinator.h
@@ -7,12 +7,14 @@
 
 #import <Foundation/Foundation.h>
 
+#import "ios/chrome/browser/push_notification/model/notifications_alert_presenter.h"
 #import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
 
 @protocol NewTabPageDelegate;
 
 // The top-level owner of the feed top section.
-@interface FeedTopSectionCoordinator : ChromeCoordinator
+@interface FeedTopSectionCoordinator
+    : ChromeCoordinator <NotificationsAlertPresenter>
 
 @property(nonatomic, readonly, strong) UIViewController* viewController;
 
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_coordinator.mm b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_coordinator.mm
index 580cb4d9..2659b2b 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_coordinator.mm
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_coordinator.mm
@@ -7,6 +7,10 @@
 #import "base/feature_list.h"
 #import "components/signin/public/base/signin_metrics.h"
 #import "components/sync/base/features.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_service.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_util.h"
+#import "ios/chrome/browser/shared/coordinator/alert/alert_coordinator.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/public/commands/application_commands.h"
@@ -23,6 +27,8 @@
 #import "ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_view_controller.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_delegate.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_utils.h"
+#import "ios/chrome/grit/ios_strings.h"
+#import "ui/base/l10n/l10n_util_mac.h"
 
 @interface FeedTopSectionCoordinator () <SigninPresenter>
 
@@ -38,6 +44,9 @@
 // Returns `YES` if the signin promo exists on the current NTP.
 @property(nonatomic, assign) BOOL isSignInPromoEnabled;
 
+// Alert Coordinator used to display the notifications system prompt.
+@property(nonatomic, strong) AlertCoordinator* alertCoordinator;
+
 @end
 
 @implementation FeedTopSectionCoordinator
@@ -65,7 +74,6 @@
            authService:authenticationService
            isIncognito:browserState->IsOffTheRecord()
            prefService:browserState->GetPrefs()];
-
   self.isSignInPromoEnabled =
       ShouldShowTopOfFeedSyncPromo() && authenticationService &&
       [self.NTPDelegate isSignInAllowed] &&
@@ -98,6 +106,7 @@
         self.signinPromoMediator;
   }
 
+  self.feedTopSectionMediator.notificationsPresenter = self;
   self.feedTopSectionMediator.NTPDelegate = self.NTPDelegate;
   self.feedTopSectionViewController.delegate = self.feedTopSectionMediator;
   self.feedTopSectionViewController.feedTopSectionMutator =
@@ -154,4 +163,52 @@
   self.feedTopSectionMediator.isSignInPromoEnabled = isSignInPromoEnabled;
 }
 
+#pragma mark - NotificationsAlertPresenter
+
+- (void)presentPushNotificationPermissionAlert {
+  NSString* settingURL = UIApplicationOpenSettingsURLString;
+  if (@available(iOS 15.4, *)) {
+    settingURL = UIApplicationOpenNotificationSettingsURLString;
+  }
+  NSString* alertTitle = l10n_util::GetNSString(
+      IDS_IOS_CONTENT_NOTIFICATIONS_SETTINGS_ALERT_TITLE);
+  NSString* alertMessage = l10n_util::GetNSString(
+      IDS_IOS_CONTENT_NOTIFICATIONS_SETTINGS_ALERT_MESSAGE);
+  NSString* cancelTitle = l10n_util::GetNSString(
+      IDS_IOS_CONTENT_NOTIFICATIONS_PERMISSION_REDIRECT_ALERT_CANCEL);
+  NSString* settingsTitle = l10n_util::GetNSString(
+      IDS_IOS_CONTENT_NOTIFICATIONS_PERMISSION_REDIRECT_ALERT_REDIRECT);
+
+  __weak FeedTopSectionCoordinator* weakSelf = self;
+  [_alertCoordinator stop];
+  _alertCoordinator =
+      [[AlertCoordinator alloc] initWithBaseViewController:_viewController
+                                                   browser:self.browser
+                                                     title:alertTitle
+                                                   message:alertMessage];
+  [_alertCoordinator addItemWithTitle:cancelTitle
+                               action:^{
+                                 [weakSelf dimissAlertCoordinator];
+                               }
+                                style:UIAlertActionStyleCancel];
+  [_alertCoordinator
+      addItemWithTitle:settingsTitle
+                action:^{
+                  [[UIApplication sharedApplication]
+                                openURL:[NSURL URLWithString:settingURL]
+                                options:{}
+                      completionHandler:nil];
+                  [weakSelf dimissAlertCoordinator];
+                }
+                 style:UIAlertActionStyleDefault];
+  [_alertCoordinator start];
+}
+
+#pragma mark - Private
+
+- (void)dimissAlertCoordinator {
+  [_alertCoordinator stop];
+  _alertCoordinator = nil;
+}
+
 @end
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.h b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.h
index e78afbfa..82e92968 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.h
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.h
@@ -11,6 +11,7 @@
 #import "ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_view_controller_delegate.h"
 
 class AuthenticationService;
+@protocol NotificationsAlertPresenter;
 @protocol FeedTopSectionConsumer;
 @protocol NewTabPageDelegate;
 class PrefService;
@@ -44,6 +45,10 @@
 // Returns `YES` if the signin promo exists on the current NTP.
 @property(nonatomic, assign) BOOL isSignInPromoEnabled;
 
+// Handler for displaying notification related alerts.
+@property(nonatomic, weak) id<NotificationsAlertPresenter>
+    notificationsPresenter;
+
 // Initializes the mediator.
 - (void)setUp;
 
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.mm b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.mm
index 1d3199e..c121ccd0 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.mm
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mediator.mm
@@ -10,10 +10,15 @@
 #import "components/signin/public/identity_manager/identity_manager.h"
 #import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
 #import "components/sync/base/features.h"
+#import "ios/chrome/browser/push_notification/model/notifications_alert_presenter.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_service.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_util.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/shared/public/features/system_flags.h"
+#import "ios/chrome/browser/signin/model/authentication_service.h"
 #import "ios/chrome/browser/signin/model/authentication_service_factory.h"
 #import "ios/chrome/browser/signin/model/identity_manager_factory.h"
 #import "ios/chrome/browser/ui/authentication/signin_promo_view_mediator.h"
@@ -138,6 +143,31 @@
   }
 }
 
+- (void)notificationsPromoViewMainButtonWasTapped {
+  // Show the Notifications promo alert.
+  PushNotificationService* service =
+      GetApplicationContext()->GetPushNotificationService();
+  id<SystemIdentity> identity = self.authenticationService->GetPrimaryIdentity(
+      signin::ConsentLevel::kSignin);
+  service->SetPreference(identity.gaiaID, PushNotificationClientId::kContent,
+                         true);
+  __weak FeedTopSectionMediator* weakSelf = self;
+  [PushNotificationUtil requestPushNotificationPermission:^(
+                            BOOL granted, BOOL promptShown, NSError* error) {
+    if (!error && !promptShown && !granted) {
+      // This callback can be executed on a background thread, make sure
+      // the UI is displayed on the main thread.
+      dispatch_async(dispatch_get_main_queue(), ^{
+        [weakSelf
+                .notificationsPresenter presentPushNotificationPermissionAlert];
+      });
+    }
+    if (error) {
+      [self updateFeedTopSectionWhenClosed];
+    }
+  }];
+}
+
 #pragma mark - Private
 
 // Handles closing the promo, and the NTP and Feed Top Section layout when the
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mutator.h b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mutator.h
index 628e621..c095186 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mutator.h
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mutator.h
@@ -7,7 +7,10 @@
 
 @protocol FeedTopSectionMutator
 
+// Handles a tap on the Close/secondary button of the notifications promo.
 - (void)notificationsPromoViewCloseButtonWasTapped;
+// Handles a tap on the main button of the notifications promo.
+- (void)notificationsPromoViewMainButtonWasTapped;
 
 @end
 
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/notifications_promo_view.mm b/ios/chrome/browser/ui/ntp/feed_top_section/notifications_promo_view.mm
index 1490a45..b38d5c53 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/notifications_promo_view.mm
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/notifications_promo_view.mm
@@ -7,6 +7,7 @@
 #import "base/apple/foundation_util.h"
 #import "base/ios/ios_util.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
+#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
 #import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
 #import "ios/chrome/browser/ui/ntp/feed_top_section/feed_top_section_mutator.h"
 #import "ios/chrome/browser/ui/ntp/feed_top_section/notifications_promo_view_constants.h"
@@ -20,25 +21,33 @@
 namespace {
 // Horizontal spacing between stackView and cell contentView.
 constexpr CGFloat kStackViewPadding = 16.0;
-constexpr CGFloat kStackViewTrailingMargin = 19.0;
-constexpr CGFloat kStackViewSubViewSpacing = 12.0;
+constexpr CGFloat kStackViewTopPadding = 24.0;
+constexpr CGFloat kStackViewTrailingMargin = 19.5;
+constexpr CGFloat kStackViewSubViewSpacing = 19.0;
 // Margins for the close button.
-constexpr CGFloat kCloseButtonTrailingMargin = -8.0;
-constexpr CGFloat kCloseButtonTopMargin = 8.0;
+constexpr CGFloat kCloseButtonTrailingMargin = -14.0;
+constexpr CGFloat kCloseButtonTopMargin = 14.0;
 // Size for the close button width and height.
-constexpr CGFloat kCloseButtonWidthHeight = 24;
+constexpr CGFloat kCloseButtonWidthHeight = 16;
 // Main button corner radius.
 constexpr CGFloat kButtonCornerRadius = 8.0;
 constexpr CGFloat kButtonTitleHorizontalContentInset = 42.0;
 constexpr CGFloat kButtonTitleVerticalContentInset = 9.0;
-// Buttons stack view spacing.
+// Spacing between the buttons.
 constexpr CGFloat kButtonStackViewSubViewSpacing = 8.0;
+// Buttons and text stack view spacing.
+constexpr CGFloat kTextAndButtonStackViewSubViewSpacing = 12.0;
 // Main button text size.
 constexpr CGFloat kButtonTextFontSize = 17.0;
+// Main text size.
+constexpr CGFloat kTextFontSize = 15.0;
+// Main image size.
+constexpr CGSize kMainImageSize = {56.0, 56.0};
 }  // namespace
 
 @interface NotificationsPromoView ()
 
+@property(nonatomic, strong) UIImageView* imageView;
 @property(nonatomic, strong) UILabel* textLabel;
 @property(nonatomic, strong) UIButton* primaryButton;
 @property(nonatomic, strong) UIButton* secondaryButton;
@@ -46,6 +55,7 @@
 
 // Stack View containing all internal views on the promo.
 @property(nonatomic, strong) UIStackView* buttonStackView;
+@property(nonatomic, strong) UIStackView* textAndButtonStackView;
 @property(nonatomic, strong) UIStackView* promoStackView;
 
 @end
@@ -70,34 +80,41 @@
     _buttonStackView =
         [self createStackViewFromViewArray:@[ _primaryButton, _secondaryButton ]
                                withSpacing:kButtonStackViewSubViewSpacing];
-    _promoStackView =
-        [self createStackViewFromViewArray:@[ _textLabel, _buttonStackView ]
-                               withSpacing:kStackViewSubViewSpacing];
+    _textAndButtonStackView = [self
+        createStackViewFromViewArray:@[ _textLabel, _buttonStackView ]
+                         withSpacing:kTextAndButtonStackViewSubViewSpacing];
+    _imageView = [self
+        createImageViewWithImage:[UIImage
+                                     imageNamed:@"notifications_promo_icon"]
+                          ofSize:kMainImageSize];
+    _promoStackView = [self
+        createStackViewFromViewArray:@[ _imageView, _textAndButtonStackView ]
+                         withSpacing:kStackViewSubViewSpacing];
     _closeButton = [self createCloseButton];
     [self addSubview:_promoStackView];
     [self addSubview:_closeButton];
-    [self activateConstraints];
+    [self activateSubViewConstraints];
   }
   return self;
 }
 
 #pragma mark - Private
 
-- (void)activateConstraints {
+- (void)activateSubViewConstraints {
   [NSLayoutConstraint activateConstraints:@[
     // Stack View Constraints.
     [self.promoStackView.leadingAnchor
         constraintEqualToAnchor:self.leadingAnchor
-                       constant:kStackViewPadding],
-    [self.promoStackView.topAnchor constraintEqualToAnchor:self.topAnchor
-                                                  constant:kStackViewPadding],
+                       constant:kStackViewTrailingMargin],
+    [self.promoStackView.topAnchor
+        constraintEqualToAnchor:self.topAnchor
+                       constant:kStackViewTopPadding],
     [self.promoStackView.bottomAnchor
         constraintEqualToAnchor:self.bottomAnchor
                        constant:-kStackViewPadding],
     [self.promoStackView.trailingAnchor
         constraintEqualToAnchor:self.trailingAnchor
                        constant:-kStackViewTrailingMargin],
-    // Close button size constraints.
     [self.closeButton.heightAnchor
         constraintEqualToConstant:kCloseButtonWidthHeight],
     [self.closeButton.widthAnchor
@@ -110,6 +127,18 @@
   ]];
 }
 
+- (UIImageView*)createImageViewWithImage:(UIImage*)image ofSize:(CGSize)size {
+  UIImageView* imageView = [[UIImageView alloc] init];
+  imageView.image = image;
+  [NSLayoutConstraint activateConstraints:@[
+    [imageView.heightAnchor constraintEqualToConstant:size.height],
+    [imageView.widthAnchor constraintEqualToConstant:size.width]
+  ]];
+  imageView.translatesAutoresizingMaskIntoConstraints = NO;
+  imageView.contentMode = UIViewContentModeScaleAspectFit;
+  return imageView;
+}
+
 - (UIStackView*)createStackViewFromViewArray:(NSArray*)views
                                  withSpacing:(CGFloat)spacing {
   UIStackView* stackView = [[UIStackView alloc] initWithArrangedSubviews:views];
@@ -182,7 +211,8 @@
   textLabel.numberOfLines = 0;
   textLabel.textAlignment = NSTextAlignmentCenter;
   textLabel.lineBreakMode = NSLineBreakByWordWrapping;
-  textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
+  textLabel.font = [[UIFont preferredFontForTextStyle:UIFontTextStyleBody]
+      fontWithSize:kTextFontSize];
   textLabel.textColor = [UIColor colorNamed:kGrey800Color];
   textLabel.text =
       l10n_util::GetNSString(IDS_IOS_CONTENT_NOTIFICATIONS_PROMO_TEXT);
@@ -197,8 +227,12 @@
   [button addTarget:self
                 action:@selector(onCloseButtonAction:)
       forControlEvents:UIControlEventTouchUpInside];
-  [button setImage:[UIImage imageNamed:@"signin_promo_close_gray"]
-          forState:UIControlStateNormal];
+  UIImageSymbolConfiguration* config = [UIImageSymbolConfiguration
+      configurationWithPointSize:kCloseButtonWidthHeight
+                          weight:UIImageSymbolWeightSemibold];
+  UIImage* closeButtonImage = DefaultSymbolWithConfiguration(@"xmark", config);
+  [button setImage:closeButtonImage forState:UIControlStateNormal];
+  button.tintColor = [UIColor colorNamed:kTextTertiaryColor];
   button.hidden = NO;
   button.pointerInteractionEnabled = YES;
   return button;
@@ -211,6 +245,7 @@
 
 // Handles the primary button action.
 - (void)onPrimaryButtonAction:(id)unused {
+  [self.mutator notificationsPromoViewMainButtonWasTapped];
 }
 
 // Handles the secondary button action.
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/resources/BUILD.gn b/ios/chrome/browser/ui/ntp/feed_top_section/resources/BUILD.gn
index 57028805..14dee97d 100644
--- a/ios/chrome/browser/ui/ntp/feed_top_section/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/resources/BUILD.gn
@@ -4,10 +4,11 @@
 
 import("//build/config/ios/asset_catalog.gni")
 
-imageset("ntp_feed_signin_promo_icon") {
+imageset("notifications_promo_icon") {
   sources = [
-    "ntp_feed_signin_promo_icon.imageset/Contents.json",
-    "ntp_feed_signin_promo_icon.imageset/ntp_feed_signin_promo_icon@2x.png",
-    "ntp_feed_signin_promo_icon.imageset/ntp_feed_signin_promo_icon@3x.png",
+    "notifications_promo_icon.imageset/Contents.json",
+    "notifications_promo_icon.imageset/notifications_promo_icon.png",
+    "notifications_promo_icon.imageset/notifications_promo_icon@2x.png",
+    "notifications_promo_icon.imageset/notifications_promo_icon@3x.png",
   ]
 }
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/Contents.json b/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/Contents.json
new file mode 100644
index 0000000..7b1202f
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+    "images": [
+        {
+            "idiom": "universal",
+            "scale": "1x",
+            "filename": "notifications_promo_icon.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "2x",
+            "filename": "notifications_promo_icon@2x.png"
+        },
+        {
+            "idiom": "universal",
+            "scale": "3x",
+            "filename": "notifications_promo_icon@3x.png"
+        }
+    ],
+    "info": {
+        "version": 1,
+        "author": "xcode"
+    }
+}
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon.png b/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon.png
new file mode 100644
index 0000000..b1857c2
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon.png
Binary files differ
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon@2x.png b/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon@2x.png
new file mode 100644
index 0000000..700b6131
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon@3x.png b/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon@3x.png
new file mode 100644
index 0000000..c53d0649
--- /dev/null
+++ b/ios/chrome/browser/ui/ntp/feed_top_section/resources/notifications_promo_icon.imageset/notifications_promo_icon@3x.png
Binary files differ
diff --git a/ios_internal b/ios_internal
index 5efd774..bd7110a 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 5efd774964ac708dfd7625fc954f8c70391c79e6
+Subproject commit bd7110a6a048f80efeaa1f23eeaabca81ff20271
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index 000088c..e60be61 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -292,6 +292,8 @@
     "seekable_buffer.h",
     "serial_runner.cc",
     "serial_runner.h",
+    "silence_detector.cc",
+    "silence_detector.h",
     "silent_sink_suspender.cc",
     "silent_sink_suspender.h",
     "simple_sync_token_client.cc",
@@ -614,6 +616,7 @@
     "renderer_factory_selector_unittest.cc",
     "seekable_buffer_unittest.cc",
     "serial_runner_unittest.cc",
+    "silence_detector_unittest.cc",
     "silent_sink_suspender_unittest.cc",
     "sinc_resampler_unittest.cc",
     "status_unittest.cc",
diff --git a/media/base/silence_detector.cc b/media/base/silence_detector.cc
new file mode 100644
index 0000000..56ee0ca
--- /dev/null
+++ b/media/base/silence_detector.cc
@@ -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.
+
+#include "media/base/silence_detector.h"
+
+#include "media/base/audio_bus.h"
+#include "media/base/audio_timestamp_helper.h"
+
+namespace media {
+
+SilenceDetector::SilenceDetector(int sample_rate, base::TimeDelta threshold)
+    : silent_samples_needed_(
+          AudioTimestampHelper::TimeToFrames(threshold, sample_rate)),
+      consecutive_silent_samples_(silent_samples_needed_) {
+  // Start out as silent, by forcing `consecutive_silent_samples_` to the
+  // minimum threshold.
+  // We prefer starting silent because the silent -> audible transition is
+  // instantaneous, whereas it takes `threshold` time to go from audible ->
+  // silent.
+}
+
+void SilenceDetector::ResetToSilence() {
+  // Must never be called when Scan() is being called, as
+  // `consecutive_silent_samples_` is not protected by lock.
+  NON_REENTRANT_SCOPE(exclusive_use_checker_);
+
+  // Reset `consecutive_silent_samples_` so we report silence.
+  // This value is read/written by Scan() on a different thread, but
+  // ResetToSilence() must not be called when Scan() could be.
+  consecutive_silent_samples_ = silent_samples_needed_;
+
+  base::AutoLock sample_lock(lock_);
+  is_silent_ = true;
+}
+
+void SilenceDetector::Scan(const AudioBus& buffer) {
+  // Must never be called when ResetToSilence() is being called, as
+  // `consecutive_silent_samples_` is not protected by lock.
+  NON_REENTRANT_SCOPE(exclusive_use_checker_);
+
+  // We can't detect silence in bitstream formats.
+  CHECK(!buffer.is_bitstream_format());
+
+  if (buffer.AreFramesZero()) {
+    consecutive_silent_samples_ += buffer.frames();
+  } else {
+    consecutive_silent_samples_ = 0;
+  }
+
+  base::AutoTryLock try_lock(lock_);
+  if (try_lock.is_acquired()) {
+    is_silent_ = consecutive_silent_samples_ >= silent_samples_needed_;
+  }
+}
+
+bool SilenceDetector::IsSilent() {
+  base::AutoLock auto_lock(lock_);
+  return is_silent_;
+}
+
+}  // namespace media
diff --git a/media/base/silence_detector.h b/media/base/silence_detector.h
new file mode 100644
index 0000000..c959752
--- /dev/null
+++ b/media/base/silence_detector.h
@@ -0,0 +1,65 @@
+// 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 MEDIA_BASE_SILENCE_DETECTOR_H_
+#define MEDIA_BASE_SILENCE_DETECTOR_H_
+
+#include "base/synchronization/lock.h"
+#include "base/time/time.h"
+#include "media/base/media_export.h"
+#include "media/base/reentrancy_checker.h"
+
+namespace media {
+
+class AudioBus;
+
+// Helper class which tracks how many zero'ed frames we have encountered, and
+// reports silence after a certain threshold has been met.
+// The class is mostly thread-safe: Scan() can be called from a real-time
+// thread while IsSilent() is read from a different thread.
+// ResetToSilence() should not be called when Scan() can be called from a
+// different thread.
+class MEDIA_EXPORT SilenceDetector {
+ public:
+  // `sample_rate` is the audio signal sample rate (Hz).
+  // `threshold` is how much zero'ed audio data must be scanned before silence
+  // is reported.
+  SilenceDetector(int sample_rate, base::TimeDelta threshold);
+
+  SilenceDetector(const SilenceDetector&) = delete;
+  SilenceDetector& operator=(const SilenceDetector&) = delete;
+
+  ~SilenceDetector() = default;
+
+  // Resets the internal state to silence.
+  // Must not be called when Scan() could be called from another thread.
+  void ResetToSilence();
+
+  // Scan audio data from `buffer` for silence.  It is safe to call this
+  // from a real-time priority thread.
+  void Scan(const AudioBus& buffer);
+
+  // Can be called from any thread.
+  bool IsSilent();
+
+ private:
+  // Use to make sure Scan() and ResetToSilence() are not called concurrently.
+  REENTRANCY_CHECKER(exclusive_use_checker_);
+
+  // Number of zero'ed samples needed before we report silence.
+  const int64_t silent_samples_needed_;
+
+  // Number of consecutive frames of silence scanned.
+  int64_t consecutive_silent_samples_;
+
+  base::Lock lock_;
+  // Starts silent, since the silent -> !silent transition is instantaneous,
+  // and we will be in the right state after the first Scan().
+  // The !silent -> silent transition takes `threshold` time.
+  bool is_silent_ GUARDED_BY(lock_) = true;
+};
+
+}  // namespace media
+
+#endif  // MEDIA_BASE_SILENCE_DETECTOR_H_
diff --git a/media/base/silence_detector_unittest.cc b/media/base/silence_detector_unittest.cc
new file mode 100644
index 0000000..dca4eddf
--- /dev/null
+++ b/media/base/silence_detector_unittest.cc
@@ -0,0 +1,142 @@
+// 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 "media/base/silence_detector.h"
+
+#include "base/time/time.h"
+#include "media/base/audio_bus.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace media {
+
+namespace {
+constexpr base::TimeDelta kSilenceThreshold = base::Milliseconds(500);
+constexpr base::TimeDelta kTypicalBufferLength = base::Milliseconds(20);
+constexpr int kSampleRate = 48000;
+constexpr int kChannels = 1;
+}  // namespace
+
+class SilenceDetectorTest : public ::testing::Test {
+ public:
+  SilenceDetectorTest() : silence_detector_(kSampleRate, kSilenceThreshold) {}
+
+  SilenceDetectorTest(const SilenceDetectorTest&) = delete;
+  SilenceDetectorTest& operator=(const SilenceDetectorTest&) = delete;
+
+  void FeedSilence(base::TimeDelta duration) {
+    auto silent_buffer = AudioBus::Create(
+        kChannels, AudioTimestampHelper::TimeToFrames(duration, kSampleRate));
+    silent_buffer->Zero();
+    silence_detector_.Scan(*silent_buffer);
+  }
+
+  void FeedData(base::TimeDelta duration) {
+    auto audio_bus = AudioBus::Create(
+        kChannels, AudioTimestampHelper::TimeToFrames(duration, kSampleRate));
+
+    // A single non-zero value should be enough for the entire buffer not be
+    // considered silence.
+    audio_bus->channel(0)[0] = 1.0;
+    audio_bus->ZeroFramesPartial(1, audio_bus->frames() - 1);
+    ASSERT_FALSE(audio_bus->AreFramesZero());
+
+    silence_detector_.Scan(*audio_bus);
+  }
+
+ protected:
+  SilenceDetector silence_detector_;
+};
+
+// Makes sure the silence detector starts silent.
+TEST_F(SilenceDetectorTest, StartsSilent) {
+  EXPECT_TRUE(silence_detector_.IsSilent());
+}
+
+// Makes sure the silence detector stays silent when it has only ever scanned
+// silence.
+TEST_F(SilenceDetectorTest, ScanningSilence_IsSilent) {
+  base::TimeDelta total_duration_scanned;
+  while (total_duration_scanned < 2 * kSilenceThreshold) {
+    FeedSilence(kTypicalBufferLength);
+    total_duration_scanned += kTypicalBufferLength;
+    EXPECT_TRUE(silence_detector_.IsSilent());
+  }
+}
+
+// Makes sure the silence detector isn't silent after a single audible buffer.
+TEST_F(SilenceDetectorTest, ScanningSingleAudibleBuffer_IsNotSilent) {
+  FeedData(kTypicalBufferLength);
+  EXPECT_FALSE(silence_detector_.IsSilent());
+}
+
+// Makes sure the silence detector isn't silent after scanning multiple audible
+// buffer.
+TEST_F(SilenceDetectorTest, ScanningMultipleAudibleBuffers_IsNotSilent) {
+  base::TimeDelta total_duration_scanned;
+  while (total_duration_scanned < 2 * kSilenceThreshold) {
+    FeedData(kTypicalBufferLength);
+    total_duration_scanned += kTypicalBufferLength;
+    EXPECT_FALSE(silence_detector_.IsSilent());
+  }
+}
+
+// Makes sure the silence detector can detect silence after scanning multiple
+// audible buffer.
+TEST_F(SilenceDetectorTest, ScanningAudibleBufferThenSilence_IsSilent) {
+  FeedData(kTypicalBufferLength);
+  EXPECT_FALSE(silence_detector_.IsSilent());
+
+  constexpr base::TimeDelta kSilenceIncrement = kTypicalBufferLength;
+
+  // Scan silence until the next buffer would push us across the silence
+  // threshold
+  base::TimeDelta total_silence_scanned;
+  while (total_silence_scanned + kSilenceIncrement < kSilenceThreshold) {
+    FeedSilence(kSilenceIncrement);
+    total_silence_scanned += kSilenceIncrement;
+    EXPECT_FALSE(silence_detector_.IsSilent());
+  }
+
+  // One more buffer of silence should push us across the silence threshold.
+  FeedSilence(kSilenceIncrement);
+  EXPECT_TRUE(silence_detector_.IsSilent());
+}
+
+// Makes sure that any audible data resets the silence threshold
+TEST_F(SilenceDetectorTest, ScanningAnyAudibleDataResetsSilence) {
+  // Start with audible data.
+  FeedData(kTypicalBufferLength);
+  EXPECT_FALSE(silence_detector_.IsSilent());
+
+  constexpr base::TimeDelta kSmallDuration = base::Milliseconds(1);
+
+  // Feed almost enough silence to trigger the detector.
+  FeedSilence(kSilenceThreshold - kSmallDuration);
+  EXPECT_FALSE(silence_detector_.IsSilent());
+
+  // Inject a bit of audible data.
+  FeedData(kSmallDuration);
+  EXPECT_FALSE(silence_detector_.IsSilent());
+
+  // A bit more silence shouldn't trigger the silence threshold.
+  FeedSilence(kSmallDuration);
+  EXPECT_FALSE(silence_detector_.IsSilent());
+
+  // We should detect silence after enough data is added.
+  FeedSilence(kSilenceThreshold - kSmallDuration);
+  EXPECT_TRUE(silence_detector_.IsSilent());
+}
+
+// Makes sure the silence detector is silent after a reset.
+TEST_F(SilenceDetectorTest, Reset_IsSilent) {
+  // Force audibility.
+  FeedData(kTypicalBufferLength);
+  EXPECT_FALSE(silence_detector_.IsSilent());
+
+  silence_detector_.ResetToSilence();
+  EXPECT_TRUE(silence_detector_.IsSilent());
+}
+
+}  // namespace media
diff --git a/net/base/features.cc b/net/base/features.cc
index 04f5d1b..cac95d61 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -447,6 +447,10 @@
              "EnableSchemeBoundCookies",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kTimeLimitedInsecureCookies,
+             "TimeLimitedInsecureCookies",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 // Enable third-party cookie blocking from the command line.
 BASE_FEATURE(kForceThirdPartyCookieBlocking,
              "ForceThirdPartyCookieBlockingEnabled",
diff --git a/net/base/features.h b/net/base/features.h
index 2040267a..32aef5e 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -450,6 +450,10 @@
 // enables domain cookie shadowing protection.
 NET_EXPORT BASE_DECLARE_FEATURE(kEnableSchemeBoundCookies);
 
+// Enables expiration duration limit (3 hours) for cookies on insecure websites.
+// This feature is a no-op unless kEnableSchemeBoundCookies is enabled.
+NET_EXPORT BASE_DECLARE_FEATURE(kTimeLimitedInsecureCookies);
+
 // Enables enabling third-party cookie blocking from the command line.
 NET_EXPORT BASE_DECLARE_FEATURE(kForceThirdPartyCookieBlocking);
 
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc
index e18e178..0c26bfaf 100644
--- a/net/cookies/canonical_cookie.cc
+++ b/net/cookies/canonical_cookie.cc
@@ -498,7 +498,8 @@
 // static
 base::Time CanonicalCookie::ValidateAndAdjustExpiryDate(
     const base::Time& expiry_date,
-    const base::Time& creation_date) {
+    const base::Time& creation_date,
+    net::CookieSourceScheme scheme) {
   if (expiry_date.is_null())
     return expiry_date;
   base::Time fixed_creation_date = creation_date;
@@ -513,7 +514,13 @@
     // * network_handler.cc::MakeCookieFromProtocolValues
     fixed_creation_date = base::Time::Now();
   }
-  base::Time maximum_expiry_date = fixed_creation_date + base::Days(400);
+  base::Time maximum_expiry_date;
+  if (!cookie_util::IsTimeLimitedInsecureCookiesEnabled() ||
+      scheme == net::CookieSourceScheme::kSecure) {
+    maximum_expiry_date = fixed_creation_date + base::Days(400);
+  } else {
+    maximum_expiry_date = fixed_creation_date + base::Hours(3);
+  }
   if (expiry_date > maximum_expiry_date) {
     return maximum_expiry_date;
   }
@@ -582,9 +589,6 @@
     cookie_server_time = server_time.value();
 
   DCHECK(!creation_time.is_null());
-  Time cookie_expires = CanonicalCookie::ParseExpiration(
-      parsed_cookie, creation_time, cookie_server_time);
-  cookie_expires = ValidateAndAdjustExpiryDate(cookie_expires, creation_time);
 
   CookiePrefix prefix_case_sensitive =
       GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/false);
@@ -676,6 +680,11 @@
   int source_port = CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(
       url, parsed_cookie.IsSecure());
 
+  Time cookie_expires = CanonicalCookie::ParseExpiration(
+      parsed_cookie, creation_time, cookie_server_time);
+  cookie_expires =
+      ValidateAndAdjustExpiryDate(cookie_expires, creation_time, source_scheme);
+
   auto cc = std::make_unique<CanonicalCookie>(
       base::PassKey<CanonicalCookie>(), parsed_cookie.Name(),
       parsed_cookie.Value(), cookie_domain, cookie_path, creation_time,
@@ -888,7 +897,8 @@
     status->AddExclusionReason(
         net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
   }
-  expiration_time = ValidateAndAdjustExpiryDate(expiration_time, creation_time);
+  expiration_time = ValidateAndAdjustExpiryDate(expiration_time, creation_time,
+                                                source_scheme);
 
   if (!status->IsInclude())
     return nullptr;
@@ -1444,8 +1454,10 @@
   // TODO(crbug.com/1264458): Eventually we should push this logic into
   // IsCanonicalForFromStorage, but for now we allow cookies already stored with
   // high expiration dates to be retrieved.
-  if (ValidateAndAdjustExpiryDate(expiry_date_, creation_date_) != expiry_date_)
+  if (ValidateAndAdjustExpiryDate(expiry_date_, creation_date_,
+                                  SourceScheme()) != expiry_date_) {
     return false;
+  }
 
   return IsCanonicalForFromStorage();
 }
diff --git a/net/cookies/canonical_cookie.h b/net/cookies/canonical_cookie.h
index 97e6e5b..599b17b 100644
--- a/net/cookies/canonical_cookie.h
+++ b/net/cookies/canonical_cookie.h
@@ -463,9 +463,9 @@
 
   // Per rfc6265bis the maximum expiry date is no further than 400 days in the
   // future.
-  static base::Time ValidateAndAdjustExpiryDate(
-      const base::Time& expiry_date,
-      const base::Time& creation_date);
+  static base::Time ValidateAndAdjustExpiryDate(const base::Time& expiry_date,
+                                                const base::Time& creation_date,
+                                                net::CookieSourceScheme scheme);
 
   // Cookie ordering methods.
 
diff --git a/net/cookies/canonical_cookie_unittest.cc b/net/cookies/canonical_cookie_unittest.cc
index 198bc6f..943c14fa 100644
--- a/net/cookies/canonical_cookie_unittest.cc
+++ b/net/cookies/canonical_cookie_unittest.cc
@@ -2739,6 +2739,47 @@
   }
 }
 
+TEST(CanonicalCookieTest, InsecureCookiesExpiryTimeLimit) {
+  GURL url("http://www.example.com/test/foo.html");
+  base::Time creation_time = base::Time::Now();
+  base::Time future_date = creation_time + base::Days(1);
+  {
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitWithFeatures(
+        {features::kEnableSchemeBoundCookies,
+         features::kTimeLimitedInsecureCookies},
+        {});
+    std::unique_ptr<CanonicalCookie> cookie = CanonicalCookie::Create(
+        url, "A=1; expires=" + HttpUtil::TimeFormatHTTP(future_date),
+        creation_time, /*server_time=*/absl::nullopt,
+        /*cookie_partition_key=*/absl::nullopt);
+    ASSERT_TRUE(cookie);
+    // With the feature enabled, expiration time should be limited to 3 hours
+    // after creation. Equality check needs to have a second margin due to
+    // microsecond rounding causing breakage.
+    EXPECT_TRUE(((creation_time + base::Hours(3)) - cookie->ExpiryDate())
+                    .FloorToMultiple(base::Seconds(1))
+                    .is_zero());
+  }
+  {
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitWithFeatures(
+        {features::kEnableSchemeBoundCookies},
+        {features::kTimeLimitedInsecureCookies});
+    std::unique_ptr<CanonicalCookie> cookie = CanonicalCookie::Create(
+        url, "A=1; expires=" + HttpUtil::TimeFormatHTTP(future_date),
+        creation_time, /*server_time=*/absl::nullopt,
+        /*cookie_partition_key=*/absl::nullopt);
+    ASSERT_TRUE(cookie);
+    // With the feature disabled, expiration time should not be limited.
+    // Equality check needs to have a second margin due to microsecond rounding
+    // causing breakage.
+    EXPECT_TRUE((future_date - cookie->ExpiryDate())
+                    .FloorToMultiple(base::Seconds(1))
+                    .is_zero());
+  }
+}
+
 TEST(CanonicalCookieTest, MultipleExclusionReasons) {
   GURL url("http://www.not-secure.com/foo");
   base::Time creation_time = base::Time::Now();
diff --git a/net/cookies/cookie_util.cc b/net/cookies/cookie_util.cc
index 89ecc0f6..db5099a 100644
--- a/net/cookies/cookie_util.cc
+++ b/net/cookies/cookie_util.cc
@@ -890,6 +890,11 @@
   return base::FeatureList::IsEnabled(features::kEnableSchemeBoundCookies);
 }
 
+bool IsTimeLimitedInsecureCookiesEnabled() {
+  return IsSchemeBoundCookiesEnabled() &&
+         base::FeatureList::IsEnabled(features::kTimeLimitedInsecureCookies);
+}
+
 bool IsSchemefulSameSiteEnabled() {
   return base::FeatureList::IsEnabled(features::kSchemefulSameSite);
 }
diff --git a/net/cookies/cookie_util.h b/net/cookies/cookie_util.h
index d707233..a58b3ab 100644
--- a/net/cookies/cookie_util.h
+++ b/net/cookies/cookie_util.h
@@ -284,6 +284,8 @@
 
 NET_EXPORT bool IsSchemeBoundCookiesEnabled();
 
+NET_EXPORT bool IsTimeLimitedInsecureCookiesEnabled();
+
 // Returns whether the respective feature is enabled.
 NET_EXPORT bool IsSchemefulSameSiteEnabled();
 
diff --git a/services/network/cookie_manager.cc b/services/network/cookie_manager.cc
index 9adc4261..ceae5b0 100644
--- a/services/network/cookie_manager.cc
+++ b/services/network/cookie_manager.cc
@@ -133,8 +133,8 @@
 
   auto cookie_ptr = std::make_unique<net::CanonicalCookie>(cookie);
   base::Time adjusted_expiry_date =
-      net::CanonicalCookie::ValidateAndAdjustExpiryDate(cookie.ExpiryDate(),
-                                                        cookie.CreationDate());
+      net::CanonicalCookie::ValidateAndAdjustExpiryDate(
+          cookie.ExpiryDate(), cookie.CreationDate(), cookie.SourceScheme());
   if (adjusted_expiry_date != cookie.ExpiryDate() || !cookie_partition_key) {
     cookie_ptr = net::CanonicalCookie::FromStorage(
         cookie.Name(), cookie.Value(), cookie.Domain(), cookie.Path(),
diff --git a/services/on_device_model/ml/chrome_ml.cc b/services/on_device_model/ml/chrome_ml.cc
index 7ffc7cb5..5e6378e 100644
--- a/services/on_device_model/ml/chrome_ml.cc
+++ b/services/on_device_model/ml/chrome_ml.cc
@@ -40,8 +40,8 @@
     &optimization_guide::features::kOptimizationGuideOnDeviceModel,
     "on_device_model_gpu_block_list",
     // These devices are nearly always crashing or have very low performance.
-    "8086:412|8086:a16|8086:41e|8086:416|8086:402|8086:166|8086:1616|1414:8c|"
-    "8086:*:*31.0.101.4824*|8086:*:*31.0.101.4676*"};
+    "8086:412|8086:a16|8086:41e|8086:416|8086:402|8086:166|8086:1616|8086:22b1|"
+    "8086:22b0|1414:8c|8086:*:*31.0.101.4824*|8086:*:*31.0.101.4676*"};
 
 // These values are persisted to logs. Entries should not be renumbered and
 // numeric values should never be reused.
diff --git a/services/viz/privileged/mojom/gl/BUILD.gn b/services/viz/privileged/mojom/gl/BUILD.gn
index 38ae102..ab1415ba 100644
--- a/services/viz/privileged/mojom/gl/BUILD.gn
+++ b/services/viz/privileged/mojom/gl/BUILD.gn
@@ -19,6 +19,7 @@
   deps = [ "//gpu/ipc/common:gmb_interface" ]
 
   public_deps = [
+    "//components/ml/webnn:features",
     "//gpu/ipc/common:interfaces",
     "//gpu/ipc/common:surface_handle",
     "//media/mojo/mojom",
diff --git a/services/viz/privileged/mojom/gl/gpu_service.mojom b/services/viz/privileged/mojom/gl/gpu_service.mojom
index ee2e1df..f42bb1e 100644
--- a/services/viz/privileged/mojom/gl/gpu_service.mojom
+++ b/services/viz/privileged/mojom/gl/gpu_service.mojom
@@ -42,6 +42,8 @@
 [EnableIf=is_win]
 import "ui/gfx/mojom/dxgi_info.mojom";
 [EnableIfNot=is_chromeos]
+import "components/ml/webnn/features.mojom";
+[EnableIfNot=is_chromeos]
 import "services/webnn/public/mojom/webnn_context_provider.mojom";
 
 interface GpuService {
@@ -139,9 +141,10 @@
 
   // Binds the pending receiver used to access the hardware accelerated OS
   // machine learning APIs.
-  [EnableIfNot=is_chromeos]
+  [EnableIfNot=is_chromeos,
+   RuntimeFeature=webnn.mojom.features.kWebMachineLearningNeuralNetwork]
   BindWebNNContextProvider(
-      pending_receiver<webnn.mojom.WebNNContextProvider> receiver, int32
+      pending_receiver<webnn.mojom.WebNNContextProvider>? receiver, int32
           client_id);
 
   [Sync, NoInterrupt]
diff --git a/services/webnn/dml/context_impl_test.cc b/services/webnn/dml/context_impl_test.cc
index e852e31..d315f5e 100644
--- a/services/webnn/dml/context_impl_test.cc
+++ b/services/webnn/dml/context_impl_test.cc
@@ -6,7 +6,9 @@
 
 #include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/webnn/dml/test_base.h"
 #include "services/webnn/public/mojom/webnn_context_provider.mojom.h"
@@ -21,6 +23,10 @@
 };
 
 TEST_F(WebNNContextDMLImplTest, CreateGraphImplTest) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      webnn::mojom::features::kWebMachineLearningNeuralNetwork);
+
   mojo::Remote<mojom::WebNNContextProvider> provider_remote;
   mojo::Remote<mojom::WebNNContext> webnn_context_remote;
   bool is_platform_supported = true;
diff --git a/services/webnn/public/mojom/BUILD.gn b/services/webnn/public/mojom/BUILD.gn
index 1ca54e2..fb43e20 100644
--- a/services/webnn/public/mojom/BUILD.gn
+++ b/services/webnn/public/mojom/BUILD.gn
@@ -10,6 +10,7 @@
     "webnn_graph.mojom",
   ]
   deps = [
+    "//components/ml/webnn:features",
     "//mojo/public/mojom/base",
     "//sandbox/policy/mojom",
   ]
diff --git a/services/webnn/public/mojom/webnn_context_provider.mojom b/services/webnn/public/mojom/webnn_context_provider.mojom
index 1b1c303..3cd94af 100644
--- a/services/webnn/public/mojom/webnn_context_provider.mojom
+++ b/services/webnn/public/mojom/webnn_context_provider.mojom
@@ -4,6 +4,7 @@
 
 module webnn.mojom;
 
+import "components/ml/webnn/features.mojom";
 import "services/webnn/public/mojom/webnn_graph.mojom";
 
 enum PowerPreference {
@@ -43,7 +44,7 @@
 // Represents the return value of `WebNNContext::CreateGraph()`. Let it be
 // `graph_remote` if the graph was successfully created and `error` otherwise.
 union CreateGraphResult {
-  pending_remote<WebNNGraph> graph_remote;
+  pending_remote<WebNNGraph>? graph_remote;
   Error error;
 };
 
@@ -51,13 +52,14 @@
 // Let it be `context_remote` if WebNNContext was successfully created and
 // `error` otherwise.
 union CreateContextResult {
-  pending_remote<WebNNContext> context_remote;
+  pending_remote<WebNNContext>? context_remote;
   Error error;
 };
 
 // Represents the `MLContext` object in the WebIDL definition that is a global
 // state of neural network compute workload and execution processes. This
 // interface runs in the GPU process and is called from the renderer process.
+[RuntimeFeature=webnn.mojom.features.kWebMachineLearningNeuralNetwork]
 interface WebNNContext {
   // Called by the renderer process to create `WebNNGraph` message pipe for
   // executing computational graph, the WebNN graph will be validated and
@@ -74,6 +76,7 @@
 // process to create `WebNNContext` interface. The renderer requests this
 // interface from the frame's BrowserInterfaceBroker, which requests it
 // from the GpuService via the GpuClient.
+[RuntimeFeature=webnn.mojom.features.kWebMachineLearningNeuralNetwork]
 interface WebNNContextProvider {
   // Called by the renderer process to create a message pipe for `MLContext`,
   // the `CreateContextResult` will be `Error` with error messages if the
diff --git a/services/webnn/webnn_context_provider_impl_unittest.cc b/services/webnn/webnn_context_provider_impl_unittest.cc
index ed877d47..03f81a3f 100644
--- a/services/webnn/webnn_context_provider_impl_unittest.cc
+++ b/services/webnn/webnn_context_provider_impl_unittest.cc
@@ -6,7 +6,9 @@
 
 #include "base/run_loop.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/webnn/public/mojom/webnn_context_provider.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -38,6 +40,10 @@
 #if !BUILDFLAG(IS_WIN)
 
 TEST_F(WebNNContextProviderImplTest, CreateWebNNContextTest) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      webnn::mojom::features::kWebMachineLearningNeuralNetwork);
+
   mojo::Remote<mojom::WebNNContextProvider> provider_remote;
 
   WebNNContextProviderImpl::Create(
diff --git a/services/webnn/webnn_graph_impl_unittest.cc b/services/webnn/webnn_graph_impl_unittest.cc
index 2f6db3f..7d930f4 100644
--- a/services/webnn/webnn_graph_impl_unittest.cc
+++ b/services/webnn/webnn_graph_impl_unittest.cc
@@ -11,7 +11,9 @@
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #include "components/ml/webnn/graph_validation_utils.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
@@ -4999,6 +5001,9 @@
 }
 
 TEST_F(WebNNGraphImplTest, ValidateInputsTest) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitFromCommandLine("WebMachineLearningNeuralNetwork", "");
+
   const std::vector<uint32_t> dimensions = {3, 5};
   // Build the graph with mojo type.
   GraphInfoBuilder builder;
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 1f967a0..f12ee29 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -12475,9 +12475,6 @@
                     },
                     "enable_features": [
                         "AdaptiveButtonInTopToolbarCustomizationV2"
-                    ],
-                    "disable_features": [
-                        "AdaptiveButtonInTopToolbar"
                     ]
                 }
             ]
diff --git a/third_party/blink/public/devtools_protocol/browser_protocol.pdl b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
index 447c534..a413656 100644
--- a/third_party/blink/public/devtools_protocol/browser_protocol.pdl
+++ b/third_party/blink/public/devtools_protocol/browser_protocol.pdl
@@ -7672,6 +7672,7 @@
       private-aggregation
       private-state-token-issuance
       private-state-token-redemption
+      publickey-credentials-create
       publickey-credentials-get
       run-ad-auction
       screen-wake-lock
diff --git a/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom b/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom
index 84e1eed0..efbc6c3 100644
--- a/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom
+++ b/third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom
@@ -240,6 +240,9 @@
   // SubApps API
   kSubApps = 116,
 
+  // Web Authentication (https://w3c.github.io/webauthn/#sctn-permissions-policy)
+  kPublicKeyCredentialsCreate = 117,
+
   // Don't change assigned numbers of any item, and don't reuse removed slots.
   // Add new features at the end of the enum.
   // Also, run update_permissions_policy_enum.py in
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index 662c12cf..4f590fe6 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -4094,6 +4094,7 @@
   kFedCmDomainHint = 4755,
   kLCPImageWasLazy = 4756,
   kEventTargetOnObservable = 4757,
+  kCredentialManagerCrossOriginPublicKeyCreateRequest = 4758,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/build/scripts/templates/element_attribute_name_lookup_trie.cc.tmpl b/third_party/blink/renderer/build/scripts/templates/element_attribute_name_lookup_trie.cc.tmpl
index 15a3275..c13287dd 100644
--- a/third_party/blink/renderer/build/scripts/templates/element_attribute_name_lookup_trie.cc.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/element_attribute_name_lookup_trie.cc.tmpl
@@ -14,10 +14,10 @@
 const QualifiedName& Lookup{{namespace}}AttributeName(const UChar* data, unsigned length) {
   DCHECK(data);
   DCHECK(length);
-  {% macro trie_return_statement(tag) -%}
+  {% macro trie_return_expression(tag) -%}
   {{namespace|lower}}_names::{{tag|symbol}}Attr
   {%- endmacro %}
-  {{ trie_length_switch(length_tries, trie_return_statement, false, false) | indent(4) }}
+  {{ trie_length_switch(length_tries, trie_return_expression, false, false) | indent(2) }}
   return g_null_name;
 }
 
@@ -25,10 +25,10 @@
 const QualifiedName& Lookup{{namespace}}AttributeName(const LChar* data, unsigned length) {
   DCHECK(data);
   DCHECK(length);
-  {% macro trie_return_statement(tag) -%}
+  {% macro trie_return_expression(tag) -%}
   {{namespace|lower}}_names::{{tag|symbol}}Attr
   {%- endmacro %}
-  {{ trie_length_switch(length_tries, trie_return_statement, false, true) | indent(4) }}
+  {{ trie_length_switch(length_tries, trie_return_expression, false, true) | indent(2) }}
   return g_null_name;
 }
 #endif
diff --git a/third_party/blink/renderer/build/scripts/templates/element_lookup_trie.cc.tmpl b/third_party/blink/renderer/build/scripts/templates/element_lookup_trie.cc.tmpl
index c0ccd5e1..2d3b4ba 100644
--- a/third_party/blink/renderer/build/scripts/templates/element_lookup_trie.cc.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/element_lookup_trie.cc.tmpl
@@ -16,10 +16,10 @@
     unsigned length) {
   DCHECK(data);
   DCHECK(length);
-  {% macro trie_return_statement(tag) -%}
-  {{namespace|lower}}_names::HTMLTag::{{tag|symbol}};
+  {% macro trie_return_expression(tag) -%}
+  {{namespace|lower}}_names::HTMLTag::{{tag|symbol}}
   {%- endmacro %}
-  {{ trie_length_switch(length_tries, trie_return_statement, false, false) | indent(4) }}
+  {{ trie_length_switch(length_tries, trie_return_expression, false, false) | indent(2) }}
   return {{namespace|lower}}_names::HTMLTag::kUnknown;
 }
 
@@ -28,10 +28,10 @@
     unsigned length) {
   DCHECK(data);
   DCHECK(length);
-  {% macro trie_return_statement(tag) -%}
-  {{namespace|lower}}_names::HTMLTag::{{tag|symbol}};
+  {% macro trie_return_expression(tag) -%}
+  {{namespace|lower}}_names::HTMLTag::{{tag|symbol}}
   {%- endmacro %}
-  {{ trie_length_switch(length_tries, trie_return_statement, false, true) | indent(4) }}
+  {{ trie_length_switch(length_tries, trie_return_expression, false, true) | indent(2) }}
   return {{namespace|lower}}_names::HTMLTag::kUnknown;
 }
 
diff --git a/third_party/blink/renderer/build/scripts/templates/macros.tmpl b/third_party/blink/renderer/build/scripts/templates/macros.tmpl
index 8d0f8ac..2fbe2144 100644
--- a/third_party/blink/renderer/build/scripts/templates/macros.tmpl
+++ b/third_party/blink/renderer/build/scripts/templates/macros.tmpl
@@ -33,11 +33,11 @@
 {% set string_prefix = "" if is_lchar else "u" %}
 {% set factor = "" if is_lchar else "2 * " %}
 {% if name|length > 1 and not lowercase_data %}
-if (memcmp(data + {{index}}, {{string_prefix}}"{{name}}", {{factor}}{{name|length}}) == 0)
+if (memcmp(data + {{index}}, {{string_prefix}}"{{name}}", {{factor}}{{name|length}}) == 0) {
   return {{ return_macro(value) }};
+}
 break;
 {% elif name|length %}
-
 if (
     {%- for c in name -%}
         {%- if lowercase_data -%}
@@ -46,8 +46,9 @@
     {{ " && " if not loop.first }}data[{{index + loop.index0}}] == '{{c}}'
         {%- endif -%}
         {%- endfor -%}
-    )
-    return {{ return_macro(value) }};
+    ) {
+  return {{ return_macro(value) }};
+}
 break;
 {% else %}
 return {{ return_macro(value) }};
@@ -66,7 +67,7 @@
     {% endif %}
     {% for char, value in trie.items()|sort %}
 case '{{char}}':
-    {{ trie_switch(value, index + 1, return_macro, lowercase_data, is_lchar) | indent(4) }}
+  {{ trie_switch(value, index + 1, return_macro, lowercase_data, is_lchar) | indent(2) -}}
     {% endfor %}
 }
 break;
@@ -78,7 +79,7 @@
 switch (length) {
 {% for length, trie in length_tries %}
 case {{ length }}:
-    {{ trie_switch(trie, 0, return_macro, lowercase_data, string_prefix) | indent(4) }}
+  {{ trie_switch(trie, 0, return_macro, lowercase_data, string_prefix) | indent(2) -}}
 {% endfor %}
 }
 {% endmacro %}
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index f086a093..c1974a6 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1510,6 +1510,7 @@
   sources += rebase_path(blink_core_tests_mobile_metrics, "", "mobile_metrics")
   sources += rebase_path(blink_core_tests_navigation_api, "", "navigation_api")
   sources += rebase_path(blink_core_tests_origin_trials, "", "origin_trials")
+  sources += rebase_path(blink_core_tests_paint, "", "paint")
   sources +=
       rebase_path(blink_core_tests_permissions_policy, "", "permissions_policy")
   sources +=
diff --git a/third_party/blink/renderer/core/animation/animatable.cc b/third_party/blink/renderer/core/animation/animatable.cc
index f65f30f..3f53e80 100644
--- a/third_party/blink/renderer/core/animation/animatable.cc
+++ b/third_party/blink/renderer/core/animation/animatable.cc
@@ -169,7 +169,7 @@
     element->GetDocument().UpdateStyleAndLayoutTreeForSubtree(
         element, DocumentUpdateReason::kWebAnimation);
   } else {
-    element->GetDocument().UpdateStyleAndLayoutTreeForNode(
+    element->GetDocument().UpdateStyleAndLayoutTreeForElement(
         element, DocumentUpdateReason::kWebAnimation);
   }
 
diff --git a/third_party/blink/renderer/core/animation/keyframe_effect.cc b/third_party/blink/renderer/core/animation/keyframe_effect.cc
index 1acaf2c..ada57fc 100644
--- a/third_party/blink/renderer/core/animation/keyframe_effect.cc
+++ b/third_party/blink/renderer/core/animation/keyframe_effect.cc
@@ -170,7 +170,7 @@
   if (!pseudo.empty()) {
     effect->target_pseudo_ = pseudo;
     if (element) {
-      element->GetDocument().UpdateStyleAndLayoutTreeForNode(
+      element->GetDocument().UpdateStyleAndLayoutTreeForElement(
           element, DocumentUpdateReason::kWebAnimation);
       PseudoId pseudo_id =
           CSSSelectorParser::ParsePseudoElement(pseudo, element);
@@ -267,7 +267,7 @@
   } else if (target_pseudo_.empty()) {
     new_target = target_element_;
   } else {
-    target_element_->GetDocument().UpdateStyleAndLayoutTreeForNode(
+    target_element_->GetDocument().UpdateStyleAndLayoutTreeForElement(
         target_element_, DocumentUpdateReason::kWebAnimation);
     PseudoId pseudoId =
         CSSSelectorParser::ParsePseudoElement(target_pseudo_, target_element_);
diff --git a/third_party/blink/renderer/core/css/css_computed_style_declaration.cc b/third_party/blink/renderer/core/css/css_computed_style_declaration.cc
index 58a9b7ba..2358c79 100644
--- a/third_party/blink/renderer/core/css/css_computed_style_declaration.cc
+++ b/third_party/blink/renderer/core/css/css_computed_style_declaration.cc
@@ -297,7 +297,7 @@
     return;
   }
 
-  document.UpdateStyleAndLayoutTreeForNode(
+  document.UpdateStyleAndLayoutTreeForElement(
       styled_element, DocumentUpdateReason::kComputedStyle);
 }
 
diff --git a/third_party/blink/renderer/core/css/cssom/computed_style_property_map.cc b/third_party/blink/renderer/core/css/cssom/computed_style_property_map.cc
index 3954c75..509ce9d 100644
--- a/third_party/blink/renderer/core/css/cssom/computed_style_property_map.cc
+++ b/third_party/blink/renderer/core/css/cssom/computed_style_property_map.cc
@@ -69,7 +69,7 @@
   // Update style before getting the value for the property
   // This could cause the element to be blown away. This code is copied from
   // CSSComputedStyleDeclaration::GetPropertyCSSValue.
-  element->GetDocument().UpdateStyleAndLayoutTreeForNode(
+  element->GetDocument().UpdateStyleAndLayoutTreeForElement(
       element, DocumentUpdateReason::kComputedStyle);
   element = StyledElement();
   if (!element) {
diff --git a/third_party/blink/renderer/core/css/layout_upgrade.cc b/third_party/blink/renderer/core/css/layout_upgrade.cc
index 36e29977..8f4e672 100644
--- a/third_party/blink/renderer/core/css/layout_upgrade.cc
+++ b/third_party/blink/renderer/core/css/layout_upgrade.cc
@@ -7,7 +7,7 @@
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/container_node.h"
 #include "third_party/blink/renderer/core/dom/document.h"
-#include "third_party/blink/renderer/core/dom/node.h"
+#include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
 
@@ -23,23 +23,23 @@
   StyleEngine& style_engine = document_.GetStyleEngine();
   return style_engine.HasViewportDependentMediaQueries() ||
          style_engine.HasViewportDependentPropertyRegistrations() ||
-         NodeLayoutUpgrade(owner_).ShouldUpgrade();
+         ElementLayoutUpgrade(owner_).ShouldUpgrade();
 }
 
-bool NodeLayoutUpgrade::ShouldUpgrade() {
-  if (!node_.isConnected()) {
+bool ElementLayoutUpgrade::ShouldUpgrade() {
+  if (!element_.isConnected()) {
     return false;
   }
   // We do not allow any elements to remain in a skipped state after a style
   // update, therefore we always upgrade whenever we've skipped something, even
   // if the current ancestors chain does not depend on layout.
-  StyleEngine& style_engine = node_.GetDocument().GetStyleEngine();
+  StyleEngine& style_engine = element_.GetDocument().GetStyleEngine();
   if (style_engine.SkippedContainerRecalc()) {
     return true;
   }
 
   bool maybe_affected_by_layout =
-      style_engine.StyleMaybeAffectedByLayout(node_);
+      style_engine.StyleMaybeAffectedByLayout(element_);
 
   if (!maybe_affected_by_layout) {
     return false;
@@ -47,8 +47,8 @@
 
   // For pseudo-style requests, we may have to update pseudo-elements of the
   // interleaving root itself. Hence we use inclusive ancestors here.
-  for (const Node* ancestor = &node_; ancestor;
-       ancestor = LayoutTreeBuilderTraversal::Parent(*ancestor)) {
+  for (const Element* ancestor = &element_; ancestor;
+       ancestor = LayoutTreeBuilderTraversal::ParentElement(*ancestor)) {
     if (ComputedStyle::IsInterleavingRoot(ancestor->GetComputedStyle())) {
       return true;
     }
diff --git a/third_party/blink/renderer/core/css/layout_upgrade.h b/third_party/blink/renderer/core/css/layout_upgrade.h
index 8ab2687..b2a5619 100644
--- a/third_party/blink/renderer/core/css/layout_upgrade.h
+++ b/third_party/blink/renderer/core/css/layout_upgrade.h
@@ -9,8 +9,8 @@
 
 namespace blink {
 
-class Node;
 class Document;
+class Element;
 class HTMLFrameOwnerElement;
 
 // Various APIs require that style information is updated immediately, e.g.
@@ -62,16 +62,16 @@
 
 // Upgrades whenever the (inclusive) ancestor chain contains an interleaving
 // root. Suitable when the style of a specific node will be accessed.
-class NodeLayoutUpgrade : public LayoutUpgrade {
+class ElementLayoutUpgrade : public LayoutUpgrade {
   STACK_ALLOCATED();
 
  public:
-  explicit NodeLayoutUpgrade(const Node& node) : node_(node) {}
+  explicit ElementLayoutUpgrade(const Element& element) : element_(element) {}
 
   bool ShouldUpgrade() override;
 
  private:
-  const Node& node_;
+  const Element& element_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 77d5462..60dc041 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -4559,9 +4559,10 @@
   // do a layout upgrade for elements that are 1) in display:none, and 2)
   // inside a container query container.
   //
-  // See implementation of `NodeLayoutUpgrade::ShouldUpgrade` for more
+  // See implementation of `ElementLayoutUpgrade::ShouldUpgrade` for more
   // information.
-  GetDocument().UpdateStyleAndLayoutTreeForNode(a, DocumentUpdateReason::kTest);
+  GetDocument().UpdateStyleAndLayoutTreeForElement(a,
+                                                   DocumentUpdateReason::kTest);
   EXPECT_FALSE(GetStyleEngine().StyleAffectedByLayout());
   EXPECT_FALSE(GetDocument().View()->NeedsLayout());
   EXPECT_FALSE(GetDocument().NeedsLayoutTreeUpdateForNode(*a));
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
index d2ef592..7b82d29 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
@@ -53,11 +53,11 @@
     friend void Document::UpdateStyleAndLayoutForRange(
         const Range* range,
         DocumentUpdateReason reason);
-    friend void Document::UpdateStyleAndLayoutTreeForNode(
-        const Node* node,
+    friend void Document::UpdateStyleAndLayoutTreeForElement(
+        const Element* node,
         DocumentUpdateReason reason);
     friend void Document::UpdateStyleAndLayoutTreeForSubtree(
-        const Node* node,
+        const Element* node,
         DocumentUpdateReason reason);
     friend void Document::EnsurePaintLocationDataValidForNode(
         const Node* node,
diff --git a/third_party/blink/renderer/core/dom/abort_controller.cc b/third_party/blink/renderer/core/dom/abort_controller.cc
index be79582..a3240dba 100644
--- a/third_party/blink/renderer/core/dom/abort_controller.cc
+++ b/third_party/blink/renderer/core/dom/abort_controller.cc
@@ -9,7 +9,6 @@
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
 #include "third_party/blink/renderer/platform/heap/visitor.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
 namespace blink {
 
@@ -24,9 +23,7 @@
 AbortController::~AbortController() = default;
 
 void AbortController::Dispose() {
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    signal_->DetachFromController();
-  }
+  signal_->DetachFromController();
 }
 
 void AbortController::abort(ScriptState* script_state) {
diff --git a/third_party/blink/renderer/core/dom/abort_signal.cc b/third_party/blink/renderer/core/dom/abort_signal.cc
index 572e2dc4..986bac3 100644
--- a/third_party/blink/renderer/core/dom/abort_signal.cc
+++ b/third_party/blink/renderer/core/dom/abort_signal.cc
@@ -27,7 +27,6 @@
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/heap/visitor.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
@@ -83,16 +82,12 @@
                          SignalType signal_type) {
   DCHECK_NE(signal_type, SignalType::kComposite);
   InitializeCommon(execution_context, signal_type);
-
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    composition_manager_ = MakeGarbageCollected<SourceSignalCompositionManager>(
-        *this, AbortSignalCompositionType::kAbort);
-  }
+  composition_manager_ = MakeGarbageCollected<SourceSignalCompositionManager>(
+      *this, AbortSignalCompositionType::kAbort);
 }
 
 AbortSignal::AbortSignal(ScriptState* script_state,
                          HeapVector<Member<AbortSignal>>& source_signals) {
-  DCHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
   InitializeCommon(ExecutionContext::From(script_state),
                    SignalType::kComposite);
 
@@ -116,8 +111,6 @@
 
 void AbortSignal::InitializeCommon(ExecutionContext* execution_context,
                                    SignalType signal_type) {
-  DCHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled() ||
-         signal_type != SignalType::kComposite);
   execution_context_ = execution_context;
   signal_type_ = signal_type;
 }
@@ -140,9 +133,7 @@
   AbortSignal* signal = MakeGarbageCollected<AbortSignal>(
       ExecutionContext::From(script_state), SignalType::kAborted);
   signal->abort_reason_ = reason;
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    signal->composition_manager_->Settle();
-  }
+  signal->composition_manager_->Settle();
   return signal;
 }
 
@@ -213,8 +204,7 @@
 }
 
 AbortSignal::AlgorithmHandle* AbortSignal::AddAlgorithm(Algorithm* algorithm) {
-  if (aborted() || (RuntimeEnabledFeatures::AbortSignalCompositionEnabled() &&
-                    composition_manager_->IsSettled())) {
+  if (aborted() || composition_manager_->IsSettled()) {
     return nullptr;
   }
   auto* handle = MakeGarbageCollected<AlgorithmHandle>(algorithm, this);
@@ -226,8 +216,7 @@
 
 AbortSignal::AlgorithmHandle* AbortSignal::AddAlgorithm(
     base::OnceClosure algorithm) {
-  if (aborted() || (RuntimeEnabledFeatures::AbortSignalCompositionEnabled() &&
-                    composition_manager_->IsSettled())) {
+  if (aborted() || composition_manager_->IsSettled()) {
     return nullptr;
   }
   auto* callback_algorithm =
@@ -241,8 +230,7 @@
 }
 
 void AbortSignal::RemoveAlgorithm(AlgorithmHandle* handle) {
-  if (aborted() || (RuntimeEnabledFeatures::AbortSignalCompositionEnabled() &&
-                    composition_manager_->IsSettled())) {
+  if (aborted() || composition_manager_->IsSettled()) {
     return;
   }
   abort_algorithms_.erase(handle);
@@ -270,28 +258,22 @@
     handle->GetAlgorithm()->Run();
   }
 
-  if (!RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    // This is cleared when the signal is settled when the feature is enabled.
-    abort_algorithms_.clear();
-  }
   dependent_signal_algorithms_.clear();
   DispatchEvent(*Event::Create(event_type_names::kAbort));
 
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    DCHECK(composition_manager_);
-    // Dependent signals are linked directly to source signals, so the abort
-    // only gets propagated for source signals.
-    if (auto* source_signal_manager = DynamicTo<SourceSignalCompositionManager>(
-            composition_manager_.Get())) {
-      // This is safe against reentrancy because new dependents are not added to
-      // already aborted signals.
-      for (auto& signal : source_signal_manager->GetDependentSignals()) {
-        CHECK(signal.Get());
-        signal->SignalAbort(script_state, abort_reason_, SignalAbortPassKey());
-      }
+  DCHECK(composition_manager_);
+  // Dependent signals are linked directly to source signals, so the abort
+  // only gets propagated for source signals.
+  if (auto* source_signal_manager = DynamicTo<SourceSignalCompositionManager>(
+          composition_manager_.Get())) {
+    // This is safe against reentrancy because new dependents are not added to
+    // already aborted signals.
+    for (auto& signal : source_signal_manager->GetDependentSignals()) {
+      CHECK(signal.Get());
+      signal->SignalAbort(script_state, abort_reason_, SignalAbortPassKey());
     }
-    composition_manager_->Settle();
   }
+  composition_manager_->Settle();
 }
 
 void AbortSignal::Follow(ScriptState* script_state, AbortSignal* parent) {
@@ -319,7 +301,6 @@
 
 AbortSignalCompositionManager* AbortSignal::GetCompositionManager(
     AbortSignalCompositionType type) {
-  DCHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
   if (type == AbortSignalCompositionType::kAbort) {
     return composition_manager_.Get();
   }
@@ -327,7 +308,6 @@
 }
 
 void AbortSignal::DetachFromController() {
-  DCHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
   if (aborted()) {
     return;
   }
@@ -335,7 +315,6 @@
 }
 
 void AbortSignal::OnSignalSettled(AbortSignalCompositionType type) {
-  CHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
   if (type == AbortSignalCompositionType::kAbort) {
     abort_algorithms_.clear();
   }
@@ -350,10 +329,7 @@
   if (aborted()) {
     return false;
   }
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    return !composition_manager_->IsSettled();
-  }
-  return true;
+  return !composition_manager_->IsSettled();
 }
 
 void AbortSignal::AddedEventListener(
@@ -378,8 +354,7 @@
 
 void AbortSignal::OnEventListenerAddedOrRemoved(const AtomicString& event_type,
                                                 AddRemoveType add_or_remove) {
-  if (!RuntimeEnabledFeatures::AbortSignalCompositionEnabled() ||
-      signal_type_ != SignalType::kComposite) {
+  if (signal_type_ != SignalType::kComposite) {
     return;
   }
   absl::optional<AbortSignalCompositionType> composition_type;
@@ -422,7 +397,6 @@
 
 bool AbortSignal::IsSettledFor(
     AbortSignalCompositionType composition_type) const {
-  CHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
   return composition_type == AbortSignalCompositionType::kAbort &&
          composition_manager_->IsSettled();
 }
diff --git a/third_party/blink/renderer/core/dom/abort_signal_test.cc b/third_party/blink/renderer/core/dom/abort_signal_test.cc
index ae949e5..a820c7a 100644
--- a/third_party/blink/renderer/core/dom/abort_signal_test.cc
+++ b/third_party/blink/renderer/core/dom/abort_signal_test.cc
@@ -6,9 +6,6 @@
 
 #include <tuple>
 
-#include "base/test/scoped_feature_list.h"
-#include "third_party/blink/public/common/features.h"
-#include "third_party/blink/public/platform/web_runtime_features.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/dom/abort_controller.h"
 #include "third_party/blink/renderer/core/dom/abort_signal_registry.h"
@@ -134,59 +131,16 @@
   EXPECT_EQ(count, 0);
 }
 
-namespace {
-
-enum class TestType { kCompositionEnabled, kNoFeatures };
-
-const char* TestTypeToString(TestType test_type) {
-  switch (test_type) {
-    case TestType::kCompositionEnabled:
-      return "CompositionEnabled";
-    case TestType::kNoFeatures:
-      return "NoFeatures";
-  }
-}
-
-}  // namespace
-
-class AbortSignalCompositionTest
-    : public AbortSignalTest,
-      public ::testing::WithParamInterface<TestType> {
- public:
-  AbortSignalCompositionTest() {
-    switch (GetParam()) {
-      case TestType::kCompositionEnabled:
-        feature_list_.InitWithFeatures({features::kAbortSignalComposition}, {});
-        break;
-      case TestType::kNoFeatures:
-        feature_list_.InitWithFeatures({}, {features::kAbortSignalComposition});
-        break;
-    }
-    WebRuntimeFeatures::UpdateStatusFromBaseFeatures();
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
-TEST_P(AbortSignalCompositionTest, CanAbort) {
+TEST_F(AbortSignalTest, CanAbort) {
   EXPECT_TRUE(signal_->CanAbort());
   SignalAbort();
   EXPECT_FALSE(signal_->CanAbort());
 }
 
-TEST_P(AbortSignalCompositionTest, CanAbortAfterGC) {
+TEST_F(AbortSignalTest, CanAbortAfterGC) {
   controller_.Clear();
   ThreadState::Current()->CollectAllGarbageForTesting();
-  EXPECT_EQ(signal_->CanAbort(), GetParam() == TestType::kNoFeatures);
+  EXPECT_FALSE(signal_->CanAbort());
 }
 
-INSTANTIATE_TEST_SUITE_P(,
-                         AbortSignalCompositionTest,
-                         testing::Values(TestType::kCompositionEnabled,
-                                         TestType::kNoFeatures),
-                         [](const testing::TestParamInfo<TestType>& info) {
-                           return TestTypeToString(info.param);
-                         });
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 84a63595..298325ec 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -2430,40 +2430,42 @@
   }
 }
 
-void Document::UpdateStyleAndLayoutTreeForNode(const Node* node,
-                                               DocumentUpdateReason) {
-  DCHECK(node);
-  if (!node->InActiveDocument()) {
+void Document::UpdateStyleAndLayoutTreeForElement(const Element* element,
+                                                  DocumentUpdateReason) {
+  DCHECK(element);
+  if (!element->InActiveDocument()) {
     // If |node| is not in the active document, we can't update its style or
     // layout tree.
-    DCHECK_EQ(node->ownerDocument(), this);
+    DCHECK_EQ(element->ownerDocument(), this);
     return;
   }
   DCHECK(!InStyleRecalc())
-      << "UpdateStyleAndLayoutTreeForNode called from within style recalc";
-  if (!NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(*node))
+      << "UpdateStyleAndLayoutTreeForElement called from within style recalc";
+  if (!NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(*element)) {
     return;
+  }
 
   DisplayLockUtilities::ScopedForcedUpdate scoped_update_forced(
-      node, DisplayLockContext::ForcedPhase::kStyleAndLayoutTree);
-  NodeLayoutUpgrade upgrade(*node);
+      element, DisplayLockContext::ForcedPhase::kStyleAndLayoutTree);
+  ElementLayoutUpgrade upgrade(*element);
   UpdateStyleAndLayoutTree(upgrade);
 }
 
-void Document::UpdateStyleAndLayoutTreeForSubtree(const Node* node,
+void Document::UpdateStyleAndLayoutTreeForSubtree(const Element* element,
                                                   DocumentUpdateReason) {
-  DCHECK(node);
-  if (!node->InActiveDocument()) {
-    DCHECK_EQ(node->ownerDocument(), this);
+  DCHECK(element);
+  if (!element->InActiveDocument()) {
+    DCHECK_EQ(element->ownerDocument(), this);
     return;
   }
   DCHECK(!InStyleRecalc())
       << "UpdateStyleAndLayoutTreeForSubtree called from within style recalc";
 
-  if (NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(*node) ||
-      node->ChildNeedsStyleRecalc() || node->ChildNeedsStyleInvalidation()) {
+  if (NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(*element) ||
+      element->ChildNeedsStyleRecalc() ||
+      element->ChildNeedsStyleInvalidation()) {
     DisplayLockUtilities::ScopedForcedUpdate scoped_update_forced(
-        node, DisplayLockContext::ForcedPhase::kStyleAndLayoutTree);
+        element, DisplayLockContext::ForcedPhase::kStyleAndLayoutTree);
     UpdateStyleAndLayoutTree();
   }
 }
@@ -5282,8 +5284,8 @@
     return true;
 
   if (new_focused_element) {
-    UpdateStyleAndLayoutTreeForNode(new_focused_element,
-                                    DocumentUpdateReason::kFocus);
+    UpdateStyleAndLayoutTreeForElement(new_focused_element,
+                                       DocumentUpdateReason::kFocus);
   }
 
   if (new_focused_element && new_focused_element->IsFocusable()) {
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 1bc79254..9d3c83b 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -722,8 +722,8 @@
   // does its own ancestor tree walk).
   void UpdateStyleAndLayoutTreeForThisDocument();
 
-  void UpdateStyleAndLayoutTreeForNode(const Node*, DocumentUpdateReason);
-  void UpdateStyleAndLayoutTreeForSubtree(const Node*, DocumentUpdateReason);
+  void UpdateStyleAndLayoutTreeForElement(const Element*, DocumentUpdateReason);
+  void UpdateStyleAndLayoutTreeForSubtree(const Element*, DocumentUpdateReason);
 
   void UpdateStyleAndLayout(DocumentUpdateReason);
   void LayoutUpdated();
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 9ef5d2db..47264f1c 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -626,8 +626,8 @@
             *this, DisplayLockActivationReason::kUserFocus)) {
       return false;
     }
-    GetDocument().UpdateStyleAndLayoutTreeForNode(this,
-                                                  DocumentUpdateReason::kFocus);
+    GetDocument().UpdateStyleAndLayoutTreeForElement(
+        this, DocumentUpdateReason::kFocus);
   }
 
   DCHECK(
@@ -2495,7 +2495,7 @@
       //
       // TODO(tkent): We should avoid updating style.  We'd like to check only
       // DOM-level focusability here.
-      GetDocument().UpdateStyleAndLayoutTreeForNode(
+      GetDocument().UpdateStyleAndLayoutTreeForElement(
           this, DocumentUpdateReason::kFocus);
       if (!IsFocusable() && !GetFocusableArea()) {
         blur();
@@ -5803,8 +5803,8 @@
       focus_options ? focus_options : params.options, params.focus_trigger);
 
   // Ensure we have clean style (including forced display locks).
-  GetDocument().UpdateStyleAndLayoutTreeForNode(this,
-                                                DocumentUpdateReason::kFocus);
+  GetDocument().UpdateStyleAndLayoutTreeForElement(
+      this, DocumentUpdateReason::kFocus);
 
   // https://html.spec.whatwg.org/C/#focusing-steps
   //
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal.cc b/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
index 6b89a5a..90b76cc9 100644
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
+++ b/third_party/blink/renderer/core/dom/flat_tree_traversal.cc
@@ -327,4 +327,14 @@
   return result;
 }
 
+const Element* FlatTreeTraversal::InclusiveParentElement(const Node& node) {
+  AssertPrecondition(node);
+  const Element* inclusive_parent = DynamicTo<Element>(node);
+  if (!inclusive_parent) {
+    inclusive_parent = ParentElement(node);
+  }
+  AssertPostcondition(inclusive_parent);
+  return inclusive_parent;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/flat_tree_traversal.h b/third_party/blink/renderer/core/dom/flat_tree_traversal.h
index 2d77f15..d5cb5af 100644
--- a/third_party/blink/renderer/core/dom/flat_tree_traversal.h
+++ b/third_party/blink/renderer/core/dom/flat_tree_traversal.h
@@ -74,6 +74,9 @@
 
   static ContainerNode* Parent(const Node&);
   static Element* ParentElement(const Node&);
+  // Return the passed in Node if it is an Element, otherwise return the
+  // ParentElement()
+  static const Element* InclusiveParentElement(const Node&);
 
   static Node* NextSibling(const Node&);
   static Node* PreviousSibling(const Node&);
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index e3c67d82..25f390336 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -1613,11 +1613,15 @@
 // questions about HTML in the core DOM class is obviously misplaced.
 bool Node::CanStartSelection() const {
   if (DisplayLockUtilities::LockedAncestorPreventingPaint(*this)) {
-    GetDocument().UpdateStyleAndLayoutTreeForNode(
-        this, DocumentUpdateReason::kSelection);
+    if (const Element* element =
+            FlatTreeTraversal::InclusiveParentElement(*this)) {
+      GetDocument().UpdateStyleAndLayoutTreeForElement(
+          element, DocumentUpdateReason::kSelection);
+    }
   }
-  if (IsEditable(*this))
+  if (IsEditable(*this)) {
     return true;
+  }
 
   if (GetLayoutObject()) {
     const ComputedStyle& style = GetLayoutObject()->StyleRef();
diff --git a/third_party/blink/renderer/core/editing/spellcheck/spell_checker.cc b/third_party/blink/renderer/core/editing/spellcheck/spell_checker.cc
index b681479..aa85ebff 100644
--- a/third_party/blink/renderer/core/editing/spellcheck/spell_checker.cc
+++ b/third_party/blink/renderer/core/editing/spellcheck/spell_checker.cc
@@ -392,7 +392,7 @@
   // TODO(editing-dev): The use of updateStyleAndLayoutIgnorePendingStylesheets
   // needs to be audited.  See http://crbug.com/590369 for more details.
   if (elements_type == ElementsType::kOnlyNonEditable) {
-    GetFrame().GetDocument()->UpdateStyleAndLayoutTreeForNode(
+    GetFrame().GetDocument()->UpdateStyleAndLayoutTreeForElement(
         &element, DocumentUpdateReason::kSpellCheck);
   }
 
diff --git a/third_party/blink/renderer/core/exported/web_node.cc b/third_party/blink/renderer/core/exported/web_node.cc
index 55e3e04..e6c58cb 100644
--- a/third_party/blink/renderer/core/exported/web_node.cc
+++ b/third_party/blink/renderer/core/exported/web_node.cc
@@ -145,8 +145,8 @@
     return false;
   if (!private_->GetDocument().HaveRenderBlockingResourcesLoaded())
     return false;
-  private_->GetDocument().UpdateStyleAndLayoutTreeForNode(
-      private_.Get(), DocumentUpdateReason::kFocus);
+  private_->GetDocument().UpdateStyleAndLayoutTreeForElement(
+      element, DocumentUpdateReason::kFocus);
   return element->IsFocusable();
 }
 
diff --git a/third_party/blink/renderer/core/frame/layout_subtree_root_list.cc b/third_party/blink/renderer/core/frame/layout_subtree_root_list.cc
index 3c41213..4103201 100644
--- a/third_party/blink/renderer/core/frame/layout_subtree_root_list.cc
+++ b/third_party/blink/renderer/core/frame/layout_subtree_root_list.cc
@@ -14,11 +14,6 @@
   Clear();
 }
 
-LayoutObject* LayoutSubtreeRootList::RandomRoot() {
-  DCHECK(!IsEmpty());
-  return *Unordered().begin().Get();
-}
-
 void LayoutSubtreeRootList::CountObjectsNeedingLayoutInRoot(
     const LayoutObject* object,
     unsigned& needs_layout_objects,
diff --git a/third_party/blink/renderer/core/frame/layout_subtree_root_list.h b/third_party/blink/renderer/core/frame/layout_subtree_root_list.h
index d6ca93c8..c84d4df 100644
--- a/third_party/blink/renderer/core/frame/layout_subtree_root_list.h
+++ b/third_party/blink/renderer/core/frame/layout_subtree_root_list.h
@@ -32,10 +32,6 @@
 
   void ClearAndMarkContainingBlocksForLayout();
 
-  // TODO(leviw): Remove this once we stop exposing to DevTools one root
-  // for a layout crbug.com/460596
-  LayoutObject* RandomRoot();
-
   void CountObjectsNeedingLayout(unsigned& needs_layout_objects,
                                  unsigned& total_objects);
 
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 838d4df..1b85f0e 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -670,12 +670,7 @@
 
   has_pending_layout_ = false;
 
-  // TODO(crbug.com/460956): The notion of a single root for layout is no
-  // longer applicable. Remove or update this code.
-  LayoutObject* root_for_this_layout = GetLayoutView();
-
   FontCachePurgePreventer font_cache_purge_preventer;
-  bool in_subtree_layout = false;
   base::AutoReset<bool> change_scheduling_enabled(&layout_scheduling_enabled_,
                                                   false);
   // If the layout view was marked as needing layout after we added items in
@@ -685,18 +680,7 @@
     ClearLayoutSubtreeRootsAndMarkContainingBlocks();
   GetLayoutView()->ClearHitTestCache();
 
-  in_subtree_layout = IsSubtreeLayout();
-
-  // TODO(crbug.com/460956): The notion of a single root for layout is no
-  // longer applicable. Remove or update this code.
-  if (in_subtree_layout)
-    root_for_this_layout = layout_subtree_root_list_.RandomRoot();
-
-  if (!root_for_this_layout) {
-    // FIXME: Do we need to set m_size here?
-    NOTREACHED();
-    return;
-  }
+  const bool in_subtree_layout = IsSubtreeLayout();
 
   Document* document = GetFrame().GetDocument();
   if (!in_subtree_layout) {
diff --git a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
index 72b5bcb..965a8eeb 100644
--- a/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
+++ b/third_party/blink/renderer/core/html/canvas/html_canvas_element.cc
@@ -1924,7 +1924,7 @@
   // TODO(junov): Computing style here will be problematic for applying the
   // NoAllocDirectCall IDL attribute to drawImage.
   if (!GetComputedStyle()) {
-    GetDocument().UpdateStyleAndLayoutTreeForNode(
+    GetDocument().UpdateStyleAndLayoutTreeForElement(
         this, DocumentUpdateReason::kCanvas);
     const_cast<HTMLCanvasElement*>(this)->EnsureComputedStyle();
   }
diff --git a/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc b/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
index c4910dc..3cd411d 100644
--- a/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
+++ b/third_party/blink/renderer/core/html/forms/date_time_edit_element.cc
@@ -653,8 +653,8 @@
     DateTimeFieldElement* old_focused_field =
         static_cast<DateTimeFieldElement*>(old_focused_element);
     wtf_size_t index = FieldIndexOf(*old_focused_field);
-    GetDocument().UpdateStyleAndLayoutTreeForNode(old_focused_field,
-                                                  DocumentUpdateReason::kFocus);
+    GetDocument().UpdateStyleAndLayoutTreeForElement(
+        old_focused_field, DocumentUpdateReason::kFocus);
     if (index != kInvalidFieldIndex && old_focused_field->IsFocusable()) {
       old_focused_field->Focus(FocusParams(FocusTrigger::kUserGesture));
       return;
diff --git a/third_party/blink/renderer/core/html/forms/html_label_element.cc b/third_party/blink/renderer/core/html/forms/html_label_element.cc
index 4232b4c..7d3cb23 100644
--- a/third_party/blink/renderer/core/html/forms/html_label_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_label_element.cc
@@ -236,8 +236,8 @@
 }
 
 void HTMLLabelElement::Focus(const FocusParams& params) {
-  GetDocument().UpdateStyleAndLayoutTreeForNode(this,
-                                                DocumentUpdateReason::kFocus);
+  GetDocument().UpdateStyleAndLayoutTreeForElement(
+      this, DocumentUpdateReason::kFocus);
   if (IsFocusable()) {
     HTMLElement::Focus(params);
     return;
diff --git a/third_party/blink/renderer/core/html/html_area_element.cc b/third_party/blink/renderer/core/html/html_area_element.cc
index 2e37b46e..9cdba6c 100644
--- a/third_party/blink/renderer/core/html/html_area_element.cc
+++ b/third_party/blink/renderer/core/html/html_area_element.cc
@@ -226,8 +226,8 @@
 void HTMLAreaElement::UpdateSelectionOnFocus(
     SelectionBehaviorOnFocus selection_behavior,
     const FocusOptions* options) {
-  GetDocument().UpdateStyleAndLayoutTreeForNode(this,
-                                                DocumentUpdateReason::kFocus);
+  GetDocument().UpdateStyleAndLayoutTreeForElement(
+      this, DocumentUpdateReason::kFocus);
   if (!IsFocusable())
     return;
 
diff --git a/third_party/blink/renderer/core/html/html_element.cc b/third_party/blink/renderer/core/html/html_element.cc
index 7c39e86..cddaf8b 100644
--- a/third_party/blink/renderer/core/html/html_element.cc
+++ b/third_party/blink/renderer/core/html/html_element.cc
@@ -821,8 +821,8 @@
     //
     // TODO(tkent): We should avoid updating style.  We'd like to check only
     // DOM-level focusability here.
-    GetDocument().UpdateStyleAndLayoutTreeForNode(this,
-                                                  DocumentUpdateReason::kFocus);
+    GetDocument().UpdateStyleAndLayoutTreeForElement(
+        this, DocumentUpdateReason::kFocus);
     if (!IsFocusable()) {
       blur();
     }
@@ -1849,8 +1849,8 @@
 void HTMLElement::SetPopoverFocusOnShow() {
   // The layout must be updated here because we call Element::isFocusable,
   // which requires an up-to-date layout.
-  GetDocument().UpdateStyleAndLayoutTreeForNode(this,
-                                                DocumentUpdateReason::kPopover);
+  GetDocument().UpdateStyleAndLayoutTreeForElement(
+      this, DocumentUpdateReason::kPopover);
 
   if (auto* dialog = DynamicTo<HTMLDialogElement>(this)) {
     if (RuntimeEnabledFeatures::DialogNewFocusBehaviorEnabled()) {
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
index b86bef5..4d8cd24d1 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -1664,7 +1664,7 @@
     }
   }
 
-  element->GetDocument().UpdateStyleAndLayoutTreeForNode(
+  element->GetDocument().UpdateStyleAndLayoutTreeForElement(
       element, DocumentUpdateReason::kInspector);
   StyleResolver& style_resolver = element->GetDocument().GetStyleResolver();
   // Container rule origin no longer known at this point, match name from all
diff --git a/third_party/blink/renderer/core/inspector/inspector_style_resolver.cc b/third_party/blink/renderer/core/inspector/inspector_style_resolver.cc
index 6e59ef0..8a6baa8 100644
--- a/third_party/blink/renderer/core/inspector/inspector_style_resolver.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_style_resolver.cc
@@ -29,7 +29,7 @@
 
   // Update style and layout tree for collecting an up-to-date set of rules
   // and animations.
-  element_->GetDocument().UpdateStyleAndLayoutTreeForNode(
+  element_->GetDocument().UpdateStyleAndLayoutTreeForElement(
       element_, DocumentUpdateReason::kInspector);
 
   // FIXME: It's really gross for the inspector to reach in and access
diff --git a/third_party/blink/renderer/core/paint/background_image_geometry.cc b/third_party/blink/renderer/core/paint/background_image_geometry.cc
index ce648f66..c89a9cd 100644
--- a/third_party/blink/renderer/core/paint/background_image_geometry.cc
+++ b/third_party/blink/renderer/core/paint/background_image_geometry.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/paint/background_image_geometry.h"
 
 #include "third_party/blink/renderer/core/paint/paint_info.h"
+#include "third_party/blink/renderer/core/paint/svg_background_paint_context.h"
 
 namespace blink {
 
@@ -702,6 +703,87 @@
   snapped_dest_rect_ = PhysicalRect(ToPixelSnappedRect(snapped_dest_rect_));
 }
 
+gfx::RectF BackgroundImageGeometry::ComputePositioningArea(
+    const FillLayer& layer,
+    const SVGBackgroundPaintContext& paint_context) const {
+  switch (layer.Origin()) {
+    case EFillBox::kNoClip:
+    case EFillBox::kText:
+      NOTREACHED();
+      [[fallthrough]];
+    case EFillBox::kBorder:
+    case EFillBox::kContent:
+    case EFillBox::kFillBox:
+    case EFillBox::kPadding:
+      return paint_context.ReferenceBox(GeometryBox::kFillBox);
+    case EFillBox::kStrokeBox:
+      return paint_context.ReferenceBox(GeometryBox::kStrokeBox);
+    case EFillBox::kViewBox:
+      return paint_context.ReferenceBox(GeometryBox::kViewBox);
+  }
+}
+
+gfx::RectF BackgroundImageGeometry::ComputePaintingArea(
+    const FillLayer& layer,
+    const SVGBackgroundPaintContext& paint_context,
+    const gfx::RectF& positioning_area) const {
+  switch (layer.Clip()) {
+    case EFillBox::kText:
+    case EFillBox::kNoClip:
+      return paint_context.VisualOverflowRect();
+    case EFillBox::kContent:
+    case EFillBox::kFillBox:
+    case EFillBox::kPadding:
+      return positioning_area;
+    case EFillBox::kStrokeBox:
+    case EFillBox::kBorder:
+      return paint_context.ReferenceBox(GeometryBox::kStrokeBox);
+    case EFillBox::kViewBox:
+      return paint_context.ReferenceBox(GeometryBox::kViewBox);
+  }
+}
+
+void BackgroundImageGeometry::Calculate(
+    const FillLayer& fill_layer,
+    const SVGBackgroundPaintContext& paint_context) {
+  const gfx::RectF positioning_area =
+      ComputePositioningArea(fill_layer, paint_context);
+  const gfx::RectF painting_area =
+      ComputePaintingArea(fill_layer, paint_context, positioning_area);
+  // Unsnapped positioning area is used to derive quantities
+  // that reference source image maps and define non-integer values, such
+  // as phase and position.
+  PhysicalRect unsnapped_positioning_area =
+      PhysicalRect::EnclosingRect(positioning_area);
+  unsnapped_dest_rect_ = PhysicalRect::EnclosingRect(painting_area);
+
+  // Additional offset from the corner of the positioning_box_
+  PhysicalOffset unsnapped_box_offset =
+      unsnapped_positioning_area.offset - unsnapped_dest_rect_.offset;
+
+  snapped_dest_rect_ = unsnapped_dest_rect_;
+
+  // Sets the tile_size_.
+  CalculateFillTileSize(fill_layer, paint_context.Style(),
+                        unsnapped_positioning_area.size,
+                        unsnapped_positioning_area.size);
+
+  // Applies *-repeat and *-position.
+  CalculateRepeatAndPosition(fill_layer, PhysicalOffset(),
+                             unsnapped_positioning_area.size,
+                             unsnapped_positioning_area.size,
+                             unsnapped_box_offset, unsnapped_box_offset);
+
+  if (!paint_context.UsesZoomedCoordinates()) {
+    const float inv_zoom = 1 / paint_context.EffectiveZoom();
+    unsnapped_dest_rect_.Scale(inv_zoom);
+    snapped_dest_rect_.Scale(inv_zoom);
+    phase_.Scale(inv_zoom);
+    tile_size_.Scale(inv_zoom);
+    repeat_spacing_.Scale(inv_zoom);
+  }
+}
+
 PhysicalOffset BackgroundImageGeometry::ComputePhase() const {
   // Given the size that the whole image should draw at, and the input phase
   // requested by the content, and the space between repeated tiles, compute a
diff --git a/third_party/blink/renderer/core/paint/background_image_geometry.h b/third_party/blink/renderer/core/paint/background_image_geometry.h
index d31e2f52..e2357ae 100644
--- a/third_party/blink/renderer/core/paint/background_image_geometry.h
+++ b/third_party/blink/renderer/core/paint/background_image_geometry.h
@@ -12,6 +12,7 @@
 namespace blink {
 
 class FillLayer;
+class SVGBackgroundPaintContext;
 struct PaintInfo;
 
 class BackgroundImageGeometry {
@@ -25,6 +26,7 @@
                  const BoxBackgroundPaintContext&,
                  const PhysicalRect& paint_rect,
                  const PaintInfo& paint_info);
+  void Calculate(const FillLayer&, const SVGBackgroundPaintContext&);
 
   // Destination rects define the area into which the image will paint.
   // For cases where no explicit background size is requested, the destination
@@ -98,6 +100,13 @@
       const PhysicalRect& unsnapped_positioning_area,
       bool disallow_border_derived_adjustment) const;
 
+  // Positioning/painting area setup for SVG.
+  gfx::RectF ComputePositioningArea(const FillLayer&,
+                                    const SVGBackgroundPaintContext&) const;
+  gfx::RectF ComputePaintingArea(const FillLayer&,
+                                 const SVGBackgroundPaintContext&,
+                                 const gfx::RectF& positioning_area) const;
+
   void AdjustPositioningArea(const FillLayer&,
                              const BoxBackgroundPaintContext&,
                              const PhysicalRect&,
diff --git a/third_party/blink/renderer/core/paint/build.gni b/third_party/blink/renderer/core/paint/build.gni
index edf69650..1d1b1e5c 100644
--- a/third_party/blink/renderer/core/paint/build.gni
+++ b/third_party/blink/renderer/core/paint/build.gni
@@ -150,6 +150,8 @@
   "selection_bounds_recorder.cc",
   "selection_bounds_recorder.h",
   "sparse_vector.h",
+  "svg_background_paint_context.cc",
+  "svg_background_paint_context.h",
   "svg_container_painter.cc",
   "svg_container_painter.h",
   "svg_foreign_object_painter.cc",
diff --git a/third_party/blink/renderer/core/paint/clip_path_clipper.cc b/third_party/blink/renderer/core/paint/clip_path_clipper.cc
index 9ed1422f..22b96ef9 100644
--- a/third_party/blink/renderer/core/paint/clip_path_clipper.cc
+++ b/third_party/blink/renderer/core/paint/clip_path_clipper.cc
@@ -276,8 +276,13 @@
     } else if (clip_path.GetType() == ClipPathOperation::kReference) {
       geometry_box = GeometryBox::kFillBox;
     }
-    return SVGResources::ReferenceBoxForEffects(
+    gfx::RectF unzoomed_reference_box = SVGResources::ReferenceBoxForEffects(
         object, geometry_box, SVGResources::ForeignObjectQuirk::kDisabled);
+    if (UsesZoomedReferenceBox(object)) {
+      return gfx::ScaleRect(unzoomed_reference_box,
+                            object.StyleRef().EffectiveZoom());
+    }
+    return unzoomed_reference_box;
   }
 
   const auto& box = To<LayoutBoxModelObject>(object);
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
index ddfa218..11b2edf 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_test.cc
@@ -35,6 +35,7 @@
 #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
 #include "third_party/blink/renderer/platform/testing/find_cc_layer.h"
 #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/wtf/text/base64.h"
 
@@ -125,6 +126,8 @@
 
  private:
   std::unique_ptr<frame_test_helpers::WebViewHelper> web_view_helper_;
+
+  test::TaskEnvironment task_environment_;
 };
 
 INSTANTIATE_TEST_SUITE_P(All,
diff --git a/third_party/blink/renderer/core/paint/line_relative_rect_test.cc b/third_party/blink/renderer/core/paint/line_relative_rect_test.cc
index 991069b..74ceba6 100644
--- a/third_party/blink/renderer/core/paint/line_relative_rect_test.cc
+++ b/third_party/blink/renderer/core/paint/line_relative_rect_test.cc
@@ -3,16 +3,18 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/core/paint/line_relative_rect.h"
-#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 
 namespace blink {
 
 class LineRelativeRectTest : public testing::Test {};
 
 TEST(LineRelativeRectTest, EnclosingRect) {
+  test::TaskEnvironment task_environment_;
   gfx::RectF r(1000, 10000, 10, 100);
   LineRelativeRect lor = LineRelativeRect::EnclosingRect(r);
   EXPECT_EQ(lor.offset.line_left, 1000) << "offset X";
@@ -32,6 +34,7 @@
 }
 
 TEST(LineRelativeRectTest, CreateFromLineBox) {
+  test::TaskEnvironment task_environment_;
   PhysicalRect r(1000, 10000, 10, 100);
   LineRelativeRect lor = LineRelativeRect::CreateFromLineBox(r, true);
   EXPECT_EQ(lor.offset.line_left, 1000) << "offset X, no rotation";
@@ -47,6 +50,7 @@
 }
 
 TEST(LineRelativeRectTest, ComputeRelativeToPhysicalTransformAtOrigin) {
+  test::TaskEnvironment task_environment_;
   LineRelativeRect r_origin = {{LayoutUnit(), LayoutUnit()},
                                {LayoutUnit(20), LayoutUnit(30)}};
 
@@ -65,6 +69,7 @@
 }
 
 TEST(LineRelativeRectTest, ComputeRelativeToPhysicalTransformNotAtOrigin) {
+  test::TaskEnvironment task_environment_;
   LineRelativeRect r_origin = {{LayoutUnit(1000), LayoutUnit(10000)},
                                {LayoutUnit(10), LayoutUnit(100)}};
 
@@ -85,6 +90,7 @@
 }
 
 TEST(LineRelativeRectTest, Create_kHorizontalTB) {
+  test::TaskEnvironment task_environment_;
   PhysicalRect r(1000, 10000, 10, 100);
 
   const WritingMode writing_mode = WritingMode::kHorizontalTb;
@@ -119,6 +125,7 @@
 }
 
 TEST(LineRelativeRectTest, Create_kSidewaysLr) {
+  test::TaskEnvironment task_environment_;
   PhysicalRect r(1000, 10000, 10, 100);
 
   const WritingMode writing_mode = WritingMode::kSidewaysLr;
@@ -159,6 +166,7 @@
 }
 
 TEST(LineRelativeRectTest, Create_kVerticalRl) {
+  test::TaskEnvironment task_environment_;
   PhysicalRect r(1000, 10000, 10, 100);
 
   const WritingMode writing_mode = WritingMode::kVerticalRl;
diff --git a/third_party/blink/renderer/core/paint/link_highlight_impl_test.cc b/third_party/blink/renderer/core/paint/link_highlight_impl_test.cc
index 3e1f2b4..250ea658 100644
--- a/third_party/blink/renderer/core/paint/link_highlight_impl_test.cc
+++ b/third_party/blink/renderer/core/paint/link_highlight_impl_test.cc
@@ -50,6 +50,7 @@
 #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
 #include "third_party/blink/renderer/platform/heap/thread_state.h"
 #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/url_loader_mock_factory.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
@@ -143,6 +144,7 @@
     return GetLinkHighlight().animation_host_;
   }
 
+  test::TaskEnvironment task_environment_;
   frame_test_helpers::WebViewHelper web_view_helper_;
 };
 
diff --git a/third_party/blink/renderer/core/paint/sparse_vector_test.cc b/third_party/blink/renderer/core/paint/sparse_vector_test.cc
index e6a316b..47138f1 100644
--- a/third_party/blink/renderer/core/paint/sparse_vector_test.cc
+++ b/third_party/blink/renderer/core/paint/sparse_vector_test.cc
@@ -4,6 +4,7 @@
 
 #include "third_party/blink/renderer/core/paint/sparse_vector.h"
 
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/googletest/src/googletest/include/gtest/gtest.h"
 
 namespace blink {
@@ -68,6 +69,7 @@
 
   const TestSparseVector& sparse_vector() { return *sparse_vector_; }
 
+  test::TaskEnvironment task_environment_;
   std::unique_ptr<TestSparseVector> sparse_vector_;
 };
 
diff --git a/third_party/blink/renderer/core/paint/svg_background_paint_context.cc b/third_party/blink/renderer/core/paint/svg_background_paint_context.cc
new file mode 100644
index 0000000..2fc250c
--- /dev/null
+++ b/third_party/blink/renderer/core/paint/svg_background_paint_context.cc
@@ -0,0 +1,49 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/paint/svg_background_paint_context.h"
+
+#include "third_party/blink/renderer/core/layout/layout_object.h"
+#include "third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.h"
+#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
+#include "third_party/blink/renderer/core/paint/paint_layer.h"
+
+namespace blink {
+
+SVGBackgroundPaintContext::SVGBackgroundPaintContext(
+    const LayoutObject& layout_object)
+    : object_(layout_object) {}
+
+gfx::RectF SVGBackgroundPaintContext::ReferenceBox(
+    GeometryBox geometry_box) const {
+  const gfx::RectF reference_box = SVGResources::ReferenceBoxForEffects(
+      object_, geometry_box, SVGResources::ForeignObjectQuirk::kDisabled);
+  return gfx::ScaleRect(reference_box, EffectiveZoom());
+}
+
+gfx::RectF SVGBackgroundPaintContext::VisualOverflowRect() const {
+  const gfx::RectF visual_rect = object_.VisualRectInLocalSVGCoordinates();
+  // <foreignObject> returns a visual rect thas has zoom applied already. We
+  // also need to include overflow from descendants.
+  if (auto* svg_fo = DynamicTo<LayoutSVGForeignObject>(object_)) {
+    const PhysicalRect visual_overflow =
+        svg_fo->Layer()->LocalBoundingBoxIncludingSelfPaintingDescendants();
+    return gfx::UnionRects(visual_rect, gfx::RectF(visual_overflow));
+  }
+  return gfx::ScaleRect(visual_rect, EffectiveZoom());
+}
+
+const ComputedStyle& SVGBackgroundPaintContext::Style() const {
+  return object_.StyleRef();
+}
+
+bool SVGBackgroundPaintContext::UsesZoomedCoordinates() const {
+  return IsA<LayoutSVGForeignObject>(object_);
+}
+
+float SVGBackgroundPaintContext::EffectiveZoom() const {
+  return Style().EffectiveZoom();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/svg_background_paint_context.h b/third_party/blink/renderer/core/paint/svg_background_paint_context.h
new file mode 100644
index 0000000..b2473c2
--- /dev/null
+++ b/third_party/blink/renderer/core/paint/svg_background_paint_context.h
@@ -0,0 +1,39 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SVG_BACKGROUND_PAINT_CONTEXT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SVG_BACKGROUND_PAINT_CONTEXT_H_
+
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+
+namespace gfx {
+class RectF;
+}  // namespace gfx
+
+namespace blink {
+
+class ComputedStyle;
+class LayoutObject;
+
+enum class GeometryBox;
+
+class SVGBackgroundPaintContext {
+  STACK_ALLOCATED();
+
+ public:
+  explicit SVGBackgroundPaintContext(const LayoutObject&);
+
+  gfx::RectF VisualOverflowRect() const;
+  gfx::RectF ReferenceBox(GeometryBox) const;
+  bool UsesZoomedCoordinates() const;
+  float EffectiveZoom() const;
+  const ComputedStyle& Style() const;
+
+ private:
+  const LayoutObject& object_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SVG_BACKGROUND_PAINT_CONTEXT_H_
diff --git a/third_party/blink/renderer/core/paint/svg_mask_painter.cc b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
index c7f8660..f4285ad 100644
--- a/third_party/blink/renderer/core/paint/svg_mask_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
@@ -10,8 +10,10 @@
 #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
+#include "third_party/blink/renderer/core/paint/background_image_geometry.h"
 #include "third_party/blink/renderer/core/paint/object_paint_properties.h"
 #include "third_party/blink/renderer/core/paint/paint_auto_dark_mode.h"
+#include "third_party/blink/renderer/core/paint/svg_background_paint_context.h"
 #include "third_party/blink/renderer/core/style/style_mask_source_image.h"
 #include "third_party/blink/renderer/core/svg/svg_length_functions.h"
 #include "third_party/blink/renderer/platform/graphics/graphics_context_state_saver.h"
@@ -69,311 +71,6 @@
                                  mask_source.GetSVGResourceClient(observer));
 }
 
-class SVGMaskGeometry {
-  STACK_ALLOCATED();
-
- public:
-  explicit SVGMaskGeometry(const LayoutObject& object) : object_(object) {}
-
-  void Calculate(const FillLayer&);
-
-  const gfx::RectF& DestRect() const { return dest_rect_; }
-  const absl::optional<gfx::RectF>& ClipRect() const { return clip_rect_; }
-  const gfx::SizeF& TileSize() const { return tile_size_; }
-  const gfx::SizeF& Spacing() const { return spacing_; }
-
-  gfx::Vector2dF ComputePhase() const;
-
- private:
-  absl::optional<gfx::RectF> ComputePaintingArea(const FillLayer&) const;
-  gfx::RectF ComputePositioningArea(const FillLayer&) const;
-  gfx::SizeF ComputeTileSize(const FillLayer&,
-                             const gfx::RectF& positioning_area) const;
-
-  const LayoutObject& object_;
-
-  gfx::RectF dest_rect_;
-  absl::optional<gfx::RectF> clip_rect_;
-  gfx::SizeF tile_size_;
-  gfx::PointF phase_;
-  gfx::SizeF spacing_;
-};
-
-absl::optional<gfx::RectF> SVGMaskGeometry::ComputePaintingArea(
-    const FillLayer& layer) const {
-  GeometryBox geometry_box = GeometryBox::kFillBox;
-  switch (layer.Clip()) {
-    case EFillBox::kText:
-    case EFillBox::kNoClip:
-      return absl::nullopt;
-    case EFillBox::kContent:
-    case EFillBox::kFillBox:
-    case EFillBox::kPadding:
-      break;
-    case EFillBox::kStrokeBox:
-    case EFillBox::kBorder:
-      geometry_box = GeometryBox::kStrokeBox;
-      break;
-    case EFillBox::kViewBox:
-      geometry_box = GeometryBox::kViewBox;
-      break;
-  }
-  gfx::RectF painting_area = SVGResources::ReferenceBoxForEffects(
-      object_, geometry_box, SVGResources::ForeignObjectQuirk::kDisabled);
-  painting_area.Scale(object_.StyleRef().EffectiveZoom());
-  return painting_area;
-}
-
-gfx::RectF SVGMaskGeometry::ComputePositioningArea(
-    const FillLayer& layer) const {
-  GeometryBox geometry_box = GeometryBox::kFillBox;
-  switch (layer.Origin()) {
-    case EFillBox::kBorder:
-    case EFillBox::kContent:
-    case EFillBox::kFillBox:
-    case EFillBox::kPadding:
-      break;
-    case EFillBox::kStrokeBox:
-      geometry_box = GeometryBox::kStrokeBox;
-      break;
-    case EFillBox::kViewBox:
-      geometry_box = GeometryBox::kViewBox;
-      break;
-    case EFillBox::kNoClip:
-    case EFillBox::kText:
-      NOTREACHED();
-      break;
-  }
-  gfx::RectF positioning_area = SVGResources::ReferenceBoxForEffects(
-      object_, geometry_box, SVGResources::ForeignObjectQuirk::kDisabled);
-  positioning_area.Scale(object_.StyleRef().EffectiveZoom());
-  return positioning_area;
-}
-
-gfx::Vector2dF SVGMaskGeometry::ComputePhase() const {
-  // Given the size that the whole image should draw at, and the input phase
-  // requested by the content, and the space between repeated tiles, compute a
-  // phase that is no more than one size + space in magnitude.
-  const gfx::SizeF step_per_tile = tile_size_ + spacing_;
-  return {std::fmod(-phase_.x(), step_per_tile.width()),
-          std::fmod(-phase_.y(), step_per_tile.height())};
-}
-
-float GetSpaceBetweenImageTiles(float area_size, float tile_size) {
-  const float number_of_tiles = std::floor(area_size / tile_size);
-  if (number_of_tiles < 1) {
-    return -1;
-  }
-  return (area_size - number_of_tiles * tile_size) / (number_of_tiles - 1);
-}
-
-float ComputeRoundedTileSize(float area_size, float tile_size) {
-  const float nr_tiles = std::max(1.0f, std::round(area_size / tile_size));
-  return area_size / nr_tiles;
-}
-
-float ComputeTilePhase(float position, float tile_extent) {
-  return tile_extent ? tile_extent - std::fmod(position, tile_extent) : 0;
-}
-
-gfx::SizeF FitToAspectRatio(const gfx::RectF& rect,
-                            const gfx::SizeF& aspect_ratio,
-                            bool grow) {
-  const float constrained_height =
-      ResolveHeightForRatio(rect.width(), aspect_ratio);
-  if ((grow && constrained_height < rect.height()) ||
-      (!grow && constrained_height > rect.height())) {
-    const float constrained_width =
-        ResolveWidthForRatio(rect.height(), aspect_ratio);
-    return {constrained_width, rect.height()};
-  }
-  return {rect.width(), constrained_height};
-}
-
-gfx::SizeF SVGMaskGeometry::ComputeTileSize(
-    const FillLayer& layer,
-    const gfx::RectF& positioning_area) const {
-  const StyleImage* image = layer.GetImage();
-  const IntrinsicSizingInfo sizing_info =
-      image->GetNaturalSizingInfo(object_.StyleRef().EffectiveZoom(),
-                                  object_.StyleRef().ImageOrientation());
-
-  switch (layer.SizeType()) {
-    case EFillSizeType::kSizeLength: {
-      const Length& layer_width = layer.SizeLength().Width();
-      const Length& layer_height = layer.SizeLength().Height();
-      gfx::SizeF tile_size(
-          FloatValueForLength(layer_width, positioning_area.width()),
-          FloatValueForLength(layer_height, positioning_area.height()));
-
-      // An auto value for one dimension is resolved by using the image's
-      // natural aspect ratio and the size of the other dimension, or failing
-      // that, using the image's natural size, or failing that, treating it as
-      // 100%.
-      // If both values are auto then the natural width and/or height of the
-      // image should be used, if any, the missing dimension (if any)
-      // behaving as auto as described above. If the image has neither
-      // natural size, its size is determined as for contain.
-      if (layer_width.IsAuto() && !layer_height.IsAuto()) {
-        if (!sizing_info.aspect_ratio.IsEmpty()) {
-          tile_size.set_width(ResolveWidthForRatio(tile_size.height(),
-                                                   sizing_info.aspect_ratio));
-        } else if (sizing_info.has_width) {
-          tile_size.set_width(sizing_info.size.width());
-        }
-      } else if (!layer_width.IsAuto() && layer_height.IsAuto()) {
-        if (!sizing_info.aspect_ratio.IsEmpty()) {
-          tile_size.set_height(ResolveHeightForRatio(tile_size.width(),
-                                                     sizing_info.aspect_ratio));
-        } else if (sizing_info.has_height) {
-          tile_size.set_height(sizing_info.size.height());
-        }
-      } else if (layer_width.IsAuto() && layer_height.IsAuto()) {
-        tile_size = image->ImageSize(object_.StyleRef().EffectiveZoom(),
-                                     positioning_area.size(),
-                                     object_.StyleRef().ImageOrientation());
-      }
-      return tile_size;
-    }
-    case EFillSizeType::kContain:
-    case EFillSizeType::kCover: {
-      if (sizing_info.aspect_ratio.IsEmpty()) {
-        return positioning_area.size();
-      }
-      return FitToAspectRatio(positioning_area, sizing_info.aspect_ratio,
-                              layer.SizeType() == EFillSizeType::kCover);
-    }
-    case EFillSizeType::kSizeNone:
-      // This value should only be used while resolving style.
-      NOTREACHED();
-      return gfx::SizeF();
-  }
-}
-
-void SVGMaskGeometry::Calculate(const FillLayer& layer) {
-  clip_rect_ = ComputePaintingArea(layer);
-  const gfx::RectF positioning_area = ComputePositioningArea(layer);
-  dest_rect_ = positioning_area;
-  tile_size_ = ComputeTileSize(layer, positioning_area);
-
-  const gfx::SizeF available_size = positioning_area.size() - tile_size_;
-  const gfx::PointF computed_position(
-      FloatValueForLength(layer.PositionX(), available_size.width()),
-      FloatValueForLength(layer.PositionY(), available_size.height()));
-  // Adjust position based on the specified edge origin.
-  const gfx::PointF offset(
-      layer.BackgroundXOrigin() == BackgroundEdgeOrigin::kRight
-          ? available_size.width() - computed_position.x()
-          : computed_position.x(),
-      layer.BackgroundYOrigin() == BackgroundEdgeOrigin::kBottom
-          ? available_size.height() - computed_position.y()
-          : computed_position.y());
-
-  const FillRepeat& repeat = layer.Repeat();
-  switch (repeat.x) {
-    case EFillRepeat::kRoundFill:
-      if (tile_size_.width() <= 0) {
-        break;
-      }
-      if (positioning_area.width() > 0) {
-        const float rounded_width = ComputeRoundedTileSize(
-            positioning_area.width(), tile_size_.width());
-        // Maintain aspect ratio if mask-size: auto is set
-        if (layer.SizeLength().Height().IsAuto() &&
-            repeat.y != EFillRepeat::kRoundFill) {
-          tile_size_.set_height(
-              ResolveHeightForRatio(rounded_width, tile_size_));
-        }
-        tile_size_.set_width(rounded_width);
-
-        // Force the first tile to line up with the edge of the positioning
-        // area.
-        phase_.set_x(ComputeTilePhase(offset.x(), tile_size_.width()));
-      }
-      break;
-    case EFillRepeat::kRepeatFill:
-      if (tile_size_.width() <= 0) {
-        break;
-      }
-      phase_.set_x(ComputeTilePhase(offset.x(), tile_size_.width()));
-      break;
-    case EFillRepeat::kSpaceFill: {
-      if (tile_size_.width() <= 0) {
-        break;
-      }
-      const float space = GetSpaceBetweenImageTiles(positioning_area.width(),
-                                                    tile_size_.width());
-      if (space >= 0) {
-        spacing_.set_width(space);
-        phase_.set_x(ComputeTilePhase(0, tile_size_.width() + space));
-        break;
-      }
-      // Handle as no-repeat.
-      [[fallthrough]];
-    }
-    case EFillRepeat::kNoRepeatFill:
-      dest_rect_.set_x(dest_rect_.x() + offset.x());
-      dest_rect_.set_width(tile_size_.width());
-      break;
-  }
-
-  switch (repeat.y) {
-    case EFillRepeat::kRoundFill:
-      if (tile_size_.height() <= 0) {
-        break;
-      }
-      if (positioning_area.height() > 0) {
-        const float rounded_height = ComputeRoundedTileSize(
-            positioning_area.height(), tile_size_.height());
-        // Maintain aspect ratio if mask-size: auto is set
-        if (layer.SizeLength().Width().IsAuto() &&
-            repeat.x != EFillRepeat::kRoundFill) {
-          tile_size_.set_width(
-              ResolveWidthForRatio(rounded_height, tile_size_));
-        }
-        tile_size_.set_height(rounded_height);
-
-        phase_.set_y(ComputeTilePhase(offset.y(), tile_size_.height()));
-      }
-      break;
-    case EFillRepeat::kRepeatFill:
-      if (tile_size_.height() <= 0) {
-        break;
-      }
-      phase_.set_y(ComputeTilePhase(offset.y(), tile_size_.height()));
-      break;
-    case EFillRepeat::kSpaceFill: {
-      if (tile_size_.height() <= 0) {
-        break;
-      }
-      const float space = GetSpaceBetweenImageTiles(positioning_area.height(),
-                                                    tile_size_.height());
-      if (space >= 0) {
-        spacing_.set_height(space);
-        phase_.set_y(ComputeTilePhase(0, tile_size_.height() + space));
-        break;
-      }
-      // Handle as no-repeat.
-      [[fallthrough]];
-    }
-    case EFillRepeat::kNoRepeatFill:
-      dest_rect_.set_y(dest_rect_.y() + offset.y());
-      dest_rect_.set_height(tile_size_.height());
-      break;
-  }
-
-  if (!object_.IsSVGForeignObject()) {
-    const float zoom = object_.StyleRef().EffectiveZoom();
-    if (clip_rect_) {
-      clip_rect_->InvScale(zoom);
-    }
-    dest_rect_.InvScale(zoom);
-    tile_size_.InvScale(zoom);
-    spacing_.InvScale(zoom);
-    phase_.InvScale(zoom);
-  }
-}
-
 void PaintSVGMask(LayoutSVGResourceMasker* masker,
                   const gfx::RectF& reference_box,
                   float zoom,
@@ -401,16 +98,6 @@
   }
 }
 
-struct FillInfo {
-  STACK_ALLOCATED();
-
- public:
-  const InterpolationQuality interpolation_quality;
-  const DynamicRangeLimit dynamic_range_limit;
-  const RespectImageOrientationEnum respect_orientation;
-  const LayoutObject& object;
-};
-
 class ScopedMaskLuminanceLayer {
   STACK_ALLOCATED();
 
@@ -435,8 +122,8 @@
 }
 
 void PaintMaskLayer(const FillLayer& layer,
-                    const FillInfo& info,
-                    SVGMaskGeometry& geometry,
+                    const LayoutObject& object,
+                    const SVGBackgroundPaintContext& bg_paint_context,
                     GraphicsContext& context) {
   const StyleImage* style_image = layer.GetImage();
   if (!style_image) {
@@ -456,49 +143,74 @@
     composite_op = SkBlendMode::kSrcOver;
   }
 
+  const ComputedStyle& style = bg_paint_context.Style();
+  const ImageResourceObserver& observer = object;
   GraphicsContextStateSaver saver(context, false);
 
   // If the "image" referenced by the FillLayer is an SVG <mask> reference (and
   // this is a layer for a mask), then repeat, position, clip, origin and size
   // should have no effect.
   if (const auto* mask_source = ToMaskSourceIfSVGMask(*style_image)) {
-    const ComputedStyle& style = info.object.StyleRef();
-    const float zoom =
-        info.object.IsSVGForeignObject() ? style.EffectiveZoom() : 1;
+    const float zoom = object.IsSVGForeignObject() ? style.EffectiveZoom() : 1;
     gfx::RectF reference_box = SVGResources::ReferenceBoxForEffects(
-        info.object, GeometryBox::kFillBox,
+        object, GeometryBox::kFillBox,
         SVGResources::ForeignObjectQuirk::kDisabled);
     reference_box.Scale(zoom);
 
     saver.Save();
     SVGMaskPainter::PaintSVGMaskLayer(
-        context, *mask_source, info.object, reference_box, zoom, composite_op,
+        context, *mask_source, observer, reference_box, zoom, composite_op,
         layer.MaskMode() == EFillMaskMode::kMatchSource);
     return;
   }
-  geometry.Calculate(layer);
+
+  BackgroundImageGeometry geometry;
+  geometry.Calculate(layer, bg_paint_context);
 
   if (geometry.TileSize().IsEmpty()) {
     return;
   }
 
-  scoped_refptr<Image> image =
-      style_image->GetImage(info.object, info.object.GetDocument(),
-                            info.object.StyleRef(), geometry.TileSize());
+  const Document& document = object.GetDocument();
+  scoped_refptr<Image> image = style_image->GetImage(
+      observer, document, style, gfx::SizeF(geometry.TileSize()));
   if (!image) {
     return;
   }
 
   ScopedImageRenderingSettings image_rendering_settings_context(
-      context, info.interpolation_quality, info.dynamic_range_limit);
+      context, style.GetInterpolationQuality(), style.GetDynamicRangeLimit());
 
-  if (auto clip_rect = geometry.ClipRect()) {
+  std::optional<GeometryBox> clip_box;
+  switch (layer.Clip()) {
+    case EFillBox::kText:
+    case EFillBox::kNoClip:
+      break;
+    case EFillBox::kContent:
+    case EFillBox::kFillBox:
+    case EFillBox::kPadding:
+      clip_box.emplace(GeometryBox::kFillBox);
+      break;
+    case EFillBox::kStrokeBox:
+    case EFillBox::kBorder:
+      clip_box.emplace(GeometryBox::kStrokeBox);
+      break;
+    case EFillBox::kViewBox:
+      clip_box.emplace(GeometryBox::kViewBox);
+      break;
+  }
+  if (clip_box) {
+    const float zoom = object.IsSVGForeignObject() ? style.EffectiveZoom() : 1;
+    gfx::RectF clip_rect = SVGResources::ReferenceBoxForEffects(
+        object, *clip_box, SVGResources::ForeignObjectQuirk::kDisabled);
+    clip_rect.Scale(zoom);
+
     saver.Save();
-    context.Clip(*clip_rect);
+    context.Clip(clip_rect);
   }
 
   const RespectImageOrientationEnum respect_orientation =
-      style_image->ForceOrientationIfNecessary(info.respect_orientation);
+      style_image->ForceOrientationIfNecessary(style.ImageOrientation());
 
   // Use the intrinsic size of the image if it has one, otherwise force the
   // generated image to be the tile size.
@@ -509,43 +221,37 @@
   const gfx::SizeF intrinsic_tile_size =
       image->SizeWithConfigAsFloat(size_config);
 
+  const gfx::RectF dest_rect(geometry.UnsnappedDestRect());
+
   // Note that this tile rect uses the image's pre-scaled size.
   ImageTilingInfo tiling_info;
   tiling_info.image_rect.set_size(intrinsic_tile_size);
-  tiling_info.phase = geometry.DestRect().origin() + geometry.ComputePhase();
-  tiling_info.spacing = geometry.Spacing();
+  tiling_info.phase =
+      dest_rect.origin() + gfx::Vector2dF(geometry.ComputePhase());
+  tiling_info.spacing = gfx::SizeF(geometry.SpaceSize());
   tiling_info.scale = {
-      geometry.TileSize().width() / tiling_info.image_rect.width(),
-      geometry.TileSize().height() / tiling_info.image_rect.height()};
+      geometry.TileSize().width / tiling_info.image_rect.width(),
+      geometry.TileSize().height / tiling_info.image_rect.height()};
 
   auto image_auto_dark_mode = ImageClassifierHelper::GetImageAutoDarkMode(
-      *info.object.GetFrame(), info.object.StyleRef(), geometry.DestRect(),
-      tiling_info.image_rect);
+      *document.GetFrame(), style, dest_rect, tiling_info.image_rect);
   // This call takes the unscaled image, applies the given scale, and paints it
   // into the dest rect using phase and the given repeat spacing. Note the
   // phase is already scaled.
   const ImagePaintTimingInfo paint_timing_info(false, false);
-  context.DrawImageTiled(*image, geometry.DestRect(), tiling_info,
-                         image_auto_dark_mode, paint_timing_info, composite_op,
-                         respect_orientation);
+  context.DrawImageTiled(*image, dest_rect, tiling_info, image_auto_dark_mode,
+                         paint_timing_info, composite_op, respect_orientation);
 }
 
 void PaintMaskLayers(GraphicsContext& context, const LayoutObject& object) {
-  const ComputedStyle& style = object.StyleRef();
   Vector<const FillLayer*, 8> layer_list;
-  for (const FillLayer* layer = &style.MaskLayers(); layer;
+  for (const FillLayer* layer = &object.StyleRef().MaskLayers(); layer;
        layer = layer->Next()) {
     layer_list.push_back(layer);
   }
-  const FillInfo fill_info = {
-      style.GetInterpolationQuality(),
-      style.GetDynamicRangeLimit(),
-      style.ImageOrientation(),
-      object,
-  };
-  SVGMaskGeometry geometry(object);
+  const SVGBackgroundPaintContext bg_paint_context(object);
   for (const auto* layer : base::Reversed(layer_list)) {
-    PaintMaskLayer(*layer, fill_info, geometry, context);
+    PaintMaskLayer(*layer, object, bg_paint_context, context);
   }
 }
 
diff --git a/third_party/blink/renderer/core/paint/timing/first_meaningful_paint_detector_test.cc b/third_party/blink/renderer/core/paint/timing/first_meaningful_paint_detector_test.cc
index b3fe23d..d9905c29 100644
--- a/third_party/blink/renderer/core/paint/timing/first_meaningful_paint_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/timing/first_meaningful_paint_detector_test.cc
@@ -13,6 +13,10 @@
 namespace blink {
 
 class FirstMeaningfulPaintDetectorTest : public PageTestBase {
+ public:
+  FirstMeaningfulPaintDetectorTest()
+      : PageTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+
  protected:
   void SetUp() override {
     EnablePlatform();
diff --git a/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc b/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
index f540529..4161a78 100644
--- a/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
+++ b/third_party/blink/renderer/core/paint/timing/image_element_timing_test.cc
@@ -12,6 +12,7 @@
 #include "third_party/blink/renderer/core/paint/timing/media_record_id.h"
 #include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h"
 #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
 #include "third_party/skia/include/core/SkImage.h"
 #include "third_party/skia/include/core/SkSurface.h"
@@ -90,6 +91,7 @@
         ->UpdateAllLifecyclePhasesForTest();
   }
 
+  test::TaskEnvironment task_environment_;
   frame_test_helpers::WebViewHelper web_view_helper_;
   WebURL base_url_;
 
diff --git a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
index 7be4ea2..ba3b441c 100644
--- a/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/timing/image_paint_timing_detector_test.cc
@@ -29,6 +29,7 @@
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 #include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
 #include "third_party/skia/include/core/SkImage.h"
@@ -272,6 +273,7 @@
     return To<LocalFrame>(GetFrame()->Tree().FirstChild());
   }
 
+  test::TaskEnvironment task_environment_;
   scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
   frame_test_helpers::WebViewHelper web_view_helper_;
 
diff --git a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector_test.cc
index ec8f201..5e13cd8 100644
--- a/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/timing/text_paint_timing_detector_test.cc
@@ -16,6 +16,7 @@
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 
 namespace blink {
 
@@ -242,6 +243,7 @@
     return web_view_helper_.GetWebView()->MainFrameImpl()->GetFrame();
   }
 
+  test::TaskEnvironment task_environment_;
   frame_test_helpers::WebViewHelper web_view_helper_;
   scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
   Persistent<MockPaintTimingCallbackManager> mock_callback_manager_;
diff --git a/third_party/blink/renderer/core/paint/video_painter_test.cc b/third_party/blink/renderer/core/paint/video_painter_test.cc
index 86d4dcf9..24f8cb41 100644
--- a/third_party/blink/renderer/core/paint/video_painter_test.cc
+++ b/third_party/blink/renderer/core/paint/video_painter_test.cc
@@ -8,12 +8,14 @@
 #include "cc/layers/layer.h"
 #include "components/paint_preview/common/paint_preview_tracker.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/core/css/css_default_style_sheets.h"
 #include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/core/html/media/html_media_element.h"
 #include "third_party/blink/renderer/core/paint/paint_controller_paint_test.h"
 #include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
 
 // Integration tests of video painting code (in CAP mode).
@@ -170,6 +172,10 @@
 class VideoPaintPreviewTest : public testing::Test,
                               public PaintTestConfigurations {
  public:
+  ~VideoPaintPreviewTest() {
+    CSSDefaultStyleSheets::Instance().PrepareForLeakDetection();
+  }
+
   void SetUp() override {
     web_view_helper_.Initialize(&web_frame_client_);
 
@@ -230,6 +236,8 @@
   }
 
  private:
+  test::TaskEnvironment task_environment_;
+
   LocalFrame* GetFrame() { return GetLocalMainFrame().GetFrame(); }
 
   frame_test_helpers::WebViewHelper web_view_helper_;
diff --git a/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5 b/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5
index c7a46d31..c407cf5 100644
--- a/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5
+++ b/third_party/blink/renderer/core/permissions_policy/permissions_policy_features.json5
@@ -338,6 +338,11 @@
       depends_on: ["PrivateStateTokens"],
     },
     {
+      name: "PublicKeyCredentialsCreate",
+      permissions_policy_name: "publickey-credentials-create",
+      depends_on: ["WebAuthAllowCreateInCrossOriginFrame"],
+    },
+    {
       name: "PublicKeyCredentialsGet",
       permissions_policy_name: "publickey-credentials-get",
     },
diff --git a/third_party/blink/renderer/core/svg/svg_animate_element.cc b/third_party/blink/renderer/core/svg/svg_animate_element.cc
index a03b2f1..a950586 100644
--- a/third_party/blink/renderer/core/svg/svg_animate_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_animate_element.cc
@@ -60,7 +60,7 @@
   // Refer to comment in Element::computedStyle.
   DCHECK(element->InActiveDocument());
 
-  element->GetDocument().UpdateStyleAndLayoutTreeForNode(
+  element->GetDocument().UpdateStyleAndLayoutTreeForElement(
       element, DocumentUpdateReason::kSMILAnimation);
 
   // Don't include any properties resulting from CSS Transitions/Animations or
diff --git a/third_party/blink/renderer/core/svg/svg_length_tear_off.cc b/third_party/blink/renderer/core/svg/svg_length_tear_off.cc
index f27a9b7..fd64eed 100644
--- a/third_party/blink/renderer/core/svg/svg_length_tear_off.cc
+++ b/third_party/blink/renderer/core/svg/svg_length_tear_off.cc
@@ -112,8 +112,8 @@
     document.UpdateStyleAndLayoutForNode(context_element,
                                          DocumentUpdateReason::kJavaScript);
   } else {
-    document.UpdateStyleAndLayoutTreeForNode(context_element,
-                                             DocumentUpdateReason::kJavaScript);
+    document.UpdateStyleAndLayoutTreeForElement(
+        context_element, DocumentUpdateReason::kJavaScript);
   }
   return true;
 }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 7ece47426..0a8fbfe 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -6545,6 +6545,18 @@
   return PhysicalRect::FastAndLossyFromRectF(computed_bounds);
 }
 
+void AXObject::UpdateStyleAndLayoutTreeForNode(Node& node) {
+  // In most cases, UpdateAllLifecyclePhasesExceptPaint() is enough, but if
+  // the action is part of a display locked node, that will not update the node
+  // because it's not part of the layout update cycle yet. In that case, calling
+  // UpdateStyleAndLayoutTreeForElement() is also necessary.
+  if (const Element* element =
+          FlatTreeTraversal::InclusiveParentElement(node)) {
+    element->GetDocument().UpdateStyleAndLayoutTreeForElement(
+        element, DocumentUpdateReason::kAccessibility);
+  }
+}
+
 //
 // Modify or take an action on an object.
 //
@@ -6563,12 +6575,7 @@
     }
   }
 
-  // In most cases, UpdateAllLifecyclePhasesExceptPaint() is enough, but if
-  // the action is part of a display locked node, that will not update the node
-  // because it's not part of the layout update cycle yet. In that case, calling
-  // UpdateStyleAndLayoutTreeForNode() is also necessary.
-  document->UpdateStyleAndLayoutTreeForNode(
-      node, DocumentUpdateReason::kAccessibility);
+  UpdateStyleAndLayoutTreeForNode(*node);
   cache.UpdateAXForAllDocuments();
 
   // Updating style and layout for the node can cause it to gain layout,
@@ -6766,12 +6773,8 @@
     }
   }
 
-  // In most cases, UpdateAllLifecyclePhasesExceptPaint() is enough, but if
-  // focus is is moving to a display locked node, that will not update the node
-  // because it's not part of the layout update cycle yet. In that case, calling
-  // UpdateStyleAndLayoutTreeForNode() is also necessary.
-  document->UpdateStyleAndLayoutTreeForNode(
-      node, DocumentUpdateReason::kAccessibility);
+  UpdateStyleAndLayoutTreeForNode(*node);
+
   document->View()->UpdateAllLifecyclePhasesExceptPaint(
       DocumentUpdateReason::kAccessibility);
 
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.h b/third_party/blink/renderer/modules/accessibility/ax_object.h
index a6fd19f..700bf518 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.h
@@ -1486,6 +1486,7 @@
  private:
   bool ComputeCanSetFocusAttribute() const;
   String KeyboardShortcut() const;
+  void UpdateStyleAndLayoutTreeForNode(Node& node);
 
   // Do the rest of the cached_* member variables need to be recomputed?
   mutable bool cached_values_need_update_ : 1;
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index 3a7db138..472c415 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -2705,7 +2705,7 @@
   if (state.GetDirection() ==
           CanvasRenderingContext2DState::kDirectionInherit &&
       canvas) {
-    canvas->GetDocument().UpdateStyleAndLayoutTreeForNode(
+    canvas->GetDocument().UpdateStyleAndLayoutTreeForElement(
         canvas, DocumentUpdateReason::kCanvas);
   }
   return ToTextDirection(state.GetDirection(), canvas) == TextDirection::kRtl
@@ -2821,7 +2821,7 @@
     // accessFont needs the style to be up to date, but updating style can cause
     // script to run, (e.g. due to autofocus) which can free the canvas (set
     // size to 0, for example), so update style before grabbing the PaintCanvas.
-    canvas->GetDocument().UpdateStyleAndLayoutTreeForNode(
+    canvas->GetDocument().UpdateStyleAndLayoutTreeForElement(
         canvas, DocumentUpdateReason::kCanvas);
   }
   cc::PaintCanvas* c = GetOrCreatePaintCanvas();
@@ -2947,7 +2947,7 @@
       return MakeGarbageCollected<TextMetrics>();
     }
 
-    canvas->GetDocument().UpdateStyleAndLayoutTreeForNode(
+    canvas->GetDocument().UpdateStyleAndLayoutTreeForElement(
         canvas, DocumentUpdateReason::kCanvas);
   }
   const Font& font = AccessFont(canvas);
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index 03cae07b..0fd722dd 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -434,8 +434,8 @@
     return false;
   }
 
-  document.UpdateStyleAndLayoutTreeForNode(element,
-                                           DocumentUpdateReason::kCanvas);
+  document.UpdateStyleAndLayoutTreeForElement(element,
+                                              DocumentUpdateReason::kCanvas);
   return true;
 }
 
diff --git a/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc b/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
index e4a379584..446d44d 100644
--- a/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
+++ b/third_party/blink/renderer/modules/credentialmanagement/credentials_container.cc
@@ -127,6 +127,15 @@
   // permissions-policy header, and may be inherited from parent browsing
   // contexts. See Permissions Policy spec.
   kSecureAndPermittedByWebAuthGetAssertionPermissionsPolicy,
+  // Must be a secure origin and the "publickey-credentials-create" permissions
+  // policy must be enabled. By default "publickey-credentials-create" is not
+  // inherited by cross-origin child frames, so if that policy is not
+  // explicitly enabled, behavior is the same as that of
+  // |kSecureAndSameWithAncestors|. Note that permissions policies can be
+  // expressed in various ways, e.g.: |allow| iframe attribute and/or
+  // permissions-policy header, and may be inherited from parent browsing
+  // contexts. See Permissions Policy spec.
+  kSecureAndPermittedByWebAuthCreateCredentialPermissionsPolicy,
   // Similar to the enum above, checks the "otp-credentials" permissions policy.
   kSecureAndPermittedByWebOTPAssertionPermissionsPolicy,
   // Similar to the enum above, checks the "identity-credentials-get"
@@ -248,6 +257,28 @@
       break;
 
     case RequiredOriginType::
+        kSecureAndPermittedByWebAuthCreateCredentialPermissionsPolicy:
+      // The 'publickey-credentials-create' feature's "default allowlist" is
+      // "self", which means the webauthn feature is allowed by default in
+      // same-origin child browsing contexts.
+      if (!resolver->GetExecutionContext()->IsFeatureEnabled(
+              mojom::blink::PermissionsPolicyFeature::
+                  kPublicKeyCredentialsCreate)) {
+        resolver->Reject(MakeGarbageCollected<DOMException>(
+            DOMExceptionCode::kNotAllowedError,
+            "The 'publickey-credentials-create' feature is not enabled in this "
+            "document. Permissions Policy may be used to delegate Web "
+            "Authentication capabilities to cross-origin child frames."));
+        return false;
+      } else if (!IsSameOriginWithAncestors(
+                     resolver->DomWindow()->GetFrame())) {
+        UseCounter::Count(
+            resolver->GetExecutionContext(),
+            WebFeature::kCredentialManagerCrossOriginPublicKeyCreateRequest);
+      }
+      break;
+
+    case RequiredOriginType::
         kSecureAndPermittedByWebOTPAssertionPermissionsPolicy:
       if (!resolver->GetExecutionContext()->IsFeatureEnabled(
               mojom::blink::PermissionsPolicyFeature::kOTPCredentials)) {
@@ -276,6 +307,8 @@
       break;
 
     case RequiredOriginType::kSecureWithPaymentPermissionPolicy:
+      // TODO(crbug.com/1512605): "publickey-credentials-create" should also
+      // work for payments.
       if (!resolver->GetExecutionContext()->IsFeatureEnabled(
               mojom::blink::PermissionsPolicyFeature::kPayment)) {
         resolver->Reject(MakeGarbageCollected<DOMException>(
@@ -320,6 +353,12 @@
       break;
 
     case RequiredOriginType::
+        kSecureAndPermittedByWebAuthCreateCredentialPermissionsPolicy:
+      SECURITY_CHECK(resolver->GetExecutionContext()->IsFeatureEnabled(
+          mojom::blink::PermissionsPolicyFeature::kPublicKeyCredentialsCreate));
+      break;
+
+    case RequiredOriginType::
         kSecureAndPermittedByWebOTPAssertionPermissionsPolicy:
       SECURITY_CHECK(
           resolver->GetExecutionContext()->IsFeatureEnabled(
@@ -1012,6 +1051,9 @@
   if (!payment->hasIsPayment() || !payment->isPayment())
     return true;
 
+  // TODO(crbug.com/1512245): Remove this check in favour of the validation in
+  // |CredentialsContainer::create|, which throws a NotAllowedError rather than
+  // a SecurityError like the SPC spec currently requires.
   if (!IsSameOriginWithAncestors(resolver->DomWindow()->GetFrame())) {
     bool has_user_activation = LocalFrame::ConsumeTransientUserActivation(
         resolver->DomWindow()->GetFrame(),
@@ -1745,11 +1787,15 @@
   if (IsForPayment(options, resolver->GetExecutionContext())) {
     required_origin_type =
         RequiredOriginType::kSecureWithPaymentPermissionPolicy;
-  } else {
+  } else if (options->hasPublicKey()) {
     // hasPublicKey() implies that this is a WebAuthn request.
-    required_origin_type = options->hasPublicKey()
-                               ? RequiredOriginType::kSecureAndSameWithAncestors
-                               : RequiredOriginType::kSecure;
+    required_origin_type =
+        RuntimeEnabledFeatures::WebAuthAllowCreateInCrossOriginFrameEnabled()
+            ? RequiredOriginType::
+                  kSecureAndPermittedByWebAuthCreateCredentialPermissionsPolicy
+            : RequiredOriginType::kSecureAndSameWithAncestors;
+  } else {
+    required_origin_type = RequiredOriginType::kSecure;
   }
   if (!CheckSecurityRequirementsBeforeRequest(resolver, required_origin_type)) {
     return promise;
@@ -1882,6 +1928,28 @@
     }
   }
 
+  // In the case of create() in a cross-origin iframe, the spec requires that
+  // the caller must have transient user activation (which is consumed).
+  // https://w3c.github.io/webauthn/#sctn-createCredential, step 2.
+  //
+  // TODO(crbug.com/1512245): This check should be used for payment credentials
+  // as well, but currently the SPC spec expects a SecurityError rather than
+  // NotAllowedError.
+  if (!IsSameOriginWithAncestors(resolver->DomWindow()->GetFrame()) &&
+      (!options->publicKey()->hasExtensions() ||
+       !options->publicKey()->extensions()->hasPayment())) {
+    bool has_user_activation = LocalFrame::ConsumeTransientUserActivation(
+        resolver->DomWindow()->GetFrame(),
+        UserActivationUpdateSource::kRenderer);
+    if (!has_user_activation) {
+      resolver->Reject(MakeGarbageCollected<DOMException>(
+          DOMExceptionCode::kNotAllowedError,
+          "A user activation is required to create a credential in a "
+          "cross-origin iframe."));
+      return promise;
+    }
+  }
+
   std::unique_ptr<ScopedAbortState> scoped_abort_state = nullptr;
   if (auto* signal = options->getSignalOr(nullptr)) {
     if (signal->aborted()) {
diff --git a/third_party/blink/renderer/modules/ml/BUILD.gn b/third_party/blink/renderer/modules/ml/BUILD.gn
index beb9ebd2..195bbc5 100644
--- a/third_party/blink/renderer/modules/ml/BUILD.gn
+++ b/third_party/blink/renderer/modules/ml/BUILD.gn
@@ -142,7 +142,7 @@
   }
 
   if (!is_chromeos) {
-    sources += [ "webnn/ml_graph_test_mojo.cc" ]
+    sources += [ "webnn/ml_graph_mojo_test.cc" ]
     deps += [ "//components/ml/webnn" ]
   }
 }
diff --git a/third_party/blink/renderer/modules/ml/ml.cc b/third_party/blink/renderer/modules/ml/ml.cc
index 7cd67ab..d689335c 100644
--- a/third_party/blink/renderer/modules/ml/ml.cc
+++ b/third_party/blink/renderer/modules/ml/ml.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/modules/ml/ml.h"
 
 #include "components/ml/mojom/web_platform_model.mojom-blink.h"
-#include "components/ml/webnn/features.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_context_options.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
index 35db673..b0ef4b2 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
@@ -7,7 +7,6 @@
 #include <algorithm>
 
 #include "base/numerics/checked_math.h"
-#include "components/ml/webnn/features.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_arg_min_max_options.h"
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_test_mojo.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
similarity index 98%
rename from third_party/blink/renderer/modules/ml/webnn/ml_graph_test_mojo.cc
rename to third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
index ecc370a5..6b9d2fa 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_test_mojo.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
@@ -4,7 +4,7 @@
 
 #include "base/memory/raw_ref.h"
 #include "base/test/scoped_feature_list.h"
-#include "components/ml/webnn/features.h"
+#include "components/ml/webnn/features.mojom-features.h"
 #include "mojo/public/cpp/base/big_buffer.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -30,6 +30,7 @@
 namespace blink {
 
 namespace blink_mojom = webnn::mojom::blink;
+namespace webnn_features = webnn::mojom::features;
 
 // Helper struct to create faked mojom result of inference.
 struct ComputeResult {
@@ -236,7 +237,7 @@
     // resoveld with an MLGraphMojo object.
     base::test::ScopedFeatureList scoped_feature_list;
     scoped_feature_list.InitAndEnableFeature(
-        webnn::features::kWebMachineLearningNeuralNetwork);
+        webnn_features::kWebMachineLearningNeuralNetwork);
 
     ScriptPromiseTester tester(script_state, BuildSimpleGraph(scope, options));
     tester.WaitUntilSettled();
@@ -303,7 +304,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -438,7 +439,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -705,7 +706,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -1155,7 +1156,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -1583,7 +1584,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -1664,7 +1665,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -1808,7 +1809,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -1907,7 +1908,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -2045,7 +2046,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -2188,7 +2189,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -2409,7 +2410,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -2610,7 +2611,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -2780,7 +2781,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -2910,7 +2911,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -3015,7 +3016,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -3214,7 +3215,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -3435,7 +3436,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -3534,7 +3535,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -3654,7 +3655,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -3803,7 +3804,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -3944,7 +3945,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -4052,7 +4053,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -4120,7 +4121,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -4209,7 +4210,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -4334,7 +4335,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -4522,7 +4523,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -4606,7 +4607,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -4749,7 +4750,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -4830,7 +4831,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -5154,7 +5155,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
@@ -5251,7 +5252,7 @@
   ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(
-      webnn::features::kWebMachineLearningNeuralNetwork);
+      webnn_features::kWebMachineLearningNeuralNetwork);
   auto* options = MLContextOptions::Create();
   // Create WebNN Context with GPU device type.
   options->setDeviceType(V8MLDeviceType::Enum::kGpu);
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
index 18a2f23..f8073d5a 100644
--- a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
+++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
@@ -637,32 +637,21 @@
     scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
     base::PassKey<PeerConnectionTracker>)
     : Supplement<LocalDOMWindow>(window),
-      // Do not set a lifecycle notifier for `peer_connection_tracker_host_` to
-      // ensure that its mojo pipe stays alive until the execution context is
-      // destroyed. `RTCPeerConnection` (which owns a `RTCPeerConnectionHandler`
-      // which persistently kepts `this` alive) will attempt to close and
-      // unregister the peer connection when the execution context is destroyed,
-      // for which this mojo pipe _must_ be alive to relay.
-      // See https://crbug.com/1426377 for details.
-      peer_connection_tracker_host_(nullptr),
       receiver_(this, &window),
       main_thread_task_runner_(std::move(main_thread_task_runner)) {
   window.GetBrowserInterfaceBroker().GetInterface(
-      peer_connection_tracker_host_.BindNewPipeAndPassReceiver(
-          main_thread_task_runner_));
+      peer_connection_tracker_host_.BindNewPipeAndPassReceiver());
 }
 
 // Constructor used for testing. Note that receiver_ doesn't have a context
 // notifier in this case.
 PeerConnectionTracker::PeerConnectionTracker(
-    mojo::PendingRemote<blink::mojom::blink::PeerConnectionTrackerHost> host,
+    mojo::Remote<blink::mojom::blink::PeerConnectionTrackerHost> host,
     scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner)
     : Supplement(nullptr),
-      peer_connection_tracker_host_(nullptr),
+      peer_connection_tracker_host_(std::move(host)),
       receiver_(this, nullptr),
-      main_thread_task_runner_(std::move(main_thread_task_runner)) {
-  peer_connection_tracker_host_.Bind(std::move(host), main_thread_task_runner_);
-}
+      main_thread_task_runner_(std::move(main_thread_task_runner)) {}
 
 PeerConnectionTracker::~PeerConnectionTracker() {}
 
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.h b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.h
index 1bc68c2..c87bc757 100644
--- a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.h
+++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.h
@@ -10,18 +10,18 @@
 #include "base/threading/thread_checker.h"
 #include "base/types/pass_key.h"
 #include "base/values.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/peerconnection/peer_connection_tracker.mojom-blink.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/modules/mediastream/media_stream.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
-#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_peer_connection_handler_client.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_rtp_transceiver_platform.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_session_description_platform.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
+#include "third_party/blink/renderer/platform/wtf/gc_plugin.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/webrtc/api/peer_connection_interface.h"
@@ -66,12 +66,12 @@
 
   // Ctors for tests.
   PeerConnectionTracker(
-      mojo::PendingRemote<mojom::blink::PeerConnectionTrackerHost> host,
+      mojo::Remote<mojom::blink::PeerConnectionTrackerHost> host,
       scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
       base::PassKey<PeerConnectionTrackerTest> key)
       : PeerConnectionTracker(std::move(host), main_thread_task_runner) {}
   PeerConnectionTracker(
-      mojo::PendingRemote<mojom::blink::PeerConnectionTrackerHost> host,
+      mojo::Remote<mojom::blink::PeerConnectionTrackerHost> host,
       scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
       base::PassKey<MockPeerConnectionTracker> key)
       : PeerConnectionTracker(std::move(host), main_thread_task_runner) {}
@@ -238,7 +238,6 @@
                                      const WTF::Vector<uint8_t>& output);
 
   void Trace(Visitor* visitor) const override {
-    visitor->Trace(peer_connection_tracker_host_);
     visitor->Trace(receiver_);
     Supplement<LocalDOMWindow>::Trace(visitor);
   }
@@ -251,7 +250,7 @@
                            ReportInitialThermalState);
 
   PeerConnectionTracker(
-      mojo::PendingRemote<mojom::blink::PeerConnectionTrackerHost> host,
+      mojo::Remote<mojom::blink::PeerConnectionTrackerHost> host,
       scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner);
 
   void Bind(mojo::PendingReceiver<blink::mojom::blink::PeerConnectionManager>
@@ -309,7 +308,8 @@
   int32_t current_speed_limit_ = mojom::blink::kSpeedLimitMax;
 
   THREAD_CHECKER(main_thread_);
-  HeapMojoRemote<blink::mojom::blink::PeerConnectionTrackerHost>
+  GC_PLUGIN_IGNORE("https://crbug.com/1381979")
+  mojo::Remote<blink::mojom::blink::PeerConnectionTrackerHost>
       peer_connection_tracker_host_;
   HeapMojoReceiver<blink::mojom::blink::PeerConnectionManager,
                    PeerConnectionTracker>
diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker_test.cc b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker_test.cc
index b67c555..acd4b6e6 100644
--- a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker_test.cc
@@ -68,11 +68,12 @@
   MOCK_METHOD2(AddStandardStats, void(int, base::Value::List));
   MOCK_METHOD2(AddLegacyStats, void(int, base::Value::List));
 
-  mojo::PendingRemote<blink::mojom::blink::PeerConnectionTrackerHost>
+  mojo::Remote<blink::mojom::blink::PeerConnectionTrackerHost>
   CreatePendingRemoteAndBind() {
     receiver_.reset();
-    return receiver_.BindNewPipeAndPassRemote(
-        blink::scheduler::GetSingleThreadTaskRunnerForTesting());
+    return mojo::Remote<blink::mojom::blink::PeerConnectionTrackerHost>(
+        receiver_.BindNewPipeAndPassRemote(
+            blink::scheduler::GetSingleThreadTaskRunnerForTesting()));
   }
 
   mojo::Receiver<blink::mojom::blink::PeerConnectionTrackerHost> receiver_{
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.h b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.h
index d92bf79..c195b41 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.h
@@ -416,8 +416,7 @@
   // detect any removals during rollback.
   Vector<uintptr_t> previous_transceiver_ids_;
 
-  // Keep persistently alive to ensure the peer connection will be unregistered.
-  Persistent<PeerConnectionTracker> peer_connection_tracker_;
+  WeakPersistent<PeerConnectionTracker> peer_connection_tracker_;
 
   MediaStreamTrackMetrics track_metrics_;
 
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
index 7f97252a..bbbed46 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler_test.cc
@@ -26,7 +26,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/values.h"
 #include "build/build_config.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_track.h"
@@ -92,7 +91,7 @@
  public:
   MockPeerConnectionTracker()
       : PeerConnectionTracker(
-            mojo::PendingRemote<mojom::blink::PeerConnectionTrackerHost>(),
+            mojo::Remote<mojom::blink::PeerConnectionTrackerHost>(),
             blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
             base::PassKey<MockPeerConnectionTracker>()) {}
 
diff --git a/third_party/blink/renderer/modules/scheduler/dom_scheduler_test.cc b/third_party/blink/renderer/modules/scheduler/dom_scheduler_test.cc
index cf5ab1d..c410a833 100644
--- a/third_party/blink/renderer/modules/scheduler/dom_scheduler_test.cc
+++ b/third_party/blink/renderer/modules/scheduler/dom_scheduler_test.cc
@@ -143,13 +143,4 @@
   EXPECT_EQ(GetDynamicPriorityTaskQueueCount(), 0u);
 }
 
-class DOMSchedulerTestWithCompositionDisabled : public DOMSchedulerTest {
- public:
-  DOMSchedulerTestWithCompositionDisabled()
-      : scoped_signal_composition_(false) {}
-
- private:
-  ScopedAbortSignalCompositionForTest scoped_signal_composition_;
-};
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/scheduler/dom_task_signal.cc b/third_party/blink/renderer/modules/scheduler/dom_task_signal.cc
index bbc2106..3e3eb6ce 100644
--- a/third_party/blink/renderer/modules/scheduler/dom_task_signal.cc
+++ b/third_party/blink/renderer/modules/scheduler/dom_task_signal.cc
@@ -16,7 +16,6 @@
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/scheduler/task_priority_change_event.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
-#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
@@ -41,26 +40,19 @@
 DOMTaskSignal* DOMTaskSignal::CreateFixedPriorityTaskSignal(
     ScriptState* script_state,
     const AtomicString& priority) {
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    HeapVector<Member<AbortSignal>> source_abort_signals;
-    return MakeGarbageCollected<DOMTaskSignal>(script_state, priority, nullptr,
-                                               source_abort_signals);
-  } else {
-    return MakeGarbageCollected<DOMTaskSignal>(
-        ExecutionContext::From(script_state), priority, SignalType::kInternal);
-  }
+  HeapVector<Member<AbortSignal>> source_abort_signals;
+  return MakeGarbageCollected<DOMTaskSignal>(script_state, priority, nullptr,
+                                             source_abort_signals);
 }
 
 DOMTaskSignal::DOMTaskSignal(ExecutionContext* context,
                              const AtomicString& priority,
                              SignalType signal_type)
     : AbortSignal(context, signal_type), priority_(priority) {
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    DCHECK_NE(signal_type, AbortSignal::SignalType::kComposite);
-    priority_composition_manager_ =
-        MakeGarbageCollected<SourceSignalCompositionManager>(
-            *this, AbortSignalCompositionType::kPriority);
-  }
+  DCHECK_NE(signal_type, AbortSignal::SignalType::kComposite);
+  priority_composition_manager_ =
+      MakeGarbageCollected<SourceSignalCompositionManager>(
+          *this, AbortSignalCompositionType::kPriority);
 }
 
 DOMTaskSignal::DOMTaskSignal(
@@ -69,8 +61,6 @@
     DOMTaskSignal* priority_source_signal,
     HeapVector<Member<AbortSignal>>& abort_source_signals)
     : AbortSignal(script_state, abort_source_signals), priority_(priority) {
-  DCHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
-
   HeapVector<Member<AbortSignal>> signals;
   if (priority_source_signal) {
     signals.push_back(priority_source_signal);
@@ -102,8 +92,7 @@
 DOMTaskSignal::AlgorithmHandle* DOMTaskSignal::AddPriorityChangeAlgorithm(
     base::RepeatingClosure algorithm) {
   CHECK_NE(GetSignalType(), SignalType::kInternal);
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled() &&
-      priority_composition_manager_->IsSettled()) {
+  if (priority_composition_manager_->IsSettled()) {
     return nullptr;
   }
   auto* callback_algorithm =
@@ -139,17 +128,15 @@
   DispatchEvent(*TaskPriorityChangeEvent::Create(
       event_type_names::kPrioritychange, init));
 
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    if (auto* source_signal_manager = DynamicTo<SourceSignalCompositionManager>(
-            *priority_composition_manager_.Get())) {
-      // Dependents can be added while dispatching events, but none are removed
-      // since having an active iterator will strongify weak references, making
-      // the following iteration safe. Signaling priority change on newly added
-      // dependent signals has no effect since the new priority is already set.
-      for (auto& abort_signal : source_signal_manager->GetDependentSignals()) {
-        To<DOMTaskSignal>(abort_signal.Get())
-            ->SignalPriorityChange(priority, exception_state);
-      }
+  if (auto* source_signal_manager = DynamicTo<SourceSignalCompositionManager>(
+          *priority_composition_manager_.Get())) {
+    // Dependents can be added while dispatching events, but none are removed
+    // since having an active iterator will strongify weak references, making
+    // the following iteration safe. Signaling priority change on newly added
+    // dependent signals has no effect since the new priority is already set.
+    for (auto& abort_signal : source_signal_manager->GetDependentSignals()) {
+      To<DOMTaskSignal>(abort_signal.Get())
+          ->SignalPriorityChange(priority, exception_state);
     }
   }
 
@@ -163,14 +150,10 @@
 }
 
 bool DOMTaskSignal::HasFixedPriority() const {
-  if (RuntimeEnabledFeatures::AbortSignalCompositionEnabled()) {
-    return priority_composition_manager_->IsSettled();
-  }
-  return GetSignalType() == SignalType::kInternal;
+  return priority_composition_manager_->IsSettled();
 }
 
 void DOMTaskSignal::DetachFromController() {
-  DCHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
   AbortSignal::DetachFromController();
 
   priority_composition_manager_->Settle();
@@ -178,7 +161,6 @@
 
 AbortSignalCompositionManager* DOMTaskSignal::GetCompositionManager(
     AbortSignalCompositionType composition_type) {
-  DCHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
   if (composition_type != AbortSignalCompositionType::kPriority) {
     return AbortSignal::GetCompositionManager(composition_type);
   }
@@ -195,7 +177,6 @@
 
 bool DOMTaskSignal::IsSettledFor(
     AbortSignalCompositionType composition_type) const {
-  CHECK(RuntimeEnabledFeatures::AbortSignalCompositionEnabled());
   if (composition_type == AbortSignalCompositionType::kPriority) {
     return priority_composition_manager_->IsSettled();
   }
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index ced2b215..e586c2b 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -203,11 +203,6 @@
     {
       name: "AbortSignalAny",
       status: "stable",
-      depends_on: ["AbortSignalComposition"]
-    },
-    {
-      name: "AbortSignalComposition",
-      status: "stable"
     },
     {
       name: "Accelerated2dCanvas",
@@ -300,7 +295,7 @@
     {
       // crbug.com/1500511
       name: "AlignContentForBlocks",
-      status: "stable",
+      status: "experimental",
     },
     {
       name: "AllowContentInitiatedDataUrlNavigations",
@@ -1252,8 +1247,11 @@
       origin_trial_type: "deprecation",
     },
     {
+      // Makes the default display value of date type <input> elements
+      // inline-block for cross-browser compatibility. Enabled in M122, should
+      // be safe to remove in M126.
       name: "DateInputInlineBlock",
-      status: "test",
+      status: "stable",
     },
     {
       // TODO(crbug.com/1396384) This is being disabled, very slowly, via
@@ -3277,7 +3275,6 @@
       name: "SchedulerYield",
       origin_trial_feature_name: "SchedulerYield",
       origin_trial_allows_third_party: true,
-      depends_on: ["AbortSignalComposition"],
       status: "experimental",
     },
     {
@@ -4123,6 +4120,12 @@
       status: "stable",
       base_feature: "none",
     },
+    // Allow creation of WebAuth credentials in cross-origin contexts (as long
+    // as the 'publickey-credentials-create' permissions policy is set).
+    {
+      name: "WebAuthAllowCreateInCrossOriginFrame",
+      status: "experimental",
+    },
     // When enabled adds the authenticator attachment used for registration and
     // authentication to the public key credential response.
     {
diff --git a/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/fuzzy_diff_analyzer.py b/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/fuzzy_diff_analyzer.py
index 99650724..1cb1b2c5 100644
--- a/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/fuzzy_diff_analyzer.py
+++ b/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/fuzzy_diff_analyzer.py
@@ -10,8 +10,11 @@
 recommended fuzzy fixable range for the test:
 
 third_party/blink/tools/run_fuzzy_diff_analyzer.py \
-  --project chrome-unexpected-pass-data \
   --sample-period 3
+
+Use 'gcloud auth login' command first for local usage.
+Use the --test-path flag to specify the tests you want to perform a fuzzy diff
+analysis, instead of all tests.
 """
 
 import argparse
@@ -36,7 +39,7 @@
         'Script to fuzzy diff analyzer for flaky image comparison web tests'))
     parser.add_argument(
         '--project',
-        required=True,
+        default='chrome-unexpected-pass-data',
         help=('The billing project to use for BigQuery queries. '
               'Must have access to the ResultDB BQ tables, e.g. '
               '"luci-resultdb.chromium.web_tests_ci_test_results".'))
diff --git a/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/queries.py b/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/queries.py
index 5c16398..4cb544c0 100644
--- a/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/queries.py
+++ b/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/queries.py
@@ -253,11 +253,16 @@
             batch=False)
 
         with open(os.devnull, 'w') as devnull:
-            completed_process = subprocess.run(cmd,
-                                               input=query,
-                                               stdout=subprocess.PIPE,
-                                               stderr=devnull,
-                                               check=True,
-                                               text=True)
+            try:
+                completed_process = subprocess.run(cmd,
+                                                   input=query,
+                                                   stdout=subprocess.PIPE,
+                                                   stderr=devnull,
+                                                   check=True,
+                                                   text=True)
+            except subprocess.CalledProcessError as error:
+                print("Failed to query result, run 'gcloud auth login'"
+                      " first.")
+                raise error
 
         return json.loads(completed_process.stdout)
diff --git a/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/slow_test_analyzer.py b/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/slow_test_analyzer.py
index 07be2c2..7ab1c41 100644
--- a/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/slow_test_analyzer.py
+++ b/third_party/blink/tools/blinkpy/web_tests/web_test_analyzers/slow_test_analyzer.py
@@ -10,6 +10,10 @@
 third_party/blink/tools/run_slow_test_analyzer.py \
   --sample-period 3
 
+Use 'gcloud auth login' command first for local usage.
+Use the --test-path flag to specify the tests you want to perform a slow test
+analysis on, instead of all tests.
+
 Example output:
 Test name: test1
 Test is slow in the below list of builders:
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 400a0b2..bf5e0f78 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -297,6 +297,7 @@
              "--disable-threaded-compositing", "--disable-threaded-animation"],
     "expires": "never"
   },
+  "This suite tests some android features on Linux and should never expire.",
   {
     "prefix": "android",
     "platforms": ["Linux"],
@@ -308,7 +309,10 @@
              "--enable-viewport",
              "--disable-canvas-aa",
              "--disable-composited-antialiasing"],
-    "expires": "Jul 1, 2023"
+    "owners": ["takumif@chromium.org",
+               "muyaoxu@google.com",
+               "fullscreen-experience@google.com"],
+    "expires": "never"
   },
   {
     "prefix": "media-foundation-for-clear-dcomp",
@@ -2224,20 +2228,6 @@
     "expires": "Jul 1, 2023"
   },
   {
-    "prefix": "scheduler-composition-disabled",
-    "platforms": ["Linux"],
-    "bases": [
-      "external/wpt/scheduler/post-task-abort-reason.any.js",
-      "external/wpt/scheduler/post-task-run-order.any.js",
-      "external/wpt/scheduler/post-task-with-abort-signal.any.js",
-      "external/wpt/scheduler/task-controller-abort-signal-and-priority.any.js",
-      "external/wpt/scheduler/task-controller-setPriority1.any.js"
-    ],
-    "args": ["--disable-blink-features=AbortSignalComposition",
-             "--disable-threaded-compositing", "--disable-threaded-animation"],
-    "expires": "Dec 21, 2023"
-  },
-  {
     "prefix": "abort-signal-any-disabled",
     "platforms": ["Linux"],
     "bases": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-url-reference-svg-foreignobject-zoomed.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-url-reference-svg-foreignobject-zoomed.html
new file mode 100644
index 0000000..df8d0cc8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-url-reference-svg-foreignobject-zoomed.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<title>CSS Masking: clip-path, &lt;clipPath> with objectBoundingBox units on a foreignObject</title>
+<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-clip-path">
+<link rel="help" href="https://bugs.chromium.org/p/chromium/issues/detail?id=1512077">
+<link rel="match" href="reference/green-100x100.html">
+<style>
+  #clipped {
+    background: green;
+    width: 50px;
+    height: 50px;
+    border-bottom: 50px solid red;
+    border-right: 50px solid red;
+  }
+</style>
+<svg style="zoom: 2">
+  <rect width="50" height="50" fill="red"/>
+  <clipPath id="c" clipPathUnits="objectBoundingBox">
+    <rect width=".5" height=".5"/>
+  </clipPath>
+  <foreignObject clip-path="url(#c)" width="100" height="100">
+    <div id="clipped"></div>
+  </foreignObject>
+</svg>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/mask-image/mask-position-8.html b/third_party/blink/web_tests/external/wpt/css/css-masking/mask-image/mask-position-8.html
new file mode 100644
index 0000000..cb99135
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/mask-image/mask-position-8.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<title>CSS Masking: mask-position with overflowing mask-size on SVG content</title>
+<link rel="help" href="https://drafts.fxtf.org/css-masking/#the-mask-position">
+<link rel="match" href="../clip-path/reference/green-100x100.html">
+<style>
+  .mask {
+    mask: linear-gradient(90deg, transparent 25%, black 25%, black 50%, transparent 50%) 50% / 200% 100%;
+  }
+</style>
+<svg>
+  <g class="mask">
+    <rect width="100" height="100" fill="green"/>
+    <rect x="100" width="100" height="100" fill="red"/>
+  </g>
+</svg>
diff --git a/third_party/blink/web_tests/virtual/scheduler-composition-disabled/README.md b/third_party/blink/web_tests/virtual/scheduler-composition-disabled/README.md
deleted file mode 100644
index 1afa0706..0000000
--- a/third_party/blink/web_tests/virtual/scheduler-composition-disabled/README.md
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing scheduling APIs with abort composition disabled
diff --git a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
index d797e75..0459c7c 100644
--- a/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
+++ b/third_party/blink/web_tests/webexposed/feature-policy-features-expected.txt
@@ -60,6 +60,7 @@
 private-aggregation
 private-state-token-issuance
 private-state-token-redemption
+publickey-credentials-create
 publickey-credentials-get
 run-ad-auction
 screen-wake-lock
diff --git a/third_party/catapult b/third_party/catapult
index 61af87d..23118cc 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 61af87d894922b368c4733137916ecfaaa607fea
+Subproject commit 23118cc3c34d629778abac83f10b39810ea1fdfb
diff --git a/third_party/libaom/BUILD.gn b/third_party/libaom/BUILD.gn
index 0bc15f7e..76708eb2 100644
--- a/third_party/libaom/BUILD.gn
+++ b/third_party/libaom/BUILD.gn
@@ -7,6 +7,7 @@
 import("//build/config/arm.gni")
 import("//build/config/sanitizers/sanitizers.gni")
 import("//testing/libfuzzer/fuzzer_test.gni")
+import("//testing/test.gni")
 import("//third_party/libaom/libaom_srcs.gni")
 import("//third_party/libaom/libaom_test_srcs.gni")
 import("//third_party/libaom/options.gni")
@@ -364,6 +365,19 @@
   additional_configs = [ ":libaom_config" ]
 }
 
+if (fuzztest_supported) {
+  test("av1_encoder_fuzz_test") {
+    sources = [ "fuzz/av1_encoder_fuzz_test.cc" ]
+
+    enable_fuzztest = true
+
+    deps = [
+      ":libaom",
+      "//third_party/fuzztest:fuzztest_gtest_main",
+    ]
+  }
+}
+
 config("test_libaom_config") {
   include_dirs = [ "//third_party/libaom/source/libaom/third_party/libwebm" ]
 }
diff --git a/third_party/libaom/fuzz/av1_encoder_fuzz_test.cc b/third_party/libaom/fuzz/av1_encoder_fuzz_test.cc
new file mode 100644
index 0000000..6c97322
--- /dev/null
+++ b/third_party/libaom/fuzz/av1_encoder_fuzz_test.cc
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2023, Alliance for Open Media. All rights reserved
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#include <cstring>
+#include <variant>
+#include <vector>
+
+#include "third_party/fuzztest/src/fuzztest/fuzztest.h"
+#include "third_party/googletest/src/googletest/include/gtest/gtest.h"
+#include "third_party/libaom/source/libaom/aom/aom_codec.h"
+#include "third_party/libaom/source/libaom/aom/aom_encoder.h"
+#include "third_party/libaom/source/libaom/aom/aom_image.h"
+#include "third_party/libaom/source/libaom/aom/aomcx.h"
+
+using fuzztest::Arbitrary;
+using fuzztest::ElementOf;
+using fuzztest::FlatMap;
+using fuzztest::InRange;
+using fuzztest::Just;
+using fuzztest::StructOf;
+using fuzztest::VariantOf;
+using fuzztest::VectorOf;
+
+// Represents a VideoEncoder::configure() method call.
+// Parameters:
+//   VideoEncoderConfig config
+struct Configure {
+  unsigned int threads;  // Not part of VideoEncoderConfig
+  unsigned int width;    // Nonzero
+  unsigned int height;   // Nonzero
+  // TODO(wtc): displayWidth, displayHeight, bitrate, framerate,
+  // scalabilityMode.
+  aom_rc_mode end_usage;  // Implies bitrateMode: constant, variable.
+                          // TODO(wtc): quantizer.
+  unsigned int usage;     // Implies LatencyMode: quality, realtime.
+  // TODO(wtc): contentHint.
+};
+
+auto AnyConfigureWithSize(unsigned int width, unsigned int height) {
+  return StructOf<Configure>(
+      // Chrome's WebCodecs uses at most 16 threads.
+      /*threads=*/InRange(0u, 16u), /*width=*/Just(width),
+      /*height=*/Just(height),
+      /*end_usage=*/ElementOf({AOM_VBR, AOM_CBR}),
+      /*usage=*/
+      Just(AOM_USAGE_REALTIME));
+}
+
+auto AnyConfigureWithMaxSize(unsigned int max_width, unsigned int max_height) {
+  return StructOf<Configure>(
+      // Chrome's WebCodecs uses at most 16 threads.
+      /*threads=*/InRange(0u, 16u),
+      /*width=*/InRange(1u, max_width),
+      /*height=*/InRange(1u, max_height),
+      /*end_usage=*/ElementOf({AOM_VBR, AOM_CBR}),
+      /*usage=*/
+      Just(AOM_USAGE_REALTIME));
+}
+
+// Represents a VideoEncoder::encode() method call.
+// Parameters:
+//   VideoFrame frame
+//   optional VideoEncoderEncodeOptions options = {}
+struct Encode {
+  bool key_frame;
+  // TODO(wtc): quantizer.
+};
+
+auto AnyEncode() {
+  return StructOf<Encode>(Arbitrary<bool>());
+}
+
+using MethodCall = std::variant<Configure, Encode>;
+
+auto AnyMethodCallWithMaxSize(unsigned int max_width, unsigned int max_height) {
+  return VariantOf(AnyConfigureWithMaxSize(max_width, max_height), AnyEncode());
+}
+
+struct CallSequence {
+  Configure initialize;
+  std::vector<MethodCall> method_calls;
+};
+
+auto AnyCallSequenceWithMaxSize(unsigned int max_width,
+                                unsigned int max_height) {
+  return StructOf<CallSequence>(
+      /*initialize=*/AnyConfigureWithSize(max_width, max_height),
+      /*method_calls=*/VectorOf(AnyMethodCallWithMaxSize(max_width, max_height))
+          .WithMaxSize(20));
+}
+
+auto AnyCallSequence() {
+  return FlatMap(AnyCallSequenceWithMaxSize,
+                 /*max_width=*/InRange(1u, 1920u),
+                 /*max_height=*/InRange(1u, 1080u));
+}
+
+void AV1EncodeArbitraryCallSequenceSucceeds(int speed,
+                                            const CallSequence& call_sequence) {
+  aom_codec_iface_t* const iface = aom_codec_av1_cx();
+  aom_codec_enc_cfg_t cfg;
+  const unsigned int usage = call_sequence.initialize.usage;
+  ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, usage), AOM_CODEC_OK);
+  cfg.g_threads = call_sequence.initialize.threads;
+  cfg.g_w = call_sequence.initialize.width;
+  cfg.g_h = call_sequence.initialize.height;
+  cfg.g_forced_max_frame_width = cfg.g_w;
+  cfg.g_forced_max_frame_height = cfg.g_h;
+  cfg.g_timebase.num = 1;
+  cfg.g_timebase.den = 1000 * 1000;  // microseconds
+  cfg.g_pass = AOM_RC_ONE_PASS;
+  cfg.g_lag_in_frames = 0;
+  cfg.rc_end_usage = call_sequence.initialize.end_usage;
+  cfg.rc_min_quantizer = 2;
+  cfg.rc_max_quantizer = 58;
+
+  aom_codec_ctx_t enc;
+  ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
+
+  ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, speed), AOM_CODEC_OK);
+
+  const aom_codec_cx_pkt_t* pkt;
+
+  int frame_index = 0;
+  for (const auto& call : call_sequence.method_calls) {
+    if (std::holds_alternative<Configure>(call)) {
+      const Configure& configure = std::get<Configure>(call);
+      cfg.g_threads = configure.threads;
+      cfg.g_w = configure.width;
+      cfg.g_h = configure.height;
+      cfg.rc_end_usage = configure.end_usage;
+      ASSERT_EQ(aom_codec_enc_config_set(&enc, &cfg), AOM_CODEC_OK)
+          << aom_codec_error_detail(&enc);
+    } else {
+      // Encode a frame.
+      const Encode& encode = std::get<Encode>(call);
+      // TODO(wtc): Support high bit depths and other YUV formats.
+      aom_image_t* const image =
+          aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
+      ASSERT_NE(image, nullptr);
+
+      for (unsigned int i = 0; i < image->d_h; ++i) {
+        memset(image->planes[0] + i * image->stride[0], 128, image->d_w);
+      }
+      const unsigned int uv_h = (image->d_h + 1) / 2;
+      const unsigned int uv_w = (image->d_w + 1) / 2;
+      for (unsigned int i = 0; i < uv_h; ++i) {
+        memset(image->planes[1] + i * image->stride[1], 128, uv_w);
+        memset(image->planes[2] + i * image->stride[2], 128, uv_w);
+      }
+
+      const aom_enc_frame_flags_t flags =
+          encode.key_frame ? AOM_EFLAG_FORCE_KF : 0;
+      ASSERT_EQ(aom_codec_encode(&enc, image, frame_index, 1, flags),
+                AOM_CODEC_OK);
+      frame_index++;
+      aom_codec_iter_t iter = nullptr;
+      while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
+        ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+        if (encode.key_frame) {
+          ASSERT_EQ(pkt->data.frame.flags & AOM_FRAME_IS_KEY, AOM_FRAME_IS_KEY);
+        }
+      }
+      aom_img_free(image);
+    }
+  }
+
+  // Flush the encoder.
+  bool got_data;
+  do {
+    ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
+    got_data = false;
+    aom_codec_iter_t iter = nullptr;
+    while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
+      ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+      got_data = true;
+    }
+  } while (got_data);
+
+  ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
+}
+
+FUZZ_TEST(AV1EncodeFuzzTest, AV1EncodeArbitraryCallSequenceSucceeds)
+    .WithDomains(/*speed=*/InRange(5, 11),
+                 /*call_sequence=*/AnyCallSequence());
+
+// speed: range 5..11
+// end_usage: Rate control mode
+void AV1EncodeSucceeds(unsigned int threads,
+                       int speed,
+                       aom_rc_mode end_usage,
+                       unsigned int width,
+                       unsigned int height,
+                       int num_frames) {
+  aom_codec_iface_t* const iface = aom_codec_av1_cx();
+  aom_codec_enc_cfg_t cfg;
+  const unsigned int usage = AOM_USAGE_REALTIME;
+  ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, usage), AOM_CODEC_OK);
+  cfg.g_threads = threads;
+  cfg.g_w = width;
+  cfg.g_h = height;
+  cfg.g_timebase.num = 1;
+  cfg.g_timebase.den = 1000 * 1000;  // microseconds
+  cfg.g_pass = AOM_RC_ONE_PASS;
+  cfg.g_lag_in_frames = 0;
+  cfg.rc_end_usage = end_usage;
+  cfg.rc_min_quantizer = 2;
+  cfg.rc_max_quantizer = 58;
+
+  aom_codec_ctx_t enc;
+  ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
+
+  ASSERT_EQ(aom_codec_control(&enc, AOME_SET_CPUUSED, speed), AOM_CODEC_OK);
+
+  // TODO(wtc): Support high bit depths and other YUV formats.
+  aom_image_t* const image =
+      aom_img_alloc(nullptr, AOM_IMG_FMT_I420, cfg.g_w, cfg.g_h, 1);
+  ASSERT_NE(image, nullptr);
+
+  for (unsigned int i = 0; i < image->d_h; ++i) {
+    memset(image->planes[0] + i * image->stride[0], 128, image->d_w);
+  }
+  const unsigned int uv_h = (image->d_h + 1) / 2;
+  const unsigned int uv_w = (image->d_w + 1) / 2;
+  for (unsigned int i = 0; i < uv_h; ++i) {
+    memset(image->planes[1] + i * image->stride[1], 128, uv_w);
+    memset(image->planes[2] + i * image->stride[2], 128, uv_w);
+  }
+
+  // Encode frames.
+  const aom_codec_cx_pkt_t* pkt;
+  for (int i = 0; i < num_frames; ++i) {
+    ASSERT_EQ(aom_codec_encode(&enc, image, i, 1, 0), AOM_CODEC_OK);
+    aom_codec_iter_t iter = nullptr;
+    while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
+      ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+    }
+  }
+
+  // Flush the encoder.
+  bool got_data;
+  do {
+    ASSERT_EQ(aom_codec_encode(&enc, nullptr, 0, 0, 0), AOM_CODEC_OK);
+    got_data = false;
+    aom_codec_iter_t iter = nullptr;
+    while ((pkt = aom_codec_get_cx_data(&enc, &iter)) != nullptr) {
+      ASSERT_EQ(pkt->kind, AOM_CODEC_CX_FRAME_PKT);
+      got_data = true;
+    }
+  } while (got_data);
+
+  aom_img_free(image);
+  ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
+}
+
+// Chrome's WebCodecs uses at most 16 threads.
+FUZZ_TEST(AV1EncodeFuzzTest, AV1EncodeSucceeds)
+    .WithDomains(/*threads=*/InRange(0u, 16u),
+                 /*speed=*/InRange(5, 11),
+                 /*end_usage=*/ElementOf({AOM_VBR, AOM_CBR}),
+                 /*width=*/InRange(1u, 1920u),
+                 /*height=*/InRange(1u, 1080u),
+                 /*num_frames=*/InRange(1, 10));
diff --git a/third_party/node/README.chromium b/third_party/node/README.chromium
index 63d4272..238d892 100644
--- a/third_party/node/README.chromium
+++ b/third_party/node/README.chromium
@@ -51,8 +51,8 @@
 paired with any testing framework.
 
 Local Modifications:
- - Applied chai.patch so that chai.js can run within a d8 shell, which is
-   used in unit_tests
+ - Applied patches/chai.patch so that chai.js can run within a d8 shell, which
+   is used in unit_tests
 
 -------------------- DEPENDENCY DIVIDER --------------------
 
@@ -94,7 +94,7 @@
 The compiler is not shipped with Chrome but code compiled using it is.
 
 Local Modifications:
-See typescript.patch to work-around
+See patches/typescript.patch to work-around
 https://github.com/microsoft/TypeScript/issues/30024
 
 -------------------- DEPENDENCY DIVIDER --------------------
@@ -113,8 +113,8 @@
 The types are only used when type checking in Chrome.
 
 Local Modifications:
-See chromium_d3_types_index.patch, which is used to reduce the types to only
-the necessary parts of the library.
+See patches/chromium_d3_types_index.patch, which is used to reduce the types to
+only the necessary parts of the library.
 
 -------------------- DEPENDENCY DIVIDER --------------------
 
@@ -400,3 +400,28 @@
 
 Local Modifications:
 (none)
+
+-------------------- DEPENDENCY DIVIDER --------------------
+
+Name: Lit
+Short Name: lit
+URL: https://lit.dev
+Version: 3.0.2
+Revision: N/A
+License: BSD 3-Clause
+License File: LICENSE
+Security Critical: no
+Shipped: yes
+
+Description:
+A thin library around native web component APIs, to be used in WebUI development
+as an alternative to Polymer.
+
+Local Modifications:
+See patches/lit_html.patch, which renames 'lit-html' trusted type policy to
+'lit-html-desktop' to workaround the fact that some CrOS-only UIs already use
+Lit via a different Lit bundle in third_party/material_web_components, which in
+turn results in an error as follows
+
+Uncaught TypeError: Failed to execute 'createPolicy' on
+'TrustedTypePolicyFactory': Policy with name "lit-html" already exists."
diff --git a/third_party/node/node_modules.tar.gz.sha1 b/third_party/node/node_modules.tar.gz.sha1
index 818539b..21d63e46 100644
--- a/third_party/node/node_modules.tar.gz.sha1
+++ b/third_party/node/node_modules.tar.gz.sha1
@@ -1 +1 @@
-7a234117fdf9e49c3dc56aa37111082123211462
+cdf182f812a5d7e4a355736ae4b929c7c32f6068
diff --git a/third_party/node/chai.patch b/third_party/node/patches/chai.patch
similarity index 100%
rename from third_party/node/chai.patch
rename to third_party/node/patches/chai.patch
diff --git a/third_party/node/chromium_d3_types_index.patch b/third_party/node/patches/chromium_d3_types_index.patch
similarity index 100%
rename from third_party/node/chromium_d3_types_index.patch
rename to third_party/node/patches/chromium_d3_types_index.patch
diff --git a/third_party/node/html_minifier.patch b/third_party/node/patches/html_minifier.patch
similarity index 100%
rename from third_party/node/html_minifier.patch
rename to third_party/node/patches/html_minifier.patch
diff --git a/third_party/node/patches/lit_html.patch b/third_party/node/patches/lit_html.patch
new file mode 100644
index 0000000..9d83747
--- /dev/null
+++ b/third_party/node/patches/lit_html.patch
@@ -0,0 +1,11 @@
+diff --git a/node_modules/lit-html/lit-html.js b/node_modules/lit-html/lit-html.js
+index d63809f23984d..d97e52ec65b10 100644
+--- a/node_modules/lit-html/lit-html.js
++++ b/node_modules/lit-html/lit-html.js
+@@ -3,5 +3,5 @@
+  * Copyright 2017 Google LLC
+  * SPDX-License-Identifier: BSD-3-Clause
+  */
+-const t=globalThis,i=t.trustedTypes,s=i?i.createPolicy("lit-html",{createHTML:t=>t}):void 0,e="$lit$",h=`lit$${(Math.random()+"").slice(9)}$`,o="?"+h,n=`<${o}>`,r=document,l=()=>r.createComment(""),c=t=>null===t||"object"!=typeof t&&"function"!=typeof t,a=Array.isArray,u=t=>a(t)||"function"==typeof t?.[Symbol.iterator],d="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,v=/-->/g,_=/>/g,m=RegExp(`>|${d}(?:([^\\s"'>=/]+)(${d}*=${d}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),p=/'/g,g=/"/g,$=/^(?:script|style|textarea|title)$/i,y=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),x=y(1),b=y(2),w=Symbol.for("lit-noChange"),T=Symbol.for("lit-nothing"),A=new WeakMap,E=r.createTreeWalker(r,129);function C(t,i){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==s?s.createHTML(i):i}const P=(t,i)=>{const s=t.length-1,o=[];let r,l=2===i?"<svg>":"",c=f;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,y=0;for(;y<s.length&&(c.lastIndex=y,u=c.exec(s),null!==u);)y=c.lastIndex,c===f?"!--"===u[1]?c=v:void 0!==u[1]?c=_:void 0!==u[2]?($.test(u[2])&&(r=RegExp("</"+u[2],"g")),c=m):void 0!==u[3]&&(c=m):c===m?">"===u[0]?(c=r??f,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?m:'"'===u[3]?g:p):c===g||c===p?c=m:c===v||c===_?c=f:(c=m,r=void 0);const x=c===m&&t[i+1].startsWith("/>")?" ":"";l+=c===f?s+n:d>=0?(o.push(a),s.slice(0,d)+e+s.slice(d)+h+x):s+h+(-2===d?i:x)}return[C(t,l+(t[s]||"<?>")+(2===i?"</svg>":"")),o]};class V{constructor({strings:t,_$litType$:s},n){let r;this.parts=[];let c=0,a=0;const u=t.length-1,d=this.parts,[f,v]=P(t,s);if(this.el=V.createElement(f,n),E.currentNode=this.el.content,2===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(r=E.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(e)){const i=v[a++],s=r.getAttribute(t).split(h),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:c,name:e[2],strings:s,ctor:"."===e[1]?k:"?"===e[1]?H:"@"===e[1]?I:R}),r.removeAttribute(t)}else t.startsWith(h)&&(d.push({type:6,index:c}),r.removeAttribute(t));if($.test(r.tagName)){const t=r.textContent.split(h),s=t.length-1;if(s>0){r.textContent=i?i.emptyScript:"";for(let i=0;i<s;i++)r.append(t[i],l()),E.nextNode(),d.push({type:2,index:++c});r.append(t[s],l())}}}else if(8===r.nodeType)if(r.data===o)d.push({type:2,index:c});else{let t=-1;for(;-1!==(t=r.data.indexOf(h,t+1));)d.push({type:7,index:c}),t+=h.length-1}c++}}static createElement(t,i){const s=r.createElement("template");return s.innerHTML=t,s}}function N(t,i,s=t,e){if(i===w)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=c(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(!1),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=N(t,h._$AS(t,i.values),h,e)),i}class S{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??r).importNode(i,!0);E.currentNode=e;let h=E.nextNode(),o=0,n=0,l=s[0];for(;void 0!==l;){if(o===l.index){let i;2===l.type?i=new M(h,h.nextSibling,this,t):1===l.type?i=new l.ctor(h,l.name,l.strings,this,t):6===l.type&&(i=new L(h,this,t)),this._$AV.push(i),l=s[++n]}o!==l?.index&&(h=E.nextNode(),o++)}return E.currentNode=r,e}p(t){let i=0;for(const s of this._$AV)void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++}}class M{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=T,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??!0}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=N(this,t,i),c(t)?t===T||null==t||""===t?(this._$AH!==T&&this._$AR(),this._$AH=T):t!==this._$AH&&t!==w&&this._(t):void 0!==t._$litType$?this.g(t):void 0!==t.nodeType?this.$(t):u(t)?this.T(t):this._(t)}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t))}_(t){this._$AH!==T&&c(this._$AH)?this._$AA.nextSibling.data=t:this.$(r.createTextNode(t)),this._$AH=t}g(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=V.createElement(C(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else{const t=new S(e,this),s=t.u(this.options);t.p(i),this.$(s),this._$AH=t}}_$AC(t){let i=A.get(t.strings);return void 0===i&&A.set(t.strings,i=new V(t)),i}T(t){a(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new M(this.k(l()),this.k(l()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e)}_$AR(t=this._$AA.nextSibling,i){for(this._$AP?.(!1,!0,i);t&&t!==this._$AB;){const i=t.nextSibling;t.remove(),t=i}}setConnected(t){void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t))}}class R{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=T,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=T}_$AI(t,i=this,s,e){const h=this.strings;let o=!1;if(void 0===h)t=N(this,t,i,0),o=!c(t)||t!==this._$AH&&t!==w,o&&(this._$AH=t);else{const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=N(this,e[s+n],i,n),r===w&&(r=this._$AH[n]),o||=!c(r)||r!==this._$AH[n],r===T?t=T:t!==T&&(t+=(r??"")+h[n+1]),this._$AH[n]=r}o&&!e&&this.O(t)}O(t){t===T?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"")}}class k extends R{constructor(){super(...arguments),this.type=3}O(t){this.element[this.name]=t===T?void 0:t}}class H extends R{constructor(){super(...arguments),this.type=4}O(t){this.element.toggleAttribute(this.name,!!t&&t!==T)}}class I extends R{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5}_$AI(t,i=this){if((t=N(this,t,i,0)??T)===w)return;const s=this._$AH,e=t===T&&s!==T||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==T&&(s===T||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t)}}class L{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s}get _$AU(){return this._$AM._$AU}_$AI(t){N(this,t)}}const z={j:e,P:h,A:o,C:1,M:P,L:S,R:u,V:N,D:M,I:R,H,N:I,U:k,B:L},Z=t.litHtmlPolyfillSupport;Z?.(V,M),(t.litHtmlVersions??=[]).push("3.1.0");const j=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new M(i.insertBefore(l(),t),t,void 0,s??{})}return h._$AI(t),h};export{z as _$LH,x as html,w as noChange,T as nothing,j as render,b as svg};
++const t=globalThis,i=t.trustedTypes,s=i?i.createPolicy("lit-html-desktop",{createHTML:t=>t}):void 0,e="$lit$",h=`lit$${(Math.random()+"").slice(9)}$`,o="?"+h,n=`<${o}>`,r=document,l=()=>r.createComment(""),c=t=>null===t||"object"!=typeof t&&"function"!=typeof t,a=Array.isArray,u=t=>a(t)||"function"==typeof t?.[Symbol.iterator],d="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,v=/-->/g,_=/>/g,m=RegExp(`>|${d}(?:([^\\s"'>=/]+)(${d}*=${d}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),p=/'/g,g=/"/g,$=/^(?:script|style|textarea|title)$/i,y=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),x=y(1),b=y(2),w=Symbol.for("lit-noChange"),T=Symbol.for("lit-nothing"),A=new WeakMap,E=r.createTreeWalker(r,129);function C(t,i){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==s?s.createHTML(i):i}const P=(t,i)=>{const s=t.length-1,o=[];let r,l=2===i?"<svg>":"",c=f;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,y=0;for(;y<s.length&&(c.lastIndex=y,u=c.exec(s),null!==u);)y=c.lastIndex,c===f?"!--"===u[1]?c=v:void 0!==u[1]?c=_:void 0!==u[2]?($.test(u[2])&&(r=RegExp("</"+u[2],"g")),c=m):void 0!==u[3]&&(c=m):c===m?">"===u[0]?(c=r??f,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?m:'"'===u[3]?g:p):c===g||c===p?c=m:c===v||c===_?c=f:(c=m,r=void 0);const x=c===m&&t[i+1].startsWith("/>")?" ":"";l+=c===f?s+n:d>=0?(o.push(a),s.slice(0,d)+e+s.slice(d)+h+x):s+h+(-2===d?i:x)}return[C(t,l+(t[s]||"<?>")+(2===i?"</svg>":"")),o]};class V{constructor({strings:t,_$litType$:s},n){let r;this.parts=[];let c=0,a=0;const u=t.length-1,d=this.parts,[f,v]=P(t,s);if(this.el=V.createElement(f,n),E.currentNode=this.el.content,2===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes)}for(;null!==(r=E.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(e)){const i=v[a++],s=r.getAttribute(t).split(h),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:c,name:e[2],strings:s,ctor:"."===e[1]?k:"?"===e[1]?H:"@"===e[1]?I:R}),r.removeAttribute(t)}else t.startsWith(h)&&(d.push({type:6,index:c}),r.removeAttribute(t));if($.test(r.tagName)){const t=r.textContent.split(h),s=t.length-1;if(s>0){r.textContent=i?i.emptyScript:"";for(let i=0;i<s;i++)r.append(t[i],l()),E.nextNode(),d.push({type:2,index:++c});r.append(t[s],l())}}}else if(8===r.nodeType)if(r.data===o)d.push({type:2,index:c});else{let t=-1;for(;-1!==(t=r.data.indexOf(h,t+1));)d.push({type:7,index:c}),t+=h.length-1}c++}}static createElement(t,i){const s=r.createElement("template");return s.innerHTML=t,s}}function N(t,i,s=t,e){if(i===w)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=c(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(!1),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=N(t,h._$AS(t,i.values),h,e)),i}class S{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??r).importNode(i,!0);E.currentNode=e;let h=E.nextNode(),o=0,n=0,l=s[0];for(;void 0!==l;){if(o===l.index){let i;2===l.type?i=new M(h,h.nextSibling,this,t):1===l.type?i=new l.ctor(h,l.name,l.strings,this,t):6===l.type&&(i=new L(h,this,t)),this._$AV.push(i),l=s[++n]}o!==l?.index&&(h=E.nextNode(),o++)}return E.currentNode=r,e}p(t){let i=0;for(const s of this._$AV)void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++}}class M{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=T,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??!0}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=N(this,t,i),c(t)?t===T||null==t||""===t?(this._$AH!==T&&this._$AR(),this._$AH=T):t!==this._$AH&&t!==w&&this._(t):void 0!==t._$litType$?this.g(t):void 0!==t.nodeType?this.$(t):u(t)?this.T(t):this._(t)}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t))}_(t){this._$AH!==T&&c(this._$AH)?this._$AA.nextSibling.data=t:this.$(r.createTextNode(t)),this._$AH=t}g(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=V.createElement(C(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else{const t=new S(e,this),s=t.u(this.options);t.p(i),this.$(s),this._$AH=t}}_$AC(t){let i=A.get(t.strings);return void 0===i&&A.set(t.strings,i=new V(t)),i}T(t){a(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new M(this.k(l()),this.k(l()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e)}_$AR(t=this._$AA.nextSibling,i){for(this._$AP?.(!1,!0,i);t&&t!==this._$AB;){const i=t.nextSibling;t.remove(),t=i}}setConnected(t){void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t))}}class R{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=T,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=T}_$AI(t,i=this,s,e){const h=this.strings;let o=!1;if(void 0===h)t=N(this,t,i,0),o=!c(t)||t!==this._$AH&&t!==w,o&&(this._$AH=t);else{const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=N(this,e[s+n],i,n),r===w&&(r=this._$AH[n]),o||=!c(r)||r!==this._$AH[n],r===T?t=T:t!==T&&(t+=(r??"")+h[n+1]),this._$AH[n]=r}o&&!e&&this.O(t)}O(t){t===T?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"")}}class k extends R{constructor(){super(...arguments),this.type=3}O(t){this.element[this.name]=t===T?void 0:t}}class H extends R{constructor(){super(...arguments),this.type=4}O(t){this.element.toggleAttribute(this.name,!!t&&t!==T)}}class I extends R{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5}_$AI(t,i=this){if((t=N(this,t,i,0)??T)===w)return;const s=this._$AH,e=t===T&&s!==T||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==T&&(s===T||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t)}}class L{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s}get _$AU(){return this._$AM._$AU}_$AI(t){N(this,t)}}const z={j:e,P:h,A:o,C:1,M:P,L:S,R:u,V:N,D:M,I:R,H,N:I,U:k,B:L},Z=t.litHtmlPolyfillSupport;Z?.(V,M),(t.litHtmlVersions??=[]).push("3.1.0");const j=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new M(i.insertBefore(l(),t),t,void 0,s??{})}return h._$AI(t),h};export{z as _$LH,x as html,w as noChange,T as nothing,j as render,b as svg};
+ //# sourceMappingURL=lit-html.js.map
diff --git a/third_party/node/typescript.patch b/third_party/node/patches/typescript.patch
similarity index 100%
rename from third_party/node/typescript.patch
rename to third_party/node/patches/typescript.patch
diff --git a/third_party/node/update_npm_deps b/third_party/node/update_npm_deps
index b7c1eb7..4bef662 100755
--- a/third_party/node/update_npm_deps
+++ b/third_party/node/update_npm_deps
@@ -25,10 +25,11 @@
 ./download_npm_deps_manually.sh
 
 echo 'Step 3: Applying local patches...'
-patch -d node_modules/chai/ -p1 < chai.patch
-patch -d node_modules/@types/d3/ -p1 < chromium_d3_types_index.patch
-patch -d node_modules/html-minifier/ -p1 < html_minifier.patch
-patch -p1 < typescript.patch
+patch -d node_modules/chai/ -p1 < patches/chai.patch
+patch -d node_modules/@types/d3/ -p1 < patches/chromium_d3_types_index.patch
+patch -d node_modules/html-minifier/ -p1 < patches/html_minifier.patch
+patch -p1 < patches/typescript.patch
+patch -p1 < patches/lit_html.patch
 
 echo 'Step 4: Filtering out unnecessary files...'
 rsync -c --delete -r -q --include-from="npm_include.txt" \
diff --git a/third_party/skia b/third_party/skia
index 276075b..63bed82 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 276075b975675ba92d907dd64247937b7b73245e
+Subproject commit 63bed826008ee7e0170ce9224c6aaab1b2f720f7
diff --git a/third_party/webrtc b/third_party/webrtc
index 2b311ea..acdc89d6 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 2b311ea96a10b87cec66e2626ecadfa08dd868b1
+Subproject commit acdc89d65328911b4e98afe0757028eca162cbb3
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 1ce18f05..8413e82 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -22210,6 +22210,7 @@
   <int value="4755" label="FedCmDomainHint"/>
   <int value="4756" label="LCPImageWasLazy"/>
   <int value="4757" label="EventTargetOnObservable"/>
+  <int value="4758" label="CredentialManagerCrossOriginPublicKeyCreateRequest"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
@@ -22335,6 +22336,7 @@
   <int value="114" label="UsbUnrestricted"/>
   <int value="115" label="CapturedSurfaceControl"/>
   <int value="116" label="SubApps"/>
+  <int value="117" label="PublicKeyCredentialsCreate"/>
 </enum>
 
 <enum name="FedCmAccountsResponseInvalidReason">
@@ -22470,7 +22472,7 @@
   <int value="3" label="Accounts not received and pop-up not closed by IDP"/>
 </enum>
 
-<enum name="FedCmPreventSilentAccessFrameType">
+<enum name="FedCmRequesterFrameType">
   <int value="0" label="Main frame"/>
   <int value="1" label="Iframe which is same-site as main frame"/>
   <int value="2" label="Iframe which is cross-site as main frame"/>
@@ -42476,6 +42478,9 @@
 </enum>
 
 <enum name="PageState">
+  <obsolete>
+    Deprecated as of Dec 2023 (M122).
+  </obsolete>
   <int value="0" label="Focused"/>
   <int value="1" label="Visible"/>
   <int value="2" label="Background"/>
@@ -50638,6 +50643,7 @@
   <int value="1" label="Download recovered from cache"/>
   <int value="2" label="Session has ongoing download"/>
   <int value="3" label="New download task created"/>
+  <int value="4" label="Rejected (too many tasks)"/>
 </enum>
 
 <enum name="UpdateClientCanUpdateResult">
diff --git a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
index 051ae25..542d02a4 100644
--- a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
@@ -10,6 +10,7 @@
 sinansahin@google.com
 mvanouwerkerk@chromium.org
 jinsukkim@chromium.org
+wenyufu@chromium.org
 # apps
 nancylingwang@chromium.org
 tby@chromium.org
diff --git a/tools/metrics/histograms/metadata/android/OWNERS b/tools/metrics/histograms/metadata/android/OWNERS
index be1b875e..29e23c64 100644
--- a/tools/metrics/histograms/metadata/android/OWNERS
+++ b/tools/metrics/histograms/metadata/android/OWNERS
@@ -9,4 +9,5 @@
 mvanouwerkerk@chromium.org
 sinansahin@google.com
 jinsukkim@chromium.org
-altimin@chromium.org
\ No newline at end of file
+altimin@chromium.org
+wenyufu@chromium.org
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index 967ed52b..746f256 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -1494,6 +1494,12 @@
   <int value="13" label="Unknown reason for failure"/>
 </enum>
 
+<enum name="SwipeEdge">
+  <summary>The edge from which the gesture is swiped from.</summary>
+  <int value="0" label="LeftEdge"/>
+  <int value="1" label="RightEdge"/>
+</enum>
+
 <enum name="TabListEditorShareActionState">
   <int value="0" label="UNKNOWN_SHARE_STATE"/>
   <int value="1" label="SUCCESSFULLY_SHARED_TABS"/>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index a40a653..e5deeb5 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -619,13 +619,19 @@
   </summary>
 </histogram>
 
-<histogram name="Android.BackPress.Intercept" enum="BackPressConsumer"
-    expires_after="2024-05-12">
+<histogram name="Android.BackPress.Intercept{Direction}"
+    enum="BackPressConsumer" expires_after="2024-05-12">
   <owner>lazzzis@chromium.org</owner>
   <owner>src/chrome/browser/back_press/android/OWNERS</owner>
   <summary>
-    Records that a specific feature intercepted the system's back press gesture.
+    Recorded when a specific feature intercepted the system's back press
+    gesture{Direction}.
   </summary>
+  <token key="Direction">
+    <variant name="" summary=""/>
+    <variant name=".LeftEdge" summary="from left edge in U+."/>
+    <variant name=".RightEdge" summary="from right edge in U+."/>
+  </token>
 </histogram>
 
 <histogram name="Android.BackPress.Interval" units="ms"
@@ -658,6 +664,20 @@
   </summary>
 </histogram>
 
+<histogram name="Android.BackPress.SwipeEdge{Type}" enum="SwipeEdge"
+    expires_after="2024-05-12">
+  <owner>lazzzis@chromium.org</owner>
+  <owner>src/chrome/browser/back_press/android/OWNERS</owner>
+  <summary>
+    Record the edge from which the gesture is swiped from {Type}.
+  </summary>
+  <token key="Type">
+    <variant name="" summary=""/>
+    <variant name=".TabHistoryNavigation"
+        summary="when a back gesture triggers a tab history navigation."/>
+  </token>
+</histogram>
+
 <histogram name="Android.BindingManger.ConnectionsDroppedDueToMaxSize"
     units="connections" expires_after="2024-04-24">
   <owner>ckitagawa@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index 3bc858c1..1d223ee 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -1314,6 +1314,18 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.FedCm.Disconnect.FrameType"
+    enum="FedCmRequesterFrameType" expires_after="2024-06-13">
+  <owner>npm@chromium.org</owner>
+  <owner>web-identity-eng@google.com</owner>
+  <summary>
+    Records the type of frame that invokes disconnect(). Records once for each
+    disconnect() call. Possible values are: main frame, same-site iframe, and
+    cross-site iframe, where the site of the iframe is compared with the site of
+    the main frame.
+  </summary>
+</histogram>
+
 <histogram name="Blink.FedCm.DomainHint.NumMatchingAccounts"
     enum="FedCmNumAccounts" expires_after="2024-06-12">
   <owner>npm@chromium.org</owner>
@@ -1495,7 +1507,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.PreventSilentAccessFrameType"
-    enum="FedCmPreventSilentAccessFrameType" expires_after="2024-05-10">
+    enum="FedCmRequesterFrameType" expires_after="2024-05-10">
   <owner>npm@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index 79c4b693..b91f224 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -160,16 +160,6 @@
   </summary>
 </histogram>
 
-<histogram name="Extensions.AppTabLaunchType" enum="ExtensionLaunchType"
-    expires_after="2023-03-11">
-  <owner>rdevlin.cronin@chromium.org</owner>
-  <owner>extensions-core@chromium.org</owner>
-  <summary>
-    The number of extension apps launched grouped by extensions::LaunchType.
-    From 2022-09 this no longer includes web app launches.
-  </summary>
-</histogram>
-
 <histogram name="Extensions.BackgroundHostCreatedForExtension"
     enum="BackgroundHostCreatedForExtensionValues" expires_after="2024-05-19">
   <owner>fdoray@chromium.org</owner>
@@ -1494,7 +1484,7 @@
 </histogram>
 
 <histogram name="Extensions.ExtensionReenabledRemotely" units="count"
-    expires_after="2023-12-31">
+    expires_after="2024-06-10">
   <owner>anunoy@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/network/enums.xml b/tools/metrics/histograms/metadata/network/enums.xml
index e7caee6..83aa1e0 100644
--- a/tools/metrics/histograms/metadata/network/enums.xml
+++ b/tools/metrics/histograms/metadata/network/enums.xml
@@ -2475,10 +2475,6 @@
   <int value="45" label="kReasonCodeCipherSuiteNotSupported"/>
 </enum>
 
-<enum name="WiFiRestartReason">
-  <int value="0" label="kRestartReasonCannotAssoc"/>
-</enum>
-
 <enum name="WiFiRoamComplete">
   <int value="0" label="Roam success"/>
   <int value="1" label="Roam failure"/>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index 6c165cd..1152122 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -3708,19 +3708,6 @@
   </summary>
 </histogram>
 
-<histogram base="true" name="Network.Shill.WiFi.RestartReason"
-    enum="WiFiRestartReason" expires_after="2024-04-18">
-  <owner>billyzhao@chromium.org</owner>
-  <owner>cros-network-metrics@google.com</owner>
-  <summary>
-    ChromeOS network metric recording instances where Shill is told to restart
-    the WiFi device. This is triggered by udev event from the iwl7000 driver.
-    See b/270746800 for more details. This metric will be removed once we notice
-    the driver not sending these requests. This removal process is tracked in
-    b/278765529.
-  </summary>
-</histogram>
-
 <histogram base="true" name="Network.Shill.WiFi.RoamComplete"
     enum="WiFiRoamComplete" expires_after="2024-05-19">
 <!-- Name completed by histogram_suffixes name="RoamSecurityType" -->
diff --git a/tools/metrics/histograms/metadata/permissions/enums.xml b/tools/metrics/histograms/metadata/permissions/enums.xml
index 337c9f6..4a7c382 100644
--- a/tools/metrics/histograms/metadata/permissions/enums.xml
+++ b/tools/metrics/histograms/metadata/permissions/enums.xml
@@ -186,8 +186,9 @@
   <int value="3" label="Page action bubble"/>
   <int value="4" label="Permission settings from Android OS"/>
   <int value="5" label="Inline settings as part of the event's UI"/>
-  <int value="6" label="Automatic revocation"/>
-  <int value="7" label="Unidentified source"/>
+  <int value="6" label="Automatic revocation from Safe Browsing"/>
+  <int value="7" label="Automatic revocation from Safety Hub"/>
+  <int value="8" label="Unidentified source"/>
 </enum>
 
 <enum name="PermissionsPolicyConfiguration">
diff --git a/tools/metrics/histograms/metadata/permissions/histograms.xml b/tools/metrics/histograms/metadata/permissions/histograms.xml
index d311dfd..b7944ae8 100644
--- a/tools/metrics/histograms/metadata/permissions/histograms.xml
+++ b/tools/metrics/histograms/metadata/permissions/histograms.xml
@@ -1204,7 +1204,7 @@
 </histogram>
 
 <histogram name="Permissions.Usage.Notifications.DidRecordUkm" enum="Boolean"
-    expires_after="2024-01-01">
+    expires_after="2024-05-05">
   <owner>engedy@chromium.org</owner>
   <owner>willxu@google.com</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/sharing/OWNERS b/tools/metrics/histograms/metadata/sharing/OWNERS
index ea8bdf0..38aa9100 100644
--- a/tools/metrics/histograms/metadata/sharing/OWNERS
+++ b/tools/metrics/histograms/metadata/sharing/OWNERS
@@ -4,3 +4,4 @@
 # Use chromium-metrics-reviews@google.com as a backup.
 ellyjones@chromium.org
 jeffreycohen@chromium.org
+wenyufu@chromium.org
diff --git a/tools/metrics/histograms/metadata/web_core/histograms.xml b/tools/metrics/histograms/metadata/web_core/histograms.xml
index a6369183..5f06883e 100644
--- a/tools/metrics/histograms/metadata/web_core/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_core/histograms.xml
@@ -106,18 +106,28 @@
   </summary>
 </histogram>
 
-<histogram name="WebCore.Fullscreen.LockStateAtEntryVia{EntryMethod}"
+<histogram name="WebCore.Fullscreen.LockStateAtEntryViaApi"
     enum="FullscreenLockState" expires_after="2024-06-07">
   <owner>takumif@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
     This metric records whether keyboard/pointer locks are engaged each time a
-    web page enters fullscreen via {EntryMethod}.
+    web page enters fullscreen via the Fullscreen API.
   </summary>
-  <token key="EntryMethod">
-    <variant name="Api" summary="the Fullscreen API"/>
-    <variant name="BrowserUi" summary="the browser UI"/>
-  </token>
+</histogram>
+
+<histogram name="WebCore.Fullscreen.LockStateAtEntryViaBrowserUi"
+    enum="FullscreenLockState" expires_after="2024-06-07">
+  <owner>takumif@chromium.org</owner>
+  <owner>openscreen-eng@google.com</owner>
+  <summary>
+    This metric records whether keyboard/pointer locks are engaged each time the
+    user enters fullscreen via the browser UI.
+
+    Note that even when keyboard lock has been requested, it doesn't get
+    activated when entering fullscreen through the browser UI. The Fullscreen
+    API must (also) be called.
+  </summary>
 </histogram>
 
 <histogram name="WebCore.HTMLDocumentParser.PreloadScannerAppCacheDelayTime"
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index bbf2ec3..96ba22c 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -4418,6 +4418,14 @@
       `kFederatedIdentityAutoReauthnEmbargoDuration`).
     </summary>
   </metric>
+  <metric name="Disconnect.FrameType" enum="FedCmRequesterFrameType">
+    <summary>
+      Records the type of frame that invokes disconnect(). Records once for each
+      disconnect() call. Possible values are: main frame, same-site iframe, and
+      cross-site iframe, where the site of the iframe is compared with the site
+      of the main frame.
+    </summary>
+  </metric>
   <metric name="Error.ErrorDialogResult" enum="FedCmErrorDialogResult">
     <summary>
       Records the outcome of the error dialog. Recorded at most once per API
@@ -4458,13 +4466,12 @@
       the count is 0. The bucketing method used is base::UmaHistogramCounts100.
     </summary>
   </metric>
-  <metric name="PreventSilentAccessFrameType"
-      enum="FedCmPreventSilentAccessFrameType">
+  <metric name="PreventSilentAccessFrameType" enum="FedCmRequesterFrameType">
     <summary>
       Records the type of frame that invokes preventSilentAccess(). Records only
       when there is an existing FedCM sharing permission on the main frame, and
       some frame in the page invokes the preventSilentAccess() JavaScript call.
-      Possible values are: main frame, same-site iframe, and cros-site iframe,
+      Possible values are: main frame, same-site iframe, and cross-site iframe,
       where the site of the iframe is compared with the site of the main frame.
     </summary>
   </metric>
@@ -4582,6 +4589,14 @@
       Records a 1 each time a request is sent to the accounts endpoint.
     </summary>
   </metric>
+  <metric name="Disconnect.FrameType" enum="FedCmRequesterFrameType">
+    <summary>
+      Records the type of frame that invokes disconnect(). Records once for each
+      disconnect() call. Possible values are: main frame, same-site iframe, and
+      cross-site iframe, where the site of the iframe is compared with the site
+      of the main frame.
+    </summary>
+  </metric>
   <metric name="Error.ErrorDialogResult" enum="FedCmErrorDialogResult">
     <summary>
       Records the outcome of the error dialog. Recorded at most once per API
@@ -20025,6 +20040,9 @@
 </event>
 
 <event name="PerformanceManager.PageTimelineState">
+  <obsolete>
+    Deprecated as of Dec 2023 (M122).
+  </obsolete>
   <owner>anthonyvd@chromium.org</owner>
   <owner>spvw@chromium.org</owner>
   <summary>
@@ -27915,6 +27933,18 @@
   </metric>
 </event>
 
+<event name="Wallet.BoardingPassDetect" singular="True">
+  <owner>hanfeng@google.com</owner>
+  <summary>
+    Records boarding pass barcode detection results.
+  </summary>
+  <metric name="Detected" enum="Boolean">
+    <summary>
+      Whether a boarding pass barocode is detected.
+    </summary>
+  </metric>
+</event>
+
 <event name="WebAPK.Install" singular="True">
   <owner>yfriedman@chromium.org</owner>
   <owner>hartmanng@chromium.org</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index fd6e38b..e8ae477 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,16 +5,16 @@
             "full_remote_path": "perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "609f6252c824a6086e3dc7aa17d1b40bf4d23524",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/84bb6bced2420d2e7bd880cbf30b348448fa95d4/trace_processor_shell.exe"
+            "hash": "d8bdf3585476c6999118803e3caacc79d5551f85",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/819068d7e83a09200ee0b3a43fe44d90465cca5e/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "8aa911491cd365131216862ae495e18424ebe1b2",
             "full_remote_path": "perfetto-luci-artifacts/3e53e144bee271ec558363df2e561a77d7e0b789/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "c584fa0ead673b142de66fb9fed98c95679f9cb9",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/84bb6bced2420d2e7bd880cbf30b348448fa95d4/trace_processor_shell"
+            "hash": "d4b484d9567370f690c6af2cf4e26fc973c673ec",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/819068d7e83a09200ee0b3a43fe44d90465cca5e/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "28d38c5eef93cf965ad3b3c656d4419f8e55f864",
diff --git a/ui/accessibility/extensions/chromevoxclassic/BUILD.gn b/ui/accessibility/extensions/chromevoxclassic/BUILD.gn
index 98c6d10..1f66997 100644
--- a/ui/accessibility/extensions/chromevoxclassic/BUILD.gn
+++ b/ui/accessibility/extensions/chromevoxclassic/BUILD.gn
@@ -357,7 +357,8 @@
   output_file = invoker.output_file
   key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEGBi/oD7Yl/Y16w3+gee/95/EUpRZ2U6c+8orV5ei+3CRsBsoXI/DPGBauZ3rWQ47aQnfoG00sXigFdJA2NhNK9OgmRA2evnsRRbjYm2BG1twpaLsgQPPus3PyczbDCvhFu8k24wzFyEtxLrfxAGBseBPb9QrCz7B4k2QgxD/CwIDAQAB"
   action(target_name) {
-    script = "//chrome/browser/resources/chromeos/accessibility/chromevox/tools/generate_manifest.py"
+    script =
+        "//chrome/browser/resources/accessibility/tools/generate_manifest.py"
     inputs = [
       version_file,
       version_script,