diff --git a/BUILD.gn b/BUILD.gn
index 8f37cd2..28ba2fe1 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -390,7 +390,6 @@
         "//third_party/androidx_javascriptengine",
         "//third_party/catapult/devil",
         "//third_party/jni_zero/sample:jni_generator_tests",
-        "//third_party/jni_zero/test:test_jni_apk",
         "//third_party/r8:custom_d8_java",
         "//tools/android:android_tools",
         "//tools/android:memconsumer",
diff --git a/DEPS b/DEPS
index a9048e52..e02b5fc 100644
--- a/DEPS
+++ b/DEPS
@@ -308,15 +308,15 @@
   # 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': 'f5048154f82911c84ab994c1546290d567a75ec4',
+  'src_internal_revision': 'e992b1f6ab810377d0c9cb55aa6a82752df9e8d9',
   # 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': '734953afbc19e81884e465829685d2574fe40fc9',
+  'skia_revision': '170fbb029e61a116431fd96a35eeeca8ee22c2b2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'c3a7867744ac5753621f6ff77120217cf165081d',
+  'v8_revision': 'b53eb026438b102249f055de1af5d8aff5bb0779',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -339,7 +339,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': '18.20240215.1.1',
+  'fuchsia_version': 'version:18.20240215.1.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -383,11 +383,11 @@
   # 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': '6f4a0d6c8731078f74f3de4fc92748c125da43d6',
+  'catapult_revision': '7fccadad2a22e004112191dcd082108eb8297b14',
   # 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.
-  'chromium_variations_revision': '1fae85ca70bdf203941f8ac90392e7d471247147',
+  'chromium_variations_revision': 'd1a16c439233c3c7199fcc3f0507b1d012611ab9',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -427,7 +427,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '996ab52aeac453060d7677672ce28e0504ff75f3',
+  'dawn_revision': '7b482f48e63500c46952160a5b71b5bb91c746f0',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -479,7 +479,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'libcxxabi_revision':    '5b35c9f06c3f8f17b43bd3da9527d6fecdf916c2',
+  'libcxxabi_revision':    '204deaa9c53f76d6f23e0d119fc0110adc3ea6f2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -827,12 +827,12 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '86871e647c038acdf24601d6955b545a59642446',
+    '15ea959101d2efe298a00841d9f8c015a387b57b',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'ecc6daec0bc6bc14edaad139f726d7a0154d4572',
+    'url': Var('chromium_git') + '/website.git' + '@' + '05de69cbec5c294245bfb96d9be826065c05e6eb',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -1164,7 +1164,7 @@
   # Tools used when building Chrome for Chrome OS. This affects both the Simple
   # Chrome workflow, as well as the chromeos-chrome ebuild.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '21c56f5c330bab014098f49c6d5b9f2f9cba3473',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '7efba5ebf4460a8038d1d8155528d960f8df82ab',
       'condition': 'checkout_chromeos',
   },
 
@@ -1199,13 +1199,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '4df61147ba67316806617f74347f408d2e4ff2f1',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'd972b831c3ca2be979c4009a1fb66baca9dc0b77',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '2173cbdf60dac9a327293104c19c93e63a3e46ee',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'f1626580921976b4de940622ede2cdd7a6911c91',
     'condition': 'checkout_src_internal',
   },
 
@@ -1665,7 +1665,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '6427f365ba48d17a474a837b2596ba683655386e',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'cd05247e5cb7251177910169dfe482f586f6a5bd',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '8ef97ff3b7332e38e61b347a2fbed425a4617151',
@@ -1850,7 +1850,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '98673cc24786be6c10dd8908e0b0b4ed27625c6a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '524a06bc5423ef2289bc6102ee5a2b9e747137cc',
+    Var('webrtc_git') + '/src.git' + '@' + 'a8c47276cb6d3188db8b5a4ba77abd07797c0071',
 
   # 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.
@@ -4036,7 +4036,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        'cf123dadc9bb84e815991922cc9d4d9277b8b4e5',
+        'ee19009832d5daab66094b1325d8d42ebb61e1ad',
       'condition': 'checkout_src_internal',
   },
 
@@ -4096,7 +4096,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        '4f50ee554da55ee6d7c1382a8b06233257b9ee81',
+        'e8610f3ba765ba96d259acd21b689db6e0cbd181',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_field_trials.cc b/android_webview/browser/aw_field_trials.cc
index 50c5edb..ef37f20 100644
--- a/android_webview/browser/aw_field_trials.cc
+++ b/android_webview/browser/aw_field_trials.cc
@@ -182,10 +182,6 @@
   // FedCM is not yet supported on WebView.
   aw_feature_overrides.DisableFeature(::features::kFedCm);
 
-  // Storage Access permission prompts are not supported on WebView.
-  aw_feature_overrides.DisableFeature(
-      permissions::features::kPermissionStorageAccessAPI);
-
   // Disable enhanced track-pad features until WebView's experiment
   // is fully rolled out to stable.
   aw_feature_overrides.DisableFeature(ui::kConvertTrackpadEventsToMouse);
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 73c3d5d..acc36ed 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -540,6 +540,8 @@
     "display/display_highlight_controller.h",
     "display/display_move_window_util.cc",
     "display/display_move_window_util.h",
+    "display/display_performance_mode_controller.cc",
+    "display/display_performance_mode_controller.h",
     "display/display_prefs.cc",
     "display/display_prefs.h",
     "display/display_shutdown_observer.cc",
@@ -3422,6 +3424,7 @@
     "display/display_highlight_controller_unittest.cc",
     "display/display_manager_unittest.cc",
     "display/display_move_window_util_unittest.cc",
+    "display/display_performance_mode_controller_unittest.cc",
     "display/display_prefs_unittest.cc",
     "display/display_util_unittest.cc",
     "display/extended_mouse_warp_controller_unittest.cc",
diff --git a/ash/accelerators/ash_accelerator_configuration.cc b/ash/accelerators/ash_accelerator_configuration.cc
index c2f63e4..6317b6f 100644
--- a/ash/accelerators/ash_accelerator_configuration.cc
+++ b/ash/accelerators/ash_accelerator_configuration.cc
@@ -19,7 +19,6 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/containers/span.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
@@ -500,7 +499,7 @@
   CHECK(*found_id == action_id);
 
   // Remove accelerator from lookup map.
-  base::Erase(found_accelerators_iter->second, accelerator);
+  std::erase(found_accelerators_iter->second, accelerator);
 
   // Remove accelerator from reverse lookup map.
   accelerator_to_id_.Erase(accelerator);
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc
index a53f93dc..7aa0cad 100644
--- a/ash/app_list/app_list_controller_impl.cc
+++ b/ash/app_list/app_list_controller_impl.cc
@@ -631,7 +631,7 @@
   if (split_view_active) {
     foreground_windows = {split_view_controller->primary_window(),
                           split_view_controller->secondary_window()};
-    base::EraseIf(foreground_windows,
+    std::erase_if(foreground_windows,
                   [](aura::Window* window) { return !window; });
   } else if (!windows.empty() && !WindowState::Get(windows[0])->IsMinimized()) {
     foreground_windows.push_back(windows[0]);
diff --git a/ash/app_list/app_list_metrics.cc b/ash/app_list/app_list_metrics.cc
index bdeb287c7d..cd0c823 100644
--- a/ash/app_list/app_list_metrics.cc
+++ b/ash/app_list/app_list_metrics.cc
@@ -52,12 +52,6 @@
 // mode AppList) and the Shelf.
 constexpr char kAppListAppLaunched[] = "Apps.AppListAppLaunchedV2";
 
-// UMA histograms that log app launches within the app list, and the shelf.
-// Split depending on whether tablet mode is active or not.
-constexpr char kAppLaunchInTablet[] = "Apps.AppList.AppLaunched.TabletMode";
-constexpr char kAppLaunchInClamshell[] =
-    "Apps.AppList.AppLaunched.ClamshellMode";
-
 // UMA histograms that log launcher workflow actions (launching an app, search
 // result, or a continue section task) in the app list UI. Split depending on
 // whether tablet mode is active or not. Note that unlike `kAppListAppLaunched`
@@ -276,13 +270,6 @@
                               bool app_list_shown) {
   UMA_HISTOGRAM_ENUMERATION(kAppListAppLaunched, launched_from);
 
-  if (is_tablet_mode) {
-    base::UmaHistogramEnumeration(kAppLaunchInTablet, launched_from);
-
-  } else {
-    base::UmaHistogramEnumeration(kAppLaunchInClamshell, launched_from);
-  }
-
   if (!is_tablet_mode) {
     if (!app_list_shown) {
       UMA_HISTOGRAM_ENUMERATION(kAppListAppLaunchedClosed, launched_from);
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index b5c4f44..6660155 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -7680,6 +7680,9 @@
       <message name="IDS_ASH_DOWNLOAD_COMMAND_TEXT_CANCEL" desc="Text of the command to cancel a download.">
         Cancel
       </message>
+      <message name="IDS_ASH_DOWNLOAD_COMMAND_TEXT_COPY_TO_CLIPBOARD" desc="Text of the command to copy the download file to clipboard.">
+        Copy to clipboard
+      </message>
       <message name="IDS_ASH_DOWNLOAD_COMMAND_TEXT_PAUSE" desc="Text of the command to pause a download.">
         Pause
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_DOWNLOAD_COMMAND_TEXT_COPY_TO_CLIPBOARD.png.sha1 b/ash/ash_strings_grd/IDS_ASH_DOWNLOAD_COMMAND_TEXT_COPY_TO_CLIPBOARD.png.sha1
new file mode 100644
index 0000000..6032559
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_DOWNLOAD_COMMAND_TEXT_COPY_TO_CLIPBOARD.png.sha1
@@ -0,0 +1 @@
+78af3668f6d2945de8f98d57b602be639ddb81e8
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_camera_controller.cc b/ash/capture_mode/capture_mode_camera_controller.cc
index 5c1b583..76421f8 100644
--- a/ash/capture_mode/capture_mode_camera_controller.cc
+++ b/ash/capture_mode/capture_mode_camera_controller.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <cstring>
+#include <vector>
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/capture_mode/capture_mode_camera_preview_view.h"
@@ -682,7 +683,7 @@
   DCHECK(camera_preview_view_);
   DCHECK_EQ(selected_camera_, camera_preview_view_->camera_id());
 
-  base::EraseIf(available_cameras_, [&](const CameraInfo& info) {
+  std::erase_if(available_cameras_, [&](const CameraInfo& info) {
     return selected_camera_ == info.camera_id;
   });
 
@@ -947,7 +948,7 @@
 
   // Move `camera_preview_snap_position_` to the beginning of `snap_positions`
   // vector, since we should always try the current snap position first.
-  base::EraseIf(snap_positions,
+  std::erase_if(snap_positions,
                 [this](CameraPreviewSnapPosition snap_position) {
                   return snap_position == camera_preview_snap_position_;
                 });
diff --git a/ash/capture_mode/capture_mode_demo_tools_controller.cc b/ash/capture_mode/capture_mode_demo_tools_controller.cc
index 5ed1a8a6..2bfdb1df 100644
--- a/ash/capture_mode/capture_mode_demo_tools_controller.cc
+++ b/ash/capture_mode/capture_mode_demo_tools_controller.cc
@@ -5,6 +5,7 @@
 #include "ash/capture_mode/capture_mode_demo_tools_controller.h"
 
 #include <memory>
+#include <vector>
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/capture_mode/capture_mode_constants.h"
@@ -18,7 +19,6 @@
 #include "ash/shell.h"
 #include "base/check_op.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/location.h"
 #include "base/notreached.h"
@@ -380,7 +380,7 @@
 
 void CaptureModeDemoToolsController::OnMouseHighlightAnimationEnded(
     PointerHighlightLayer* pointer_highlight_layer_ptr) {
-  base::EraseIf(mouse_highlight_layers_,
+  std::erase_if(mouse_highlight_layers_,
                 base::MatchesUniquePtr(pointer_highlight_layer_ptr));
 
   if (on_mouse_highlight_animation_ended_callback_for_test_)
diff --git a/ash/capture_mode/capture_mode_menu_group.cc b/ash/capture_mode/capture_mode_menu_group.cc
index 0330ad7..50f0065 100644
--- a/ash/capture_mode/capture_mode_menu_group.cc
+++ b/ash/capture_mode/capture_mode_menu_group.cc
@@ -16,7 +16,6 @@
 #include "ash/style/ash_color_id.h"
 #include "ash/style/color_util.h"
 #include "ash/style/style_util.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/memory/raw_ptr.h"
 #include "base/ranges/algorithm.h"
 #include "ui/accessibility/ax_enums.mojom-shared.h"
@@ -418,7 +417,7 @@
     return;
 
   options_container_->RemoveChildViewT(option);
-  base::Erase(options_, option);
+  std::erase(options_, option);
 }
 
 void CaptureModeMenuGroup::AddMenuItem(views::Button::PressedCallback callback,
diff --git a/ash/clipboard/clipboard_history_resource_manager.cc b/ash/clipboard/clipboard_history_resource_manager.cc
index 916ca78..b1414be3a 100644
--- a/ash/clipboard/clipboard_history_resource_manager.cc
+++ b/ash/clipboard/clipboard_history_resource_manager.cc
@@ -5,6 +5,7 @@
 #include "ash/clipboard/clipboard_history_resource_manager.h"
 
 #include <string>
+#include <vector>
 
 #include "ash/clipboard/clipboard_history_item.h"
 #include "ash/clipboard/clipboard_history_url_title_fetcher.h"
@@ -243,7 +244,7 @@
 
   // If `item` was attached to a pending request, make sure it is not updated
   // when rendering finishes.
-  base::Erase(image_model_request->clipboard_history_item_ids, item.id());
+  std::erase(image_model_request->clipboard_history_item_ids, item.id());
 
   if (image_model_request->clipboard_history_item_ids.empty()) {
     // If no more items are waiting on the image model, cancel the request.
diff --git a/ash/components/arc/test/fake_file_system_instance.cc b/ash/components/arc/test/fake_file_system_instance.cc
index 04429b9..954a8bc9 100644
--- a/ash/components/arc/test/fake_file_system_instance.cc
+++ b/ash/components/arc/test/fake_file_system_instance.cc
@@ -11,9 +11,9 @@
 #include <optional>
 #include <sstream>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/files/file.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -603,7 +603,7 @@
 
   // Remove this document from lists of children.
   for (auto& child_iter : child_documents_) {
-    base::Erase(child_iter.second, key);
+    std::erase(child_iter.second, key);
   }
 
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 7b4fd69..8b543b1a 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -3301,6 +3301,10 @@
   return base::FeatureList::IsEnabled(kDeskTemplateSync);
 }
 
+bool IsDisplayPerformanceModeEnabled() {
+  return base::FeatureList::IsEnabled(kDisplayPerformanceMode);
+}
+
 bool IsInputDeviceSettingsLoggingEnabled() {
   return base::FeatureList::IsEnabled(kEnableInputDeviceSettingsLogging);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index f629f00..1e1f195 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -959,6 +959,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDemoModeGMSCoreWindowCloserEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDeskButtonEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDeskTemplateSyncEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsDisplayPerformanceModeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsInputDeviceSettingsLoggingEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsInputDeviceSettingsSplitEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsPeripheralCustomizationEnabled();
diff --git a/ash/display/display_performance_mode_controller.cc b/ash/display/display_performance_mode_controller.cc
new file mode 100644
index 0000000..e704b44
--- /dev/null
+++ b/ash/display/display_performance_mode_controller.cc
@@ -0,0 +1,71 @@
+// Copyright 2024 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/display/display_performance_mode_controller.h"
+
+namespace ash {
+
+using ModeState = DisplayPerformanceModeController::ModeState;
+
+DisplayPerformanceModeController::DisplayPerformanceModeController()
+    : power_status_(PowerStatus::Get()->GetWeakPtr()) {
+  power_status_->AddObserver(this);
+}
+
+DisplayPerformanceModeController::~DisplayPerformanceModeController() {
+  if (power_status_) {
+    power_status_->RemoveObserver(this);
+  }
+}
+
+ModeState DisplayPerformanceModeController::AddObserver(Observer* observer) {
+  observers_.AddObserver(observer);
+  return current_state_;
+}
+
+void DisplayPerformanceModeController::RemoveObserver(Observer* observer) {
+  observers_.RemoveObserver(observer);
+}
+
+void DisplayPerformanceModeController::OnPowerStatusChanged() {
+  UpdateCurrentStateAndNotifyIfChanged();
+}
+
+void DisplayPerformanceModeController::SetHighPerformanceModeByUser(
+    bool is_high_performance_enabled) {
+  is_high_performance_enabled_ = is_high_performance_enabled;
+  UpdateCurrentStateAndNotifyIfChanged();
+}
+
+void DisplayPerformanceModeController::UpdateCurrentStateAndNotifyIfChanged() {
+  //   Implementation Logic:
+  //   1. If the user has enabled the high performance mode in the UI, then the
+  //      display features should be in the high performance mode regardless of
+  //      the power status.
+  //   2. If the user has not enabled the high performance mode in the UI, then
+  //      the display features should be in the power saver mode if the power
+  //      status is in battery saver mode.
+  //   3. If the user has not enabled the high performance mode in the UI, then
+  //      the display features should be in the intelligent mode if the power
+  //      status is not in battery saver mode.
+  ModeState new_state = ModeState::kIntelligent;
+  if (is_high_performance_enabled_) {
+    new_state = ModeState::kHighPerformance;
+  } else if (PowerStatus::Get()->IsBatterySaverActive()) {
+    new_state = ModeState::kPowerSaver;
+  }
+
+  if (new_state != current_state_) {
+    current_state_ = new_state;
+    NotifyObservers();
+  }
+}
+
+void DisplayPerformanceModeController::NotifyObservers() {
+  for (Observer& observer : observers_) {
+    observer.OnDisplayPerformanceModeChanged(current_state_);
+  }
+}
+
+}  // namespace ash
diff --git a/ash/display/display_performance_mode_controller.h b/ash/display/display_performance_mode_controller.h
new file mode 100644
index 0000000..7244059
--- /dev/null
+++ b/ash/display/display_performance_mode_controller.h
@@ -0,0 +1,93 @@
+// Copyright 2024 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_DISPLAY_DISPLAY_PERFORMANCE_MODE_CONTROLLER_H_
+#define ASH_DISPLAY_DISPLAY_PERFORMANCE_MODE_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "ash/system/power/power_status.h"
+#include "base/scoped_observation.h"
+
+namespace ash {
+
+// DisplayPerformanceModeController listens to the power status change and the
+// Display Performance Mode change in the UI to dictate what state the display
+// features should be at. Display features that want to depend on the power and
+// user preference would listen to this controller and update their state
+// accordingly.
+class ASH_EXPORT DisplayPerformanceModeController
+    : public PowerStatus::Observer {
+ public:
+  // DisplayPerformanceModeController exposes 3 different modes that a client
+  // observing a mode change gets:
+
+  // kHighPerformance: This mode is enabled by the user that wants the best and
+  // smoothest display experience. This mode does not necessarily indicate that
+  // a power source is connected. However, in this mode, the display features
+  // will prioritize performance over power efficiency.
+
+  // kIntelligent: This mode represents an intelligent state for the display
+  // features. Users can expect that the display features will dynamically
+  // adjust their behavior based on the power status and user preferences. The
+  // display features will strive to balance performance and power efficiency,
+  // optimizing the user experience. This mode is the default mode.
+
+  // kPowerSaver: This mode represents a power-saving state for the display
+  // features. Users can expect that the display features will prioritize
+  // power efficiency over performance. This mode is triggered by the system
+  // Power Saver mode and is currently not user-configurable.
+  enum ModeState {
+    kHighPerformance,
+    kIntelligent,
+    kPowerSaver,
+    kDefault = kIntelligent
+  };
+
+  class Observer : public base::CheckedObserver {
+   public:
+    virtual void OnDisplayPerformanceModeChanged(ModeState new_state) = 0;
+
+   protected:
+    ~Observer() override = default;
+  };
+
+  explicit DisplayPerformanceModeController();
+  DisplayPerformanceModeController(const DisplayPerformanceModeController&) =
+      delete;
+  DisplayPerformanceModeController& operator=(
+      const DisplayPerformanceModeController&) = delete;
+  ~DisplayPerformanceModeController() override;
+
+  // When an observer is added, the current state should be sent to be up to
+  // date.
+  ModeState AddObserver(Observer* observer);
+  void RemoveObserver(Observer* observer);
+
+  void SetHighPerformanceModeByUser(bool is_high_performance_enabled);
+
+  // PowerStatus::Observer:
+  void OnPowerStatusChanged() override;
+
+ private:
+  void UpdateCurrentStateAndNotifyIfChanged();
+  void NotifyObservers();
+
+  ModeState current_state_ = ModeState::kIntelligent;
+  bool is_high_performance_enabled_ = false;
+
+  base::ObserverList<Observer> observers_;
+
+  // Not owned.
+  // TODO(b/327054689): This pointer is needed because some power tests delete
+  // PowerStatus without the observers knowing about it, so we have to check for
+  // its validity before using it.
+  base::WeakPtr<PowerStatus> power_status_;
+
+  base::WeakPtrFactory<DisplayPerformanceModeController> weak_ptr_factory_{
+      this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_DISPLAY_DISPLAY_PERFORMANCE_MODE_CONTROLLER_H_
diff --git a/ash/display/display_performance_mode_controller_unittest.cc b/ash/display/display_performance_mode_controller_unittest.cc
new file mode 100644
index 0000000..7ce1de0
--- /dev/null
+++ b/ash/display/display_performance_mode_controller_unittest.cc
@@ -0,0 +1,210 @@
+// Copyright 2024 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/display/display_performance_mode_controller.h"
+
+#include "ash/shell.h"
+#include "ash/test/ash_test_base.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace ash {
+
+namespace {
+power_manager::PowerSupplyProperties BuildFakePowerSupplyProperties(
+    power_manager::PowerSupplyProperties::ExternalPower charger_state,
+    double battery_percent) {
+  power_manager::PowerSupplyProperties fake_power;
+  fake_power.set_external_power(charger_state);
+  fake_power.set_battery_percent(battery_percent);
+  return fake_power;
+}
+}  // namespace
+
+class DisplayPerformanceModeControllerTest : public AshTestBase {
+ public:
+  DisplayPerformanceModeControllerTest() = default;
+  DisplayPerformanceModeControllerTest(
+      const DisplayPerformanceModeControllerTest&) = delete;
+  DisplayPerformanceModeControllerTest& operator=(
+      const DisplayPerformanceModeControllerTest&) = delete;
+  ~DisplayPerformanceModeControllerTest() override = default;
+
+  void SetUp() override {
+    AshTestBase::SetUp();
+
+    Shell::Get()->display_performance_mode_controller()->AddObserver(
+        &observer_);
+  }
+
+  void TearDown() override {
+    Shell::Get()->display_performance_mode_controller()->RemoveObserver(
+        &observer_);
+
+    AshTestBase::TearDown();
+  }
+
+ protected:
+  using ModeState = DisplayPerformanceModeController::ModeState;
+
+  class MockObserver : public DisplayPerformanceModeController::Observer {
+   public:
+    MOCK_METHOD(void,
+                OnDisplayPerformanceModeChanged,
+                (ModeState new_state),
+                (override));
+  };
+
+  MockObserver observer_;
+};
+
+TEST_F(DisplayPerformanceModeControllerTest, TestPowerSaverOnLowBattery) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kPowerSaver));
+
+  PowerStatus::Get()->SetProtoForTesting(BuildFakePowerSupplyProperties(
+      power_manager::PowerSupplyProperties::DISCONNECTED, 10.f));
+  PowerStatus::Get()->SetBatterySaverStateForTesting(true);
+  Shell::Get()->display_performance_mode_controller()->OnPowerStatusChanged();
+}
+
+TEST_F(DisplayPerformanceModeControllerTest, TestPowerSaverOnAc) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kPowerSaver));
+
+  PowerStatus::Get()->SetProtoForTesting(BuildFakePowerSupplyProperties(
+      power_manager::PowerSupplyProperties::AC, 100.f));
+  PowerStatus::Get()->SetBatterySaverStateForTesting(true);
+  Shell::Get()->display_performance_mode_controller()->OnPowerStatusChanged();
+}
+
+TEST_F(DisplayPerformanceModeControllerTest, TestIntelligentOnAc) {
+  // Should not be called as the default is already intelligent.
+  EXPECT_CALL(observer_, OnDisplayPerformanceModeChanged).Times(0);
+
+  PowerStatus::Get()->SetProtoForTesting(BuildFakePowerSupplyProperties(
+      power_manager::PowerSupplyProperties::AC, 15.f));
+  Shell::Get()->display_performance_mode_controller()->OnPowerStatusChanged();
+}
+
+TEST_F(DisplayPerformanceModeControllerTest,
+       TestResetToIntelligentAfterPowerSaver) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kPowerSaver));
+
+  PowerStatus::Get()->SetProtoForTesting(BuildFakePowerSupplyProperties(
+      power_manager::PowerSupplyProperties::DISCONNECTED, 15.f));
+  PowerStatus::Get()->SetBatterySaverStateForTesting(true);
+  Shell::Get()->display_performance_mode_controller()->OnPowerStatusChanged();
+
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kIntelligent));
+  PowerStatus::Get()->SetBatterySaverStateForTesting(false);
+  Shell::Get()->display_performance_mode_controller()->OnPowerStatusChanged();
+}
+
+TEST_F(DisplayPerformanceModeControllerTest, TestHighPerformance) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kHighPerformance));
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(true);
+}
+
+TEST_F(DisplayPerformanceModeControllerTest, AvoidDuplicateSetting) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kHighPerformance))
+      .Times(1);
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(true);
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(true);
+}
+
+TEST_F(DisplayPerformanceModeControllerTest,
+       TestHighPerformanceModeBeforeOnPowerSaverBattery) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kHighPerformance))
+      .Times(1);
+
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(true);
+
+  PowerStatus::Get()->SetProtoForTesting(BuildFakePowerSupplyProperties(
+      power_manager::PowerSupplyProperties::AC, 100.f));
+  PowerStatus::Get()->SetBatterySaverStateForTesting(true);
+  Shell::Get()->display_performance_mode_controller()->OnPowerStatusChanged();
+}
+
+TEST_F(DisplayPerformanceModeControllerTest,
+       TestHighPerformanceModeAfterOnPowerSaverBattery) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kPowerSaver));
+  PowerStatus::Get()->SetProtoForTesting(BuildFakePowerSupplyProperties(
+      power_manager::PowerSupplyProperties::AC, 100.f));
+  PowerStatus::Get()->SetBatterySaverStateForTesting(true);
+  Shell::Get()->display_performance_mode_controller()->OnPowerStatusChanged();
+
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kHighPerformance));
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(true);
+}
+
+TEST_F(DisplayPerformanceModeControllerTest,
+       TestTurnOffHighPerformanceToIntelligent) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kHighPerformance));
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(true);
+
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kIntelligent));
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(false);
+}
+
+TEST_F(DisplayPerformanceModeControllerTest,
+       TestTurnOffHighPerformanceToPowerSaver) {
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kPowerSaver));
+  PowerStatus::Get()->SetProtoForTesting(BuildFakePowerSupplyProperties(
+      power_manager::PowerSupplyProperties::DISCONNECTED, 10.f));
+  PowerStatus::Get()->SetBatterySaverStateForTesting(true);
+  Shell::Get()->display_performance_mode_controller()->OnPowerStatusChanged();
+
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kHighPerformance));
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(true);
+
+  EXPECT_CALL(observer_,
+              OnDisplayPerformanceModeChanged(ModeState::kPowerSaver));
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(false);
+}
+
+TEST_F(DisplayPerformanceModeControllerTest, TestModeStateOnAddObserver) {
+  // Set Shiny Mode to go into High Performance mode.
+  Shell::Get()
+      ->display_performance_mode_controller()
+      ->SetHighPerformanceModeByUser(true);
+
+  // Check that adding the controller reports back High Performance mode.
+  Shell::Get()->display_performance_mode_controller()->RemoveObserver(
+      &observer_);
+  ModeState current_state =
+      Shell::Get()->display_performance_mode_controller()->AddObserver(
+          &observer_);
+  EXPECT_EQ(current_state, ModeState::kHighPerformance);
+}
+
+}  // namespace ash
diff --git a/ash/game_dashboard/game_dashboard_constants.h b/ash/game_dashboard/game_dashboard_constants.h
index 0f0d34c..9b25a65c 100644
--- a/ash/game_dashboard/game_dashboard_constants.h
+++ b/ash/game_dashboard/game_dashboard_constants.h
@@ -8,7 +8,7 @@
 namespace ash::game_dashboard {
 
 // Toolbar padding from the border of the game window.
-inline constexpr int kToolbarEdgePadding = 10;
+inline constexpr int kToolbarEdgePadding = 16;
 
 // Interior margin padding around the game window for the
 // `GameDashboardWelcomeDialog`.
diff --git a/ash/game_dashboard/game_dashboard_main_menu_view.cc b/ash/game_dashboard/game_dashboard_main_menu_view.cc
index a60dc50..dda00707 100644
--- a/ash/game_dashboard/game_dashboard_main_menu_view.cc
+++ b/ash/game_dashboard/game_dashboard_main_menu_view.cc
@@ -623,7 +623,8 @@
 void GameDashboardMainMenuView::OnFeedbackButtonPressed() {
   Shell::Get()->shell_delegate()->OpenFeedbackDialog(
       ShellDelegate::FeedbackSource::kGameDashboard,
-      /*description_template=*/"#GameDashboard\n\n");
+      /*description_template=*/"#GameDashboard\n\n",
+      /*category_tag=*/std::string());
 }
 
 void GameDashboardMainMenuView::OnHelpButtonPressed() {
diff --git a/ash/game_dashboard/game_dashboard_toolbar_view.cc b/ash/game_dashboard/game_dashboard_toolbar_view.cc
index 1af24ef..bc2f6a1 100644
--- a/ash/game_dashboard/game_dashboard_toolbar_view.cc
+++ b/ash/game_dashboard/game_dashboard_toolbar_view.cc
@@ -17,11 +17,13 @@
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/icon_button.h"
+#include "ash/style/system_shadow.h"
 #include "base/check.h"
 #include "base/types/cxx23_to_underlying.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/color/color_id.h"
 #include "ui/compositor/layer.h"
 #include "ui/events/event_handler.h"
 #include "ui/events/keycodes/keyboard_codes_posix.h"
@@ -31,6 +33,8 @@
 #include "ui/gfx/geometry/vector2d.h"
 #include "ui/gfx/geometry/vector2d_conversions.h"
 #include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/highlight_border.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -48,16 +52,20 @@
 // Padding between children in the toolbar.
 constexpr int kBetweenChildSpacing = 4;
 
-std::unique_ptr<IconButton> CreateIconButton(base::RepeatingClosure callback,
-                                             const gfx::VectorIcon* icon,
-                                             int view_id,
-                                             const std::u16string& text,
-                                             bool is_togglable) {
+std::unique_ptr<IconButton> CreateIconButton(
+    base::RepeatingClosure callback,
+    const gfx::VectorIcon* icon,
+    int view_id,
+    const std::u16string& text,
+    bool is_togglable,
+    ui::ColorId icon_color = cros_tokens::kCrosSysOnSurface) {
   // TODO(b/290696780): Update logic so the toolbar can drag from icon buttons.
   auto button = std::make_unique<IconButton>(
       std::move(callback), IconButton::Type::kMedium, icon, text,
       /*is_togglable=*/is_togglable, /*has_border=*/true);
   button->SetID(view_id);
+  button->SetIconColor(icon_color);
+  button->SetBackgroundColor(SK_ColorTRANSPARENT);
   return button;
 }
 
@@ -232,9 +240,11 @@
   SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter);
   SetBackground(views::CreateThemedRoundedRectBackground(
       cros_tokens::kCrosSysSystemBaseElevatedOpaque, kCornerRadius));
-  SetPaintToLayer();
-  layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(kCornerRadius));
-  layer()->SetFillsBoundsOpaquely(false);
+  SetBorder(views::CreateThemedRoundedRectBorder(
+      1, kCornerRadius, ui::ColorIds::kColorCrosSystemHighlightBorder));
+  shadow_ = SystemShadow::CreateShadowOnNinePatchLayerForView(
+      this, SystemShadow::Type::kElevation12);
+  shadow_->SetRoundedCornerRadius(kCornerRadius);
 
   AddShortcutTiles();
 }
@@ -357,7 +367,7 @@
       &kGdToolbarIcon, base::to_underlying(ToolbarViewId::kGamepadButton),
       l10n_util::GetStringUTF16(
           IDS_ASH_GAME_DASHBOARD_TOOLBAR_TILE_BUTTON_TITLE),
-      /*is_togglable=*/false));
+      /*is_togglable=*/false, /*icon_color=*/cros_tokens::kCrosSysPrimary));
 
   MayAddGameControlsTile();
 
@@ -372,8 +382,6 @@
             IDS_ASH_GAME_DASHBOARD_RECORD_GAME_TILE_BUTTON_TITLE),
         /*is_togglable=*/true));
     record_game_button_->SetVectorIcon(kGdRecordGameIcon);
-    record_game_button_->SetIconColor(cros_tokens::kCrosSysOnSurface);
-
     record_game_button_->SetBackgroundToggledColor(cros_tokens::kCrosSysError);
     record_game_button_->SetToggledVectorIcon(kCaptureModeCircleStopIcon);
     record_game_button_->SetIconToggledColor(cros_tokens::kCrosSysOnError);
diff --git a/ash/game_dashboard/game_dashboard_toolbar_view.h b/ash/game_dashboard/game_dashboard_toolbar_view.h
index 9f1eecdc..d24cc484 100644
--- a/ash/game_dashboard/game_dashboard_toolbar_view.h
+++ b/ash/game_dashboard/game_dashboard_toolbar_view.h
@@ -16,6 +16,7 @@
 class GameDashboardContext;
 class IconButton;
 class ToolbarDragHandler;
+class SystemShadow;
 
 // GameDashboardToolbarView is the movable toolbar that's attached to the game
 // window. It contains various quick action tiles for users to access without
@@ -104,6 +105,8 @@
 
   // Handles all dragging logic for the toolbar.
   std::unique_ptr<ToolbarDragHandler> drag_handler_;
+
+  std::unique_ptr<SystemShadow> shadow_;
 };
 
 }  // namespace ash
diff --git a/ash/public/cpp/assistant/assistant_state_base.cc b/ash/public/cpp/assistant/assistant_state_base.cc
index 873f0f2..c687dfe 100644
--- a/ash/public/cpp/assistant/assistant_state_base.cc
+++ b/ash/public/cpp/assistant/assistant_state_base.cc
@@ -11,41 +11,16 @@
 #include "base/functional/bind.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_piece.h"
+#include "base/strings/to_string.h"
+#include "base/strings/strcat.h"
 #include "chromeos/ash/components/audio/cras_audio_handler.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
 
 namespace ash {
 
-namespace {
-
 using assistant::prefs::AssistantOnboardingMode;
 
-#define PRINT_VALUE(value) PrintValue(&result, #value, value())
-
-template <typename T, std::enable_if_t<std::is_enum<T>::value>* = nullptr>
-void PrintValue(std::stringstream* result, const std::optional<T>& value) {
-  *result << base::NumberToString(static_cast<int>(value.value()));
-}
-
-template <typename T, std::enable_if_t<!std::is_enum<T>::value>* = nullptr>
-void PrintValue(std::stringstream* result, const std::optional<T>& value) {
-  *result << value.value();
-}
-
-template <typename T>
-void PrintValue(std::stringstream* result,
-                const std::string& name,
-                const std::optional<T>& value) {
-  *result << std::endl << "  " << name << ": ";
-  if (value.has_value())
-    PrintValue(result, value);
-  else
-    *result << ("(no value)");
-}
-
-}  // namespace
-
 AssistantStateBase::AssistantStateBase() = default;
 
 AssistantStateBase::~AssistantStateBase() {
@@ -54,18 +29,15 @@
 }
 
 std::string AssistantStateBase::ToString() const {
-  std::stringstream result;
-  result << "AssistantStatus: ";
-  result << assistant_status_;
-  PRINT_VALUE(settings_enabled);
-  PRINT_VALUE(context_enabled);
-  PRINT_VALUE(hotword_enabled);
-  PRINT_VALUE(allowed_state);
-  PRINT_VALUE(locale);
-  PRINT_VALUE(arc_play_store_enabled);
-  PRINT_VALUE(locked_full_screen_enabled);
-  PRINT_VALUE(onboarding_mode);
-  return result.str();
+#define STRINGIFY(field) \
+  #field, (field.has_value() ? base::ToString(field.value()) : "(no value)")
+  return base::StrCat(
+      {"AssistantStatus: ", base::ToString(assistant_status_),
+       STRINGIFY(settings_enabled()), STRINGIFY(context_enabled()),
+       STRINGIFY(hotword_enabled()), STRINGIFY(allowed_state()),
+       STRINGIFY(locale()), STRINGIFY(arc_play_store_enabled()),
+       STRINGIFY(locked_full_screen_enabled()), STRINGIFY(onboarding_mode())});
+#undef STRINGIFY
 }
 
 void AssistantStateBase::AddObserver(AssistantStateObserver* observer) {
diff --git a/ash/public/cpp/test/test_desk_profiles_delegate.cc b/ash/public/cpp/test/test_desk_profiles_delegate.cc
index 92f4043..13a2d0c 100644
--- a/ash/public/cpp/test/test_desk_profiles_delegate.cc
+++ b/ash/public/cpp/test/test_desk_profiles_delegate.cc
@@ -4,7 +4,8 @@
 
 #include "ash/public/cpp/test/test_desk_profiles_delegate.h"
 
-#include "base/containers/cxx20_erase_vector.h"
+#include <vector>
+
 #include "base/ranges/algorithm.h"
 
 namespace ash {
@@ -28,7 +29,7 @@
 bool TestDeskProfilesDelegate::RemoveTestProfile(uint64_t profile_id) {
   CHECK(profile_id != primary_user_profile_id_);
 
-  if (base::EraseIf(profiles_, [&](const auto& profile) {
+  if (std::erase_if(profiles_, [&](const auto& profile) {
         return profile.profile_id == profile_id;
       })) {
     for (auto& observer : observers_) {
diff --git a/ash/shelf/window_scale_animation.cc b/ash/shelf/window_scale_animation.cc
index 4046458..80537e9e 100644
--- a/ash/shelf/window_scale_animation.cc
+++ b/ash/shelf/window_scale_animation.cc
@@ -5,6 +5,7 @@
 #include "ash/shelf/window_scale_animation.h"
 
 #include <optional>
+#include <vector>
 
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/window_backdrop.h"
@@ -194,7 +195,7 @@
   // `animation_observer` will get deleted on the next line.
   auto* window = animation_observer->window();
 
-  base::EraseIf(window_animation_observers_,
+  std::erase_if(window_animation_observers_,
                 base::MatchesUniquePtr(animation_observer));
 
   if (window_animation_observers_.empty()) {
diff --git a/ash/shell.cc b/ash/shell.cc
index 72bed64..2fc9765 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -63,6 +63,7 @@
 #include "ash/display/display_configuration_observer.h"
 #include "ash/display/display_error_observer.h"
 #include "ash/display/display_highlight_controller.h"
+#include "ash/display/display_performance_mode_controller.h"
 #include "ash/display/display_prefs.h"
 #include "ash/display/display_shutdown_observer.h"
 #include "ash/display/event_transformation_handler.h"
@@ -880,6 +881,8 @@
 
   display_highlight_controller_.reset();
 
+  display_performance_mode_controller_.reset();
+
   // VideoActivityNotifier must be deleted before |video_detector_| is
   // deleted because it's observing video activity through
   // VideoDetector::Observer interface.
@@ -1730,6 +1733,9 @@
   display_highlight_controller_ =
       std::make_unique<DisplayHighlightController>();
 
+  display_performance_mode_controller_ =
+      std::make_unique<DisplayPerformanceModeController>();
+
   if (features::IsDisplayAlignmentAssistanceEnabled()) {
     display_alignment_controller_ =
         std::make_unique<DisplayAlignmentController>();
diff --git a/ash/shell.h b/ash/shell.h
index 6c441f80..8eb8b38 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -144,6 +144,7 @@
 class DisplayConfigurationObserver;
 class DisplayErrorObserver;
 class DisplayHighlightController;
+class DisplayPerformanceModeController;
 class DisplayPrefs;
 class DisplayShutdownObserver;
 class DisplaySpeakerController;
@@ -522,6 +523,10 @@
     return display_highlight_controller_.get();
   }
 
+  DisplayPerformanceModeController* display_performance_mode_controller() {
+    return display_performance_mode_controller_.get();
+  }
+
   DockedMagnifierController* docked_magnifier_controller() {
     return docked_magnifier_controller_.get();
   }
@@ -1023,6 +1028,8 @@
   std::unique_ptr<diagnostics::DiagnosticsLogController>
       diagnostics_log_controller_;
   std::unique_ptr<DisplayHighlightController> display_highlight_controller_;
+  std::unique_ptr<DisplayPerformanceModeController>
+      display_performance_mode_controller_;
   std::unique_ptr<DisplaySpeakerController> display_speaker_controller_;
   std::unique_ptr<DragDropController> drag_drop_controller_;
   std::unique_ptr<FirmwareUpdateManager> firmware_update_manager_;
diff --git a/ash/shell_delegate.h b/ash/shell_delegate.h
index 819d98f..4cebfcf3 100644
--- a/ash/shell_delegate.h
+++ b/ash/shell_delegate.h
@@ -58,6 +58,7 @@
   enum class FeedbackSource {
     kFocusMode,
     kGameDashboard,
+    kOverview,
     kWindowLayoutMenu,
   };
 
@@ -184,7 +185,8 @@
   // `description_template` fields. Note, this will only be used by features
   // before they are fully launched or removed.
   virtual void OpenFeedbackDialog(FeedbackSource source,
-                                  const std::string& description_template) = 0;
+                                  const std::string& description_template,
+                                  const std::string& category_tag) = 0;
 
   // Calls browser service to open the profile manager.
   virtual void OpenProfileManager() = 0;
diff --git a/ash/system/focus_mode/focus_mode_detailed_view.cc b/ash/system/focus_mode/focus_mode_detailed_view.cc
index 498c118..5709fab 100644
--- a/ash/system/focus_mode/focus_mode_detailed_view.cc
+++ b/ash/system/focus_mode/focus_mode_detailed_view.cc
@@ -841,7 +841,8 @@
 void FocusModeDetailedView::OnFeedbackButtonPressed() {
   Shell::Get()->shell_delegate()->OpenFeedbackDialog(
       ShellDelegate::FeedbackSource::kFocusMode,
-      /*description_template=*/"#FocusMode");
+      /*description_template=*/"#FocusMode",
+      /*category_tag=*/std::string());
 }
 
 void FocusModeDetailedView::OnClockMinutePassed() {
diff --git a/ash/system/focus_mode/focus_mode_tasks_provider.cc b/ash/system/focus_mode/focus_mode_tasks_provider.cc
index 0394e76e..f6073fc 100644
--- a/ash/system/focus_mode/focus_mode_tasks_provider.cc
+++ b/ash/system/focus_mode/focus_mode_tasks_provider.cc
@@ -5,9 +5,9 @@
 #include "ash/system/focus_mode/focus_mode_tasks_provider.h"
 
 #include <optional>
+#include <vector>
 
 #include "ash/api/tasks/tasks_types.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
@@ -144,7 +144,7 @@
 }
 
 void FocusModeTasksProvider::MarkAsCompleted(const std::string& task_id) {
-  base::EraseIf(tasks_data_,
+  std::erase_if(tasks_data_,
                 [task_id](const auto& task) { return task->id == task_id; });
 }
 
diff --git a/ash/system/holding_space/holding_space_tray.cc b/ash/system/holding_space/holding_space_tray.cc
index bcc87c1..a072195 100644
--- a/ash/system/holding_space/holding_space_tray.cc
+++ b/ash/system/holding_space/holding_space_tray.cc
@@ -5,6 +5,7 @@
 #include "ash/system/holding_space/holding_space_tray.h"
 
 #include <memory>
+#include <vector>
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/ash_element_identifiers.h"
@@ -36,7 +37,6 @@
 #include "base/check.h"
 #include "base/containers/adapters.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/ranges/algorithm.h"
@@ -101,7 +101,7 @@
                                            /*fallback_to_filenames=*/true);
 
   HoldingSpaceModel* const model = HoldingSpaceController::Get()->model();
-  base::EraseIf(unpinned_file_paths, [model](const base::FilePath& file_path) {
+  std::erase_if(unpinned_file_paths, [model](const base::FilePath& file_path) {
     return model->ContainsItem(HoldingSpaceItem::Type::kPinnedFile, file_path);
   });
 
diff --git a/ash/system/holding_space/holding_space_tray_icon.cc b/ash/system/holding_space/holding_space_tray_icon.cc
index 2f085fcce..f49f6c1 100644
--- a/ash/system/holding_space/holding_space_tray_icon.cc
+++ b/ash/system/holding_space/holding_space_tray_icon.cc
@@ -4,6 +4,8 @@
 
 #include "ash/system/holding_space/holding_space_tray_icon.h"
 
+#include <vector>
+
 #include "ash/public/cpp/holding_space/holding_space_constants.h"
 #include "ash/public/cpp/holding_space/holding_space_item.h"
 #include "ash/public/cpp/holding_space/holding_space_metrics.h"
@@ -18,7 +20,6 @@
 #include "base/barrier_closure.h"
 #include "base/containers/adapters.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/functional/bind.h"
 #include "base/i18n/rtl.h"
@@ -373,7 +374,7 @@
 void HoldingSpaceTrayIcon::OnOldItemAnimatedOut(
     HoldingSpaceTrayIconPreview* preview,
     const base::RepeatingClosure& callback) {
-  base::EraseIf(removed_previews_, base::MatchesUniquePtr(preview));
+  std::erase_if(removed_previews_, base::MatchesUniquePtr(preview));
   callback.Run();
 }
 
diff --git a/ash/system/holding_space/holding_space_view_delegate.cc b/ash/system/holding_space/holding_space_view_delegate.cc
index d2f265a8..ce7ac56 100644
--- a/ash/system/holding_space/holding_space_view_delegate.cc
+++ b/ash/system/holding_space/holding_space_view_delegate.cc
@@ -4,6 +4,8 @@
 
 #include "ash/system/holding_space/holding_space_view_delegate.h"
 
+#include <vector>
+
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/holding_space/holding_space_client.h"
 #include "ash/public/cpp/holding_space/holding_space_constants.h"
@@ -21,7 +23,6 @@
 #include "ash/system/holding_space/holding_space_tray.h"
 #include "ash/system/holding_space/holding_space_tray_bubble.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ref.h"
 #include "base/memory/weak_ptr.h"
@@ -613,7 +614,7 @@
       if (!in_progress_commands.has_value()) {
         in_progress_commands = item->in_progress_commands();
       } else {
-        base::EraseIf(in_progress_commands.value(),
+        std::erase_if(in_progress_commands.value(),
                       [&](const HoldingSpaceItem::InProgressCommand&
                               in_progress_command) {
                         return !holding_space_util::SupportsInProgressCommand(
diff --git a/ash/system/input_device_settings/input_device_notifier.cc b/ash/system/input_device_settings/input_device_notifier.cc
index 7b21ff6..0a5a4c1 100644
--- a/ash/system/input_device_settings/input_device_notifier.cc
+++ b/ash/system/input_device_settings/input_device_notifier.cc
@@ -5,6 +5,7 @@
 #include "ash/system/input_device_settings/input_device_notifier.h"
 
 #include <functional>
+#include <vector>
 
 #include "ash/bluetooth_devices_observer.h"
 #include "ash/public/cpp/input_device_settings_controller.h"
@@ -16,7 +17,6 @@
 #include "ash/system/input_device_settings/input_device_settings_pref_names.h"
 #include "ash/system/input_device_settings/input_device_settings_utils.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/containers/flat_map.h"
 #include "base/functional/bind.h"
 #include "base/ranges/algorithm.h"
@@ -219,7 +219,7 @@
   // Remove any devices marked as imposters as well.
   base::ranges::sort(updated_device_list, base::ranges::less(),
                      ExtractDeviceIdFromInputDevice);
-  base::EraseIf(updated_device_list, [&](const ui::InputDevice& device) {
+  std::erase_if(updated_device_list, [&](const ui::InputDevice& device) {
     return IsDeviceASuspectedImposter<DeviceMojomPtr>(bluetooth_observer,
                                                       device);
   });
@@ -373,7 +373,7 @@
 std::vector<ui::InputDevice>
 InputDeviceNotifier<mojom::MousePtr, ui::InputDevice>::GetUpdatedDeviceList() {
   auto mice = ui::DeviceDataManager::GetInstance()->GetMouseDevices();
-  base::EraseIf(mice, [](const auto& mouse) {
+  std::erase_if(mice, [](const auto& mouse) {
     if (floss::features::IsFlossEnabled()) {
       if (kFlossExtraMouseVidPid ==
               VendorProductId{mouse.vendor_id, mouse.product_id} &&
diff --git a/ash/system/notification_center/views/ash_notification_view_unittest.cc b/ash/system/notification_center/views/ash_notification_view_unittest.cc
index 35bf9eb..e97aa9f 100644
--- a/ash/system/notification_center/views/ash_notification_view_unittest.cc
+++ b/ash/system/notification_center/views/ash_notification_view_unittest.cc
@@ -824,9 +824,7 @@
             GetHeaderRow(notification_view())->bounds().y());
 }
 
-// TODO(crbug.com/326337073): Re-enable this test
-TEST_F(AshNotificationViewTest,
-       DISABLED_ExpandCollapseAnimationsRecordSmoothness) {
+TEST_F(AshNotificationViewTest, ExpandCollapseAnimationsRecordSmoothness) {
   // Enable animations.
   ui::ScopedAnimationDurationScaleMode duration(
       ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
@@ -887,8 +885,7 @@
 }
 
 // TODO(crbug.com/1522231): Re-enable this test
-TEST_F(AshNotificationViewTest,
-       DISABLED_ImageExpandCollapseAnimationsRecordSmoothness) {
+TEST_F(AshNotificationViewTest, ImageExpandCollapseAnimationsRecordSmoothness) {
   // Enable animations.
   ui::ScopedAnimationDurationScaleMode duration(
       ui::ScopedAnimationDurationScaleMode::FAST_DURATION);
@@ -953,9 +950,7 @@
                           "ScaleAndTranslate.AnimationSmoothness");
 }
 
-// TODO(crbug.com/1520190): Re-enable when flakiness is resolved.
-TEST_F(AshNotificationViewTest,
-       DISABLED_GroupExpandCollapseAnimationsRecordSmoothness) {
+TEST_F(AshNotificationViewTest, GroupExpandCollapseAnimationsRecordSmoothness) {
   base::HistogramTester histograms;
 
   // Enable animations.
@@ -1096,16 +1091,7 @@
       "Ash.NotificationView.InlineReply.FadeOut.AnimationSmoothness");
 }
 
-// TODO(crbug.com/1518434): Flaky on ChromeOS.
-#if BUILDFLAG(IS_CHROMEOS)
-#define MAYBE_InlineSettingsAnimationsRecordSmoothness \
-  DISABLED_InlineSettingsAnimationsRecordSmoothness
-#else
-#define MAYBE_InlineSettingsAnimationsRecordSmoothness \
-  InlineSettingsAnimationsRecordSmoothness
-#endif
-TEST_F(AshNotificationViewTest,
-       MAYBE_InlineSettingsAnimationsRecordSmoothness) {
+TEST_F(AshNotificationViewTest, InlineSettingsAnimationsRecordSmoothness) {
   base::HistogramTester histograms;
 
   // Enable animations.
diff --git a/ash/system/power/power_status.cc b/ash/system/power/power_status.cc
index 16e0a775..9404112 100644
--- a/ash/system/power/power_status.cc
+++ b/ash/system/power/power_status.cc
@@ -451,6 +451,10 @@
   return battery_saver_active_;
 }
 
+base::WeakPtr<PowerStatus> PowerStatus::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 PowerStatus::PowerStatus() {
   chromeos::PowerManagerClient::Get()->AddObserver(this);
   chromeos::PowerManagerClient::Get()->RequestStatusUpdate();
diff --git a/ash/system/power/power_status.h b/ash/system/power/power_status.h
index 5ff0192..56089e2 100644
--- a/ash/system/power/power_status.h
+++ b/ash/system/power/power_status.h
@@ -263,6 +263,11 @@
   // Returns true if battery saver is active.
   bool IsBatterySaverActive() const;
 
+  // TODO(b/327054689): This pointer is needed because some power tests delete
+  // PowerStatus without the observers knowing about it, so observers have to
+  // check for its validity before using it.
+  base::WeakPtr<PowerStatus> GetWeakPtr();
+
   // Updates |proto_|. Does not notify observers.
   void SetProtoForTesting(const power_manager::PowerSupplyProperties& proto);
 
diff --git a/ash/system/video_conference/effects/fake_video_conference_tray_effects_manager.cc b/ash/system/video_conference/effects/fake_video_conference_tray_effects_manager.cc
index 316fbd8..aa8b599 100644
--- a/ash/system/video_conference/effects/fake_video_conference_tray_effects_manager.cc
+++ b/ash/system/video_conference/effects/fake_video_conference_tray_effects_manager.cc
@@ -4,12 +4,13 @@
 
 #include "ash/system/video_conference/effects/fake_video_conference_tray_effects_manager.h"
 
+#include <vector>
+
 #include "ash/constants/ash_features.h"
 #include "ash/system/video_conference/bubble/vc_tile_ui_controller.h"
 #include "ash/system/video_conference/effects/video_conference_tray_effects_delegate.h"
 #include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
 #include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
-#include "base/containers/cxx20_erase_vector.h"
 
 namespace ash {
 
@@ -26,7 +27,7 @@
   // Delete any tile UI controllers associated with any of `delegate`'s effects.
   for (auto* effect : delegate->GetEffects(VcEffectType::kToggle)) {
     const VcEffectId id = effect->id();
-    base::EraseIf(
+    std::erase_if(
         tile_ui_controllers_,
         [&, id](const std::unique_ptr<video_conference::VcTileUiController>&
                     controller) {
diff --git a/ash/system/video_conference/effects/video_conference_tray_effects_manager.cc b/ash/system/video_conference/effects/video_conference_tray_effects_manager.cc
index fe910017..ce106b8 100644
--- a/ash/system/video_conference/effects/video_conference_tray_effects_manager.cc
+++ b/ash/system/video_conference/effects/video_conference_tray_effects_manager.cc
@@ -5,6 +5,7 @@
 #include "ash/system/video_conference/effects/video_conference_tray_effects_manager.h"
 
 #include <memory>
+#include <vector>
 
 #include "ash/constants/ash_features.h"
 #include "ash/system/video_conference/bubble/vc_tile_ui_controller.h"
@@ -12,7 +13,6 @@
 #include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
 #include "base/check.h"
 #include "base/check_op.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/observer_list.h"
 
 namespace ash {
@@ -42,7 +42,7 @@
     VcEffectsDelegate* delegate) {
   DCHECK(delegate);
   size_t num_items_erased =
-      base::EraseIf(effect_delegates_,
+      std::erase_if(effect_delegates_,
                     [delegate](VcEffectsDelegate* d) { return delegate == d; });
   DCHECK_EQ(num_items_erased, 1UL);
 
diff --git a/ash/test_shell_delegate.h b/ash/test_shell_delegate.h
index 7e76c44..bca83b4 100644
--- a/ash/test_shell_delegate.h
+++ b/ash/test_shell_delegate.h
@@ -101,7 +101,8 @@
   bool IsLoggingRedirectDisabled() const override;
   base::FilePath GetPrimaryUserDownloadsFolder() const override;
   void OpenFeedbackDialog(FeedbackSource source,
-                          const std::string& description_template) override {}
+                          const std::string& description_template,
+                          const std::string& category_tag) override {}
   void OpenProfileManager() override {}
   void SetLastCommittedURLForWindow(const GURL& url);
   version_info::Channel GetChannel() override;
diff --git a/ash/user_education/holding_space_wallpaper_nudge/holding_space_wallpaper_nudge_controller.cc b/ash/user_education/holding_space_wallpaper_nudge/holding_space_wallpaper_nudge_controller.cc
index 4293c66..e1b9be84 100644
--- a/ash/user_education/holding_space_wallpaper_nudge/holding_space_wallpaper_nudge_controller.cc
+++ b/ash/user_education/holding_space_wallpaper_nudge/holding_space_wallpaper_nudge_controller.cc
@@ -41,7 +41,6 @@
 #include "ash/wallpaper/wallpaper_drag_drop_delegate.h"
 #include "base/check_is_test.h"
 #include "base/check_op.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/files/file_path.h"
 #include "base/scoped_observation.h"
 #include "base/timer/elapsed_timer.h"
@@ -84,7 +83,7 @@
       holding_space_util::ExtractFilePaths(data,
                                            /*fallback_to_filenames=*/false);
 
-  base::EraseIf(unpinned_file_paths, [&](const base::FilePath& file_path) {
+  std::erase_if(unpinned_file_paths, [&](const base::FilePath& file_path) {
     return model->ContainsItem(HoldingSpaceItem::Type::kPinnedFile, file_path);
   });
 
diff --git a/ash/webui/print_preview_cros/resources/BUILD.gn b/ash/webui/print_preview_cros/resources/BUILD.gn
index b997790..0eff57e 100644
--- a/ash/webui/print_preview_cros/resources/BUILD.gn
+++ b/ash/webui/print_preview_cros/resources/BUILD.gn
@@ -16,6 +16,9 @@
   non_web_component_files = [
     "js/data/print_ticket_manager.ts",
     "js/summary_panel_controller.ts",
+    "js/fakes/fake_print_preview_page_handler.ts",
+    "js/utils/mojo_data_providers.ts",
+    "js/utils/print_preview_cros_app_types.ts",
   ]
 
   web_component_files = [
diff --git a/ash/webui/print_preview_cros/resources/js/data/print_ticket_manager.ts b/ash/webui/print_preview_cros/resources/js/data/print_ticket_manager.ts
index 1da0485..02b8f515 100644
--- a/ash/webui/print_preview_cros/resources/js/data/print_ticket_manager.ts
+++ b/ash/webui/print_preview_cros/resources/js/data/print_ticket_manager.ts
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {assert} from 'chrome://resources/js/assert.js';
+
+import {getPrintPreviewPageHandler} from '../utils/mojo_data_providers.js';
+import {type PrintPreviewPageHandler} from '../utils/print_preview_cros_app_types.js';
 
 /**
  * @fileoverview
@@ -12,11 +16,6 @@
 export class PrintTicketManager extends EventTarget {
   private static instance: PrintTicketManager|null = null;
 
-  // Prevent additional initialization.
-  private constructor() {
-    super();
-  }
-
   static getInstance(): PrintTicketManager {
     if (PrintTicketManager.instance === null) {
       PrintTicketManager.instance = new PrintTicketManager();
@@ -29,8 +28,25 @@
     PrintTicketManager.instance = null;
   }
 
+  // Non-static properties:
+  private printPreviewPageHandler: PrintPreviewPageHandler|null;
+
+  // Prevent additional initialization.
+  private constructor() {
+    super();
+
+    // Setup mojo data providers.
+    this.printPreviewPageHandler = getPrintPreviewPageHandler();
+  }
+
   // TODO(b/323421684): Takes current print ticket uses PrintPreviewPageHandler
   // to initiate actual print request. Also handles print request start and
   // finish events.
-  sendPrintRequest(): void {}
+  sendPrintRequest(): void {
+    assert(this.printPreviewPageHandler);
+
+    // TODO(b/323421684): Handle result from page handler and update UI if error
+    // occurred.
+    this.printPreviewPageHandler!.print();
+  }
 }
diff --git a/ash/webui/print_preview_cros/resources/js/fakes/fake_print_preview_page_handler.ts b/ash/webui/print_preview_cros/resources/js/fakes/fake_print_preview_page_handler.ts
new file mode 100644
index 0000000..7729230
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/js/fakes/fake_print_preview_page_handler.ts
@@ -0,0 +1,61 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {FakeMethodResolver} from 'chrome://resources/ash/common/fake_method_resolver.js';
+
+import {type PrintPreviewPageHandler, type PrintRequestOutcome} from '../utils/print_preview_cros_app_types.js';
+
+/**
+ * @fileoverview
+ * 'fake_print_preview_page_handler' is a mock implementation of the
+ * `PrintPreviewPageHandler` mojo interface.
+ */
+
+const PRINT_METHOD = 'print';
+export const FAKE_PRINT_REQUEST_SUCCESSFUL: PrintRequestOutcome = {
+  success: true,
+};
+
+export const FAKE_PRINT_REQUEST_FAILURE_INVALID_SETTINGS_ERROR:
+    PrintRequestOutcome = {
+      success: false,
+      error: 'Invalid settings',
+    };
+
+// Fake implementation of the PrintPreviewPageHandler for tests and UI.
+export class FakePrintPreviewPageHandler implements PrintPreviewPageHandler {
+  private methods: FakeMethodResolver = new FakeMethodResolver();
+  private callCount: Map<string, number> = new Map<string, number>();
+  constructor() {
+    this.registerMethods();
+  }
+
+  private registerMethods() {
+    this.methods.register(PRINT_METHOD);
+    this.methods.setResult(PRINT_METHOD, FAKE_PRINT_REQUEST_SUCCESSFUL);
+    this.callCount.set(PRINT_METHOD, 0);
+  }
+
+  // Handles restoring state of fake to initial state.
+  reset(): void {
+    this.callCount.clear();
+    this.methods = new FakeMethodResolver();
+    this.registerMethods();
+  }
+
+  setPrintResult(result: PrintRequestOutcome) {
+    this.methods.setResult(PRINT_METHOD, result);
+  }
+
+  // Mock implementation of print.
+  print(): Promise<PrintRequestOutcome> {
+    const prevCallCount = this.callCount.get(PRINT_METHOD) ?? 0;
+    this.callCount.set(PRINT_METHOD, prevCallCount + 1);
+    return this.methods.resolveMethod(PRINT_METHOD);
+  }
+
+  getCallCount(method: string): number {
+    return this.callCount.get(method) ?? 0;
+  }
+}
diff --git a/ash/webui/print_preview_cros/resources/js/utils/mojo_data_providers.ts b/ash/webui/print_preview_cros/resources/js/utils/mojo_data_providers.ts
new file mode 100644
index 0000000..78c1785
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/js/utils/mojo_data_providers.ts
@@ -0,0 +1,33 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert} from 'chrome://resources/js/assert.js';
+
+import {FakePrintPreviewPageHandler} from '../fakes/fake_print_preview_page_handler.js';
+
+import {type PrintPreviewPageHandler} from './print_preview_cros_app_types.js';
+
+/**
+ * @fileoverview
+ * 'mojo_data_providers' contains accessors to shared mojo data providers. As
+ * well as an override method to be used in tests.
+ */
+
+let printPreviewPageHandler: PrintPreviewPageHandler|null = null;
+
+// Returns shared instance of PrintPreviewPageHandler.
+export function getPrintPreviewPageHandler(): PrintPreviewPageHandler {
+  if (printPreviewPageHandler == null) {
+    printPreviewPageHandler = new FakePrintPreviewPageHandler();
+  }
+
+  assert(printPreviewPageHandler);
+  return printPreviewPageHandler;
+}
+
+// Override shared instance of PrintPreviewPageHandle for testing.
+export function setPrintPreviewPageHandlerForTesting(
+    handler: PrintPreviewPageHandler): void {
+  printPreviewPageHandler = handler;
+}
diff --git a/ash/webui/print_preview_cros/resources/js/utils/print_preview_cros_app_types.ts b/ash/webui/print_preview_cros/resources/js/utils/print_preview_cros_app_types.ts
new file mode 100644
index 0000000..1975c8e
--- /dev/null
+++ b/ash/webui/print_preview_cros/resources/js/utils/print_preview_cros_app_types.ts
@@ -0,0 +1,21 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview
+ * 'print_preview_cros_app_types' contains app specific and mojo placeholder
+ * types.
+ */
+
+export interface PrintRequestOutcome {
+  success: boolean;
+  error?: string;
+}
+
+// Placeholder for PrintPreviewPageHandler mojo interface.
+export interface PrintPreviewPageHandler {
+  // Start the print job and close the window. Needs to wait for result to
+  // display error messaging if starting the print job fails.
+  print(): Promise<PrintRequestOutcome>;
+}
diff --git a/ash/wm/desks/desk.cc b/ash/wm/desks/desk.cc
index 1318a68..130a83f 100644
--- a/ash/wm/desks/desk.cc
+++ b/ash/wm/desks/desk.cc
@@ -6,6 +6,7 @@
 
 #include <absl/cleanup/cleanup.h>
 #include <utility>
+#include <vector>
 
 #include "ash/constants/app_types.h"
 #include "ash/public/cpp/window_properties.h"
@@ -351,7 +352,7 @@
   const auto windows = windows_;
   for (aura::Window* window : windows) {
     if (window->GetRootWindow() == root)
-      base::Erase(windows_, window);
+      std::erase(windows_, window);
   }
 
   if (last_active_root_ == root) {
@@ -430,7 +431,7 @@
 void Desk::RemoveWindowFromDesk(aura::Window* window) {
   DCHECK(base::Contains(windows_, window));
 
-  base::Erase(windows_, window);
+  std::erase(windows_, window);
   // No need to refresh the mini_views if the destroyed window doesn't show up
   // there in the first place. Also don't refresh for visible on all desks
   // windows since they're already refreshed in OnWindowRemoved().
diff --git a/ash/wm/desks/templates/admin_template_launch_tracker.cc b/ash/wm/desks/templates/admin_template_launch_tracker.cc
index c77494cd..ecb2ec4 100644
--- a/ash/wm/desks/templates/admin_template_launch_tracker.cc
+++ b/ash/wm/desks/templates/admin_template_launch_tracker.cc
@@ -4,6 +4,8 @@
 
 #include "ash/wm/desks/templates/admin_template_launch_tracker.h"
 
+#include <vector>
+
 #include "ash/public/cpp/saved_desk_delegate.h"
 #include "ash/root_window_settings.h"
 #include "ash/shell.h"
@@ -493,7 +495,7 @@
 
 void AdminTemplateLaunchTracker::OnObserverDone(
     base::CheckedObserver* observer) {
-  base::EraseIf(window_observers_,
+  std::erase_if(window_observers_,
                 [&](const auto& ptr) { return ptr.get() == observer; });
 }
 
diff --git a/ash/wm/desks/templates/saved_desk_presenter.cc b/ash/wm/desks/templates/saved_desk_presenter.cc
index 1919fba..3caafe6 100644
--- a/ash/wm/desks/templates/saved_desk_presenter.cc
+++ b/ash/wm/desks/templates/saved_desk_presenter.cc
@@ -4,6 +4,8 @@
 
 #include "ash/wm/desks/templates/saved_desk_presenter.h"
 
+#include <vector>
+
 #include "ash/constants/notifier_catalogs.h"
 #include "ash/public/cpp/app_types_util.h"
 #include "ash/public/cpp/desk_template.h"
@@ -25,7 +27,6 @@
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "ash/wm/overview/overview_session.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/functional/bind.h"
 #include "base/i18n/number_formatting.h"
 #include "base/memory/raw_ptr.h"
@@ -692,7 +693,7 @@
           Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk);
 
       // Get rid of transient windows and all-desks windows.
-      base::EraseIf(windows, [](aura::Window* window) {
+      std::erase_if(windows, [](aura::Window* window) {
         return wm::GetTransientParent(window) != nullptr ||
                desks_util::IsWindowVisibleOnAllWorkspaces(window);
       });
diff --git a/ash/wm/mru_window_tracker.cc b/ash/wm/mru_window_tracker.cc
index 3edf561..add5e79 100644
--- a/ash/wm/mru_window_tracker.cc
+++ b/ash/wm/mru_window_tracker.cc
@@ -4,6 +4,8 @@
 
 #include "ash/wm/mru_window_tracker.h"
 
+#include <vector>
+
 #include "ash/constants/app_types.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
@@ -301,7 +303,7 @@
   // If nothing was erased, this is a window not currently observed so we want
   // to observe it as windows created from window restore aren't activated on
   // creation.
-  size_t num_erased = base::Erase(mru_windows_, window);
+  size_t num_erased = std::erase(mru_windows_, window);
   if (num_erased == 0u)
     window->AddObserver(this);
 
@@ -345,7 +347,7 @@
   // It's possible for OnWindowActivated() to be called after
   // OnWindowDestroying(). This means we need to override OnWindowDestroyed()
   // else we may end up with a deleted window in |mru_windows_|.
-  base::Erase(mru_windows_, window);
+  std::erase(mru_windows_, window);
   window->RemoveObserver(this);
 }
 
diff --git a/ash/wm/overview/birch/birch_bar_view.cc b/ash/wm/overview/birch/birch_bar_view.cc
index 0086a2fb..193cf68 100644
--- a/ash/wm/overview/birch/birch_bar_view.cc
+++ b/ash/wm/overview/birch/birch_bar_view.cc
@@ -4,6 +4,8 @@
 
 #include "ash/wm/overview/birch/birch_bar_view.h"
 
+#include <vector>
+
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/window_properties.h"
@@ -154,7 +156,7 @@
 void BirchBarView::RemoveChip(BirchChipButton* chip) {
   CHECK(base::Contains(chips_, chip));
 
-  base::Erase(chips_, chip);
+  std::erase(chips_, chip);
   // Remove the chip from its owner.
   if (primary_row_->Contains(chip)) {
     primary_row_->RemoveChildViewT(chip);
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index d59eb4b..441053c 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -5,6 +5,7 @@
 #include "ash/wm/overview/overview_controller.h"
 
 #include <utility>
+#include <vector>
 
 #include "ash/frame_throttler/frame_throttling_controller.h"
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
@@ -290,7 +291,7 @@
 void OverviewController::RemoveAndDestroyExitAnimationObserver(
     DelayedAnimationObserver* animation_observer) {
   const bool previous_empty = delayed_animations_.empty();
-  base::EraseIf(delayed_animations_,
+  std::erase_if(delayed_animations_,
                 base::MatchesUniquePtr(animation_observer));
 
   if (!overview_session_ && !previous_empty && delayed_animations_.empty())
@@ -306,7 +307,7 @@
 void OverviewController::RemoveAndDestroyEnterAnimationObserver(
     DelayedAnimationObserver* animation_observer) {
   const bool previous_empty = start_animations_.empty();
-  base::EraseIf(start_animations_, base::MatchesUniquePtr(animation_observer));
+  std::erase_if(start_animations_, base::MatchesUniquePtr(animation_observer));
 
   if (!previous_empty && start_animations_.empty())
     OnStartingAnimationComplete(/*canceled=*/false);
@@ -353,7 +354,7 @@
   auto end = base::ranges::copy_if(windows, hide_windows.begin(),
                                    should_hide_for_overview);
   hide_windows.resize(end - hide_windows.begin());
-  base::EraseIf(windows, window_util::ShouldExcludeForOverview);
+  std::erase_if(windows, window_util::ShouldExcludeForOverview);
   // Overview windows will handle showing their transient related windows, so if
   // a window in |windows| has a transient root also in |windows|, we can remove
   // it as the transient root will handle showing the window.
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 5c55c932..52556cc 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/constants/app_types.h"
@@ -1030,7 +1031,7 @@
   // Copy to a local first to avoid a dangling pointer.
   OverviewDropTarget* drop_target_ptr = std::exchange(drop_target_, nullptr);
 
-  size_t erased_count = base::EraseIf(
+  size_t erased_count = std::erase_if(
       item_list_, base::MatchesUniquePtr<OverviewItemBase>(drop_target_ptr));
   CHECK_EQ(1u, erased_count);
 
@@ -3185,8 +3186,10 @@
   if (!feedback_widget_) {
     auto contents_view = std::make_unique<PillButton>(
         // TODO(hewer): Add callback to open a feedback page.
-        views::Button::PressedCallback(), u"Send Feedback",
-        PillButton::Type::kDefaultWithIconLeading, &kFeedbackIcon);
+        base::BindRepeating(&OverviewGrid::ShowFeedbackPage,
+                            base::Unretained(this)),
+        u"Send Feedback", PillButton::Type::kDefaultWithIconLeading,
+        &kFeedbackIcon);
 
     views::Widget::InitParams params;
     params.init_properties_container.SetProperty(kHideInDeskMiniViewKey, true);
@@ -3213,4 +3216,11 @@
                 contents_size.width(), contents_size.height()));
 }
 
+void OverviewGrid::ShowFeedbackPage() {
+  Shell::Get()->shell_delegate()->OpenFeedbackDialog(
+      ShellDelegate::FeedbackSource::kOverview,
+      /*description_template=*/std::string(),
+      /*category_tag=*/"FromForest");
+}
+
 }  // namespace ash
diff --git a/ash/wm/overview/overview_grid.h b/ash/wm/overview/overview_grid.h
index 7b887f8..d4a1e31 100644
--- a/ash/wm/overview/overview_grid.h
+++ b/ash/wm/overview/overview_grid.h
@@ -632,6 +632,9 @@
   // feedback page when clicked. The widget will not show in partial overview.
   void UpdateFeedbackButton();
 
+  // Shows the feedback page with preset information for overview.
+  void ShowFeedbackPage();
+
   // The drop target is created when a window or overview item is being dragged,
   // and is destroyed when the drag ends or overview mode is ended. The drop
   // target is hidden when a snap preview area is shown. You can drop a window
diff --git a/ash/wm/snap_group/snap_group_controller.cc b/ash/wm/snap_group/snap_group_controller.cc
index b87e340..a8a81ad 100644
--- a/ash/wm/snap_group/snap_group_controller.cc
+++ b/ash/wm/snap_group/snap_group_controller.cc
@@ -4,6 +4,8 @@
 
 #include "ash/wm/snap_group/snap_group_controller.h"
 
+#include <vector>
+
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/wm/mru_window_tracker.h"
@@ -114,7 +116,7 @@
     observer.OnSnapGroupRemoved(snap_group);
   }
 
-  base::EraseIf(snap_groups_, base::MatchesUniquePtr(snap_group));
+  std::erase_if(snap_groups_, base::MatchesUniquePtr(snap_group));
 
   return true;
 }
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index 502d6d2..515055ec 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -9,6 +9,7 @@
 #include <cstdint>
 #include <limits>
 #include <optional>
+#include <vector>
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/constants/app_types.h"
@@ -2307,17 +2308,17 @@
       static_cast<float>(min_right_size) / divider_upper_limit;
   if (min_size_left_ratio > chromeos::kOneThirdSnapRatio) {
     // If `primary_window_` can't fit in 1/3, remove 0.33f divider position.
-    base::Erase(*out_position_ratios, chromeos::kOneThirdSnapRatio);
+    std::erase(*out_position_ratios, chromeos::kOneThirdSnapRatio);
   }
   if (min_size_right_ratio > chromeos::kOneThirdSnapRatio) {
     // If `secondary_window_` can't fit in 1/3, remove 0.67f divider position.
-    base::Erase(*out_position_ratios, chromeos::kTwoThirdSnapRatio);
+    std::erase(*out_position_ratios, chromeos::kTwoThirdSnapRatio);
   }
   // Remove 0.5f if a window cannot be snapped. We can get into this state by
   // snapping a window to two thirds.
   if (min_size_left_ratio > chromeos::kDefaultSnapRatio ||
       min_size_right_ratio > chromeos::kDefaultSnapRatio) {
-    base::Erase(*out_position_ratios, chromeos::kDefaultSnapRatio);
+    std::erase(*out_position_ratios, chromeos::kDefaultSnapRatio);
   }
 }
 
diff --git a/ash/wm/splitview/split_view_metrics_controller.cc b/ash/wm/splitview/split_view_metrics_controller.cc
index aaa77fcc..2b582ec 100644
--- a/ash/wm/splitview/split_view_metrics_controller.cc
+++ b/ash/wm/splitview/split_view_metrics_controller.cc
@@ -4,6 +4,8 @@
 
 #include "ash/wm/splitview/split_view_metrics_controller.h"
 
+#include <vector>
+
 #include "ash/root_window_controller.h"
 #include "ash/root_window_settings.h"
 #include "ash/shell.h"
@@ -551,7 +553,7 @@
     }
     first_minimized_window_state_ = nullptr;
   }
-  if (base::Erase(observed_windows_, window)) {
+  if (std::erase(observed_windows_, window)) {
     WindowState::Get(window)->RemoveObserver(this);
     window->RemoveObserver(this);
   }
diff --git a/ash/wm/splitview/split_view_utils.cc b/ash/wm/splitview/split_view_utils.cc
index bf11777e..404fed3 100644
--- a/ash/wm/splitview/split_view_utils.cc
+++ b/ash/wm/splitview/split_view_utils.cc
@@ -915,10 +915,6 @@
     return false;
   }
 
-  // TODO(michelefan): Currently apply the snap source limitations for faster
-  // flag only. It will be removed when we figure out a good way to restore two
-  // windows in a snap group.
-  if (features::IsFasterSplitScreenSetupEnabled()) {
     if (PrefService* pref =
             Shell::Get()->session_controller()->GetActivePrefService();
         pref && !pref->GetBoolean(prefs::kSnapWindowSuggestions)) {
@@ -928,7 +924,6 @@
     if (!CanSnapActionSourceStartFasterSplitView(snap_action_source)) {
       return false;
     }
-  }
 
   return !IsInOverviewSession();
 }
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.cc b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
index 3108d73..5db006b 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
@@ -5,6 +5,7 @@
 #include "ash/wm/tablet_mode/tablet_mode_window_manager.h"
 
 #include <memory>
+#include <vector>
 
 #include "ash/public/cpp/window_properties.h"
 #include "ash/root_window_controller.h"
@@ -32,7 +33,6 @@
 #include "ash/wm/workspace/workspace_layout_manager.h"
 #include "ash/wm/workspace_controller.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/memory/raw_ptr.h"
 #include "chromeos/ui/base/window_properties.h"
 #include "ui/aura/client/aura_constants.h"
@@ -546,10 +546,9 @@
   // IsCarryOverCandidateForSplitView() to be carried over to splitscreen.
   MruWindowTracker::WindowList mru_windows =
       Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
-  base::EraseIf(mru_windows, [](aura::Window* window) {
-        return window->GetProperty(
-                chromeos::kIsShowingInOverviewKey);
-    });
+  std::erase_if(mru_windows, [](aura::Window* window) {
+    return window->GetProperty(chromeos::kIsShowingInOverviewKey);
+  });
   aura::Window* root_window = Shell::GetPrimaryRootWindow();
   if (IsCarryOverCandidateForSplitView(mru_windows, 0u, root_window)) {
     if (GetWindowStateType(mru_windows[0], clamshell_to_tablet) ==
diff --git a/ash/wm/window_cycle/window_cycle_view.cc b/ash/wm/window_cycle/window_cycle_view.cc
index 846482ad..1129d10c 100644
--- a/ash/wm/window_cycle/window_cycle_view.cc
+++ b/ash/wm/window_cycle/window_cycle_view.cc
@@ -455,8 +455,8 @@
     // With no remaining child mini views contained in `preview`, we need to
     // remove `preview` and clean up the `preview` in `cycle_views_` and
     // `no_previews_list_`.
-    base::Erase(cycle_views_, preview);
-    base::Erase(no_previews_list_, preview);
+    std::erase(cycle_views_, preview);
+    std::erase(no_previews_list_, preview);
     parent->RemoveChildViewT(preview);
   }
   // With one of its children now gone, we must re-layout `mirror_container_`.
diff --git a/base/json/json_correctness_fuzzer.cc b/base/json/json_correctness_fuzzer.cc
index eb76ead..532d037 100644
--- a/base/json/json_correctness_fuzzer.cc
+++ b/base/json/json_correctness_fuzzer.cc
@@ -10,6 +10,7 @@
 #include <stdint.h>
 
 #include <string>
+#include <string_view>
 
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
@@ -29,7 +30,7 @@
   std::unique_ptr<char[]> input(new char[size - 1]);
   memcpy(input.get(), data, size - 1);
 
-  base::StringPiece input_string(input.get(), size - 1);
+  std::string_view input_string(input.get(), size - 1);
 
   const int options = data[size - 1];
   auto result =
diff --git a/base/json/json_parser.cc b/base/json/json_parser.cc
index db7c85a..d149095 100644
--- a/base/json/json_parser.cc
+++ b/base/json/json_parser.cc
@@ -6,7 +6,7 @@
 
 #include <cmath>
 #include <iterator>
-#include <optional>
+#include <string_view>
 #include <utility>
 #include <vector>
 
@@ -19,7 +19,6 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversion_utils.h"
@@ -70,7 +69,7 @@
 // UnprefixedHexStringToInt acts like |HexStringToInt|, but enforces that the
 // input consists purely of hex digits. I.e. no "0x" nor "OX" prefix is
 // permitted.
-bool UnprefixedHexStringToInt(StringPiece input, int* output) {
+bool UnprefixedHexStringToInt(std::string_view input, int* output) {
   for (size_t i = 0; i < input.size(); i++) {
     if (!IsHexDigit(input[i])) {
       return false;
@@ -127,7 +126,7 @@
 
 JSONParser::~JSONParser() = default;
 
-std::optional<Value> JSONParser::Parse(StringPiece input) {
+std::optional<Value> JSONParser::Parse(std::string_view input) {
   input_ = input;
   index_ = 0;
   // Line and column counting is 1-based, but |index_| is 0-based. For example,
@@ -230,30 +229,30 @@
 
 // JSONParser private //////////////////////////////////////////////////////////
 
-std::optional<StringPiece> JSONParser::PeekChars(size_t count) {
+std::optional<std::string_view> JSONParser::PeekChars(size_t count) {
   if (index_ + count > input_.length())
     return std::nullopt;
   // Using StringPiece::substr() is significantly slower (according to
   // base_perftests) than constructing a substring manually.
-  return StringPiece(input_.data() + index_, count);
+  return std::string_view(input_.data() + index_, count);
 }
 
 std::optional<char> JSONParser::PeekChar() {
-  std::optional<StringPiece> chars = PeekChars(1);
+  std::optional<std::string_view> chars = PeekChars(1);
   if (chars)
     return (*chars)[0];
   return std::nullopt;
 }
 
-std::optional<StringPiece> JSONParser::ConsumeChars(size_t count) {
-  std::optional<StringPiece> chars = PeekChars(count);
+std::optional<std::string_view> JSONParser::ConsumeChars(size_t count) {
+  std::optional<std::string_view> chars = PeekChars(count);
   if (chars)
     index_ += count;
   return chars;
 }
 
 std::optional<char> JSONParser::ConsumeChar() {
-  std::optional<StringPiece> chars = ConsumeChars(1);
+  std::optional<std::string_view> chars = ConsumeChars(1);
   if (chars)
     return (*chars)[0];
   return std::nullopt;
@@ -335,7 +334,7 @@
 }
 
 bool JSONParser::EatComment() {
-  std::optional<StringPiece> comment_start = PeekChars(2);
+  std::optional<std::string_view> comment_start = PeekChars(2);
   if (!comment_start)
     return false;
 
@@ -530,7 +529,7 @@
     return false;
   }
 
-  // StringBuilder will internally build a StringPiece unless a UTF-16
+  // StringBuilder will internally build a std::string_view unless a UTF-16
   // conversion occurs, at which point it will perform a copy into a
   // std::string.
   StringBuilder string(pos());
@@ -589,12 +588,12 @@
     } else {
       // And if it is an escape sequence, the input string will be adjusted
       // (either by combining the two characters of an encoded escape sequence,
-      // or with a UTF conversion), so using StringPiece isn't possible -- force
-      // a conversion.
+      // or with a UTF conversion), so using std::string_view isn't possible --
+      // force a conversion.
       string.Convert();
 
       // Read past the escape '\' and ensure there's a character following.
-      std::optional<StringPiece> escape_sequence = ConsumeChars(2);
+      std::optional<std::string_view> escape_sequence = ConsumeChars(2);
       if (!escape_sequence) {
         ReportError(JSON_INVALID_ESCAPE, -1);
         return false;
@@ -685,7 +684,7 @@
 
 // Entry is at the first X in \uXXXX.
 bool JSONParser::DecodeUTF16(base_icu::UChar32* out_code_point) {
-  std::optional<StringPiece> escape_sequence = ConsumeChars(4);
+  std::optional<std::string_view> escape_sequence = ConsumeChars(4);
   if (!escape_sequence)
     return false;
 
@@ -800,7 +799,7 @@
 
   index_ = exit_index;
 
-  StringPiece num_string(num_start, end_index - start_index);
+  std::string_view num_string(num_start, end_index - start_index);
 
   int num_int;
   if (StringToInt(num_string, &num_int)) {
@@ -858,7 +857,7 @@
   return std::nullopt;
 }
 
-bool JSONParser::ConsumeIfMatch(StringPiece match) {
+bool JSONParser::ConsumeIfMatch(std::string_view match) {
   if (match == PeekChars(match.size())) {
     ConsumeChars(match.size());
     return true;
diff --git a/base/json/json_parser.h b/base/json/json_parser.h
index 7a9cc688..d0c5caa 100644
--- a/base/json/json_parser.h
+++ b/base/json/json_parser.h
@@ -11,12 +11,12 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <string_view>
 
 #include "base/base_export.h"
 #include "base/compiler_specific.h"
 #include "base/gtest_prod_util.h"
 #include "base/json/json_common.h"
-#include "base/strings/string_piece.h"
 #include "base/third_party/icu/icu_utf.h"
 #include "base/values.h"
 
@@ -81,7 +81,7 @@
   // result as a Value.
   // Wrap this in base::FooValue::From() to check the Value is of type Foo and
   // convert to a FooValue at the same time.
-  std::optional<Value> Parse(StringPiece input);
+  std::optional<Value> Parse(std::string_view input);
 
   // Returns the error code.
   JsonParseError error_code() const;
@@ -115,7 +115,7 @@
   };
 
   // A helper class used for parsing strings. One optimization performed is to
-  // create base::Value with a StringPiece to avoid unnecessary std::string
+  // create base::Value with a std::string_view to avoid unnecessary std::string
   // copies. This is not possible if the input string needs to be decoded from
   // UTF-16 to UTF-8, or if an escape sequence causes characters to be skipped.
   // This class centralizes that logic.
@@ -136,9 +136,9 @@
     // converted, or by appending the UTF8 bytes for the code point.
     void Append(base_icu::UChar32 point);
 
-    // Converts the builder from its default StringPiece to a full std::string,
-    // performing a copy. Once a builder is converted, it cannot be made a
-    // StringPiece again.
+    // Converts the builder from its default std::string_view to a full
+    // std::string, performing a copy. Once a builder is converted, it cannot be
+    // made a std::string_view again.
     void Convert();
 
     // Returns the builder as a string, invalidating all state. This allows
@@ -160,14 +160,14 @@
 
   // Returns the next |count| bytes of the input stream, or nullopt if fewer
   // than |count| bytes remain.
-  std::optional<StringPiece> PeekChars(size_t count);
+  std::optional<std::string_view> PeekChars(size_t count);
 
   // Calls PeekChars() with a |count| of 1.
   std::optional<char> PeekChar();
 
   // Returns the next |count| bytes of the input stream, or nullopt if fewer
   // than |count| bytes remain, and advances the parser position by |count|.
-  std::optional<StringPiece> ConsumeChars(size_t count);
+  std::optional<std::string_view> ConsumeChars(size_t count);
 
   // Calls ConsumeChars() with a |count| of 1.
   std::optional<char> ConsumeChar();
@@ -230,7 +230,7 @@
   // consumed at the current parser position. Returns false if there are fewer
   // than |match|-length bytes or if the sequence does not match, and the
   // parser state is unchanged.
-  bool ConsumeIfMatch(StringPiece match);
+  bool ConsumeIfMatch(std::string_view match);
 
   // Sets the error information to |code| at the current column, based on
   // |index_| and |index_last_line_|, with an optional positive/negative
@@ -249,7 +249,7 @@
   const size_t max_depth_;
 
   // The input stream being parsed. Note: Not guaranteed to NUL-terminated.
-  StringPiece input_;
+  std::string_view input_;
 
   // The index in the input stream to which the parser is wound.
   size_t index_;
diff --git a/base/json/json_reader.cc b/base/json/json_reader.cc
index 62e670c..ec86924 100644
--- a/base/json/json_reader.cc
+++ b/base/json/json_reader.cc
@@ -4,7 +4,7 @@
 
 #include "base/json/json_reader.h"
 
-#include <optional>
+#include <string_view>
 #include <utility>
 
 #include "base/features.h"
@@ -81,7 +81,7 @@
   dict.Set(base::RustStrToStringPiece(key), base::Value(As{v}));
 }
 
-JSONReader::Result DecodeJSONInRust(const base::StringPiece& json,
+JSONReader::Result DecodeJSONInRust(std::string_view json,
                                     int options,
                                     size_t max_depth) {
   const serde_json_lenient::JsonOptions rust_options = {
@@ -134,7 +134,7 @@
 #endif  // BUILDFLAG(BUILD_RUST_JSON_READER)
 
 // static
-std::optional<Value> JSONReader::Read(StringPiece json,
+std::optional<Value> JSONReader::Read(std::string_view json,
                                       int options,
                                       size_t max_depth) {
 #if BUILDFLAG(BUILD_RUST_JSON_READER)
@@ -156,7 +156,7 @@
 }
 
 // static
-std::optional<Value::Dict> JSONReader::ReadDict(StringPiece json,
+std::optional<Value::Dict> JSONReader::ReadDict(std::string_view json,
                                                 int options,
                                                 size_t max_depth) {
   std::optional<Value> value = Read(json, options, max_depth);
@@ -167,8 +167,9 @@
 }
 
 // static
-JSONReader::Result JSONReader::ReadAndReturnValueWithError(StringPiece json,
-                                                           int options) {
+JSONReader::Result JSONReader::ReadAndReturnValueWithError(
+    std::string_view json,
+    int options) {
 #if BUILDFLAG(BUILD_RUST_JSON_READER)
   SCOPED_UMA_HISTOGRAM_TIMER_MICROS(kSecurityJsonParsingTime);
   if (UsingRust()) {
diff --git a/base/json/json_reader.h b/base/json/json_reader.h
index 52966a5..5ab1f88 100644
--- a/base/json/json_reader.h
+++ b/base/json/json_reader.h
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// A JSON parser, converting from a base::StringPiece to a base::Value.
+// A JSON parser, converting from a std::string_view to a base::Value.
 //
 // The JSON spec is:
 // https://tools.ietf.org/rfc/rfc8259.txt
@@ -14,7 +14,7 @@
 // Implementation choices permitted by the RFC:
 // - Nesting is limited (to a configurable depth, 200 by default).
 // - Numbers are limited to those representable by a finite double. The
-//   conversion from a JSON number (in the base::StringPiece input) to a
+//   conversion from a JSON number (in the std::string_view input) to a
 //   double-flavored base::Value may also be lossy.
 // - The input (which must be UTF-8) may begin with a BOM (Byte Order Mark).
 // - Duplicate object keys (strings) are silently allowed. Last key-value pair
@@ -38,11 +38,11 @@
 
 #include <optional>
 #include <string>
+#include <string_view>
 
 #include "base/base_export.h"
 #include "base/json/json_common.h"
 #include "base/strings/string_number_conversions.h"
-#include "base/strings/string_piece.h"
 #include "base/types/expected.h"
 #include "base/values.h"
 
@@ -105,16 +105,16 @@
   JSONReader& operator=(const JSONReader&) = delete;
 
   // Reads and parses |json|, returning a Value.
-  // If |json| is not a properly formed JSON string, returns std::nullopt.
+  // If |json| is not a properly formed JSON string, returns absl::nullopt.
   static std::optional<Value> Read(
-      StringPiece json,
+      std::string_view json,
       int options = JSON_PARSE_CHROMIUM_EXTENSIONS,
       size_t max_depth = internal::kAbsoluteMaxDepth);
 
   // Reads and parses |json|, returning a Value::Dict.
-  // If |json| is not a properly formed JSON dict string, returns std::nullopt.
+  // If |json| is not a properly formed JSON dict string, returns absl::nullopt.
   static std::optional<Value::Dict> ReadDict(
-      StringPiece json,
+      std::string_view json,
       int options = JSON_PARSE_CHROMIUM_EXTENSIONS,
       size_t max_depth = internal::kAbsoluteMaxDepth);
 
@@ -123,7 +123,7 @@
   // formatted error message, an error code, and the error location if
   // appropriate as the error value of the expected type.
   static Result ReadAndReturnValueWithError(
-      StringPiece json,
+      std::string_view json,
       int options = JSON_PARSE_CHROMIUM_EXTENSIONS);
 
   // Determine whether the Rust parser is in use.
diff --git a/base/json/json_reader_fuzzer.cc b/base/json/json_reader_fuzzer.cc
index 17a56062..43953a0 100644
--- a/base/json/json_reader_fuzzer.cc
+++ b/base/json/json_reader_fuzzer.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <string_view>
+
 #include "base/json/json_reader.h"
 
 #include <optional>
@@ -21,7 +23,7 @@
   std::unique_ptr<char[]> input(new char[size - 1]);
   memcpy(input.get(), data, size - 1);
 
-  StringPiece input_string(input.get(), size - 1);
+  std::string_view input_string(input.get(), size - 1);
 
   const int options = data[size - 1];
 
@@ -35,7 +37,7 @@
     CHECK(JSONWriter::Write(value, &serialized));
 
     std::optional<Value> deserialized =
-        JSONReader::Read(StringPiece(serialized));
+        JSONReader::Read(std::string_view(serialized));
     CHECK(deserialized);
     CHECK_EQ(value, deserialized.value());
   }
diff --git a/base/json/json_reader_unittest.cc b/base/json/json_reader_unittest.cc
index 10155e4..8b1194ee 100644
--- a/base/json/json_reader_unittest.cc
+++ b/base/json/json_reader_unittest.cc
@@ -7,7 +7,7 @@
 #include <stddef.h>
 
 #include <cmath>
-#include <optional>
+#include <string_view>
 #include <utility>
 
 #include "base/base_paths.h"
@@ -16,7 +16,6 @@
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "base/rust_buildflags.h"
-#include "base/strings/string_piece.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/gmock_expected_support.h"
@@ -31,13 +30,13 @@
 
 // MSan will do a better job detecting over-read errors if the input is not
 // nul-terminated on the heap. This will copy |input| to a new buffer owned by
-// |owner|, returning a base::StringPiece to |owner|.
-base::StringPiece MakeNotNullTerminatedInput(const char* input,
-                                             std::unique_ptr<char[]>* owner) {
+// |owner|, returning a std::string_view to |owner|.
+std::string_view MakeNotNullTerminatedInput(const char* input,
+                                            std::unique_ptr<char[]>* owner) {
   size_t str_len = strlen(input);
   owner->reset(new char[str_len]);
   memcpy(owner->get(), input, str_len);
-  return base::StringPiece(owner->get(), str_len);
+  return std::string_view(owner->get(), str_len);
 }
 
 }  // namespace
@@ -988,7 +987,7 @@
     SCOPED_TRACE(StringPrintf("case %u: \"%s\"", i, test_case.input));
 
     std::unique_ptr<char[]> input_owner;
-    StringPiece input =
+    std::string_view input =
         MakeNotNullTerminatedInput(test_case.input, &input_owner);
 
     std::optional<Value> result = JSONReader::Read(input);
@@ -1029,7 +1028,8 @@
     SCOPED_TRACE(StringPrintf("case %u: \"%s\"", i, test_case));
 
     std::unique_ptr<char[]> input_owner;
-    StringPiece input = MakeNotNullTerminatedInput(test_case, &input_owner);
+    std::string_view input =
+        MakeNotNullTerminatedInput(test_case, &input_owner);
 
     EXPECT_FALSE(JSONReader::Read(input));
   }
diff --git a/base/json/json_string_value_serializer.cc b/base/json/json_string_value_serializer.cc
index 561bfa07..ee858b6 100644
--- a/base/json/json_string_value_serializer.cc
+++ b/base/json/json_string_value_serializer.cc
@@ -4,6 +4,8 @@
 
 #include "base/json/json_string_value_serializer.h"
 
+#include <string_view>
+
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 
@@ -40,7 +42,7 @@
 }
 
 JSONStringValueDeserializer::JSONStringValueDeserializer(
-    const base::StringPiece& json_string,
+    std::string_view json_string,
     int options)
     : json_string_(json_string), options_(options) {}
 
diff --git a/base/json/json_string_value_serializer.h b/base/json/json_string_value_serializer.h
index 5386546..d14fecf 100644
--- a/base/json/json_string_value_serializer.h
+++ b/base/json/json_string_value_serializer.h
@@ -7,11 +7,11 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 
 #include "base/base_export.h"
 #include "base/json/json_reader.h"
 #include "base/memory/raw_ptr.h"
-#include "base/strings/string_piece.h"
 #include "base/values.h"
 
 class BASE_EXPORT JSONStringValueSerializer : public base::ValueSerializer {
@@ -53,7 +53,7 @@
   // must outlive the JSONStringValueDeserializer. |options| is a bitmask of
   // JSONParserOptions.
   explicit JSONStringValueDeserializer(
-      const base::StringPiece& json_string,
+      std::string_view json_string,
       int options = base::JSON_PARSE_CHROMIUM_EXTENSIONS);
 
   JSONStringValueDeserializer(const JSONStringValueDeserializer&) = delete;
@@ -76,7 +76,7 @@
 
  private:
   // Data is owned by the caller of the constructor.
-  base::StringPiece json_string_;
+  std::string_view json_string_;
   const int options_;
 };
 
diff --git a/base/json/json_value_converter.h b/base/json/json_value_converter.h
index 918afce2..5afcdb6f 100644
--- a/base/json/json_value_converter.h
+++ b/base/json/json_value_converter.h
@@ -9,13 +9,13 @@
 
 #include <memory>
 #include <string>
+#include <string_view>
 #include <utility>
 #include <vector>
 
 #include "base/base_export.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
-#include "base/strings/string_piece.h"
 #include "base/values.h"
 
 // JSONValueConverter converts a JSON value into a C++ struct in a
@@ -70,8 +70,8 @@
 //
 // Sometimes JSON format uses string representations for other types such
 // like enum, timestamp, or URL.  You can use RegisterCustomField method
-// and specify a function to convert a StringPiece to your type.
-//   bool ConvertFunc(StringPiece s, YourEnum* result) {
+// and specify a function to convert a std::string_view to your type.
+//   bool ConvertFunc(std::string_view s, YourEnum* result) {
 //     // do something and return true if succeed...
 //   }
 //   struct Message {
@@ -219,7 +219,7 @@
 template <typename FieldType>
 class CustomFieldConverter : public ValueConverter<FieldType> {
  public:
-  typedef bool (*ConvertFunc)(StringPiece value, FieldType* field);
+  typedef bool (*ConvertFunc)(std::string_view value, FieldType* field);
 
   explicit CustomFieldConverter(ConvertFunc convert_func)
       : convert_func_(convert_func) {}
@@ -415,7 +415,7 @@
   template <typename FieldType>
   void RegisterCustomField(const std::string& field_name,
                            FieldType StructType::*field,
-                           bool (*convert_func)(StringPiece, FieldType*)) {
+                           bool (*convert_func)(std::string_view, FieldType*)) {
     fields_.push_back(
         std::make_unique<internal::FieldConverter<StructType, FieldType>>(
             field_name, field,
diff --git a/base/json/json_value_converter_unittest.cc b/base/json/json_value_converter_unittest.cc
index 9a95421..921aeda 100644
--- a/base/json/json_value_converter_unittest.cc
+++ b/base/json/json_value_converter_unittest.cc
@@ -7,10 +7,10 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "base/json/json_reader.h"
-#include "base/strings/string_piece.h"
 #include "base/values.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -32,7 +32,7 @@
   std::vector<std::unique_ptr<std::string>> string_values;
   SimpleMessage() : foo(0), baz(false), bstruct(false), simple_enum(FOO) {}
 
-  static bool ParseSimpleEnum(StringPiece value, SimpleEnum* field) {
+  static bool ParseSimpleEnum(std::string_view value, SimpleEnum* field) {
     if (value == "foo") {
       *field = FOO;
       return true;
diff --git a/base/json/json_value_serializer_unittest.cc b/base/json/json_value_serializer_unittest.cc
index 30ab7bc..12e10a4 100644
--- a/base/json/json_value_serializer_unittest.cc
+++ b/base/json/json_value_serializer_unittest.cc
@@ -5,6 +5,7 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <string_view>
 
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
@@ -13,7 +14,6 @@
 #include "base/json/json_string_value_serializer.h"
 #include "base/json/json_writer.h"
 #include "base/path_service.h"
-#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
@@ -103,11 +103,11 @@
   CheckJSONIsStillTheSame(*value);
 }
 
-// Test proper JSON deserialization from a StringPiece substring.
+// Test proper JSON deserialization from a std::string_view substring.
 TEST(JSONValueDeserializerTest, ReadProperJSONFromStringPiece) {
-  // Create a StringPiece for the substring of kProperJSONPadded that matches
-  // kProperJSON.
-  StringPiece proper_json(kProperJSONPadded);
+  // Create a std::string_view for the substring of kProperJSONPadded that
+  // matches kProperJSON.
+  std::string_view proper_json(kProperJSONPadded);
   proper_json = proper_json.substr(5, proper_json.length() - 10);
   JSONStringValueDeserializer str_deserializer(proper_json);
 
diff --git a/base/json/json_writer.cc b/base/json/json_writer.cc
index a58ec155..ef6de84e 100644
--- a/base/json/json_writer.cc
+++ b/base/json/json_writer.cc
@@ -8,6 +8,7 @@
 
 #include <cmath>
 #include <limits>
+#include <string_view>
 
 #include "base/json/string_escape.h"
 #include "base/logging.h"
@@ -106,7 +107,7 @@
   return true;
 }
 
-bool JSONWriter::BuildJSONString(StringPiece node, size_t depth) {
+bool JSONWriter::BuildJSONString(std::string_view node, size_t depth) {
   EscapeJSONString(node, true, json_string_);
   return true;
 }
diff --git a/base/json/json_writer.h b/base/json/json_writer.h
index 05aef57..c9eb3bd 100644
--- a/base/json/json_writer.h
+++ b/base/json/json_writer.h
@@ -10,11 +10,11 @@
 #include <cstdint>
 #include <optional>
 #include <string>
+#include <string_view>
 
 #include "base/base_export.h"
 #include "base/json/json_common.h"
 #include "base/memory/raw_ptr.h"
-#include "base/strings/string_piece.h"
 #include "base/values.h"
 
 namespace base {
@@ -106,7 +106,7 @@
   bool BuildJSONString(bool node, size_t depth);
   bool BuildJSONString(int node, size_t depth);
   bool BuildJSONString(double node, size_t depth);
-  bool BuildJSONString(StringPiece node, size_t depth);
+  bool BuildJSONString(std::string_view node, size_t depth);
   bool BuildJSONString(const Value::BlobStorage& node, size_t depth);
   bool BuildJSONString(const Value::Dict& node, size_t depth);
   bool BuildJSONString(const Value::List& node, size_t depth);
diff --git a/base/json/string_escape.cc b/base/json/string_escape.cc
index 84db0f8..aab1a8e 100644
--- a/base/json/string_escape.cc
+++ b/base/json/string_escape.cc
@@ -9,6 +9,7 @@
 
 #include <limits>
 #include <string>
+#include <string_view>
 
 #include "base/check_op.h"
 #include "base/strings/string_util.h"
@@ -112,29 +113,31 @@
 
 }  // namespace
 
-bool EscapeJSONString(StringPiece str, bool put_in_quotes, std::string* dest) {
-  return EscapeJSONStringImpl(str, put_in_quotes, dest);
-}
-
-bool EscapeJSONString(StringPiece16 str,
+bool EscapeJSONString(std::string_view str,
                       bool put_in_quotes,
                       std::string* dest) {
   return EscapeJSONStringImpl(str, put_in_quotes, dest);
 }
 
-std::string GetQuotedJSONString(StringPiece str) {
+bool EscapeJSONString(std::u16string_view str,
+                      bool put_in_quotes,
+                      std::string* dest) {
+  return EscapeJSONStringImpl(str, put_in_quotes, dest);
+}
+
+std::string GetQuotedJSONString(std::string_view str) {
   std::string dest;
   EscapeJSONStringImpl(str, true, &dest);
   return dest;
 }
 
-std::string GetQuotedJSONString(StringPiece16 str) {
+std::string GetQuotedJSONString(std::u16string_view str) {
   std::string dest;
   EscapeJSONStringImpl(str, true, &dest);
   return dest;
 }
 
-std::string EscapeBytesAsInvalidJSONString(StringPiece str,
+std::string EscapeBytesAsInvalidJSONString(std::string_view str,
                                            bool put_in_quotes) {
   std::string dest;
 
diff --git a/base/json/string_escape.h b/base/json/string_escape.h
index ca7f7fa..6c93ed2 100644
--- a/base/json/string_escape.h
+++ b/base/json/string_escape.h
@@ -8,9 +8,9 @@
 #define BASE_JSON_STRING_ESCAPE_H_
 
 #include <string>
+#include <string_view>
 
 #include "base/base_export.h"
-#include "base/strings/string_piece.h"
 
 namespace base {
 
@@ -26,21 +26,21 @@
 //
 // If |put_in_quotes| is true, then a leading and trailing double-quote mark
 // will be appended to |dest| as well.
-BASE_EXPORT bool EscapeJSONString(StringPiece str,
+BASE_EXPORT bool EscapeJSONString(std::string_view str,
                                   bool put_in_quotes,
                                   std::string* dest);
 
-// Performs a similar function to the UTF-8 StringPiece version above,
+// Performs a similar function to the UTF-8 std::string_view version above,
 // converting UTF-16 code units to UTF-8 code units and escaping non-printing
 // control characters. On return, |dest| will contain a valid UTF-8 JSON string.
-BASE_EXPORT bool EscapeJSONString(StringPiece16 str,
+BASE_EXPORT bool EscapeJSONString(std::u16string_view str,
                                   bool put_in_quotes,
                                   std::string* dest);
 
 // Helper functions that wrap the above two functions but return the value
 // instead of appending. |put_in_quotes| is always true.
-BASE_EXPORT std::string GetQuotedJSONString(StringPiece str);
-BASE_EXPORT std::string GetQuotedJSONString(StringPiece16 str);
+BASE_EXPORT std::string GetQuotedJSONString(std::string_view str);
+BASE_EXPORT std::string GetQuotedJSONString(std::u16string_view str);
 
 // Given an arbitrary byte string |str|, this will escape all non-ASCII bytes
 // as \uXXXX escape sequences. This function is *NOT* meant to be used with
@@ -53,7 +53,7 @@
 //
 // The output of this function takes the *appearance* of JSON but is not in
 // fact valid according to RFC 8259.
-BASE_EXPORT std::string EscapeBytesAsInvalidJSONString(StringPiece str,
+BASE_EXPORT std::string EscapeBytesAsInvalidJSONString(std::string_view str,
                                                        bool put_in_quotes);
 
 }  // namespace base
diff --git a/base/json/string_escape_fuzzer.cc b/base/json/string_escape_fuzzer.cc
index 9a63aab..dd67a32 100644
--- a/base/json/string_escape_fuzzer.cc
+++ b/base/json/string_escape_fuzzer.cc
@@ -2,9 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/json/string_escape.h"
-
 #include <memory>
+#include <string_view>
+
+#include "base/json/string_escape.h"
 
 // Entry point for LibFuzzer.
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
@@ -19,7 +20,7 @@
   std::unique_ptr<char[]> input(new char[actual_size_char8]);
   memcpy(input.get(), data, actual_size_char8);
 
-  base::StringPiece input_string(input.get(), actual_size_char8);
+  std::string_view input_string(input.get(), actual_size_char8);
   std::string escaped_string;
   base::EscapeJSONString(input_string, put_in_quotes, &escaped_string);
 
@@ -28,7 +29,7 @@
     return 0;
 
   size_t actual_size_char16 = actual_size_char8 / 2;
-  base::StringPiece16 input_string16(reinterpret_cast<char16_t*>(input.get()),
+  std::u16string_view input_string16(reinterpret_cast<char16_t*>(input.get()),
                                      actual_size_char16);
   escaped_string.clear();
   base::EscapeJSONString(input_string16, put_in_quotes, &escaped_string);
diff --git a/base/json/values_util_unittest.cc b/base/json/values_util_unittest.cc
index ef7cb98..c5a6894 100644
--- a/base/json/values_util_unittest.cc
+++ b/base/json/values_util_unittest.cc
@@ -5,9 +5,9 @@
 #include "base/json/values_util.h"
 
 #include <limits>
+#include <string_view>
 
 #include "base/files/file_path.h"
-#include "base/strings/string_piece.h"
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -19,7 +19,7 @@
 TEST(ValuesUtilTest, BasicInt64Limits) {
   constexpr struct {
     int64_t input;
-    StringPiece expected;
+    std::string_view expected;
   } kTestCases[] = {
       {0, "0"},
       {-1234, "-1234"},
@@ -71,7 +71,7 @@
 
 TEST(ValuesUtilTest, FilePath) {
   // Ω is U+03A9 GREEK CAPITAL LETTER OMEGA, a non-ASCII character.
-  constexpr StringPiece kTestCases[] = {
+  constexpr std::string_view kTestCases[] = {
       "/unix/Ω/path.dat",
       "C:\\windows\\Ω\\path.dat",
   };
@@ -89,7 +89,7 @@
   constexpr struct {
     uint64_t high;
     uint64_t low;
-    StringPiece expected;
+    std::string_view expected;
   } kTestCases[] = {
       {0x123456u, 0x9ABCu, "5634120000000000BC9A000000000000"},
   };
diff --git a/base/metrics/OWNERS b/base/metrics/OWNERS
index b9a5227..a334389 100644
--- a/base/metrics/OWNERS
+++ b/base/metrics/OWNERS
@@ -13,4 +13,3 @@
 mpearson@chromium.org
 rkaplow@chromium.org
 rogerm@chromium.org
-sweilun@chromium.org
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
index 9cd7f3ea..cf93dc6 100644
--- a/base/test/BUILD.gn
+++ b/base/test/BUILD.gn
@@ -278,6 +278,7 @@
     deps += [
       ":google_test_runner_shared_headers",
       "//build:blink_buildflags",
+      "//build:ios_buildflags",
     ]
 
     # With blink, we use the standard unit_test_launcher.cc.
diff --git a/base/test/test_support_ios.mm b/base/test/test_support_ios.mm
index 97be8460..e28fa9af 100644
--- a/base/test/test_support_ios.mm
+++ b/base/test/test_support_ios.mm
@@ -15,6 +15,7 @@
 #include "base/test/test_suite.h"
 #include "base/test/test_switches.h"
 #include "build/blink_buildflags.h"
+#include "build/ios_buildflags.h"
 #include "testing/coverage_util_ios.h"
 
 // Springboard will kill any iOS app that fails to check in after launch within
@@ -245,10 +246,12 @@
 #endif
   _window = nil;
 
+#if !BUILDFLAG(IS_IOS_APP_EXTENSION)
   // Use the hidden selector to try and cleanly take down the app (otherwise
   // things can think the app crashed even on a zero exit status).
   UIApplication* application = [UIApplication sharedApplication];
   [application _terminateWithStatus:exitStatus];
+#endif
 
   exit(exitStatus);
 }
diff --git a/base/win/hstring_reference.cc b/base/win/hstring_reference.cc
index b254fc64..f98f1a2 100644
--- a/base/win/hstring_reference.cc
+++ b/base/win/hstring_reference.cc
@@ -14,16 +14,13 @@
 
 namespace base::win {
 
-HStringReference::HStringReference(const wchar_t* str, size_t length) {
+HStringReference::HStringReference(const wchar_t* str) {
   // String must be null terminated for WindowsCreateStringReference.
   // nullptr str is OK so long as the length is 0.
-  DCHECK(str ? str[length] == L'\0' : length == 0);
+  size_t length = str ? wcslen(str) : 0;
   const HRESULT hr = ::WindowsCreateStringReference(
       str, checked_cast<UINT32>(length), &hstring_header_, &hstring_);
   DCHECK_EQ(hr, S_OK);
 }
 
-HStringReference::HStringReference(const wchar_t* str)
-    : HStringReference(str, str ? wcslen(str) : 0) {}
-
 }  // namespace base::win
diff --git a/base/win/hstring_reference.h b/base/win/hstring_reference.h
index 3cde4ac..41a753da 100644
--- a/base/win/hstring_reference.h
+++ b/base/win/hstring_reference.h
@@ -23,7 +23,7 @@
 //
 class BASE_EXPORT HStringReference {
  public:
-  HStringReference(const wchar_t* str, size_t len);
+  // Creates an HStringReference from `str`, which must be null terminated.
   explicit HStringReference(const wchar_t* str);
 
   HSTRING Get() const { return hstring_; }
diff --git a/base/win/hstring_reference_unittest.cc b/base/win/hstring_reference_unittest.cc
index 3f67ff2..0a32399 100644
--- a/base/win/hstring_reference_unittest.cc
+++ b/base/win/hstring_reference_unittest.cc
@@ -35,8 +35,8 @@
   EXPECT_EQ(empty_string.Get(), nullptr);
   VerifyHSTRINGEquals(empty_string.Get(), kEmptyString);
 
-  // Passing a zero length and null string should also return a null HSTRING.
-  const HStringReference null_string(nullptr, 0);
+  // Passing a null string should also return a null HSTRING.
+  const HStringReference null_string(nullptr);
   EXPECT_EQ(null_string.Get(), nullptr);
   VerifyHSTRINGEquals(null_string.Get(), kEmptyString);
 }
diff --git a/build/config/BUILD.gn b/build/config/BUILD.gn
index 9410bc6..f3416871 100644
--- a/build/config/BUILD.gn
+++ b/build/config/BUILD.gn
@@ -223,12 +223,6 @@
   }
 }
 
-_toolchain_marker_name =
-    "toolchain_marker_" + get_label_info(current_toolchain, "name")
-group(_toolchain_marker_name) {
-  # Can be used as an assert_no_deps target (assert_no_deps ignores toolchains).
-}
-
 group("common_deps") {
   visibility = [
     ":executable_deps",
@@ -238,7 +232,7 @@
 
   # WARNING: This group is a dependency of **every executable and shared
   # library**.  Please be careful adding new dependencies here.
-  public_deps = [ ":$_toolchain_marker_name" ]
+  public_deps = []
 
   if (using_sanitizer) {
     public_deps += [ "//build/config/sanitizers:deps" ]
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni
index 8853527..5f27a3f 100644
--- a/build/config/android/internal_rules.gni
+++ b/build/config/android/internal_rules.gni
@@ -3036,7 +3036,6 @@
 
       if (target_name == "chrome_java__header") {
         # Regression test for: https://crbug.com/1154302
-        # Ensures that header jars never depend on non-header jars.
         assert_no_deps = [ "//base:base_java__compile_java" ]
       }
 
@@ -3753,9 +3752,6 @@
       if (defined(invoker.public_deps)) {
         possible_config_public_deps = invoker.public_deps
       }
-      if (defined(invoker.asset_deps)) {
-        possible_config_deps += invoker.asset_deps
-      }
       if (defined(apk_under_test)) {
         possible_config_deps += [ apk_under_test ]
       }
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index d6a6b1b..018c1b2a 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -2659,7 +2659,6 @@
                                "android_manifest_dep",
                                "annotation_processor_deps",
                                "apk_under_test",
-                               "asset_deps",
                                "base_module_target",
                                "chromium_code",
                                "deps",
@@ -2698,9 +2697,6 @@
       if (defined(_final_dex_path)) {
         final_dex_path = _final_dex_path
       }
-      if (defined(invoker.assert_no_native_deps)) {
-        assert_no_deps = invoker.assert_no_native_deps
-      }
 
       if (_is_bundle_module) {
         proto_resources_path = _proto_resources_path
@@ -2884,9 +2880,6 @@
                        ":$_compile_resources_target",
                        ":$_merge_manifest_target",
                      ] + _all_native_libs_deps
-      if (defined(invoker.asset_deps)) {
-        _final_deps += invoker.asset_deps
-      }
       if (_optimize_resources) {
         _final_deps += [ ":$_optimize_resources_target" ]
       }
@@ -2947,9 +2940,6 @@
         # Need full deps rather than _non_java_deps, because loadable_modules
         # may include .so files extracted by __unpack_aar targets.
         deps = _invoker_deps + [ ":$_build_config_target" ]
-        if (defined(invoker.asset_deps)) {
-          deps += invoker.asset_deps
-        }
 
         if (_incremental_apk) {
           _dex_target = "//build/android/incremental_install:apk_dex"
@@ -3227,8 +3217,6 @@
             "app_as_shared_lib",
             "art_profile_path",
             "assert_no_deps",
-            "assert_no_native_deps",
-            "asset_deps",
             "baseline_profile_path",
             "build_config_include_product_version_resource",
             "bundles_supported",
@@ -3379,8 +3367,6 @@
             "annotation_processor_deps",
             "app_as_shared_lib",
             "assert_no_deps",
-            "assert_no_native_deps",
-            "asset_deps",
             "base_module_target",
             "build_config_include_product_version_resource",
             "bundle_target",
diff --git a/build/ios/extension_bundle_data.gni b/build/ios/extension_bundle_data.gni
index 3e824e8..b47f0320 100644
--- a/build/ios/extension_bundle_data.gni
+++ b/build/ios/extension_bundle_data.gni
@@ -23,6 +23,7 @@
     _extension_name = invoker.extension_name
   }
 
+  forward_variables_from(invoker, [ "testonly" ])
   bundle_data(target_name) {
     public_deps = [ invoker.extension_target ]
     outputs = [ "{{bundle_contents_dir}}/PlugIns/{{source_file_part}}" ]
diff --git a/cc/paint/record_paint_canvas.cc b/cc/paint/record_paint_canvas.cc
index 4ff7deb..93ff950 100644
--- a/cc/paint/record_paint_canvas.cc
+++ b/cc/paint/record_paint_canvas.cc
@@ -367,6 +367,13 @@
     const gfx::Size& size)
     : canvas_(size.width(), size.height()) {}
 
+InspectableRecordPaintCanvas::InspectableRecordPaintCanvas(
+    CreateChildCanvasTag,
+    const InspectableRecordPaintCanvas& parent)
+    : canvas_(SkIRect::MakeSize(parent.imageInfo().dimensions())) {
+  canvas_.setMatrix(parent.canvas_.getLocalToDevice());
+}
+
 InspectableRecordPaintCanvas::~InspectableRecordPaintCanvas() = default;
 
 int InspectableRecordPaintCanvas::save() {
diff --git a/cc/paint/record_paint_canvas.h b/cc/paint/record_paint_canvas.h
index 16116e4..008290d 100644
--- a/cc/paint/record_paint_canvas.h
+++ b/cc/paint/record_paint_canvas.h
@@ -237,6 +237,14 @@
   // Don't shadow non-virtual helper functions.
   using RecordPaintCanvas::clipRect;
 
+ protected:
+  // Creates a child canvas that has the same transform matrix and size as
+  // `parent`. `CreateChildCanvasTag` is used to differentiate this from a copy
+  // constructor.
+  struct CreateChildCanvasTag {};
+  InspectableRecordPaintCanvas(CreateChildCanvasTag,
+                               const InspectableRecordPaintCanvas& parent);
+
  private:
   void clipRRectInternal(const SkRRect& rrect,
                          SkClipOp op,
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index c73ee3d..09f1c67 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -87,7 +87,6 @@
     chrome_public_apk_or_module_tmpl(_base_module_target_name) {
       forward_variables_from(invoker,
                              [
-                               "assert_no_deps",
                                "add_view_trace_events",
                                "expected_android_manifest",
                                "is_64_bit_browser",
@@ -2537,12 +2536,6 @@
     target_type = "android_apk"
     apk_name = "ChromePublic"
     art_profile_path = "//chrome/android/baseline_profiles/profile.txt"
-    if (android_64bit_target_cpu) {
-      # Ensure 64-bit chrome does not depend on 32-bit things.
-      assert_no_deps =
-          [ "//build/config:toolchain_marker_" +
-            get_label_info(android_secondary_abi_toolchain, "name") ]
-    }
   }
 
   chrome_public_bundle("chrome_public_bundle") {
@@ -3870,11 +3863,6 @@
   }
 
   module_descs = chrome_module_descs
-
-  # Java and native targets form two independent compile graphs. Deps from java targets
-  # onto native ones (or vice versa) are unnecessary and reduce parallelism.
-  # This prevents deps from native -> java.
-  assert_no_deps = [ "//build/android:build_java" ]
 }
 
 chrome_common_shared_library("libchromefortest") {
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index fd6e0d5..dcaf483 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -8,7 +8,6 @@
 import("//build/config/android/rules.gni")
 import("//build/config/compiler/compiler.gni")
 import("//build/config/locales.gni")
-import("//build/toolchain/gcc_toolchain.gni")
 import("//chrome/android/features/dev_ui/dev_ui_module.gni")
 import("//chrome/android/modules/chrome_bundle_tmpl.gni")
 import("//chrome/android/trichrome.gni")
@@ -381,24 +380,10 @@
       }
     }
 
-    # shared_resources_allowlist_target causes a native dep to appear via resources.
-    if (!_is_monochrome) {
-      # Java and native targets form two independent compile graphs. Deps from java targets
-      # onto native ones (or vice versa) are unnecessary and reduce parallelism.
-      # This prevents most deps from java->native.
-      # One common violation is generate_jni() targets, which generate
-      # .srcjars, but also .h files, and so export their native deps.
-      # Tip: If the dep is due to loadable_modules or android_assets, use "asset_deps" rather than
-      #      "deps".
-      assert_no_native_deps = [
-        "//base",
-        "//build/config/compiler:compiler_buildflags",
-        "//build/rust:cxx_cppdeps",
-        "//third_party/abseil-cpp:absl",
-      ]
-    }
-
-    deps = [ "//chrome/android:chrome_base_module_resources" ]
+    deps = [
+      "//chrome/android:chrome_base_module_resources",
+      "//chrome/android:chrome_public_non_pak_assets",
+    ]
 
     # TODO(agrieve): Make unconditional when moving to trampoline.
     if (_is_monochrome || _is_trichrome) {
@@ -461,23 +446,17 @@
       }
     }
 
-    asset_deps = [ "//chrome/android:chrome_public_non_pak_assets" ]
-    if (defined(invoker.asset_deps)) {
-      asset_deps += invoker.asset_deps
-    }
-
     if (_is_bundle && _is_monochrome) {
-      asset_deps += [ "//chrome/android:monochrome_bundle_module_pak_assets" ]
+      deps += [ "//chrome/android:monochrome_bundle_module_pak_assets" ]
     } else if (_is_bundle && _is_trichrome) {
-      asset_deps +=
-          [ "//chrome/android:trichrome_chrome_bundle_module_pak_assets" ]
+      deps += [ "//chrome/android:trichrome_chrome_bundle_module_pak_assets" ]
     } else if (_is_bundle) {
-      asset_deps += [ "//chrome/android:chrome_bundle_module_pak_assets" ]
+      deps += [ "//chrome/android:chrome_bundle_module_pak_assets" ]
     } else if (_is_monochrome) {
-      asset_deps += [ "//chrome/android:monochrome_apk_pak_assets" ]
+      deps += [ "//chrome/android:monochrome_apk_pak_assets" ]
     } else {
       assert(!_is_trichrome)
-      asset_deps += [ "//chrome/android:chrome_apk_pak_assets" ]
+      deps += [ "//chrome/android:chrome_apk_pak_assets" ]
     }
 
     if (defined(invoker.add_upstream_only_deps) &&
@@ -487,9 +466,9 @@
       } else if (!_is_trichrome) {
         deps += [
           "//chrome/android:chrome_public_apk_base_module_resources",
+          "//chrome/android:chrome_public_non_pak_assets",
           "//components/browser_ui/styles/android:chrome_public_apk_resources",
         ]
-        asset_deps += [ "//chrome/android:chrome_public_non_pak_assets" ]
       }
       if (_is_bundle) {
         deps += [
@@ -536,12 +515,11 @@
         (target_cpu == "arm" ||
          (target_cpu == "arm64" && !_is_64_bit_browser))) {
       if (_is_test) {
-        asset_deps +=
-            [ "//chrome/android:libchromefortest_unwind_table_assets" ]
+        deps += [ "//chrome/android:libchromefortest_unwind_table_assets" ]
       } else if (_is_monochrome || _is_trichrome) {
-        asset_deps += [ "//chrome/android:libmonochrome_unwind_table_assets" ]
+        deps += [ "//chrome/android:libmonochrome_unwind_table_assets" ]
       } else {
-        asset_deps += [ "//chrome/android:libchrome_unwind_table_assets" ]
+        deps += [ "//chrome/android:libchrome_unwind_table_assets" ]
       }
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditor.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditor.java
index 36b7fbf..ac0187c 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditor.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditor.java
@@ -6,14 +6,7 @@
 
 import android.content.Context;
 
-import org.chromium.base.ValueChangedCallback;
-import org.chromium.base.supplier.ObservableSupplier;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tabmodel.TabModelObserver;
-import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
-import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -24,72 +17,9 @@
  */
 public abstract class TabGroupTitleEditor {
     private final Context mContext;
-    private final ObservableSupplier<TabModelFilter> mCurrentTabModelFilterSupplier;
-    private final TabModelObserver mTabModelObserver;
-    private final TabGroupModelFilterObserver mFilterObserver;
-    private final ValueChangedCallback<TabModelFilter> mCurrentTabModelFilterObserver =
-            new ValueChangedCallback<>(this::onTabModelFilterChanged);
 
-    public TabGroupTitleEditor(
-            Context context, ObservableSupplier<TabModelFilter> tabModelFilterSupplier) {
+    public TabGroupTitleEditor(Context context) {
         mContext = context;
-        mCurrentTabModelFilterSupplier = tabModelFilterSupplier;
-
-        mTabModelObserver =
-                new TabModelObserver() {
-                    @Override
-                    public void tabClosureCommitted(Tab tab) {
-                        var filter = (TabGroupModelFilter) mCurrentTabModelFilterSupplier.get();
-                        int rootId = tab.getRootId();
-                        Tab groupTab = filter.getGroupLastShownTab(rootId);
-                        if (groupTab == null || !filter.isTabInTabGroup(groupTab)) {
-                            deleteTabGroupTitle(rootId);
-                        }
-                    }
-                };
-
-        mFilterObserver =
-                new TabGroupModelFilterObserver() {
-                    @Override
-                    public void willMergeTabToGroup(Tab movedTab, int newRootId) {
-                        String sourceGroupTitle = getTabGroupTitle(movedTab.getRootId());
-                        String targetGroupTitle = getTabGroupTitle(newRootId);
-                        if (sourceGroupTitle == null) return;
-                        // If the target group has no title but the source group has a title,
-                        // handover the stored title to the group after merge.
-                        if (targetGroupTitle == null) {
-                            storeTabGroupTitle(newRootId, sourceGroupTitle);
-                        }
-                    }
-
-                    @Override
-                    public void willMoveTabOutOfGroup(Tab movedTab, int newRootId) {
-                        int rootId = movedTab.getRootId();
-                        String title = getTabGroupTitle(rootId);
-                        if (title == null) return;
-                        // If the group size is 2, i.e. the group becomes a single tab after
-                        // ungroup, delete the stored title. When tab groups of size 1 are supported
-                        // this behavior is no longer valid.
-                        var filter = (TabGroupModelFilter) mCurrentTabModelFilterSupplier.get();
-                        int sizeThreshold =
-                                ChromeFeatureList.sAndroidTabGroupStableIds.isEnabled() ? 1 : 2;
-                        boolean shouldDeleteTitle =
-                                filter.getRelatedTabCountForRootId(rootId) <= sizeThreshold;
-                        if (shouldDeleteTitle) {
-                            deleteTabGroupTitle(rootId);
-                            return;
-                        }
-                        // If the root tab in group is moved out, re-assign the title to the new
-                        // root tab in group.
-                        if (rootId != newRootId) {
-                            deleteTabGroupTitle(rootId);
-                            storeTabGroupTitle(newRootId, title);
-                        }
-                    }
-                };
-
-        mCurrentTabModelFilterObserver.onResult(
-                mCurrentTabModelFilterSupplier.addObserver(mCurrentTabModelFilterObserver));
     }
 
     /**
@@ -146,28 +76,4 @@
      * @return The stored title of the related group.
      */
     protected abstract String getTabGroupTitle(int tabRootId);
-
-    /** Destroy any members that needs clean up. */
-    public void destroy() {
-        removeTabModelFilterObservers(mCurrentTabModelFilterSupplier.get());
-        mCurrentTabModelFilterSupplier.removeObserver(mCurrentTabModelFilterObserver);
-    }
-
-    private void onTabModelFilterChanged(TabModelFilter newFilter, TabModelFilter oldFilter) {
-        removeTabModelFilterObservers(oldFilter);
-
-        if (newFilter != null) {
-            TabGroupModelFilter newGroupFilter = (TabGroupModelFilter) newFilter;
-            newGroupFilter.addObserver(mTabModelObserver);
-            newGroupFilter.addTabGroupObserver(mFilterObserver);
-        }
-    }
-
-    private void removeTabModelFilterObservers(TabModelFilter filter) {
-        if (filter != null) {
-            TabGroupModelFilter groupFilter = (TabGroupModelFilter) filter;
-            groupFilter.removeObserver(mTabModelObserver);
-            groupFilter.removeTabGroupObserver(mFilterObserver);
-        }
-    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupVisualDataManager.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupVisualDataManager.java
new file mode 100644
index 0000000..731fd25
--- /dev/null
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupVisualDataManager.java
@@ -0,0 +1,165 @@
+// Copyright 2024 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.tasks.tab_management;
+
+import androidx.annotation.NonNull;
+
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupColorUtils;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupTitleUtils;
+import org.chromium.components.tab_groups.TabGroupColorId;
+
+/**
+ * Manages observers that monitor for updates to tab group visual aspects such as colors and titles.
+ */
+public class TabGroupVisualDataManager {
+    private static final int INVALID_COLOR_ID = -1;
+
+    private final TabModelSelector mTabModelSelector;
+    private TabModelObserver mTabModelObserver;
+    private TabGroupModelFilterObserver mFilterObserver;
+
+    public TabGroupVisualDataManager(@NonNull TabModelSelector tabModelSelector) {
+        assert tabModelSelector.isTabStateInitialized();
+        mTabModelSelector = tabModelSelector;
+
+        TabModelFilterProvider tabModelFilterProvider =
+                mTabModelSelector.getTabModelFilterProvider();
+
+        mTabModelObserver =
+                new TabModelObserver() {
+                    @Override
+                    public void tabClosureCommitted(Tab tab) {
+                        TabGroupModelFilter filter =
+                                (TabGroupModelFilter)
+                                        tabModelFilterProvider.getTabModelFilter(tab.isIncognito());
+                        int rootId = tab.getRootId();
+                        Tab groupTab = filter.getGroupLastShownTab(rootId);
+                        if (groupTab == null || !filter.isTabInTabGroup(groupTab)) {
+                            TabGroupTitleUtils.deleteTabGroupTitle(rootId);
+
+                            if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
+                                TabGroupColorUtils.deleteTabGroupColor(rootId);
+                            }
+                        }
+                    }
+                };
+
+        mFilterObserver =
+                new TabGroupModelFilterObserver() {
+                    @Override
+                    public void didCreateNewGroup(int newRootId, TabGroupModelFilter filter) {
+                        // TODO(b/41490324): Store a default color as none will exist, but this
+                        // should be enforced later on with the intro of TabGroupCreationDialog.
+                        if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
+                            final @TabGroupColorId int colorId =
+                                    TabGroupColorUtils.getNextSuggestedColorId(filter);
+                            TabGroupColorUtils.storeTabGroupColor(newRootId, colorId);
+                        }
+                    }
+
+                    @Override
+                    public void willMergeTabToGroup(Tab movedTab, int newRootId) {
+                        String sourceGroupTitle =
+                                TabGroupTitleUtils.getTabGroupTitle(movedTab.getRootId());
+                        String targetGroupTitle = TabGroupTitleUtils.getTabGroupTitle(newRootId);
+                        // If the target group has no title but the source group has a title,
+                        // handover the stored title to the group after merge.
+                        if (sourceGroupTitle != null && targetGroupTitle == null) {
+                            TabGroupTitleUtils.storeTabGroupTitle(newRootId, sourceGroupTitle);
+                        }
+
+                        if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
+                            int sourceGroupColor =
+                                    TabGroupColorUtils.getTabGroupColor(movedTab.getRootId());
+                            int targetGroupColor = TabGroupColorUtils.getTabGroupColor(newRootId);
+                            // If the target group has no color but the source group has a color,
+                            // handover the stored color to the group after merge.
+                            if (sourceGroupColor != INVALID_COLOR_ID
+                                    && targetGroupColor == INVALID_COLOR_ID) {
+                                TabGroupColorUtils.storeTabGroupColor(newRootId, sourceGroupColor);
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void willMoveTabOutOfGroup(Tab movedTab, int newRootId) {
+                        int rootId = movedTab.getRootId();
+                        String title = TabGroupTitleUtils.getTabGroupTitle(rootId);
+
+                        // If the group size is 2, i.e. the group becomes a single tab after
+                        // ungroup, delete the stored visual data. When tab groups of size 1 are
+                        // supported this behavior is no longer valid.
+                        TabGroupModelFilter filter =
+                                (TabGroupModelFilter)
+                                        tabModelFilterProvider.getTabModelFilter(
+                                                movedTab.isIncognito());
+                        int sizeThreshold =
+                                ChromeFeatureList.sAndroidTabGroupStableIds.isEnabled() ? 1 : 2;
+                        boolean shouldDeleteVisualData =
+                                filter.getRelatedTabCountForRootId(rootId) <= sizeThreshold;
+                        if (shouldDeleteVisualData) {
+                            if (title != null) {
+                                TabGroupTitleUtils.deleteTabGroupTitle(rootId);
+                            }
+
+                            if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
+                                TabGroupColorUtils.deleteTabGroupColor(rootId);
+                            }
+
+                            return;
+                        }
+                        // If the root tab in group is moved out, re-assign the visual data to the
+                        // new root tab in group.
+                        if (rootId != newRootId) {
+                            if (title != null) {
+                                TabGroupTitleUtils.deleteTabGroupTitle(rootId);
+                                TabGroupTitleUtils.storeTabGroupTitle(newRootId, title);
+                            }
+
+                            if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
+                                int colorId = TabGroupColorUtils.getTabGroupColor(rootId);
+                                assert colorId != INVALID_COLOR_ID;
+
+                                TabGroupColorUtils.deleteTabGroupColor(rootId);
+                                TabGroupColorUtils.storeTabGroupColor(newRootId, colorId);
+                            }
+                        }
+                    }
+                };
+
+        tabModelFilterProvider.addTabModelFilterObserver(mTabModelObserver);
+
+        ((TabGroupModelFilter) tabModelFilterProvider.getTabModelFilter(false))
+                .addTabGroupObserver(mFilterObserver);
+        ((TabGroupModelFilter) tabModelFilterProvider.getTabModelFilter(true))
+                .addTabGroupObserver(mFilterObserver);
+    }
+
+    /** Destroy any members that need clean up. */
+    public void destroy() {
+        TabModelFilterProvider tabModelFilterProvider =
+                mTabModelSelector.getTabModelFilterProvider();
+
+        if (mTabModelObserver != null) {
+            tabModelFilterProvider.removeTabModelFilterObserver(mTabModelObserver);
+            mTabModelObserver = null;
+        }
+
+        if (mFilterObserver != null) {
+            ((TabGroupModelFilter) tabModelFilterProvider.getTabModelFilter(false))
+                    .removeTabGroupObserver(mFilterObserver);
+            ((TabGroupModelFilter) tabModelFilterProvider.getTabModelFilter(true))
+                    .removeTabGroupObserver(mFilterObserver);
+            mFilterObserver = null;
+        }
+    }
+}
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index b925b2e0..a7ad1a3 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -1137,7 +1137,7 @@
                 mCurrentTabModelFilterSupplier.addObserver(mOnTabModelFilterChanged));
 
         mTabGroupTitleEditor =
-                new TabGroupTitleEditor(mContext, mCurrentTabModelFilterSupplier) {
+                new TabGroupTitleEditor(mContext) {
                     @Override
                     protected void updateTabGroupTitle(Tab tab, String title) {
                         // Only update title in PropertyModel for tab switcher.
@@ -1689,9 +1689,6 @@
         if (mComponentCallbacks != null) {
             mContext.unregisterComponentCallbacks(mComponentCallbacks);
         }
-        if (mTabGroupTitleEditor != null) {
-            mTabGroupTitleEditor.destroy();
-        }
         unregisterOnScrolledListener();
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/UndoGroupSnackbarController.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/UndoGroupSnackbarController.java
index 6d5600b1..04150a53 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/UndoGroupSnackbarController.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/UndoGroupSnackbarController.java
@@ -11,6 +11,7 @@
 
 import org.chromium.base.Token;
 import org.chromium.chrome.R;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab.TabCreationState;
 import org.chromium.chrome.browser.tab.TabLaunchType;
@@ -18,6 +19,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupColorUtils;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupTitleUtils;
@@ -33,6 +35,8 @@
  * and shows a undo snackbar.
  */
 public class UndoGroupSnackbarController implements SnackbarManager.SnackbarController {
+    private static final int INVALID_COLOR_ID = -1;
+
     private final Context mContext;
     private final TabModelSelector mTabModelSelector;
     private final SnackbarManager mSnackbarManager;
@@ -46,18 +50,21 @@
         public final int tabOriginalRootId;
         public final @Nullable Token tabOriginalTabGroupId;
         public final String destinationGroupTitle;
+        public final int destinationGroupColorId;
 
         TabUndoInfo(
                 Tab tab,
                 int tabIndex,
                 int rootId,
                 @Nullable Token tabGroupId,
-                String destinationGroupTitle) {
+                String destinationGroupTitle,
+                int destinationGroupColorId) {
             this.tab = tab;
             this.tabOriginalIndex = tabIndex;
             this.tabOriginalRootId = rootId;
             this.tabOriginalTabGroupId = tabGroupId;
             this.destinationGroupTitle = destinationGroupTitle;
+            this.destinationGroupColorId = destinationGroupColorId;
         }
     }
 
@@ -81,7 +88,8 @@
                             List<Integer> tabOriginalIndex,
                             List<Integer> originalRootId,
                             List<Token> originalTabGroupId,
-                            String destinationGroupTitle) {
+                            String destinationGroupTitle,
+                            int destinationGroupColorId) {
                         assert tabs.size() == tabOriginalIndex.size();
 
                         List<TabUndoInfo> tabUndoInfo = new ArrayList<>();
@@ -93,7 +101,12 @@
 
                             tabUndoInfo.add(
                                     new TabUndoInfo(
-                                            tab, index, rootId, tabGroupId, destinationGroupTitle));
+                                            tab,
+                                            index,
+                                            rootId,
+                                            tabGroupId,
+                                            destinationGroupTitle,
+                                            destinationGroupColorId));
                         }
                         showUndoGroupSnackbar(tabUndoInfo);
                     }
@@ -183,11 +196,16 @@
 
     @Override
     public void onDismissNoAction(Object actionData) {
-        // Delete the original tab group titles of the merging tabs once the merge is committed.
+        // Delete the original tab group titles and colors of the merging tabs once the merge is
+        // committed.
         for (TabUndoInfo info : (List<TabUndoInfo>) actionData) {
             if (info.tab.getRootId() == info.tabOriginalRootId) continue;
 
             TabGroupTitleUtils.deleteTabGroupTitle(info.tabOriginalRootId);
+
+            if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
+                TabGroupColorUtils.deleteTabGroupColor(info.tabOriginalRootId);
+            }
         }
     }
 
@@ -207,6 +225,17 @@
             TabGroupTitleUtils.deleteTabGroupTitle(data.get(0).tab.getRootId());
         }
 
+        if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
+            // If the destination rootID previously did not have a color id associated with it since
+            // it was either created from a new tab group or was originally a single tab before
+            // merge, delete that color id on undo. This check deletes the group color for that
+            // destination rootID, as all tabs still currently share that ID before the undo
+            // operation is performed.
+            if (data.get(0).destinationGroupColorId == INVALID_COLOR_ID) {
+                TabGroupColorUtils.deleteTabGroupColor(data.get(0).tab.getRootId());
+            }
+        }
+
         for (int i = data.size() - 1; i >= 0; i--) {
             TabUndoInfo info = data.get(i);
             tabGroupModelFilter.undoGroupedTab(
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
index 5bea146..472e081 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherAndStartSurfaceLayoutTest.java
@@ -110,6 +110,7 @@
 import org.chromium.chrome.browser.tab.TabUtils;
 import org.chromium.chrome.browser.tabmodel.TabCreator;
 import org.chromium.chrome.browser.tabmodel.TabModel;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupColorUtils;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_groups.TabGroupTitleUtils;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
@@ -168,6 +169,8 @@
 
     private static final String TEST_URL = "/chrome/test/data/android/google.html";
 
+    private static final int INVALID_COLOR_ID = -1;
+
     // Tests need animation on.
     @ClassRule
     public static DisableAnimationsTestRule sEnableAnimationsRule =
@@ -1864,6 +1867,7 @@
     @Test
     @MediumTest
     @UseMethodParameter(RefactorTestParams.class)
+    @EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
     public void testUndoGroupMergeInTabSwitcher_TabToTab(boolean isStartSurfaceRefactorEnabled) {
         final ChromeTabbedActivity cta = mActivityTestRule.getActivity();
         SnackbarManager snackbarManager = mActivityTestRule.getActivity().getSnackbarManager();
@@ -1871,22 +1875,40 @@
         enterTabSwitcher(cta);
         verifyTabSwitcherCardCount(cta, 2);
 
+        // Get the next suggested color id.
+        int nextSuggestedColorId =
+                TabGroupColorUtils.getNextSuggestedColorId(
+                        (TabGroupModelFilter)
+                                cta.getTabModelSelectorSupplier()
+                                        .get()
+                                        .getTabModelFilterProvider()
+                                        .getCurrentTabModelFilter());
+
         // Create a tab group.
         mergeAllNormalTabsToAGroup(cta);
         assertTrue(
                 snackbarManager.getCurrentSnackbarForTesting().getController()
                         instanceof UndoGroupSnackbarController);
 
+        // Assert that the suggested default color was set.
+        TabModel normalTabModel = cta.getTabModelSelectorSupplier().get().getModel(false);
+        int tabGroupRootId = normalTabModel.getTabAt(0).getRootId();
+        assertEquals(nextSuggestedColorId, TabGroupColorUtils.getTabGroupColor(tabGroupRootId));
+
         // Undo merge in tab switcher.
         verifyTabSwitcherCardCount(cta, 1);
         assertEquals("2", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
         CriteriaHelper.pollInstrumentationThread(TabUiTestHelper::verifyUndoBarShowingAndClickUndo);
         verifyTabSwitcherCardCount(cta, 2);
+
+        // Assert the color is no longer set for that group.
+        assertEquals(INVALID_COLOR_ID, TabGroupColorUtils.getTabGroupColor(tabGroupRootId));
     }
 
     @Test
     @MediumTest
     @UseMethodParameter(RefactorTestParams.class)
+    @EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
     public void testUndoGroupMergeInTabSwitcher_TabToGroupAdjacent(
             boolean isStartSurfaceRefactorEnabled) {
         final ChromeTabbedActivity cta = mActivityTestRule.getActivity();
@@ -1895,6 +1917,15 @@
         enterTabSwitcher(cta);
         verifyTabSwitcherCardCount(cta, 3);
 
+        // Get the next suggested color id.
+        int nextSuggestedColorId =
+                TabGroupColorUtils.getNextSuggestedColorId(
+                        (TabGroupModelFilter)
+                                cta.getTabModelSelectorSupplier()
+                                        .get()
+                                        .getTabModelFilterProvider()
+                                        .getCurrentTabModelFilter());
+
         // Merge first two tabs into a group.
         TabModel normalTabModel = cta.getTabModelSelector().getModel(false);
         List<Tab> tabGroup =
@@ -1912,12 +1943,22 @@
                         instanceof UndoGroupSnackbarController);
         assertEquals("2", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
 
+        // Assert default color was set properly.
+        assertEquals(
+                nextSuggestedColorId,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(0).getRootId()));
+
         // Merge tab group of 2 at first index with the 3rd tab.
         mergeAllNormalTabsToAGroup(cta);
         assertTrue(
                 snackbarManager.getCurrentSnackbarForTesting().getController()
                         instanceof UndoGroupSnackbarController);
 
+        // Assert the default color is still the tab group color
+        assertEquals(
+                nextSuggestedColorId,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(0).getRootId()));
+
         // Undo merge in tab switcher.
         verifyTabSwitcherCardCount(cta, 1);
         assertEquals("3", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
@@ -1932,12 +1973,21 @@
                     assertNull(
                             TabGroupTitleUtils.getTabGroupTitle(
                                     normalTabModel.getTabAt(2).getRootId()));
+                    assertEquals(
+                            nextSuggestedColorId,
+                            TabGroupColorUtils.getTabGroupColor(
+                                    normalTabModel.getTabAt(1).getRootId()));
+                    assertEquals(
+                            INVALID_COLOR_ID,
+                            TabGroupColorUtils.getTabGroupColor(
+                                    normalTabModel.getTabAt(2).getRootId()));
                 });
     }
 
     @Test
     @MediumTest
     @UseMethodParameter(RefactorTestParams.class)
+    @EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
     public void testUndoGroupMergeInTabSwitcher_GroupToGroupNonAdjacent(
             boolean isStartSurfaceRefactorEnabled) {
         final ChromeTabbedActivity cta = mActivityTestRule.getActivity();
@@ -1946,6 +1996,15 @@
         enterTabSwitcher(cta);
         verifyTabSwitcherCardCount(cta, 5);
 
+        // Get the next suggested color id.
+        int nextSuggestedColorId1 =
+                TabGroupColorUtils.getNextSuggestedColorId(
+                        (TabGroupModelFilter)
+                                cta.getTabModelSelectorSupplier()
+                                        .get()
+                                        .getTabModelFilterProvider()
+                                        .getCurrentTabModelFilter());
+
         // Merge last two tabs into a group.
         TabModel normalTabModel = cta.getTabModelSelector().getModel(false);
         List<Tab> tabGroup =
@@ -1958,6 +2017,20 @@
                         instanceof UndoGroupSnackbarController);
         assertEquals("2", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
 
+        // Assert default color 1 was set properly.
+        assertEquals(
+                nextSuggestedColorId1,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(4).getRootId()));
+
+        // Get the next suggested color id.
+        int nextSuggestedColorId2 =
+                TabGroupColorUtils.getNextSuggestedColorId(
+                        (TabGroupModelFilter)
+                                cta.getTabModelSelectorSupplier()
+                                        .get()
+                                        .getTabModelFilterProvider()
+                                        .getCurrentTabModelFilter());
+
         // Merge first two tabs into a group.
         List<Tab> tabGroup2 =
                 new ArrayList<>(
@@ -1976,6 +2049,11 @@
                         instanceof UndoGroupSnackbarController);
         assertEquals("2", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
 
+        // Assert default color 2 was set properly.
+        assertEquals(
+                nextSuggestedColorId2,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(1).getRootId()));
+
         // Merge the two tab groups into a group.
         List<Tab> tabGroup3 =
                 new ArrayList<>(
@@ -1985,6 +2063,11 @@
                 snackbarManager.getCurrentSnackbarForTesting().getController()
                         instanceof UndoGroupSnackbarController);
 
+        // Assert default color 2 was set as the overall merged group color.
+        assertEquals(
+                nextSuggestedColorId2,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(3).getRootId()));
+
         // Undo merge in tab switcher.
         verifyTabSwitcherCardCount(cta, 2);
         assertEquals("4", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
@@ -2000,12 +2083,21 @@
                             "Bar",
                             TabGroupTitleUtils.getTabGroupTitle(
                                     normalTabModel.getTabAt(0).getRootId()));
+                    assertEquals(
+                            nextSuggestedColorId1,
+                            TabGroupColorUtils.getTabGroupColor(
+                                    normalTabModel.getTabAt(4).getRootId()));
+                    assertEquals(
+                            nextSuggestedColorId2,
+                            TabGroupColorUtils.getTabGroupColor(
+                                    normalTabModel.getTabAt(0).getRootId()));
                 });
     }
 
     @Test
     @MediumTest
     @UseMethodParameter(RefactorTestParams.class)
+    @EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
     public void testUndoGroupMergeInTabSwitcher_PostMergeGroupTitleCommit(
             boolean isStartSurfaceRefactorEnabled) {
         final ChromeTabbedActivity cta = mActivityTestRule.getActivity();
@@ -2014,6 +2106,15 @@
         enterTabSwitcher(cta);
         verifyTabSwitcherCardCount(cta, 3);
 
+        // Get the next suggested color id.
+        int nextSuggestedColorId =
+                TabGroupColorUtils.getNextSuggestedColorId(
+                        (TabGroupModelFilter)
+                                cta.getTabModelSelectorSupplier()
+                                        .get()
+                                        .getTabModelFilterProvider()
+                                        .getCurrentTabModelFilter());
+
         // Merge first two tabs into a group.
         TabModel normalTabModel = cta.getTabModelSelector().getModel(false);
         List<Tab> tabGroup =
@@ -2034,12 +2135,22 @@
                         instanceof UndoGroupSnackbarController);
         assertEquals("2", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
 
+        // Assert default color was set properly.
+        assertEquals(
+                nextSuggestedColorId,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(1).getRootId()));
+
         // Merge tab group of 2 at first index with the 3rd tab.
         mergeAllNormalTabsToAGroup(cta);
         assertTrue(
                 snackbarManager.getCurrentSnackbarForTesting().getController()
                         instanceof UndoGroupSnackbarController);
 
+        // Assert default color was set properly for the overall merged group.
+        assertEquals(
+                nextSuggestedColorId,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(2).getRootId()));
+
         // Check that the old group title was handed over when the group merge is committed
         // and no longer exists.
         TestThreadUtils.runOnUiThreadBlocking(() -> snackbarManager.dismissAllSnackbars());
@@ -2051,6 +2162,130 @@
                             TabGroupTitleUtils.getTabGroupTitle(
                                     normalTabModel.getTabAt(0).getRootId()));
                 });
+
+        // Assert color still exists post snackbar dismissal.
+        assertEquals(
+                nextSuggestedColorId,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(1).getRootId()));
+    }
+
+    @Test
+    @MediumTest
+    @UseMethodParameter(RefactorTestParams.class)
+    @EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
+    public void testUndoClosure_UndoGroupClosure(boolean isStartSurfaceRefactorEnabled) {
+        ChromeTabbedActivity cta = mActivityTestRule.getActivity();
+        SnackbarManager snackbarManager = mActivityTestRule.getActivity().getSnackbarManager();
+        createTabs(cta, false, 2);
+
+        enterTabSwitcher(cta);
+        verifyTabSwitcherCardCount(cta, 2);
+        assertNull(snackbarManager.getCurrentSnackbarForTesting());
+
+        // Get the next suggested color id.
+        int nextSuggestedColorId =
+                TabGroupColorUtils.getNextSuggestedColorId(
+                        (TabGroupModelFilter)
+                                cta.getTabModelSelectorSupplier()
+                                        .get()
+                                        .getTabModelFilterProvider()
+                                        .getCurrentTabModelFilter());
+
+        // Merge first two tabs into a group.
+        TabModel normalTabModel = cta.getTabModelSelector().getModel(false);
+        List<Tab> tabGroup =
+                new ArrayList<>(
+                        Arrays.asList(normalTabModel.getTabAt(0), normalTabModel.getTabAt(1)));
+        createTabGroup(cta, false, tabGroup);
+        verifyTabSwitcherCardCount(cta, 1);
+        assertTrue(
+                snackbarManager.getCurrentSnackbarForTesting().getController()
+                        instanceof UndoGroupSnackbarController);
+        assertEquals("2", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
+
+        // Assert default color was set properly.
+        assertEquals(
+                nextSuggestedColorId,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(1).getRootId()));
+        TestThreadUtils.runOnUiThreadBlocking(() -> snackbarManager.dismissAllSnackbars());
+
+        // Temporarily save the rootID to check during closure.
+        int groupRootId = normalTabModel.getTabAt(1).getRootId();
+
+        closeFirstTabInTabSwitcher(cta);
+        assertTrue(
+                snackbarManager.getCurrentSnackbarForTesting().getController()
+                        instanceof UndoBarController);
+        verifyTabSwitcherCardCount(cta, 0);
+
+        // Assert default color still persists.
+        assertEquals(nextSuggestedColorId, TabGroupColorUtils.getTabGroupColor(groupRootId));
+
+        CriteriaHelper.pollInstrumentationThread(TabUiTestHelper::verifyUndoBarShowingAndClickUndo);
+        verifyTabSwitcherCardCount(cta, 1);
+
+        // Assert default color still persists.
+        assertEquals(
+                nextSuggestedColorId,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(1).getRootId()));
+    }
+
+    @Test
+    @MediumTest
+    @UseMethodParameter(RefactorTestParams.class)
+    @EnableFeatures({ChromeFeatureList.TAB_GROUP_PARITY_ANDROID})
+    public void testUndoClosure_AcceptGroupClosure(boolean isStartSurfaceRefactorEnabled) {
+        ChromeTabbedActivity cta = mActivityTestRule.getActivity();
+        SnackbarManager snackbarManager = mActivityTestRule.getActivity().getSnackbarManager();
+        createTabs(cta, false, 2);
+
+        enterTabSwitcher(cta);
+        verifyTabSwitcherCardCount(cta, 2);
+        assertNull(snackbarManager.getCurrentSnackbarForTesting());
+
+        // Get the next suggested color id.
+        int nextSuggestedColorId =
+                TabGroupColorUtils.getNextSuggestedColorId(
+                        (TabGroupModelFilter)
+                                cta.getTabModelSelectorSupplier()
+                                        .get()
+                                        .getTabModelFilterProvider()
+                                        .getCurrentTabModelFilter());
+
+        // Merge first two tabs into a group.
+        TabModel normalTabModel = cta.getTabModelSelector().getModel(false);
+        List<Tab> tabGroup =
+                new ArrayList<>(
+                        Arrays.asList(normalTabModel.getTabAt(0), normalTabModel.getTabAt(1)));
+        createTabGroup(cta, false, tabGroup);
+        verifyTabSwitcherCardCount(cta, 1);
+        assertTrue(
+                snackbarManager.getCurrentSnackbarForTesting().getController()
+                        instanceof UndoGroupSnackbarController);
+        assertEquals("2", snackbarManager.getCurrentSnackbarForTesting().getTextForTesting());
+
+        // Assert default color was set properly.
+        assertEquals(
+                nextSuggestedColorId,
+                TabGroupColorUtils.getTabGroupColor(normalTabModel.getTabAt(1).getRootId()));
+        TestThreadUtils.runOnUiThreadBlocking(() -> snackbarManager.dismissAllSnackbars());
+
+        // Temporarily save the rootID to check during closure.
+        int groupRootId = normalTabModel.getTabAt(1).getRootId();
+
+        closeFirstTabInTabSwitcher(cta);
+        assertTrue(
+                snackbarManager.getCurrentSnackbarForTesting().getController()
+                        instanceof UndoBarController);
+        verifyTabSwitcherCardCount(cta, 0);
+
+        // Assert default color still persists.
+        assertEquals(nextSuggestedColorId, TabGroupColorUtils.getTabGroupColor(groupRootId));
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> snackbarManager.dismissAllSnackbars());
+
+        // Assert default color is cleared.
+        assertEquals(INVALID_COLOR_ID, TabGroupColorUtils.getTabGroupColor(groupRootId));
     }
 
     @Test
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditorUnitTest.java
index 77f61d2e..f77e09c5 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditorUnitTest.java
@@ -4,108 +4,42 @@
 
 package org.chromium.chrome.browser.tasks.tab_management;
 
-import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
 
-import static org.hamcrest.CoreMatchers.equalTo;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import androidx.annotation.Nullable;
-
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 
-import org.chromium.base.Token;
-import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Features;
-import org.chromium.base.test.util.Features.DisableFeatures;
-import org.chromium.base.test.util.Features.EnableFeatures;
-import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tabmodel.TabModel;
-import org.chromium.chrome.browser.tabmodel.TabModelFilter;
-import org.chromium.chrome.browser.tabmodel.TabModelObserver;
-import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
-import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
 import org.chromium.chrome.tab_ui.R;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 /** Tests for {@link TabGroupTitleEditor}. */
 @SuppressWarnings({"ArraysAsListWithZeroOrOneArgument", "ResultOfMethodCallIgnored"})
 @RunWith(BaseRobolectricTestRunner.class)
-@EnableFeatures(ChromeFeatureList.ANDROID_TAB_GROUP_STABLE_IDS)
 public class TabGroupTitleEditorUnitTest {
     @Rule public TestRule mProcessor = new Features.JUnitProcessor();
 
-    private static final String TAB1_TITLE = "Tab1";
-    private static final String TAB2_TITLE = "Tab2";
-    private static final String TAB3_TITLE = "Tab3";
-    private static final String TAB4_TITLE = "Tab4";
-    private static final String CUSTOMIZED_TITLE1 = "Some cool tabs";
-    private static final String CUSTOMIZED_TITLE2 = "Other cool tabs";
-    private static final int TAB1_ID = 456;
-    private static final int TAB2_ID = 789;
-    private static final int TAB3_ID = 123;
-    private static final int TAB4_ID = 357;
-    private static final Token GROUP_1_ID = new Token(1L, 2L);
-    private static final Token GROUP_2_ID = new Token(2L, 3L);
-
-    @Mock TabModel mTabModel;
-    @Mock TabGroupModelFilter mTabGroupModelFilter;
-    @Mock TabModel mIncognitoTabModel;
-    @Mock TabGroupModelFilter mIncognitoTabGroupModelFilter;
-    @Captor ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
-    @Captor ArgumentCaptor<TabGroupModelFilterObserver> mTabGroupModelFilterObserverCaptor;
-
-    private final ObservableSupplierImpl<TabModelFilter> mTabModelFilterSupplier =
-            new ObservableSupplierImpl<>();
-    private Tab mTab1;
-    private Tab mTab2;
-    private Tab mTab3;
-    private Tab mTab4;
     private Map<String, String> mStorage;
     private TabGroupTitleEditor mTabGroupTitleEditor;
 
     @Before
     public void setUp() {
-
         MockitoAnnotations.initMocks(this);
 
-        mTab1 = TabUiUnitTestUtils.prepareTab(TAB1_ID, TAB1_TITLE);
-        mTab2 = TabUiUnitTestUtils.prepareTab(TAB2_ID, TAB2_TITLE);
-        mTab3 = TabUiUnitTestUtils.prepareTab(TAB3_ID, TAB3_TITLE);
-        mTab4 = TabUiUnitTestUtils.prepareTab(TAB4_ID, TAB4_TITLE);
-        doReturn(mTabModel).when(mTabGroupModelFilter).getTabModel();
-        doReturn(mIncognitoTabModel).when(mIncognitoTabGroupModelFilter).getTabModel();
-        mTabModelFilterSupplier.set(mTabGroupModelFilter);
-        doNothing().when(mTabGroupModelFilter).addObserver(mTabModelObserverCaptor.capture());
-        doNothing()
-                .when(mTabGroupModelFilter)
-                .addTabGroupObserver(mTabGroupModelFilterObserverCaptor.capture());
-
         mTabGroupTitleEditor =
-                new TabGroupTitleEditor(RuntimeEnvironment.application, mTabModelFilterSupplier) {
+                new TabGroupTitleEditor(RuntimeEnvironment.application) {
                     @Override
                     protected void updateTabGroupTitle(Tab tab, String title) {}
 
@@ -125,226 +59,6 @@
                     }
                 };
         mStorage = new HashMap<>();
-        assertTrue(mTabModelFilterSupplier.hasObservers());
-    }
-
-    @After
-    public void tearDown() {
-        mTabGroupTitleEditor.destroy();
-        assertFalse(mTabModelFilterSupplier.hasObservers());
-    }
-
-    @Test
-    public void testChangeModels() {
-        verify(mTabGroupModelFilter).addObserver(any());
-        verify(mTabGroupModelFilter).addTabGroupObserver(any());
-        mTabModelFilterSupplier.set(mIncognitoTabGroupModelFilter);
-        verify(mIncognitoTabGroupModelFilter).addObserver(any());
-        verify(mIncognitoTabGroupModelFilter).addTabGroupObserver(any());
-        verify(mTabGroupModelFilter).removeObserver(any());
-        verify(mTabGroupModelFilter).removeTabGroupObserver(any());
-    }
-
-    @Test
-    public void tabClosureCommitted_RootTab_NotDeleteStoredTitle() {
-        // Mock that we have a stored title stored with reference to root ID of tab1.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-        assertThat(mStorage.size(), equalTo(1));
-
-        // Mock that tab1, tab2, new tab are in the same group and group root id is TAB1_ID.
-        Tab newTab = TabUiUnitTestUtils.prepareTab(TAB3_ID, TAB3_TITLE);
-        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
-        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
-
-        // Mock that the root tab of the group, tab1, is closed.
-        List<Tab> groupAfterClosure = new ArrayList<>(Arrays.asList(mTab2, newTab));
-        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID))
-                .thenReturn(groupAfterClosure.size());
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
-
-        assertThat(mStorage.size(), equalTo(1));
-        assertThat(mTabGroupTitleEditor.getTabGroupTitle(TAB1_ID), equalTo(CUSTOMIZED_TITLE1));
-    }
-
-    @Test
-    public void tabClosureCommitted_NotRootTab_NotDeleteStoredTitle() {
-        // Mock that we have a stored title stored with reference to root ID of tab1.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-
-        // Mock that tab1, tab2, new tab are in the same group and group root id is TAB1_ID.
-        Tab newTab = TabUiUnitTestUtils.prepareTab(TAB3_ID, TAB3_TITLE);
-        List<Tab> groupBeforeClosure = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
-        createTabGroup(groupBeforeClosure, TAB1_ID, GROUP_1_ID);
-
-        // Mock that tab2 is closed and tab2 is not the root tab.
-        List<Tab> groupAfterClosure = new ArrayList<>(Arrays.asList(mTab1, newTab));
-        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID))
-                .thenReturn(groupAfterClosure.size());
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab2);
-
-        assertThat(mStorage.size(), equalTo(1));
-        assertThat(mTabGroupTitleEditor.getTabGroupTitle(TAB1_ID), equalTo(CUSTOMIZED_TITLE1));
-    }
-
-    @Test
-    @DisableFeatures(ChromeFeatureList.ANDROID_TAB_GROUP_STABLE_IDS)
-    public void tabClosureCommitted_DeleteStoredTitle_GroupSize1NotSupported() {
-        // Mock that we have a stored title stored with reference to root ID of tab1.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-        assertThat(mStorage.size(), equalTo(1));
-
-        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
-        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
-
-        // Mock that tab1 is closed and the group becomes a single tab.
-        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID)).thenReturn(1);
-        when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
-        when(mTabGroupModelFilter.isTabInTabGroup(mTab2)).thenReturn(false);
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab2);
-
-        // The stored title should be deleted.
-        assertThat(mStorage.size(), equalTo(0));
-    }
-
-    @Test
-    public void tabClosureCommitted_DeleteStoredTitle_GroupSize1Supported() {
-        // Mock that we have a stored title stored with reference to root ID of tab1.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-        assertThat(mStorage.size(), equalTo(1));
-
-        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
-        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
-
-        // Mock that tab1 is closed and the group becomes a single tab.
-        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID)).thenReturn(1);
-        when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(true);
-        when(mTabGroupModelFilter.isTabInTabGroup(mTab2)).thenReturn(false);
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab2);
-
-        // The stored title should not be deleted.
-        assertThat(mStorage.size(), equalTo(1));
-
-        when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
-        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
-
-        // The stored title should be deleted.
-        assertThat(mStorage.size(), equalTo(0));
-    }
-
-    @Test
-    public void tabMergeIntoGroup_NotDeleteStoredTitle() {
-        // Mock that we have two stored titles with reference to root ID of tab1 and tab3.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB3_ID, CUSTOMIZED_TITLE2);
-        assertThat(mStorage.size(), equalTo(2));
-
-        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID; tab3 and tab4
-        // are in the same group and group root id is TAB3_ID.
-        List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        createTabGroup(group1, TAB1_ID, GROUP_1_ID);
-        List<Tab> group2 = new ArrayList<>(Arrays.asList(mTab3, mTab4));
-        createTabGroup(group2, TAB3_ID, GROUP_2_ID);
-
-        mTabGroupModelFilterObserverCaptor.getValue().willMergeTabToGroup(mTab1, TAB3_ID);
-
-        // The title of the source group will not be deleted until the merge is committed, after
-        // SnackbarController#onDismissNoAction is called for the UndoGroupSnackbarController.
-        assertThat(mStorage.size(), equalTo(2));
-        assertThat(mTabGroupTitleEditor.getTabGroupTitle(TAB1_ID), equalTo(CUSTOMIZED_TITLE1));
-        assertThat(mTabGroupTitleEditor.getTabGroupTitle(TAB3_ID), equalTo(CUSTOMIZED_TITLE2));
-    }
-
-    @Test
-    public void tabMergeIntoGroup_HandOverStoredTitle() {
-        // Mock that we have a stored title stored with reference to root ID of tab1.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-        assertThat(mStorage.size(), equalTo(1));
-
-        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID; tab3 and tab4
-        // are in the same group and group root id is TAB3_ID.
-        List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        createTabGroup(group1, TAB1_ID, GROUP_1_ID);
-        List<Tab> group2 = new ArrayList<>(Arrays.asList(mTab3, mTab4));
-        createTabGroup(group2, TAB3_ID, GROUP_2_ID);
-
-        mTabGroupModelFilterObserverCaptor.getValue().willMergeTabToGroup(mTab1, TAB3_ID);
-
-        // The stored title should be assigned to the new root id. The title of the source group
-        // will not be deleted until the merge is committed, after
-        // SnackbarController#onDismissNoAction is called for the UndoGroupSnackbarController.
-        assertThat(mStorage.size(), equalTo(2));
-        assertThat(mTabGroupTitleEditor.getTabGroupTitle(TAB1_ID), equalTo(CUSTOMIZED_TITLE1));
-        assertThat(mTabGroupTitleEditor.getTabGroupTitle(TAB3_ID), equalTo(CUSTOMIZED_TITLE1));
-    }
-
-    @Test
-    @DisableFeatures(ChromeFeatureList.ANDROID_TAB_GROUP_STABLE_IDS)
-    public void tabMoveOutOfGroup_DeleteStoredTitle_GroupSize1NotSupported() {
-        // Mock that we have a stored title stored with reference to root ID of tab1.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-        assertThat(mStorage.size(), equalTo(1));
-
-        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
-        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
-
-        // Mock that we are going to ungroup tab1, and the group becomes a single tab after ungroup.
-        mTabGroupModelFilterObserverCaptor.getValue().willMoveTabOutOfGroup(mTab1, TAB2_ID);
-
-        // The stored title should be deleted.
-        assertThat(mStorage.size(), equalTo(0));
-    }
-
-    @Test
-    public void tabMoveOutOfGroup_DeleteStoredTitle_GroupSize1Supported() {
-        // Mock that we have a stored title stored with reference to root ID of tab1.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-        assertThat(mStorage.size(), equalTo(1));
-
-        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
-        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
-        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
-
-        // Mock that we are going to ungroup tab1, and the group becomes a single tab after ungroup.
-        mTabGroupModelFilterObserverCaptor.getValue().willMoveTabOutOfGroup(mTab1, TAB2_ID);
-        when(mTabGroupModelFilter.getGroupLastShownTab(TAB1_ID)).thenReturn(mTab1);
-        when(mTabGroupModelFilter.getGroupLastShownTab(TAB2_ID)).thenReturn(mTab2);
-        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID)).thenReturn(1);
-        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB2_ID)).thenReturn(1);
-        when(mTab1.getRootId()).thenReturn(TAB1_ID);
-        when(mTab1.getTabGroupId()).thenReturn(null);
-        when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
-        when(mTab2.getRootId()).thenReturn(TAB2_ID);
-
-        // The stored title should not be deleted.
-        assertThat(mStorage.size(), equalTo(1));
-
-        mTabGroupModelFilterObserverCaptor.getValue().willMoveTabOutOfGroup(mTab2, TAB2_ID);
-
-        // The stored title should be deleted.
-        assertThat(mStorage.size(), equalTo(0));
-    }
-
-    @Test
-    public void tabMoveOutOfGroup_HandOverStoredTitle() {
-        // Mock that we have a stored title stored with reference to root ID of tab1.
-        mTabGroupTitleEditor.storeTabGroupTitle(TAB1_ID, CUSTOMIZED_TITLE1);
-
-        // Mock that tab1, tab2 and newTab are in the same group and group root id is TAB1_ID.
-        Tab newTab = TabUiUnitTestUtils.prepareTab(TAB3_ID, TAB3_TITLE);
-        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
-        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
-
-        // Mock that we are going to ungroup tab1, and the group is still a group after ungroup with
-        // root id become TAB2_ID.
-        mTabGroupModelFilterObserverCaptor.getValue().willMoveTabOutOfGroup(mTab1, TAB2_ID);
-
-        // The stored title should be assigned to the new root id.
-        assertThat(mStorage.size(), equalTo(1));
-        assertThat(mTabGroupTitleEditor.getTabGroupTitle(TAB1_ID), equalTo(null));
-        assertThat(mTabGroupTitleEditor.getTabGroupTitle(TAB2_ID), equalTo(CUSTOMIZED_TITLE1));
     }
 
     @Test
@@ -373,15 +87,4 @@
         assertFalse(mTabGroupTitleEditor.isDefaultTitle(fourTabsTitle, 3));
         assertFalse(mTabGroupTitleEditor.isDefaultTitle("Foo", fourTabsCount));
     }
-
-    private void createTabGroup(List<Tab> tabs, int rootId, @Nullable Token groupId) {
-        Tab lastTab = tabs.isEmpty() ? null : tabs.get(0);
-        when(mTabGroupModelFilter.getGroupLastShownTab(rootId)).thenReturn(lastTab);
-        when(mTabGroupModelFilter.getRelatedTabCountForRootId(rootId)).thenReturn(tabs.size());
-        for (Tab tab : tabs) {
-            when(mTabGroupModelFilter.isTabInTabGroup(tab)).thenReturn(tabs.size() != 1);
-            when(tab.getRootId()).thenReturn(rootId);
-            when(tab.getTabGroupId()).thenReturn(groupId);
-        }
-    }
 }
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupVisualDataManagerUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupVisualDataManagerUnitTest.java
new file mode 100644
index 0000000..91095c4e
--- /dev/null
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupVisualDataManagerUnitTest.java
@@ -0,0 +1,440 @@
+// Copyright 2024 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.tasks.tab_management;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.annotation.Nullable;
+import androidx.collection.ArraySet;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Token;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Features;
+import org.chromium.base.test.util.Features.DisableFeatures;
+import org.chromium.base.test.util.Features.EnableFeatures;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
+import org.chromium.chrome.browser.tabmodel.TabModelObserver;
+import org.chromium.chrome.browser.tabmodel.TabModelSelector;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilterObserver;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/** Tests for {@link TabGroupVisualDataManager}. */
+@SuppressWarnings({"ArraysAsListWithZeroOrOneArgument", "ResultOfMethodCallIgnored"})
+@RunWith(BaseRobolectricTestRunner.class)
+@EnableFeatures({
+    ChromeFeatureList.ANDROID_TAB_GROUP_STABLE_IDS,
+    ChromeFeatureList.TAB_GROUP_PARITY_ANDROID
+})
+public class TabGroupVisualDataManagerUnitTest {
+    @Rule public TestRule mProcessor = new Features.JUnitProcessor();
+
+    private static final String TAB_GROUP_TITLES_FILE_NAME = "tab_group_titles";
+    private static final String TAB_GROUP_COLORS_FILE_NAME = "tab_group_colors";
+
+    private static final String TAB1_TITLE = "Tab1";
+    private static final String TAB2_TITLE = "Tab2";
+    private static final String TAB3_TITLE = "Tab3";
+    private static final String TAB4_TITLE = "Tab4";
+    private static final String CUSTOMIZED_TITLE1 = "Some cool tabs";
+    private static final String CUSTOMIZED_TITLE2 = "Other cool tabs";
+    private static final int COLOR1_ID = 0;
+    private static final int COLOR2_ID = 1;
+    private static final int INVALID_COLOR_ID = -1;
+    private static final int TAB1_ID = 456;
+    private static final int TAB2_ID = 789;
+    private static final int TAB3_ID = 123;
+    private static final int TAB4_ID = 357;
+    private static final Token GROUP_1_ID = new Token(1L, 2L);
+    private static final Token GROUP_2_ID = new Token(2L, 3L);
+
+    @Mock private Context mContext;
+    @Mock private TabGroupModelFilter mTabGroupModelFilter;
+    @Mock private TabGroupModelFilter mIncognitoTabGroupModelFilter;
+    @Mock private TabModelSelector mTabModelSelector;
+    @Mock private TabModelFilterProvider mTabModelFilterProvider;
+    @Mock private SharedPreferences mSharedPreferencesTitle;
+    @Mock private SharedPreferences.Editor mEditorTitle;
+    @Mock private SharedPreferences.Editor mPutStringEditorTitle;
+    @Mock private SharedPreferences.Editor mRemoveEditorTitle;
+    @Mock private SharedPreferences mSharedPreferencesColor;
+    @Mock private SharedPreferences.Editor mEditorColor;
+    @Mock private SharedPreferences.Editor mPutIntEditorColor;
+    @Mock private SharedPreferences.Editor mRemoveEditorColor;
+    @Captor private ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
+    @Captor private ArgumentCaptor<TabGroupModelFilterObserver> mTabGroupModelFilterObserverCaptor;
+
+    @Captor
+    private ArgumentCaptor<TabGroupModelFilterObserver> mIncognitoTabGroupModelFilterObserverCaptor;
+
+    private Tab mTab1;
+    private Tab mTab2;
+    private Tab mTab3;
+    private Tab mTab4;
+    private TabGroupVisualDataManager mTabGroupVisualDataManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mTab1 = TabUiUnitTestUtils.prepareTab(TAB1_ID, TAB1_TITLE);
+        mTab2 = TabUiUnitTestUtils.prepareTab(TAB2_ID, TAB2_TITLE);
+        mTab3 = TabUiUnitTestUtils.prepareTab(TAB3_ID, TAB3_TITLE);
+        mTab4 = TabUiUnitTestUtils.prepareTab(TAB4_ID, TAB4_TITLE);
+
+        doReturn(true).when(mTabModelSelector).isTabStateInitialized();
+        doReturn(mTabModelFilterProvider).when(mTabModelSelector).getTabModelFilterProvider();
+        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
+        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getTabModelFilter(false);
+        doReturn(mIncognitoTabGroupModelFilter)
+                .when(mTabModelFilterProvider)
+                .getTabModelFilter(true);
+
+        doNothing()
+                .when(mTabModelFilterProvider)
+                .addTabModelFilterObserver(mTabModelObserverCaptor.capture());
+        doNothing()
+                .when(mTabGroupModelFilter)
+                .addTabGroupObserver(mTabGroupModelFilterObserverCaptor.capture());
+        doNothing()
+                .when(mIncognitoTabGroupModelFilter)
+                .addTabGroupObserver(mIncognitoTabGroupModelFilterObserverCaptor.capture());
+
+        mTabGroupVisualDataManager = new TabGroupVisualDataManager(mTabModelSelector);
+
+        doReturn(mSharedPreferencesTitle)
+                .when(mContext)
+                .getSharedPreferences(TAB_GROUP_TITLES_FILE_NAME, Context.MODE_PRIVATE);
+        doReturn(mEditorTitle).when(mSharedPreferencesTitle).edit();
+        doReturn(mRemoveEditorTitle).when(mEditorTitle).remove(any(String.class));
+        doReturn(mPutStringEditorTitle)
+                .when(mEditorTitle)
+                .putString(any(String.class), any(String.class));
+
+        doReturn(mSharedPreferencesColor)
+                .when(mContext)
+                .getSharedPreferences(TAB_GROUP_COLORS_FILE_NAME, Context.MODE_PRIVATE);
+        doReturn(mEditorColor).when(mSharedPreferencesColor).edit();
+        doReturn(mRemoveEditorColor).when(mEditorColor).remove(any(String.class));
+        doReturn(mPutIntEditorColor)
+                .when(mEditorColor)
+                .putInt(any(String.class), any(Integer.class));
+
+        ContextUtils.initApplicationContextForTests(mContext);
+    }
+
+    @After
+    public void tearDown() {
+        mTabGroupVisualDataManager.destroy();
+    }
+
+    @Test
+    public void tabClosureCommitted_RootTab_NotDeleteStoredTitle() {
+        // Assume that CUSTOMIZED_TITLE1 and COLOR1_ID are associated with the tab group.
+        // Mock that tab1, tab2, new tab are in the same group and group root id is TAB1_ID.
+        Tab newTab = TabUiUnitTestUtils.prepareTab(TAB3_ID, TAB3_TITLE);
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
+        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
+
+        // Mock that the root tab of the group, tab1, is closed.
+        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
+
+        // Verify that the title and color were not deleted.
+        verify(mEditorTitle, never()).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorTitle, never()).apply();
+        verify(mEditorColor, never()).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorColor, never()).apply();
+    }
+
+    @Test
+    public void tabClosureCommitted_NotRootTab_NotDeleteStoredTitle() {
+        // Assume that CUSTOMIZED_TITLE1 and COLOR1_ID are associated with the tab group.
+        // Mock that tab1, tab2, new tab are in the same group and group root id is TAB1_ID.
+        Tab newTab = TabUiUnitTestUtils.prepareTab(TAB3_ID, TAB3_TITLE);
+        List<Tab> groupBeforeClosure = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
+        createTabGroup(groupBeforeClosure, TAB1_ID, GROUP_1_ID);
+
+        // Mock that tab2 is closed and tab2 is not the root tab.
+        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab2);
+
+        // Verify that the title and color were not deleted.
+        verify(mEditorTitle, never()).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorTitle, never()).apply();
+        verify(mEditorColor, never()).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorColor, never()).apply();
+    }
+
+    @Test
+    @DisableFeatures(ChromeFeatureList.ANDROID_TAB_GROUP_STABLE_IDS)
+    public void tabClosureCommitted_DeleteStoredTitle_GroupSize1NotSupported() {
+        // Assume that CUSTOMIZED_TITLE1 and COLOR1_ID are associated with the tab group.
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
+
+        // Mock that tab1 is closed and the group becomes a single tab.
+        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID)).thenReturn(1);
+        when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
+        when(mTabGroupModelFilter.isTabInTabGroup(mTab2)).thenReturn(false);
+        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab2);
+
+        // Verify that the title and color were deleted.
+        verify(mEditorTitle).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorTitle).apply();
+        verify(mEditorColor).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorColor).apply();
+    }
+
+    @Test
+    public void tabClosureCommitted_DeleteStoredTitle_GroupSize1Supported() {
+        // Assume that CUSTOMIZED_TITLE1 and COLOR1_ID are associated with the tab group.
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
+
+        // Mock that tab1 is closed and the group becomes a single tab.
+        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID)).thenReturn(1);
+        when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(true);
+        when(mTabGroupModelFilter.isTabInTabGroup(mTab2)).thenReturn(false);
+        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab2);
+
+        // Verify that the title and color were not deleted.
+        verify(mEditorTitle, never()).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorTitle, never()).apply();
+        verify(mEditorColor, never()).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorColor, never()).apply();
+
+        when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
+        mTabModelObserverCaptor.getValue().tabClosureCommitted(mTab1);
+
+        // Verify that the title and color were deleted.
+        verify(mEditorTitle).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorTitle).apply();
+        verify(mEditorColor).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorColor).apply();
+    }
+
+    // TODO(b/41490324): Remove this test when introducing TabGroupCreationDialog logic.
+    @Test
+    public void didCreateNewGroup_StoreColor() {
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        // None of the tab groups have colors associated with them.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
+
+        // Create roodId set and mock that it has no color stored.
+        Set<Integer> rootIdsSet = new ArraySet<>();
+        rootIdsSet.add(TAB1_ID);
+        when(mTabGroupModelFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB1_ID), INVALID_COLOR_ID))
+                .thenReturn(INVALID_COLOR_ID);
+
+        mTabGroupModelFilterObserverCaptor
+                .getValue()
+                .didCreateNewGroup(TAB1_ID, mTabGroupModelFilter);
+
+        // Verify that a default color was stored.
+        verify(mEditorColor).putInt(eq(String.valueOf(TAB1_ID)), eq(COLOR1_ID));
+        verify(mPutIntEditorColor).apply();
+    }
+
+    @Test
+    public void tabMergeIntoGroup_NotDeleteStoredTitle() {
+        // Mock that TITLE1, TITLE2 and COLOR1_ID, COLOR2_ID are associated with the groups.
+        when(mSharedPreferencesTitle.getString(String.valueOf(TAB1_ID), null))
+                .thenReturn(CUSTOMIZED_TITLE1);
+        when(mSharedPreferencesTitle.getString(String.valueOf(TAB3_ID), null))
+                .thenReturn(CUSTOMIZED_TITLE2);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB1_ID), INVALID_COLOR_ID))
+                .thenReturn(COLOR1_ID);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB3_ID), INVALID_COLOR_ID))
+                .thenReturn(COLOR2_ID);
+
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID; tab3 and tab4
+        // are in the same group and group root id is TAB3_ID.
+        List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(group1, TAB1_ID, GROUP_1_ID);
+        List<Tab> group2 = new ArrayList<>(Arrays.asList(mTab3, mTab4));
+        createTabGroup(group2, TAB3_ID, GROUP_2_ID);
+
+        mTabGroupModelFilterObserverCaptor.getValue().willMergeTabToGroup(mTab1, TAB3_ID);
+
+        // The title of the source group will not be deleted until the merge is committed, after
+        // SnackbarController#onDismissNoAction is called for the UndoGroupSnackbarController.
+        verify(mEditorTitle, never()).putString(eq(String.valueOf(TAB3_ID)), eq(CUSTOMIZED_TITLE1));
+        verify(mRemoveEditorTitle, never()).apply();
+        verify(mEditorColor, never()).putInt(eq(String.valueOf(TAB3_ID)), eq(COLOR1_ID));
+        verify(mRemoveEditorColor, never()).apply();
+    }
+
+    @Test
+    public void tabMergeIntoGroup_HandOverStoredTitle() {
+        // Mock that TITLE1 and COLOR1_ID are associated with the group of TAB1_ID.
+        when(mSharedPreferencesTitle.getString(String.valueOf(TAB1_ID), null))
+                .thenReturn(CUSTOMIZED_TITLE1);
+        when(mSharedPreferencesTitle.getString(String.valueOf(TAB3_ID), null)).thenReturn(null);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB1_ID), INVALID_COLOR_ID))
+                .thenReturn(COLOR1_ID);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB3_ID), INVALID_COLOR_ID))
+                .thenReturn(INVALID_COLOR_ID);
+
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID; tab3 and tab4
+        // are in the same group and group root id is TAB3_ID.
+        List<Tab> group1 = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(group1, TAB1_ID, GROUP_1_ID);
+        List<Tab> group2 = new ArrayList<>(Arrays.asList(mTab3, mTab4));
+        createTabGroup(group2, TAB3_ID, GROUP_2_ID);
+
+        mTabGroupModelFilterObserverCaptor.getValue().willMergeTabToGroup(mTab1, TAB3_ID);
+
+        // The stored title should be assigned to the new root id. The title of the source group
+        // will not be deleted until the merge is committed, after
+        // SnackbarController#onDismissNoAction is called for the UndoGroupSnackbarController.
+        verify(mEditorTitle).putString(eq(String.valueOf(TAB3_ID)), eq(CUSTOMIZED_TITLE1));
+        verify(mPutStringEditorTitle).apply();
+        verify(mEditorColor).putInt(eq(String.valueOf(TAB3_ID)), eq(COLOR1_ID));
+        verify(mPutIntEditorColor).apply();
+    }
+
+    @Test
+    @DisableFeatures(ChromeFeatureList.ANDROID_TAB_GROUP_STABLE_IDS)
+    public void tabMoveOutOfGroup_DeleteStoredTitle_GroupSize1NotSupported() {
+        // Mock that TITLE1 and COLOR1_ID are associated with the group of TAB1_ID.
+        when(mSharedPreferencesTitle.getString(String.valueOf(TAB1_ID), null))
+                .thenReturn(CUSTOMIZED_TITLE1);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB1_ID), INVALID_COLOR_ID))
+                .thenReturn(COLOR1_ID);
+
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
+
+        // Mock that we are going to ungroup tab1, and the group becomes a single tab after ungroup.
+        mTabGroupModelFilterObserverCaptor.getValue().willMoveTabOutOfGroup(mTab1, TAB2_ID);
+
+        // Verify that the title and color were deleted.
+        verify(mEditorTitle).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorTitle).apply();
+        verify(mEditorColor).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorColor).apply();
+    }
+
+    @Test
+    public void tabMoveOutOfGroup_DeleteStoredTitle_GroupSize1Supported() {
+        // Mock that TITLE1 and COLOR1_ID are associated with the group of TAB1_ID.
+        when(mSharedPreferencesTitle.getString(String.valueOf(TAB1_ID), null))
+                .thenReturn(CUSTOMIZED_TITLE1);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB1_ID), INVALID_COLOR_ID))
+                .thenReturn(COLOR1_ID);
+
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
+
+        // Mock that we are going to ungroup tab1, and the group becomes a single tab after ungroup.
+        when(mTabGroupModelFilter.getGroupLastShownTab(TAB1_ID)).thenReturn(mTab1);
+        when(mTabGroupModelFilter.getGroupLastShownTab(TAB2_ID)).thenReturn(mTab2);
+        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID)).thenReturn(2);
+        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB2_ID)).thenReturn(2);
+        when(mTab1.getRootId()).thenReturn(TAB1_ID);
+        when(mTab1.getTabGroupId()).thenReturn(null);
+        when(mTabGroupModelFilter.isTabInTabGroup(mTab1)).thenReturn(false);
+        when(mTab2.getRootId()).thenReturn(TAB2_ID);
+
+        // Mock the situation that the root tab is not the tab being moved out.
+        mTabGroupModelFilterObserverCaptor.getValue().willMoveTabOutOfGroup(mTab1, TAB1_ID);
+
+        // Verify that the title and color were not deleted.
+        verify(mEditorTitle, never()).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorTitle, never()).apply();
+        verify(mEditorColor, never()).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorColor, never()).apply();
+
+        // Mock that TITLE1 and COLOR1_ID are associated with the group of TAB1_ID.
+        when(mSharedPreferencesTitle.getString(String.valueOf(TAB2_ID), null))
+                .thenReturn(CUSTOMIZED_TITLE1);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB2_ID), INVALID_COLOR_ID))
+                .thenReturn(COLOR1_ID);
+
+        // Mock that we are going to ungroup the last tab in a size 1 tab group, and it is the root
+        // tab.
+        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB1_ID)).thenReturn(1);
+        when(mTabGroupModelFilter.getRelatedTabCountForRootId(TAB2_ID)).thenReturn(1);
+
+        mTabGroupModelFilterObserverCaptor.getValue().willMoveTabOutOfGroup(mTab2, TAB1_ID);
+
+        // Verify that the title and color were deleted.
+        verify(mEditorTitle).remove(eq(String.valueOf(TAB2_ID)));
+        verify(mRemoveEditorTitle).apply();
+        verify(mEditorColor).remove(eq(String.valueOf(TAB2_ID)));
+        verify(mRemoveEditorColor).apply();
+    }
+
+    @Test
+    public void tabMoveOutOfGroup_HandOverStoredTitle() {
+        // Mock that TITLE1 and COLOR1_ID are associated with the group of TAB1_ID.
+        when(mSharedPreferencesTitle.getString(String.valueOf(TAB1_ID), null))
+                .thenReturn(CUSTOMIZED_TITLE1);
+        when(mSharedPreferencesColor.getInt(String.valueOf(TAB1_ID), INVALID_COLOR_ID))
+                .thenReturn(COLOR1_ID);
+
+        // Mock that tab1, tab2 and newTab are in the same group and group root id is TAB1_ID.
+        Tab newTab = TabUiUnitTestUtils.prepareTab(TAB3_ID, TAB3_TITLE);
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
+        createTabGroup(tabs, TAB1_ID, GROUP_1_ID);
+
+        // Mock that we are going to ungroup tab1, and the group is still a group after ungroup with
+        // root id become TAB2_ID.
+        mTabGroupModelFilterObserverCaptor.getValue().willMoveTabOutOfGroup(mTab1, TAB2_ID);
+
+        // The stored title should be assigned to the new root id.
+        verify(mEditorTitle).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorTitle).apply();
+        verify(mEditorTitle).putString(eq(String.valueOf(TAB2_ID)), eq(CUSTOMIZED_TITLE1));
+        verify(mPutStringEditorTitle).apply();
+        verify(mEditorColor).remove(eq(String.valueOf(TAB1_ID)));
+        verify(mRemoveEditorColor).apply();
+        verify(mEditorColor).putInt(eq(String.valueOf(TAB2_ID)), eq(COLOR1_ID));
+        verify(mPutIntEditorColor).apply();
+    }
+
+    private void createTabGroup(List<Tab> tabs, int rootId, @Nullable Token groupId) {
+        Tab lastTab = tabs.isEmpty() ? null : tabs.get(0);
+        when(mTabGroupModelFilter.getGroupLastShownTab(rootId)).thenReturn(lastTab);
+        when(mTabGroupModelFilter.getRelatedTabCountForRootId(rootId)).thenReturn(tabs.size());
+        for (Tab tab : tabs) {
+            when(mTabGroupModelFilter.isTabInTabGroup(tab)).thenReturn(tabs.size() != 1);
+            when(tab.getRootId()).thenReturn(rootId);
+            when(tab.getTabGroupId()).thenReturn(groupId);
+        }
+    }
+}
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index 57666c0..bb719bc 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -1130,7 +1130,7 @@
         mMediator.initWithNative(mProfile);
 
         // mTabModelObserverCaptor captures on every initWithNative call.
-        verify(mTabGroupModelFilter, times(4)).addObserver(mTabModelObserverCaptor.capture());
+        verify(mTabGroupModelFilter, times(2)).addObserver(mTabModelObserverCaptor.capture());
         initAndAssertAllProperties();
 
         ThumbnailFetcher tab1Fetcher = mModel.get(0).model.get(TabProperties.THUMBNAIL_FETCHER);
@@ -3204,11 +3204,11 @@
     @Test
     public void testChangingTabModelFilters() {
         mCurrentTabModelFilterSupplier.set(mIncognitoTabGroupModelFilter);
-        // Once for the Mediator and once for the TabGroupTitleEditor.
-        verify(mTabGroupModelFilter, times(2)).removeObserver(any());
-        verify(mTabGroupModelFilter, times(2)).removeTabGroupObserver(any());
-        verify(mIncognitoTabGroupModelFilter, times(2)).addObserver(any());
-        verify(mIncognitoTabGroupModelFilter, times(2)).addTabGroupObserver(any());
+        // Once for the Mediator.
+        verify(mTabGroupModelFilter).removeObserver(any());
+        verify(mTabGroupModelFilter).removeTabGroupObserver(any());
+        verify(mIncognitoTabGroupModelFilter).addObserver(any());
+        verify(mIncognitoTabGroupModelFilter).addTabGroupObserver(any());
     }
 
     @Test
@@ -3566,10 +3566,10 @@
         mMediator.initWithNative(mProfile);
 
         assertThat(
-                mTabModelObserverCaptor.getAllValues().size(), equalTo(tabModelObserverCount + 2));
+                mTabModelObserverCaptor.getAllValues().size(), equalTo(tabModelObserverCount + 1));
         assertThat(
                 mTabGroupModelFilterObserverCaptor.getAllValues().size(),
-                equalTo(tabGroupModelFilterObserverCount + 2));
+                equalTo(tabGroupModelFilterObserverCount + 1));
 
         mMediatorTabModelObserver =
                 mTabModelObserverCaptor.getAllValues().get(tabModelObserverCount);
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index 33102dd..28472d07 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -14,6 +14,7 @@
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/ColorPickerUtils.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/RecyclerViewPosition.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUi.java",
+  "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupVisualDataManager.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListFaviconProvider.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java",
   "//chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcher.java",
@@ -203,6 +204,7 @@
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupCreationTextInputLayoutTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditorUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java",
+  "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupVisualDataManagerUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListContainerViewBinderUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorActionUnitTestHelper.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListEditorBookmarkActionUnitTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 97b8366..083de24 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -209,8 +209,11 @@
 import org.chromium.chrome.browser.tasks.ReturnToChromeUtil;
 import org.chromium.chrome.browser.tasks.ReturnToChromeUtil.ReturnToChromeBackPressHandler;
 import org.chromium.chrome.browser.tasks.TasksUma;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupColorUtils;
+import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
 import org.chromium.chrome.browser.tasks.tab_management.CloseAllTabsDialog;
 import org.chromium.chrome.browser.tasks.tab_management.TabGroupUi;
+import org.chromium.chrome.browser.tasks.tab_management.TabGroupVisualDataManager;
 import org.chromium.chrome.browser.tasks.tab_management.TabManagementDelegateProvider;
 import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
@@ -498,6 +501,9 @@
                 RecordUserAction.record("MobileNewTabOpened");
             };
 
+    // Manager for tab group visual data lifecycle updates.
+    private TabGroupVisualDataManager mTabGroupVisualDataManager;
+
     /**
      * This class is used to warm up the chrome split ClassLoader. See SplitChromeApplication for
      * more info
@@ -674,6 +680,14 @@
 
             mTabModelOrchestrator.onNativeLibraryReady(getTabContentManager());
 
+            TabModelUtils.runOnTabStateInitialized(
+                    mTabModelSelector,
+                    (tabModelSelector) -> {
+                        assert tabModelSelector != null;
+                        mTabGroupVisualDataManager =
+                                new TabGroupVisualDataManager(tabModelSelector);
+                    });
+
             // For saving non-incognito tab closures for Recent Tabs.
             mHistoricalTabModelObserver =
                     new HistoricalTabModelObserver(mTabModelSelector.getModel(false));
@@ -2301,6 +2315,20 @@
                         this::getSnackbarManager,
                         dialogVisibilitySupplier);
 
+        if (ChromeFeatureList.sTabGroupParityAndroid.isEnabled()) {
+            TabModelUtils.runOnTabStateInitialized(
+                    getTabModelSelectorSupplier().get(),
+                    (tabModelSelectorReturn) -> {
+                        TabGroupColorUtils.assignTabGroupColorsIfApplicable(
+                                (TabGroupModelFilter)
+                                        tabModelSelectorReturn
+                                                .getTabModelFilterProvider()
+                                                .getCurrentTabModelFilter());
+                    });
+        } else {
+            PostTask.postTask(TaskTraits.UI_DEFAULT, TabGroupColorUtils::clearTabGroupColorInfo);
+        }
+
         mInactivityTracker =
                 new ChromeInactivityTracker(
                         ChromePreferenceKeys.TABBED_ACTIVITY_LAST_BACKGROUNDED_TIME_MS_PREF);
@@ -3586,6 +3614,11 @@
 
         if (mHubProvider != null) mHubProvider.destroy();
 
+        if (mTabGroupVisualDataManager != null) {
+            mTabGroupVisualDataManager.destroy();
+            mTabGroupVisualDataManager = null;
+        }
+
         super.onDestroyInternal();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/settings/AccessibilitySettings.java b/chrome/android/java/src/org/chromium/chrome/browser/accessibility/settings/AccessibilitySettings.java
index 6625699..67621d75 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/settings/AccessibilitySettings.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/accessibility/settings/AccessibilitySettings.java
@@ -14,9 +14,9 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.image_descriptions.ImageDescriptionsController;
+import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.settings.SettingsLauncherImpl;
 import org.chromium.components.browser_ui.accessibility.AccessibilitySettingsDelegate;
-import org.chromium.components.browser_ui.accessibility.AccessibilitySettingsDelegate.BooleanPreferenceDelegate;
 import org.chromium.components.browser_ui.accessibility.FontSizePrefs;
 import org.chromium.components.browser_ui.accessibility.FontSizePrefs.FontSizePrefsObserver;
 import org.chromium.components.browser_ui.accessibility.PageZoomPreference;
@@ -28,6 +28,7 @@
 import org.chromium.components.browser_ui.site_settings.AllSiteSettings;
 import org.chromium.components.browser_ui.site_settings.SingleCategorySettings;
 import org.chromium.components.browser_ui.site_settings.SiteSettingsCategory;
+import org.chromium.components.prefs.PrefService;
 import org.chromium.content_public.browser.ContentFeatureList;
 import org.chromium.content_public.browser.ContentFeatureMap;
 
@@ -52,8 +53,8 @@
     private ChromeSwitchPreference mForceEnableZoomPref;
     private boolean mRecordFontSizeChangeOnStop;
     private AccessibilitySettingsDelegate mDelegate;
-    private BooleanPreferenceDelegate mReaderForAccessibilityDelegate;
     private double mPageZoomLatestDefaultZoomPrefValue;
+    private PrefService mPrefService;
 
     private FontSizePrefs mFontSizePrefs;
     private FontSizePrefsObserver mFontSizePrefsObserver =
@@ -71,6 +72,10 @@
                 }
             };
 
+    public void setPrefService(PrefService prefService) {
+        mPrefService = prefService;
+    }
+
     public void setDelegate(AccessibilitySettingsDelegate delegate) {
         mDelegate = delegate;
         mFontSizePrefs = FontSizePrefs.getInstance(delegate.getBrowserContextHandle());
@@ -134,13 +139,9 @@
 
         ChromeSwitchPreference readerForAccessibilityPref =
                 (ChromeSwitchPreference) findPreference(PREF_READER_FOR_ACCESSIBILITY);
-        mReaderForAccessibilityDelegate = mDelegate.getReaderForAccessibilityDelegate();
-        if (mReaderForAccessibilityDelegate != null) {
-            readerForAccessibilityPref.setChecked(mReaderForAccessibilityDelegate.isEnabled());
-            readerForAccessibilityPref.setOnPreferenceChangeListener(this);
-        } else {
-            getPreferenceScreen().removePreference(readerForAccessibilityPref);
-        }
+        readerForAccessibilityPref.setChecked(
+                mPrefService.getBoolean(Pref.READER_FOR_ACCESSIBILITY));
+        readerForAccessibilityPref.setOnPreferenceChangeListener(this);
 
         Preference captions = findPreference(PREF_CAPTIONS);
         captions.setOnPreferenceClickListener(
@@ -214,9 +215,7 @@
         } else if (PREF_FORCE_ENABLE_ZOOM.equals(preference.getKey())) {
             mFontSizePrefs.setForceEnableZoomFromUser((Boolean) newValue);
         } else if (PREF_READER_FOR_ACCESSIBILITY.equals(preference.getKey())) {
-            if (mReaderForAccessibilityDelegate != null) {
-                mReaderForAccessibilityDelegate.setEnabled((Boolean) newValue);
-            }
+            mPrefService.setBoolean(Pref.READER_FOR_ACCESSIBILITY, (Boolean) newValue);
         } else if (PREF_PAGE_ZOOM_DEFAULT_ZOOM.equals(preference.getKey())) {
             mPageZoomLatestDefaultZoomPrefValue =
                     PageZoomUtils.convertSeekBarValueToZoomLevel((Integer) newValue);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/settings/ChromeAccessibilitySettingsDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/accessibility/settings/ChromeAccessibilitySettingsDelegate.java
index 29c22e3..69cfc1e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/accessibility/settings/ChromeAccessibilitySettingsDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/accessibility/settings/ChromeAccessibilitySettingsDelegate.java
@@ -12,24 +12,6 @@
 
 /** The Chrome implementation of AccessibilitySettingsDelegate. */
 public class ChromeAccessibilitySettingsDelegate implements AccessibilitySettingsDelegate {
-    private static class ReaderForAccessibilityDelegate implements BooleanPreferenceDelegate {
-        private final Profile mProfile;
-
-        ReaderForAccessibilityDelegate(Profile profile) {
-            mProfile = profile;
-        }
-
-        @Override
-        public boolean isEnabled() {
-            return UserPrefs.get(mProfile).getBoolean(Pref.READER_FOR_ACCESSIBILITY);
-        }
-
-        @Override
-        public void setEnabled(boolean value) {
-            UserPrefs.get(mProfile).setBoolean(Pref.READER_FOR_ACCESSIBILITY, (Boolean) value);
-        }
-    }
-
     private static class TextSizeContrastAccessibilityDelegate
             implements IntegerPreferenceDelegate {
         private final BrowserContextHandle mBrowserContextHandle;
@@ -67,11 +49,6 @@
     }
 
     @Override
-    public BooleanPreferenceDelegate getReaderForAccessibilityDelegate() {
-        return new ReaderForAccessibilityDelegate(mProfile);
-    }
-
-    @Override
     public IntegerPreferenceDelegate getTextSizeContrastAccessibilityDelegate() {
         return new TextSizeContrastAccessibilityDelegate(getBrowserContextHandle());
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragment.java
index 504d940..51689cb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragment.java
@@ -30,6 +30,7 @@
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
 
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Callback;
 import org.chromium.base.CollectionUtil;
 import org.chromium.base.metrics.RecordHistogram;
@@ -43,9 +44,12 @@
 import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
 import org.chromium.chrome.browser.preferences.Pref;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.quick_delete.QuickDeleteController;
 import org.chromium.chrome.browser.settings.ProfileDependentSetting;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.signin.services.SigninManager;
+import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.ui.signin.SignOutDialogCoordinator;
 import org.chromium.components.browser_ui.settings.ClickableSpansTextMessagePreference;
 import org.chromium.components.browser_ui.settings.CustomDividerFragment;
@@ -234,6 +238,9 @@
     // important domains from being cleared.
     private ConfirmImportantSitesDialogFragment mConfirmImportantSitesDialog;
 
+    private @TimePeriod int mLastSelectedTimePeriod;
+    private boolean mShouldShowSnackbar;
+
     /**
      * @return All available {@link DialogOption} entries.
      */
@@ -404,21 +411,21 @@
 
         Object spinnerSelection =
                 ((SpinnerPreference) findPreference(PREF_TIME_RANGE)).getSelectedOption();
-        @TimePeriod int timePeriod = ((TimePeriodSpinnerOption) spinnerSelection).getTimePeriod();
+        mLastSelectedTimePeriod = ((TimePeriodSpinnerOption) spinnerSelection).getTimePeriod();
         int[] dataTypesArray = CollectionUtil.integerCollectionToIntArray(dataTypes);
         if (excludedDomains != null && excludedDomains.length != 0) {
             BrowsingDataBridge.getForProfile(mProfile)
                     .clearBrowsingDataExcludingDomains(
                             this,
                             dataTypesArray,
-                            timePeriod,
+                            mLastSelectedTimePeriod,
                             excludedDomains,
                             excludedDomainReasons,
                             ignoredDomains,
                             ignoredDomainReasons);
         } else {
             BrowsingDataBridge.getForProfile(mProfile)
-                    .clearBrowsingData(this, dataTypesArray, timePeriod);
+                    .clearBrowsingData(this, dataTypesArray, mLastSelectedTimePeriod);
         }
 
         // Clear all reported entities.
@@ -457,6 +464,7 @@
     @Override
     public void onBrowsingDataCleared() {
         if (getActivity() == null) return;
+        mShouldShowSnackbar = QuickDeleteController.isQuickDeleteFollowupEnabled();
 
         // If the user deleted their browsing history, the dialog about other forms of history
         // is enabled, and it has never been shown before, show it. Note that opening a new
@@ -698,6 +706,9 @@
             item.destroy();
         }
         mSigninManager.removeSignInStateObserver(this);
+        if (mShouldShowSnackbar) {
+            showSnackbar();
+        }
     }
 
     // We either show the dialog, or modify the current one to display our messages.  This avoids
@@ -871,4 +882,47 @@
     public void setHelpAndFeedbackLauncher(HelpAndFeedbackLauncher helpAndFeedbackLauncher) {
         mHelpAndFeedbackLauncher = helpAndFeedbackLauncher;
     }
+
+    /** Get the last focused activity that has not been destroyed. */
+    private Activity getLastFocusedActivity() {
+        if (ApplicationStatus.hasVisibleActivities()) {
+            return ApplicationStatus.getLastTrackedFocusedActivity();
+        } else {
+            return null;
+        }
+    }
+
+    public SnackbarManager getSnackbarManager() {
+        Activity activity = getLastFocusedActivity();
+        if (activity instanceof SnackbarManager.SnackbarManageable) {
+            return ((SnackbarManager.SnackbarManageable) activity).getSnackbarManager();
+        }
+        return null;
+    }
+
+    /** A method to show the post-delete snack-bar confirmation. */
+    private void showSnackbar() {
+        SnackbarManager snackbarManager = getSnackbarManager();
+        if (snackbarManager == null) return;
+
+        String snackbarMessage;
+        if (mLastSelectedTimePeriod == TimePeriod.ALL_TIME) {
+            snackbarMessage =
+                    getActivity().getString(R.string.quick_delete_snackbar_all_time_message);
+        } else {
+            snackbarMessage =
+                    getActivity()
+                            .getString(
+                                    R.string.quick_delete_snackbar_message,
+                                    TimePeriodUtils.getTimePeriodString(
+                                            getActivity(), mLastSelectedTimePeriod));
+        }
+        Snackbar snackbar =
+                Snackbar.make(
+                        snackbarMessage,
+                        /* controller= */ null,
+                        Snackbar.TYPE_NOTIFICATION,
+                        Snackbar.UMA_CLEAR_BROWSING_DATA);
+        snackbarManager.showSnackbar(snackbar);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/HistorySyncFirstRunFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/HistorySyncFirstRunFragment.java
index 0e30d43..ca388b33 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/HistorySyncFirstRunFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/HistorySyncFirstRunFragment.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.firstrun;
 
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -34,13 +35,7 @@
     @Override
     public void onResume() {
         super.onResume();
-        assert getPageDelegate().getProfileProviderSupplier().get() != null;
-        Profile profile = getPageDelegate().getProfileProviderSupplier().get().getOriginalProfile();
-        mHistorySyncCoordinator =
-                new HistorySyncCoordinator(
-                        getLayoutInflater(), this, profile, SigninAccessPoint.START_PAGE);
-        mFragmentView.removeAllViews();
-        mFragmentView.addView(mHistorySyncCoordinator.getView());
+        createCoordinatorAndAddToFragment();
     }
 
     @Override
@@ -52,6 +47,15 @@
         }
     }
 
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mHistorySyncCoordinator == null) {
+            return;
+        }
+        createCoordinatorAndAddToFragment();
+    }
+
     /** Implements {@link FirstRunFragment}. */
     @Override
     public void setInitialA11yFocus() {
@@ -69,4 +73,22 @@
         mHistorySyncCoordinator.destroy();
         mHistorySyncCoordinator = null;
     }
+
+    @Override
+    public boolean canUseLandscapeLayout() {
+        return getPageDelegate().canUseLandscapeLayout();
+    }
+
+    private void createCoordinatorAndAddToFragment() {
+        if (mHistorySyncCoordinator != null) {
+            mHistorySyncCoordinator.destroy();
+        }
+        assert getPageDelegate().getProfileProviderSupplier().get() != null;
+        Profile profile = getPageDelegate().getProfileProviderSupplier().get().getOriginalProfile();
+        mHistorySyncCoordinator =
+                new HistorySyncCoordinator(
+                        getLayoutInflater(), this, profile, SigninAccessPoint.START_PAGE);
+        mFragmentView.removeAllViews();
+        mFragmentView.addView(mHistorySyncCoordinator.getView());
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
index 5146dbe..aace464f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/SettingsActivity.java
@@ -606,6 +606,7 @@
         if (fragment instanceof AccessibilitySettings) {
             ((AccessibilitySettings) fragment)
                     .setDelegate(new ChromeAccessibilitySettingsDelegate(mProfile));
+            ((AccessibilitySettings) fragment).setPrefService(UserPrefs.get(mProfile));
         }
         if (fragment instanceof PasswordSettings) {
             ((PasswordSettings) fragment).setBottomSheetController(mBottomSheetController);
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 8ff37af9..0f68819 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
@@ -46,8 +46,6 @@
 import org.chromium.components.content_settings.ContentSettingsType;
 import org.chromium.components.embedder_support.util.Origin;
 import org.chromium.components.favicon.LargeIconBridge;
-import org.chromium.components.permissions.PermissionsAndroidFeatureList;
-import org.chromium.components.permissions.PermissionsAndroidFeatureMap;
 import org.chromium.components.prefs.PrefService;
 import org.chromium.components.user_prefs.UserPrefs;
 import org.chromium.content_public.browser.BrowserContextHandle;
@@ -141,9 +139,6 @@
                 return ContentFeatureMap.isEnabled(ContentFeatures.FED_CM);
             case SiteSettingsCategory.Type.NFC:
                 return ContentFeatureMap.isEnabled(ContentFeatureList.WEB_NFC);
-            case SiteSettingsCategory.Type.STORAGE_ACCESS:
-                return PermissionsAndroidFeatureMap.isEnabled(
-                        PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS);
             case SiteSettingsCategory.Type.ZOOM:
                 return ContentFeatureMap.isEnabled(ContentFeatureList.ACCESSIBILITY_PAGE_ZOOM)
                         && ContentFeatureMap.isEnabled(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index f17bc86..b865847 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -99,6 +99,7 @@
 import org.chromium.chrome.browser.price_tracking.PriceTrackingUtilities;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.readaloud.ReadAloudController;
 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
 import org.chromium.chrome.browser.share.ShareDelegate;
 import org.chromium.chrome.browser.tab.SadTab;
@@ -329,10 +330,7 @@
 
     private final boolean mIsCustomTab;
 
-    @Nullable ObservableSupplier<String> mReadAloudReadabilitySupplier;
-
-    private final Callback<String> mOnReadAloudReadabilitySuccess =
-            this::onReadAloudReadabilitySuccess;
+    private ReadAloudController mReadAloudController;
 
     private static class TabObscuringCallback implements Callback<Boolean> {
         private final TabObscuringHandler mTabObscuringHandler;
@@ -558,7 +556,8 @@
             Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
             boolean initializeWithIncognitoColors,
             @Nullable BackPressManager backPressManager,
-            @Nullable BooleanSupplier overviewIncognitoSupplier) {
+            @Nullable BooleanSupplier overviewIncognitoSupplier,
+            ObservableSupplier<ReadAloudController> readAloudControllerSupplier) {
         TraceEvent.begin("ToolbarManager.ToolbarManager");
         mActivity = activity;
         mWindowAndroid = windowAndroid;
@@ -1289,23 +1288,22 @@
                     }
                 };
         profileSupplier.addObserver(profileObserver);
+        readAloudControllerSupplier.addObserver(
+                readAloudController -> {
+                    mReadAloudController = readAloudController;
+                    if (readAloudController != null) {
+                        readAloudController.addReadabilityUpdateListener(
+                                this::onReadAloudReadabilityUpdated);
+                    }
+                });
         TraceEvent.end("ToolbarManager.ToolbarManager");
     }
 
     // TODO(b/315204103): add tests
-    public void setReadAloudReadabilitySupplier(
-            ObservableSupplier<String> readAloudReadabilitySupplier) {
-        mReadAloudReadabilitySupplier = readAloudReadabilitySupplier;
-        mReadAloudReadabilitySupplier.addObserver(mOnReadAloudReadabilitySuccess);
-    }
-
-    // TODO(b/315204103): add tests
-    private void onReadAloudReadabilitySuccess(String url) {
-        // Checks if ReadAloud is set as the customized button and the readable url matches the
-        // current tab
+    private void onReadAloudReadabilityUpdated() {
+        // Update the button if ReadAloud is set as the customized button.
         if (ChromeSharedPreferences.getInstance().readInt(ADAPTIVE_TOOLBAR_CUSTOMIZATION_SETTINGS)
-                        == AdaptiveToolbarButtonVariant.READ_ALOUD
-                && mTabModelSelector.getCurrentTab().getUrl().getSpec().equals(url)) {
+                == AdaptiveToolbarButtonVariant.READ_ALOUD) {
             updateButtonStatus();
         }
     }
@@ -1997,9 +1995,10 @@
             mStartSurfaceHeaderOffsetChangeListener = null;
         }
 
-        if (mReadAloudReadabilitySupplier != null) {
-            mReadAloudReadabilitySupplier.removeObserver(mOnReadAloudReadabilitySuccess);
-            mReadAloudReadabilitySupplier = null;
+        if (mReadAloudController != null) {
+            mReadAloudController.removeReadabilityUpdateListener(
+                    this::onReadAloudReadabilityUpdated);
+            mReadAloudController = null;
         }
 
         mTabObscuringHandler.removeObserver(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
index b6578e93..4b1698b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/RootUiCoordinator.java
@@ -916,7 +916,6 @@
                             controller.maybeShowPlayer();
                         }
                     };
-            mToolbarManager.setReadAloudReadabilitySupplier(controller.getReadabilitySupplier());
             if (mContextualSearchManagerSupplier.get() != null) {
                 mContextualSearchManagerSupplier
                         .get()
@@ -1436,7 +1435,8 @@
                             mEphemeralTabCoordinatorSupplier,
                             mInitializeUiWithIncognitoColors,
                             mBackPressManager,
-                            mOverviewIncognitoSupplier);
+                            mOverviewIncognitoSupplier,
+                            mReadAloudControllerSupplier);
             if (!mSupportsAppMenuSupplier.getAsBoolean()) {
                 mToolbarManager.getToolbar().disableMenuButton();
             }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
index ee1d7da5..63b8710 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browsing_data/ClearBrowsingDataFragmentTest.java
@@ -68,6 +68,7 @@
 import org.chromium.base.test.util.Features;
 import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.browsing_data.BrowsingDataBridge.OnClearBrowsingDataListener;
 import org.chromium.chrome.browser.browsing_data.ClearBrowsingDataFragment.DialogOption;
 import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher;
@@ -79,6 +80,7 @@
 import org.chromium.chrome.browser.settings.SettingsActivity;
 import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.R;
@@ -790,6 +792,32 @@
         ChromeFeatureList.QUICK_DELETE_FOR_ANDROID,
         ChromeFeatureList.QUICK_DELETE_ANDROID_FOLLOWUP
     })
+    public void testSnackbarShown_defaultTimePeriod_withQuickDeleteV2Enabled() throws Exception {
+        setDataTypesToClear(DialogOption.CLEAR_CACHE);
+
+        final ClearBrowsingDataFragment preferences =
+                (ClearBrowsingDataFragment) startPreferences().getMainFragment();
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    clickClearButton(preferences);
+                });
+
+        waitForProgressToComplete(preferences);
+        mCallbackHelper.waitForFirst();
+
+        ChromeTabbedActivity activity = mActivityTestRule.getActivity();
+        final String expectedSnackbarMessage =
+                activity.getResources().getString(R.string.quick_delete_snackbar_all_time_message);
+        waitForSnackbar(expectedSnackbarMessage);
+    }
+
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.QUICK_DELETE_FOR_ANDROID,
+        ChromeFeatureList.QUICK_DELETE_ANDROID_FOLLOWUP
+    })
     public void testTabsCheckbox_withQuickDeleteV2Enabled() {
         ClearBrowsingDataFragment preferences =
                 (ClearBrowsingDataFragment) startPreferences().getMainFragment();
@@ -799,6 +827,50 @@
         assertNotNull(checkboxPreference);
     }
 
+    @Test
+    @MediumTest
+    @Features.EnableFeatures({
+        ChromeFeatureList.QUICK_DELETE_FOR_ANDROID,
+        ChromeFeatureList.QUICK_DELETE_ANDROID_FOLLOWUP
+    })
+    public void testSnackbarShown_changeTimePeriod_withQuickDeleteV2Enabled() throws Exception {
+        setDataTypesToClear(DialogOption.CLEAR_CACHE);
+
+        final ClearBrowsingDataFragment preferences =
+                (ClearBrowsingDataFragment) startPreferences().getMainFragment();
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    changeTimePeriodTo(preferences, TimePeriod.LAST_HOUR);
+                    clickClearButton(preferences);
+                });
+
+        waitForProgressToComplete(preferences);
+        mCallbackHelper.waitForFirst();
+
+        ChromeTabbedActivity activity = mActivityTestRule.getActivity();
+        final String expectedSnackbarMessage =
+                activity.getString(
+                        R.string.quick_delete_snackbar_message,
+                        TimePeriodUtils.getTimePeriodString(activity, TimePeriod.LAST_HOUR));
+        waitForSnackbar(expectedSnackbarMessage);
+    }
+
+    /** Wait for the snackbar to show on the main activity post deletion. */
+    private void waitForSnackbar(String expectedSnackbarMessage) {
+        ChromeTabbedActivity activity = mActivityTestRule.getActivity();
+        CriteriaHelper.pollUiThread(
+                () -> {
+                    SnackbarManager snackbarManager = activity.getSnackbarManager();
+                    Criteria.checkThat(snackbarManager.isShowing(), Matchers.is(true));
+                    TextView snackbarMessage = activity.findViewById(R.id.snackbar_message);
+                    Criteria.checkThat(snackbarMessage, Matchers.notNullValue());
+                    Criteria.checkThat(
+                            snackbarMessage.getText().toString(),
+                            Matchers.is(expectedSnackbarMessage));
+                });
+    }
+
     private void setDataTypesToClear(final Integer... typesToClear) {
         Set<Integer> typesToClearSet = new ArraySet<Integer>(Arrays.asList(typesToClear));
         TestThreadUtils.runOnUiThreadBlocking(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
index b266e2a..63577805 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/firstrun/FirstRunActivitySigninAndSyncTest.java
@@ -75,6 +75,7 @@
 import org.chromium.components.signin.base.AccountCapabilities;
 import org.chromium.components.signin.identitymanager.ConsentLevel;
 import org.chromium.components.signin.identitymanager.IdentityManager;
+import org.chromium.components.signin.metrics.SigninAccessPoint;
 import org.chromium.components.signin.test.util.AccountCapabilitiesBuilder;
 import org.chromium.components.signin.test.util.FakeAccountManagerFacade;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -180,6 +181,9 @@
     @Restriction({DeviceRestriction.RESTRICTION_TYPE_NON_AUTO})
     @Features.EnableFeatures(ChromeFeatureList.REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS)
     public void continueButtonClickShowsHistorySyncPage() {
+        HistogramWatcher histogramWatcher =
+                HistogramWatcher.newSingleRecordWatcher(
+                        "Signin.HistorySyncOptIn.Started", SigninAccessPoint.START_PAGE);
         mAccountManagerTestRule.addAccount(TEST_EMAIL);
         launchFirstRunActivityAndWaitForNativeInitialization();
         waitUntilCurrentPageIs(SigninFirstRunFragment.class);
@@ -187,6 +191,7 @@
 
         clickButton(R.id.signin_fre_continue_button);
 
+        histogramWatcher.assertExpected();
         waitUntilCurrentPageIs(HistorySyncFirstRunFragment.class);
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SingleWebsiteSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SingleWebsiteSettingsTest.java
index 3c663e8..134e5ef 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SingleWebsiteSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SingleWebsiteSettingsTest.java
@@ -35,7 +35,6 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisableIf;
-import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.profiles.ProfileManager;
 import org.chromium.chrome.browser.settings.SettingsActivity;
@@ -52,7 +51,6 @@
 import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge;
 import org.chromium.components.content_settings.ContentSettingValues;
 import org.chromium.components.content_settings.ContentSettingsType;
-import org.chromium.components.permissions.PermissionsAndroidFeatureList;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.url.GURL;
 
@@ -202,7 +200,6 @@
 
     @Test
     @SmallTest
-    @EnableFeatures(PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS)
     public void testStorageAccessPermission() {
         int type = ContentSettingsType.STORAGE_ACCESS;
         GURL example = new GURL("https://example.com");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
index a65efada..8410972 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/SiteSettingsTest.java
@@ -132,7 +132,6 @@
 import org.chromium.components.content_settings.CookieControlsMode;
 import org.chromium.components.embedder_support.util.Origin;
 import org.chromium.components.location.LocationUtils;
-import org.chromium.components.permissions.PermissionsAndroidFeatureList;
 import org.chromium.components.permissions.nfc.NfcSystemLevelSetting;
 import org.chromium.components.policy.test.annotations.Policies;
 import org.chromium.components.prefs.PrefService;
@@ -1405,7 +1404,6 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testOnlyExpectedPreferencesStorageAccess() {
         testExpectedPreferences(
                 SiteSettingsCategory.Type.STORAGE_ACCESS,
@@ -1416,7 +1414,6 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testExpectedExceptionsStorageAccess() {
         createStorageAccessExceptions();
         SiteSettingsTestUtils.startSiteSettingsCategory(SiteSettingsCategory.Type.STORAGE_ACCESS);
@@ -1437,7 +1434,6 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testResetExceptionGroupStorageAccess() {
         createStorageAccessExceptions();
         SiteSettingsTestUtils.startSiteSettingsCategory(SiteSettingsCategory.Type.STORAGE_ACCESS);
@@ -1481,7 +1477,6 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testBlockExceptionGroupStorageAccess() {
         createStorageAccessExceptions();
         SiteSettingsTestUtils.startSiteSettingsCategory(SiteSettingsCategory.Type.STORAGE_ACCESS);
@@ -1523,7 +1518,6 @@
     @Test
     @SmallTest
     @Feature({"Preferences"})
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testStorageAccessSubpage() {
         createStorageAccessExceptions();
         final SettingsActivity settingsActivity =
@@ -2632,7 +2626,6 @@
     @Test
     @SmallTest
     @Feature({"RenderTest"})
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testRenderStorageAccessPage() throws Exception {
         createStorageAccessExceptions();
         renderCategoryPage(
@@ -2642,7 +2635,6 @@
     @Test
     @SmallTest
     @Feature({"RenderTest"})
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testRenderStorageAccessSubpage() throws Exception {
         createStorageAccessExceptions();
         final SettingsActivity settingsActivity =
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
index 14c7125..28c274f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/site_settings/WebsitePermissionsFetcherTest.java
@@ -33,7 +33,6 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.chrome.browser.browsing_data.BrowsingDataBridge;
 import org.chromium.chrome.browser.browsing_data.BrowsingDataType;
 import org.chromium.chrome.browser.browsing_data.TimePeriod;
@@ -59,7 +58,6 @@
 import org.chromium.components.content_settings.ContentSettingValues;
 import org.chromium.components.content_settings.ContentSettingsType;
 import org.chromium.components.content_settings.SessionModel;
-import org.chromium.components.permissions.PermissionsAndroidFeatureList;
 import org.chromium.content_public.browser.BrowserContextHandle;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
@@ -1471,7 +1469,6 @@
 
     @Test
     @SmallTest
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testIncognitoFetching() throws TimeoutException {
         WebsitePermissionsFetcher fetcher =
                 new WebsitePermissionsFetcher(UNUSED_BROWSER_CONTEXT_HANDLE);
@@ -1513,7 +1510,6 @@
 
     @Test
     @SmallTest
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     public void testFetchAllSites() {
         WebsitePermissionsFetcher fetcher =
                 new WebsitePermissionsFetcher(UNUSED_BROWSER_CONTEXT_HANDLE);
@@ -1612,7 +1608,6 @@
 
     @Test
     @SmallTest
-    @EnableFeatures({PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS})
     @UseMethodParameter(EmbargoedParams.class)
     public void testFetchPreferencesForCategoryEmbeddedPermissionTypes(boolean isEmbargoed) {
         WebsitePermissionsFetcher fetcher =
diff --git a/chrome/android/modules/chrome_feature_module_tmpl.gni b/chrome/android/modules/chrome_feature_module_tmpl.gni
index a7e92b7c..d14a8f4 100644
--- a/chrome/android/modules/chrome_feature_module_tmpl.gni
+++ b/chrome/android/modules/chrome_feature_module_tmpl.gni
@@ -152,7 +152,6 @@
     # Adds unwind table asset to the chrome apk for the given library target. This
     # is not part of generic apk assets target since it depends on the main shared
     # library of the apk, to extract unwind tables.
-    asset_deps = []
     if (defined(_module_desc.include_unwind_assets) &&
         _module_desc.include_unwind_assets) {
       _needs_32bit_lib =
@@ -160,14 +159,11 @@
 
       if (_needs_32bit_lib) {
         if (_is_monochrome_or_trichrome) {
-          asset_deps += [ "//chrome/android:libmonochrome_unwind_table_assets" ]
+          deps += [ "//chrome/android:libmonochrome_unwind_table_assets" ]
         } else {
-          asset_deps += [ "//chrome/android:libchrome_unwind_table_assets" ]
+          deps += [ "//chrome/android:libchrome_unwind_table_assets" ]
         }
       }
     }
-    if (defined(invoker.asset_deps)) {
-      asset_deps += invoker.asset_deps
-    }
   }
 }
diff --git a/chrome/app/helper-Info.plist b/chrome/app/helper-Info.plist
index 6f8600e3d..a8c71a54 100644
--- a/chrome/app/helper-Info.plist
+++ b/chrome/app/helper-Info.plist
@@ -29,6 +29,8 @@
 	<string>${CHROMIUM_MIN_SYSTEM_VERSION}</string>
 	<key>LSUIElement</key>
 	<string>1</string>
+	<key>NSCameraReactionEffectGesturesEnabledDefault</key>
+	<false/>
 	<key>NSSupportsAutomaticGraphicsSwitching</key>
 	<true/>
 </dict>
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index cf60ed32..f2f650e1 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -5588,6 +5588,9 @@
   <message name="IDS_SETTINGS_DISPLAY_NIGHT_LIGHT_TEMP_SLIDER_MIN_LABEL" desc="In Device Settings > Displays, label of the minimum value settable by the color temperature slider.">
     Cooler
   </message>
+  <message name="IDS_SETTINGS_DISPLAY_SHINY_PERFORMANCE_LABEL" desc="In Device Settings > Displays, label of the toggle of enabling Shiny Performance." translateable="false">
+    Shiny Performance
+  </message>
   <message name="IDS_SETTINGS_DISPLAY_UNIFIED_DESKTOP" desc="In Device Settings > Displays, the label for the control for the unified desktop feature.">
     Allow windows to span displays
   </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 5a1fe61..5cdf70fb 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1864,8 +1864,6 @@
     "visibility_timer_tab_helper.h",
     "vr/vr_tab_helper.cc",
     "vr/vr_tab_helper.h",
-    "web_data_service_factory.cc",
-    "web_data_service_factory.h",
     "webapps/chrome_webapps_client.cc",
     "webapps/chrome_webapps_client.h",
     "webapps/web_app_offline.cc",
@@ -1874,6 +1872,8 @@
     "webauthn/webauthn_metrics_util.h",
     "webauthn/webauthn_pref_names.cc",
     "webauthn/webauthn_pref_names.h",
+    "webdata_services/web_data_service_factory.cc",
+    "webdata_services/web_data_service_factory.h",
     "webid/federated_identity_account_keyed_permission_context.cc",
     "webid/federated_identity_account_keyed_permission_context.h",
     "webid/federated_identity_api_permission_context.cc",
@@ -2241,6 +2241,7 @@
     "//components/history_clusters/core",
     "//components/history_clusters/history_clusters_internals/webui",
     "//components/history_clusters/history_clusters_internals/webui:constants",
+    "//components/history_embeddings",
     "//components/infobars/content",
     "//components/infobars/core",
     "//components/invalidation/impl",
@@ -4657,6 +4658,7 @@
       "//ui/webui/resources/cr_components/app_management:mojo_bindings",
       "//ui/webui/resources/cr_components/help_bubble:mojo_bindings",
       "//ui/webui/resources/cr_components/history_clusters:mojo_bindings",
+      "//ui/webui/resources/cr_components/history_embeddings:mojo_bindings",
     ]
     public_deps += [
       "//chrome/common:buildflags",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0bcc649b..375169fa 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -10139,12 +10139,6 @@
                                     "WebAuthenticationAndroidCredMan")},
 #endif  // BUILDFLAG(IS_ANDROID)
 
-    {"permission-storage-access-api",
-     flag_descriptions::kPermissionStorageAccessAPIName,
-     flag_descriptions::kPermissionStorageAccessAPIDescription,
-     kOsDesktop | kOsAndroid,
-     FEATURE_VALUE_TYPE(permissions::features::kPermissionStorageAccessAPI)},
-
 #if BUILDFLAG(IS_ANDROID)
     {"android-extended-keyboard-shortcuts",
      flag_descriptions::kAndroidExtendedKeyboardShortcutsName,
diff --git a/chrome/browser/ash/DEPS b/chrome/browser/ash/DEPS
index 52895cf..ea251c4 100644
--- a/chrome/browser/ash/DEPS
+++ b/chrome/browser/ash/DEPS
@@ -516,6 +516,7 @@
   "+chrome/browser/web_applications/web_app_id_constants.h",
   "+chrome/browser/web_applications/web_app_install_finalizer.h",
   "+chrome/browser/web_applications/web_app_install_info.h",
+  "+chrome/browser/web_applications/web_app_install_params.h",
   "+chrome/browser/web_applications/web_app_install_utils.h",
   "+chrome/browser/web_applications/web_app_launch_queue.h",
   "+chrome/browser/web_applications/web_app_provider.h",
diff --git a/chrome/browser/ash/app_list/app_list_client_impl.cc b/chrome/browser/ash/app_list/app_list_client_impl.cc
index d6bf501c1..a8175bf 100644
--- a/chrome/browser/ash/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ash/app_list/app_list_client_impl.cc
@@ -155,24 +155,6 @@
   auto* user_manager = user_manager::UserManager::Get();
   user_manager->RemoveSessionStateObserver(this);
 
-  // We assume that the current user is new if `state_for_new_user_` has value.
-  if (state_for_new_user_.has_value() &&
-      !state_for_new_user_->showing_recorded) {
-    DCHECK(user_manager->IsCurrentUserNew());
-
-    // Prefer the function to the macro because the usage data is recorded no
-    // more than once per second.
-    if (display::Screen::GetScreen()->InTabletMode()) {
-      base::UmaHistogramEnumeration(
-          "Apps.AppListUsageByNewUsers.TabletMode",
-          AppListUsageStateByNewUsers::kNotUsedBeforeDestruction);
-    } else {
-      base::UmaHistogramEnumeration(
-          "Apps.AppListUsageByNewUsers.ClamshellMode",
-          AppListUsageStateByNewUsers::kNotUsedBeforeDestruction);
-    }
-  }
-
   session_manager::SessionManager::Get()->RemoveObserver(this);
 
   DCHECK_EQ(this, g_app_list_client_instance);
@@ -413,24 +395,6 @@
     RecordViewShown();
   } else if (current_model_updater_) {
     current_model_updater_->OnAppListHidden();
-
-    // Record whether user took action first time they opened the launcher.
-    // Note that this is recorded only on first user session (otherwise
-    // `state_for_new_user_` will not be set).
-    if (state_for_new_user_ && state_for_new_user_->showing_recorded &&
-        !state_for_new_user_->first_open_success_recorded) {
-      state_for_new_user_->first_open_success_recorded = true;
-
-      if (state_for_new_user_->shown_in_tablet_mode) {
-        base::UmaHistogramBoolean(
-            "Apps.AppList.SuccessfulFirstUsageByNewUsers.TabletMode",
-            state_for_new_user_->action_recorded);
-      } else {
-        base::UmaHistogramBoolean(
-            "Apps.AppList.SuccessfulFirstUsageByNewUsers.ClamshellMode",
-            state_for_new_user_->action_recorded);
-      }
-    }
     // If the user started search, record no action if a result open event has
     // not been yet recorded.
     if (state_for_new_user_ && state_for_new_user_->started_search &&
@@ -465,19 +429,6 @@
     // be both new. It should not happen in the real world.
     state_for_new_user_ = StateForNewUser();
   } else if (state_for_new_user_) {
-    if (!state_for_new_user_->showing_recorded) {
-      // We assume that the previous user before switching was new if
-      // `state_for_new_user_` is not null.
-      if (display::Screen::GetScreen()->InTabletMode()) {
-        base::UmaHistogramEnumeration(
-            "Apps.AppListUsageByNewUsers.TabletMode",
-            AppListUsageStateByNewUsers::kNotUsedBeforeSwitchingAccounts);
-      } else {
-        base::UmaHistogramEnumeration(
-            "Apps.AppListUsageByNewUsers.ClamshellMode",
-            AppListUsageStateByNewUsers::kNotUsedBeforeSwitchingAccounts);
-      }
-    }
     state_for_new_user_.reset();
   }
 
@@ -811,9 +762,6 @@
           "TabletMode",
           /*sample=*/opening_duration, kTimeMetricsMin, kTimeMetricsMax,
           kTimeMetricsBucketCount);
-
-      base::UmaHistogramEnumeration("Apps.AppListUsageByNewUsers.TabletMode",
-                                    AppListUsageStateByNewUsers::kUsed);
     } else {
       UMA_HISTOGRAM_CUSTOM_TIMES(
           /*name=*/
@@ -822,9 +770,6 @@
           "ClamshellMode",
           /*sample=*/opening_duration, kTimeMetricsMin, kTimeMetricsMax,
           kTimeMetricsBucketCount);
-
-      base::UmaHistogramEnumeration("Apps.AppListUsageByNewUsers.ClamshellMode",
-                                    AppListUsageStateByNewUsers::kUsed);
     }
   }
 }
diff --git a/chrome/browser/ash/app_list/app_list_client_impl.h b/chrome/browser/ash/app_list/app_list_client_impl.h
index d1af99e..2511db8 100644
--- a/chrome/browser/ash/app_list/app_list_client_impl.h
+++ b/chrome/browser/ash/app_list/app_list_client_impl.h
@@ -169,10 +169,6 @@
     // Indicates whether any launcher action has been recorded.
     bool action_recorded = false;
 
-    // Indicates whether the metric to track whether the user took action when
-    // the launcher was first shown was recorded.
-    bool first_open_success_recorded = false;
-
     // Whether the user entered a query into the search box.
     bool started_search = false;
     // Whether the result that the user launched during their first search
diff --git a/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc b/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
index 497525cc..f545d6f 100644
--- a/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
+++ b/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
@@ -1047,10 +1047,6 @@
       "Apps.TimeDurationBetweenNewUserSessionActivationAndFirstLauncherOpening."
       "ClamshellMode",
       1);
-  tester.ExpectBucketCount(
-      "Apps.AppListUsageByNewUsers.ClamshellMode",
-      static_cast<int>(AppListClientImpl::AppListUsageStateByNewUsers::kUsed),
-      1);
 }
 
 // The duration between OOBE and the first launcher showing should not be
@@ -1063,11 +1059,6 @@
   // Verify that the launcher usage state is recorded when switching accounts.
   base::HistogramTester tester;
   AddUser(registered_user_id_);
-  tester.ExpectBucketCount(
-      "Apps.AppListUsageByNewUsers.ClamshellMode",
-      static_cast<int>(AppListClientImpl::AppListUsageStateByNewUsers::
-                           kNotUsedBeforeSwitchingAccounts),
-      1);
 
   // Verify that the metric is not recorded.
   ShowAppListAndVerify();
@@ -1075,10 +1066,6 @@
       "Apps.TimeDurationBetweenNewUserSessionActivationAndFirstLauncherOpening."
       "ClamshellMode",
       0);
-  tester.ExpectBucketCount(
-      "Apps.AppListUsageByNewUsers.ClamshellMode",
-      static_cast<int>(AppListClientImpl::AppListUsageStateByNewUsers::kUsed),
-      0);
 }
 
 // The duration between OOBE and the first launcher showing should not be
@@ -1099,44 +1086,4 @@
       "Apps.TimeDurationBetweenNewUserSessionActivationAndFirstLauncherOpening."
       "ClamshellMode",
       0);
-  tester.ExpectBucketCount(
-      "Apps.AppListUsageByNewUsers.ClamshellMode",
-      static_cast<int>(AppListClientImpl::AppListUsageStateByNewUsers::kUsed),
-      0);
-}
-
-class DurationBetweenSeesionActivationAndFirstLauncherShowingShutdownTest
-    : public DurationBetweenSeesionActivationAndFirstLauncherShowingBrowserTest {
- public:
-  DurationBetweenSeesionActivationAndFirstLauncherShowingShutdownTest() =
-      default;
-  ~DurationBetweenSeesionActivationAndFirstLauncherShowingShutdownTest()
-      override = default;
-
- protected:
-  // DurationBetweenSeesionActivationAndFirstLauncherShowingBrowserTest:
-  void SetUpOnMainThread() override {
-    DurationBetweenSeesionActivationAndFirstLauncherShowingBrowserTest::
-        SetUpOnMainThread();
-    histogram_tester_ = std::make_unique<base::HistogramTester>();
-  }
-
-  void TearDown() override {
-    histogram_tester_->ExpectBucketCount(
-        "Apps.AppListUsageByNewUsers.ClamshellMode",
-        static_cast<int>(AppListClientImpl::AppListUsageStateByNewUsers::
-                             kNotUsedBeforeDestruction),
-        1);
-    DurationBetweenSeesionActivationAndFirstLauncherShowingBrowserTest::
-        TearDown();
-  }
-
-  std::unique_ptr<base::HistogramTester> histogram_tester_;
-};
-
-// Verify that the launcher usage state is recorded when shutting down.
-IN_PROC_BROWSER_TEST_F(
-    DurationBetweenSeesionActivationAndFirstLauncherShowingShutdownTest,
-    NotUseLauncherBeforeShuttingDown) {
-  // Do nothing. Verify the histogram after the browser process is terminated.
 }
diff --git a/chrome/browser/ash/apps/apk_web_app_installer.cc b/chrome/browser/ash/apps/apk_web_app_installer.cc
index 421ed861..ebf779a 100644
--- a/chrome/browser/ash/apps/apk_web_app_installer.cc
+++ b/chrome/browser/ash/apps/apk_web_app_installer.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
+#include "chrome/browser/web_applications/web_app_install_params.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
@@ -209,12 +210,13 @@
     // Doesn't overwrite already existing web app with manifest fields from the
     // apk.
     GURL start_url = web_app_install_info_->start_url;
-    provider->scheduler().InstallFromInfo(
+    provider->scheduler().InstallFromInfoWithParams(
         std::move(web_app_install_info_),
         /*overwrite_existing_manifest_fields=*/false,
         webapps::WebappInstallSource::ARC,
         base::BindOnce(&ApkWebAppInstaller::OnWebAppCreated,
-                       base::Unretained(this), std::move(start_url)));
+                       base::Unretained(this), std::move(start_url)),
+        web_app::WebAppInstallParams());
   }
 }
 
diff --git a/chrome/browser/ash/display/refresh_rate_controller.cc b/chrome/browser/ash/display/refresh_rate_controller.cc
index 4e3ff9a..665f055e 100644
--- a/chrome/browser/ash/display/refresh_rate_controller.cc
+++ b/chrome/browser/ash/display/refresh_rate_controller.cc
@@ -92,10 +92,16 @@
     return;
   }
 
-  // Enable VRR if battery saver is inactive.
+  // Enable VRR on the borealis-hosting display if battery saver is inactive.
   const bool battery_saver_mode_enabled = power_status_->IsBatterySaverActive();
-  display_configurator_->SetVrrEnabled(!battery_saver_mode_enabled &&
-                                       borealis_window_observer_.IsObserving());
+  if (borealis_window_observer_.IsObserving() && !battery_saver_mode_enabled) {
+    display_configurator_->SetVrrEnabled(
+        {display::Screen::GetScreen()
+             ->GetDisplayNearestWindow(borealis_window_observer_.GetSource())
+             .id()});
+  } else {
+    display_configurator_->SetVrrEnabled({});
+  }
 }
 
 display::RefreshRateThrottleState
diff --git a/chrome/browser/ash/display/refresh_rate_controller.h b/chrome/browser/ash/display/refresh_rate_controller.h
index fe367171..82b6a88 100644
--- a/chrome/browser/ash/display/refresh_rate_controller.h
+++ b/chrome/browser/ash/display/refresh_rate_controller.h
@@ -35,8 +35,7 @@
                         bool force_throttle = false);
 
   RefreshRateController(const RefreshRateController&) = delete;
-  RefreshRateController& operator=(
-      const RefreshRateController&) = delete;
+  RefreshRateController& operator=(const RefreshRateController&) = delete;
 
   ~RefreshRateController() override;
 
diff --git a/chrome/browser/ash/display/refresh_rate_controller_unittest.cc b/chrome/browser/ash/display/refresh_rate_controller_unittest.cc
index 85b052d1..aa7b9d8 100644
--- a/chrome/browser/ash/display/refresh_rate_controller_unittest.cc
+++ b/chrome/browser/ash/display/refresh_rate_controller_unittest.cc
@@ -90,10 +90,9 @@
                               ::features::kEnableVariableRefreshRate},
         /*disabled_features=*/{});
   }
-  RefreshRateControllerTest(const RefreshRateControllerTest&) =
+  RefreshRateControllerTest(const RefreshRateControllerTest&) = delete;
+  RefreshRateControllerTest& operator=(const RefreshRateControllerTest&) =
       delete;
-  RefreshRateControllerTest& operator=(
-      const RefreshRateControllerTest&) = delete;
   ~RefreshRateControllerTest() override = default;
 
   void SetUp() override {
@@ -335,8 +334,8 @@
 
 TEST_F(RefreshRateControllerTest,
        ThrottlingUnaffectedForBorealisOnExternalDisplay) {
-  constexpr int64_t internal_id = 12345;
   const int64_t external_id = GetPrimaryDisplay().id();
+  const int64_t internal_id = external_id + 1;
   std::vector<std::unique_ptr<DisplaySnapshot>> snapshots;
   snapshots.push_back(BuildDualRefreshPanelSnapshot(
       internal_id, display::DISPLAY_CONNECTION_TYPE_INTERNAL));
@@ -549,32 +548,44 @@
 }
 
 TEST_F(RefreshRateControllerTest, ShouldEnableVrrForBorealis) {
-  const int64_t display_id = GetPrimaryDisplay().id();
+  const int64_t internal_id = GetPrimaryDisplay().id();
+  const int64_t external_id = internal_id + 1;
   std::vector<std::unique_ptr<DisplaySnapshot>> snapshots;
   snapshots.push_back(BuildVrrPanelSnapshot(
-      display_id, display::DISPLAY_CONNECTION_TYPE_INTERNAL));
+      internal_id, display::DISPLAY_CONNECTION_TYPE_INTERNAL));
+  snapshots.push_back(BuildVrrPanelSnapshot(
+      external_id, display::DISPLAY_CONNECTION_TYPE_HDMI));
   SetUpDisplays(std::move(snapshots));
-  ScopedSetInternalDisplayIds set_internal(display_id);
+  ScopedSetInternalDisplayIds set_internal(internal_id);
   std::unique_ptr<aura::Window> window(
       CreateTestWindowInShellWithBounds(GetPrimaryDisplay().work_area()));
 
   // Expect VRR to be initially disabled.
   {
-    const DisplaySnapshot* snapshot = GetDisplaySnapshot(display_id);
-    ASSERT_NE(snapshot, nullptr);
-    ASSERT_TRUE(snapshot->IsVrrCapable());
-    EXPECT_FALSE(snapshot->IsVrrEnabled());
+    const DisplaySnapshot* internal_snapshot = GetDisplaySnapshot(internal_id);
+    ASSERT_NE(internal_snapshot, nullptr);
+    ASSERT_TRUE(internal_snapshot->IsVrrCapable());
+    EXPECT_FALSE(internal_snapshot->IsVrrEnabled());
+
+    const DisplaySnapshot* external_snapshot = GetDisplaySnapshot(external_id);
+    ASSERT_NE(external_snapshot, nullptr);
+    ASSERT_TRUE(external_snapshot->IsVrrCapable());
+    EXPECT_FALSE(external_snapshot->IsVrrEnabled());
   }
 
   // Set the game mode to indicate the user is gaming.
   game_mode_controller_->NotifySetGameMode(GameMode::BOREALIS,
                                            ash::WindowState::Get(window.get()));
 
-  // Expect the new state to have VRR enabled.
+  // Expect the new state to have VRR enabled on the Borealis display only.
   {
-    const DisplaySnapshot* snapshot = GetDisplaySnapshot(display_id);
-    ASSERT_NE(snapshot, nullptr);
-    EXPECT_TRUE(snapshot->IsVrrEnabled());
+    const DisplaySnapshot* internal_snapshot = GetDisplaySnapshot(internal_id);
+    ASSERT_NE(internal_snapshot, nullptr);
+    EXPECT_TRUE(internal_snapshot->IsVrrEnabled());
+
+    const DisplaySnapshot* external_snapshot = GetDisplaySnapshot(external_id);
+    ASSERT_NE(external_snapshot, nullptr);
+    EXPECT_FALSE(external_snapshot->IsVrrEnabled());
   }
 
   // Reset the game mode.
@@ -583,9 +594,13 @@
 
   // Expect the new state to have VRR disabled.
   {
-    const DisplaySnapshot* snapshot = GetDisplaySnapshot(display_id);
-    ASSERT_NE(snapshot, nullptr);
-    EXPECT_FALSE(snapshot->IsVrrEnabled());
+    const DisplaySnapshot* internal_snapshot = GetDisplaySnapshot(internal_id);
+    ASSERT_NE(internal_snapshot, nullptr);
+    EXPECT_FALSE(internal_snapshot->IsVrrEnabled());
+
+    const DisplaySnapshot* external_snapshot = GetDisplaySnapshot(external_id);
+    ASSERT_NE(external_snapshot, nullptr);
+    EXPECT_FALSE(external_snapshot->IsVrrEnabled());
   }
 
   game_mode_controller_->NotifySetGameMode(GameMode::OFF,
diff --git a/chrome/browser/ash/growth/install_web_app_action_performer.cc b/chrome/browser/ash/growth/install_web_app_action_performer.cc
index 367188e..f97157f 100644
--- a/chrome/browser/ash/growth/install_web_app_action_performer.cc
+++ b/chrome/browser/ash/growth/install_web_app_action_performer.cc
@@ -98,10 +98,11 @@
 void InstallWebAppImpl(web_app::WebAppProvider& provider,
                        std::unique_ptr<web_app::WebAppInstallInfo> web_app_info,
                        growth::ActionPerformer::Callback callback) {
-  provider.scheduler().InstallFromInfo(
+  provider.scheduler().InstallFromInfoWithParams(
       std::move(web_app_info), /* overwrite_existing_manifest_fields= */ true,
       webapps::WebappInstallSource::EXTERNAL_DEFAULT,
-      base::BindOnce(&InstallWebAppResult, std::move(callback)));
+      base::BindOnce(&InstallWebAppResult, std::move(callback)),
+      web_app::WebAppInstallParams());
 }
 
 web_app::WebAppProvider* GetWebAppProvider() {
diff --git a/chrome/browser/ash/login/screens/management_transition_screen_browsertest.cc b/chrome/browser/ash/login/screens/management_transition_screen_browsertest.cc
index eccbf90..c5b48f35 100644
--- a/chrome/browser/ash/login/screens/management_transition_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/management_transition_screen_browsertest.cc
@@ -159,7 +159,9 @@
   arc::SetArcPlayStoreEnabledForProfile(profile, true);
 }
 
-IN_PROC_BROWSER_TEST_P(ManagementTransitionScreenTest, SuccessfulTransition) {
+// TODO(https://crbug.com/316993299) disabled due to flake.
+IN_PROC_BROWSER_TEST_P(ManagementTransitionScreenTest,
+                       DISABLED_SuccessfulTransition) {
   OobeScreenWaiter(ManagementTransitionScreenView::kScreenId).Wait();
 
   test::OobeJS().ExpectVisiblePath(kManagementDialog);
diff --git a/chrome/browser/ash/login/ui/captive_portal_dialog_delegate_browsertest.cc b/chrome/browser/ash/login/ui/captive_portal_dialog_delegate_browsertest.cc
index b9d5748..9ed16c3a 100644
--- a/chrome/browser/ash/login/ui/captive_portal_dialog_delegate_browsertest.cc
+++ b/chrome/browser/ash/login/ui/captive_portal_dialog_delegate_browsertest.cc
@@ -31,7 +31,8 @@
     // Dialogs that take focus must have a name and role to pass accessibility
     // checks.
     GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-    GetViewAccessibility().OverrideName("Test dialog");
+    GetViewAccessibility().SetName("Test dialog",
+                                   ax::mojom::NameFrom::kAttribute);
   }
   ChildModalDialogDelegate(const ChildModalDialogDelegate&) = delete;
   ChildModalDialogDelegate& operator=(const ChildModalDialogDelegate&) = delete;
diff --git a/chrome/browser/autofill/autocomplete_browsertest.cc b/chrome/browser/autofill/autocomplete_browsertest.cc
index c7bc4c57..ffcc2ca 100644
--- a/chrome/browser/autofill/autocomplete_browsertest.cc
+++ b/chrome/browser/autofill/autocomplete_browsertest.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/autofill/content/browser/content_autofill_driver.h"
diff --git a/chrome/browser/autofill/autocomplete_history_manager_factory.cc b/chrome/browser/autofill/autocomplete_history_manager_factory.cc
index e7dc811..86468c9 100644
--- a/chrome/browser/autofill/autocomplete_history_manager_factory.cc
+++ b/chrome/browser/autofill/autocomplete_history_manager_factory.cc
@@ -6,7 +6,7 @@
 
 #include "base/no_destructor.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/autofill/core/browser/autocomplete_history_manager.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 
diff --git a/chrome/browser/autofill/autofill_browsertest.cc b/chrome/browser/autofill/autofill_browsertest.cc
index 955b5c7..682dc4e 100644
--- a/chrome/browser/autofill/autofill_browsertest.cc
+++ b/chrome/browser/autofill/autofill_browsertest.cc
@@ -29,7 +29,7 @@
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/test_switches.h"
 #include "chrome/test/base/ui_test_utils.h"
diff --git a/chrome/browser/autofill/personal_data_manager_factory.cc b/chrome/browser/autofill/personal_data_manager_factory.cc
index 85ebd8b4..08c51813 100644
--- a/chrome/browser/autofill/personal_data_manager_factory.cc
+++ b/chrome/browser/autofill/personal_data_manager_factory.cc
@@ -12,7 +12,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/sync/sync_service_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/autofill/content/browser/content_autofill_shared_storage_handler.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
 #include "components/autofill/core/browser/strike_databases/strike_database.h"
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index 7749b54..49d2647 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -77,8 +77,8 @@
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/ui/find_bar/find_bar_state.h"
 #include "chrome/browser/ui/find_bar/find_bar_state_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
 #include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/url_constants.h"
 #include "components/autofill/core/browser/personal_data_manager.h"
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.cc
index 9b69d82..358e67a 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_factory.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/sessions/tab_restore_service_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "content/public/browser/browser_context.h"
 #include "extensions/buildflags/buildflags.h"
 
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index 4b785a4..e33dded 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -78,7 +78,7 @@
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/translate/chrome_translate_client.h"
 #include "chrome/browser/trusted_vault/trusted_vault_service_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/browser/webid/federated_identity_permission_context.h"
 #include "chrome/common/chrome_constants.h"
 #include "chrome/common/chrome_features.h"
diff --git a/chrome/browser/browsing_data/counters/autofill_counter_browsertest.cc b/chrome/browser/browsing_data/counters/autofill_counter_browsertest.cc
index 2d295bf..dacacc7 100644
--- a/chrome/browser/browsing_data/counters/autofill_counter_browsertest.cc
+++ b/chrome/browser/browsing_data/counters/autofill_counter_browsertest.cc
@@ -17,7 +17,7 @@
 #include "base/uuid.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/autofill_type.h"
diff --git a/chrome/browser/browsing_data/counters/browsing_data_counter_factory.cc b/chrome/browser/browsing_data/counters/browsing_data_counter_factory.cc
index 0815cfb..b0223c7d 100644
--- a/chrome/browser/browsing_data/counters/browsing_data_counter_factory.cc
+++ b/chrome/browser/browsing_data/counters/browsing_data_counter_factory.cc
@@ -22,8 +22,8 @@
 #include "chrome/browser/password_manager/profile_password_store_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/sync_service_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
 #include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/browsing_data/core/counters/autofill_counter.h"
 #include "components/browsing_data/core/counters/browsing_data_counter.h"
 #include "components/browsing_data/core/counters/history_counter.h"
diff --git a/chrome/browser/browsing_data/counters/sync_aware_counter_browsertest.cc b/chrome/browser/browsing_data/counters/sync_aware_counter_browsertest.cc
index b66a0b4..2cb9ac0 100644
--- a/chrome/browser/browsing_data/counters/sync_aware_counter_browsertest.cc
+++ b/chrome/browser/browsing_data/counters/sync_aware_counter_browsertest.cc
@@ -16,7 +16,7 @@
 #include "chrome/browser/sync/test/integration/sync_service_impl_harness.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
 #include "components/browsing_data/core/browsing_data_utils.h"
diff --git a/chrome/browser/chrome_browser_interface_binders.cc b/chrome/browser/chrome_browser_interface_binders.cc
index 010216a..44a658a 100644
--- a/chrome/browser/chrome_browser_interface_binders.cc
+++ b/chrome/browser/chrome_browser_interface_binders.cc
@@ -71,6 +71,7 @@
 #include "components/history_clusters/core/features.h"
 #include "components/history_clusters/core/history_clusters_service.h"
 #include "components/history_clusters/history_clusters_internals/webui/history_clusters_internals_ui.h"
+#include "components/history_embeddings/history_embeddings_features.h"
 #include "components/live_caption/caption_util.h"
 #include "components/live_caption/pref_names.h"
 #include "components/no_state_prefetch/browser/no_state_prefetch_contents.h"
@@ -216,6 +217,7 @@
 #include "ui/webui/resources/cr_components/customize_themes/customize_themes.mojom.h"
 #include "ui/webui/resources/cr_components/help_bubble/help_bubble.mojom.h"
 #include "ui/webui/resources/cr_components/history_clusters/history_clusters.mojom.h"
+#include "ui/webui/resources/cr_components/history_embeddings/history_embeddings.mojom.h"
 #include "ui/webui/resources/cr_components/most_visited/most_visited.mojom.h"
 #include "ui/webui/resources/cr_components/theme_color_picker/theme_color_picker.mojom.h"
 #include "ui/webui/resources/js/browser_command/browser_command.mojom.h"
@@ -1201,6 +1203,10 @@
           history_clusters::mojom::PageHandler, HistoryUI>(map);
     }
   }
+  if (base::FeatureList::IsEnabled(history_embeddings::kHistoryEmbeddings)) {
+    RegisterWebUIControllerInterfaceBinder<
+        history_embeddings::mojom::PageHandler, HistoryUI>(map);
+  }
 
   RegisterWebUIControllerInterfaceBinder<
       page_image_service::mojom::PageImageServiceHandler, HistoryUI,
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index cc98eb9..a8116f2 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -1384,7 +1384,7 @@
     ]
   }
 
-  if (enable_pdf) {
+  if (enable_pdf && enable_screen_ai_service) {
     sources += [
       "api/pdf_viewer_private/pdf_viewer_private_event_router.cc",
       "api/pdf_viewer_private/pdf_viewer_private_event_router.h",
diff --git a/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc b/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc
index bb02cc3..cf418cd 100644
--- a/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc
+++ b/chrome/browser/extensions/api/api_browser_context_keyed_service_factories.cc
@@ -42,6 +42,7 @@
 #include "components/services/screen_ai/buildflags/buildflags.h"
 #include "extensions/browser/api/bluetooth_low_energy/bluetooth_low_energy_api.h"
 #include "extensions/browser/api/networking_private/networking_private_delegate_factory.h"
+#include "pdf/buildflags.h"
 #include "printing/buildflags/buildflags.h"
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
@@ -63,9 +64,9 @@
 #include "chrome/browser/extensions/api/image_writer_private/image_writer_controller_lacros.h"
 #endif
 
-#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+#if BUILDFLAG(ENABLE_PDF) && BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 #include "chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_event_router_factory.h"
-#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+#endif  // BUILDFLAG(ENABLE_PDF) && BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
 
 #if BUILDFLAG(ENABLE_SERVICE_DISCOVERY)
 #include "chrome/browser/extensions/api/mdns/mdns_api.h"
@@ -113,9 +114,9 @@
   extensions::OmniboxAPI::GetFactoryInstance();
   extensions::PasswordsPrivateDelegateFactory::GetInstance();
   extensions::PasswordsPrivateEventRouterFactory::GetInstance();
-#if BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+#if BUILDFLAG(ENABLE_PDF) && BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   extensions::PdfViewerPrivateEventRouterFactory::GetInstance();
-#endif  // BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
+#endif  // BUILDFLAG(ENABLE_PDF) && BUILDFLAG(ENABLE_SCREEN_AI_SERVICE)
   extensions::PreferenceAPI::GetFactoryInstance();
 #if BUILDFLAG(IS_CHROMEOS) && BUILDFLAG(USE_CUPS)
   extensions::PrintingAPIHandler::GetFactoryInstance();
diff --git a/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc b/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
index 6aaa6206..109cbb3 100644
--- a/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
+++ b/chrome/browser/extensions/api/management/chrome_management_api_delegate.cc
@@ -236,12 +236,13 @@
     auto* provider = web_app::WebAppProvider::GetForWebApps(
         Profile::FromBrowserContext(context));
 
-    provider->scheduler().InstallFromInfo(
+    provider->scheduler().InstallFromInfoWithParams(
         std::move(web_app_info),
         /*overwrite_existing_manifest_fields=*/false,
         webapps::WebappInstallSource::MANAGEMENT_API,
         base::BindOnce(OnGenerateAppForLinkCompleted,
-                       base::RetainedRef(function)));
+                       base::RetainedRef(function)),
+        web_app::WebAppInstallParams());
   }
 
   extensions::api::management::ExtensionInfo CreateExtensionInfoFromWebApp(
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 274efcd..593dc46 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -1027,8 +1027,8 @@
       << message_;
 }
 
-// TODO(crbug.com/1450976): test is flaky on Mac11.
-#if BUILDFLAG(IS_MAC)
+// TODO: crbug.com/1450976 - Re-enable tests on Mac and Lacros.
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_CHROMEOS_LACROS)
 #define MAYBE_WebRequestCORSWithExtraHeaders \
   DISABLED_WebRequestCORSWithExtraHeaders
 #else
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index efe27d5..e6aee84 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -6476,11 +6476,6 @@
     "expiry_milestone": 118
   },
   {
-    "name": "permission-storage-access-api",
-    "owners": [ "dullweber@chromium.org", "fsenra@google.com"],
-    "expiry_milestone": 124
-  },
-  {
     "name": "permissive-usb-passthrough",
     "owners": [
       "drmasquatch@chromium.org"
@@ -8505,7 +8500,7 @@
   {
     "name": "web-midi",
     "owners": [ "//third_party/blink/renderer/modules/webaudio/OWNERS", "reillyg@chromium.org", "deviceapi-team@google.com" ],
-    "expiry_milestone": 124
+    "expiry_milestone": 126
   },
   {
     "name": "web-otp-backend",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index dc950633..83292f6 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2888,11 +2888,6 @@
     "right-hand side address bar icon for quiet permission prompts. Requires "
     "chrome://flags/#quiet-notification-prompts to be enabled.";
 
-const char kPermissionStorageAccessAPIName[] =
-    "Storage Access API permission UI";
-const char kPermissionStorageAccessAPIDescription[] =
-    "Enables the new Storage Access API permission UI on Desktop";
-
 const char kShowRelatedWebsiteSetsPermissionGrantsName[] =
     "Show permission grants from Related Website Sets";
 const char kShowRelatedWebsiteSetsPermissionGrantsDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 33aeeee..b050577 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1666,9 +1666,6 @@
 extern const char kPermissionQuietChipName[];
 extern const char kPermissionQuietChipDescription[];
 
-extern const char kPermissionStorageAccessAPIName[];
-extern const char kPermissionStorageAccessAPIDescription[];
-
 extern const char kShowRelatedWebsiteSetsPermissionGrantsName[];
 extern const char kShowRelatedWebsiteSetsPermissionGrantsDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index fe8e5f3e..28c33085 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -280,6 +280,7 @@
     &kTestDefaultDisabled,
     &kTestDefaultEnabled,
     &kTotallyEdgeToEdge,
+    &kSafetyHub,
     &kStartSurfaceAndroid,
     &kStartSurfaceOnTablet,
     &kStartSurfaceReturnTime,
@@ -879,6 +880,8 @@
              "TestDefaultEnabled",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kSafetyHub, "SafetyHub", base::FEATURE_DISABLED_BY_DEFAULT);
+
 // This feature updates the triggering logic for the default search engine
 // choice promo. See crbug.com/1471643 for more details.
 BASE_FEATURE(kSearchEnginesPromoV3,
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 25977e2b..da32d38 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -161,6 +161,7 @@
 BASE_DECLARE_FEATURE(kTestDefaultEnabled);
 BASE_DECLARE_FEATURE(kToolbarUseHardwareBitmapDraw);
 BASE_DECLARE_FEATURE(kTotallyEdgeToEdge);
+BASE_DECLARE_FEATURE(kSafetyHub);
 BASE_DECLARE_FEATURE(kStartSurfaceAndroid);
 BASE_DECLARE_FEATURE(kStartSurfaceOnTablet);
 BASE_DECLARE_FEATURE(kStartSurfaceReturnTime);
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 1e96af7..865cc58 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
@@ -409,6 +409,7 @@
     public static final String RENAME_JOURNEYS = "RenameJourneys";
     public static final String REPLACE_SYNC_PROMOS_WITH_SIGN_IN_PROMOS =
             "ReplaceSyncPromosWithSignInPromos";
+    public static final String SAFETY_HUB = "SafetyHub";
     public static final String SAFE_BROWSING_DELAYED_WARNINGS = "SafeBrowsingDelayedWarnings";
     public static final String SAFE_BROWSING_CALL_NEW_GMS_API_ON_STARTUP =
             "SafeBrowsingCallNewGmsApiOnStartup";
@@ -803,6 +804,8 @@
             newMutableFlagWithSafeDefault(READER_MODE_IN_CCT, false);
     public static final MutableFlagWithSafeDefault sRecordSuppressionMetrics =
             newMutableFlagWithSafeDefault(RECORD_SUPPRESSION_METRICS, true);
+    public static final MutableFlagWithSafeDefault sSafetyHub =
+            newMutableFlagWithSafeDefault(SAFETY_HUB, false);
     public static final MutableFlagWithSafeDefault sSearchInCCT =
             newMutableFlagWithSafeDefault(SEARCH_IN_CCT, false);
     public static final MutableFlagWithSafeDefault sSearchReadyOmniboxAllowQueryEdit =
diff --git a/chrome/browser/importer/profile_writer.cc b/chrome/browser/importer/profile_writer.cc
index b623f1f..88713b9 100644
--- a/chrome/browser/importer/profile_writer.cc
+++ b/chrome/browser/importer/profile_writer.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/password_manager/profile_password_store_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/common/importer/imported_bookmark_entry.h"
 #include "chrome/common/pref_names.h"
 #include "components/autofill/core/browser/webdata/autofill_webdata_service.h"
diff --git a/chrome/browser/lacros/web_app_provider_bridge_lacros.cc b/chrome/browser/lacros/web_app_provider_bridge_lacros.cc
index 0c867a2..6b0c4788 100644
--- a/chrome/browser/lacros/web_app_provider_bridge_lacros.cc
+++ b/chrome/browser/lacros/web_app_provider_bridge_lacros.cc
@@ -159,10 +159,11 @@
         std::move(*arc_install_info->additional_policy_ids);
   }
 
-  provider->scheduler().InstallFromInfo(
+  provider->scheduler().InstallFromInfoWithParams(
       std::move(install_info),
       /*overwrite_existing_manifest_fields=*/false,
-      webapps::WebappInstallSource::ARC, std::move(callback));
+      webapps::WebappInstallSource::ARC, std::move(callback),
+      web_app::WebAppInstallParams());
 }
 
 // static
diff --git a/chrome/browser/magic_stack/android/BUILD.gn b/chrome/browser/magic_stack/android/BUILD.gn
index 65b5dbcf..734617ab 100644
--- a/chrome/browser/magic_stack/android/BUILD.gn
+++ b/chrome/browser/magic_stack/android/BUILD.gn
@@ -13,6 +13,7 @@
     "java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java",
     "java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMediator.java",
     "java/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtils.java",
+    "java/src/org/chromium/chrome/browser/magic_stack/HomeModulesRecyclerView.java",
     "java/src/org/chromium/chrome/browser/magic_stack/ModuleConfigChecker.java",
     "java/src/org/chromium/chrome/browser/magic_stack/ModuleDelegate.java",
     "java/src/org/chromium/chrome/browser/magic_stack/ModuleDelegateHost.java",
@@ -72,6 +73,7 @@
     "junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java",
     "junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMediatorUnitTest.java",
     "junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesMetricsUtilsUnitTest.java",
+    "junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesRecyclerViewUnitTest.java",
     "junit/src/org/chromium/chrome/browser/magic_stack/ModuleRegistryUnitTest.java",
   ]
 
diff --git a/chrome/browser/magic_stack/android/java/res/layout/home_modules_recycler_view_layout.xml b/chrome/browser/magic_stack/android/java/res/layout/home_modules_recycler_view_layout.xml
index 7dc588d9..cc0f5b1 100644
--- a/chrome/browser/magic_stack/android/java/res/layout/home_modules_recycler_view_layout.xml
+++ b/chrome/browser/magic_stack/android/java/res/layout/home_modules_recycler_view_layout.xml
@@ -5,7 +5,7 @@
 found in the LICENSE file.
 -->
 
-<androidx.recyclerview.widget.RecyclerView
+<org.chromium.chrome.browser.magic_stack.HomeModulesRecyclerView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/home_modules_recycler_view"
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/CirclePagerIndicatorDecoration.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/CirclePagerIndicatorDecoration.java
index db75191..14cb15c 100644
--- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/CirclePagerIndicatorDecoration.java
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/CirclePagerIndicatorDecoration.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.magic_stack;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -13,16 +11,19 @@
 import android.graphics.Paint.Style;
 import android.graphics.Rect;
 import android.view.View;
-import android.view.ViewGroup.MarginLayoutParams;
 
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.text.TextUtilsCompat;
+import androidx.core.view.ViewCompat;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
 import org.chromium.components.browser_ui.widget.displaystyle.UiConfig.DisplayStyle;
 
+import java.util.Locale;
+
 /** Circle pager indicator for recyclerview. */
 public class CirclePagerIndicatorDecoration extends RecyclerView.ItemDecoration {
     private final @ColorInt int mColorActive;
@@ -43,11 +44,11 @@
     /** Padding between indicators in pixel. */
     private final float mIndicatorItemPaddingPx;
 
-    private final int mModuleInternalPaddingPx;
-
     private final Paint mPaint = new Paint();
     private final boolean mIsTablet;
 
+    private final boolean mIsLeftToRight;
+
     /** The start margin of the recyclerview in pixel. */
     private int mStartMarginPx;
 
@@ -83,7 +84,10 @@
         mIndicatorHeightPx =
                 (int) mIndicatorItemDiameterPx
                         + resources.getDimensionPixelSize(R.dimen.page_indicator_top_margin);
-        mModuleInternalPaddingPx = resources.getDimensionPixelSize(R.dimen.module_internal_padding);
+
+        mIsLeftToRight =
+                TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
+                        == ViewCompat.LAYOUT_DIRECTION_LTR;
     }
 
     @Override
@@ -194,38 +198,20 @@
         // they are hidden.
         outRect.bottom = itemCount <= mItemPerScreen ? 0 : mIndicatorHeightPx;
 
-        // Don't need to change the width of a child view on phones since there is only one item
-        // shown per screen, and it never changes.
-        if (!mIsTablet) return;
+        // If showing one card per screen, the view's width should match the parent recyclerview.
+        // Thus, we don't need to add extra padding on the left side of any card.
+        if (!mIsTablet || mItemPerScreen == 1 || itemCount == 1) return;
 
-        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
-        if (mItemPerScreen == 1 || itemCount == 1) {
-            // If showing one item per screen, the view's width should match the parent
-            // recyclerview.
-            marginLayoutParams.width = MATCH_PARENT;
-            if (mItemPerScreen == 1) {
-                updateMargin(view, marginLayoutParams);
-            }
-            return;
+        // On a wide screen, we will show 2 cards instead of 1 per screen. The card's width is
+        // calculated and doesn't match parent's width any more. Add a padding on the left side of
+        // any card except the first one if it is a left to right language; on the right side if it
+        // is a right to left language.
+        int padding = parent.getChildAdapterPosition(view) == 0 ? 0 : (int) mIndicatorItemPaddingPx;
+        if (mIsLeftToRight) {
+            outRect.left = padding;
+        } else {
+            outRect.right = padding;
         }
-
-        // On a wide screen, we will show 2 cards instead of 1 on the magic stack.
-        int position = parent.getChildAdapterPosition(view);
-        boolean isFirstPosition = position == 0;
-
-        // Updates the width of the view.
-        outRect.left = isFirstPosition ? 0 : (int) mIndicatorItemPaddingPx;
-        int width =
-                (parent.getMeasuredWidth() - mModuleInternalPaddingPx * (mItemPerScreen - 1))
-                        / mItemPerScreen;
-        marginLayoutParams.width = width;
-        updateMargin(view, marginLayoutParams);
-    }
-
-    private void updateMargin(View view, MarginLayoutParams marginLayoutParams) {
-        marginLayoutParams.setMarginEnd(mStartMarginPx);
-        marginLayoutParams.setMarginStart(mStartMarginPx);
-        view.setLayoutParams(marginLayoutParams);
     }
 
     void onDisplayStyleChanged(int startMarginPx, int itemPerScreen) {
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java
index 70b44f5..561616b 100644
--- a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinator.java
@@ -45,7 +45,7 @@
     private final ModuleDelegateHost mModuleDelegateHost;
     private HomeModulesMediator mMediator;
     private final SimpleRecyclerViewAdapter mAdapter;
-    private final RecyclerView mRecyclerView;
+    private final HomeModulesRecyclerView mRecyclerView;
     private final ModelList mModel;
     private final HomeModulesContextMenuManager mHomeModulesContextMenuManager;
     private final ObservableSupplier<Profile> mProfileSupplier;
@@ -132,11 +132,19 @@
 
     private void setupRecyclerView(Activity activity) {
         boolean isTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(activity);
+        int startMargin = mModuleDelegateHost.getStartMargin();
         mUiConfig = isTablet ? mModuleDelegateHost.getUiConfig() : null;
+        mItemPerScreen =
+                mUiConfig == null
+                        ? 1
+                        : CirclePagerIndicatorDecoration.getItemPerScreen(
+                                mUiConfig.getCurrentDisplayStyle());
+        mRecyclerView.initialize(isTablet, startMargin, mItemPerScreen);
+
         mPageIndicatorDecoration =
                 new CirclePagerIndicatorDecoration(
                         activity,
-                        mModuleDelegateHost.getStartMargin(),
+                        startMargin,
                         SemanticColorUtils.getDefaultIconColorSecondary(activity),
                         activity.getColor(
                                 org.chromium.components.browser_ui.styles.R.color
@@ -153,9 +161,6 @@
                     }
                 };
 
-        // Sets the default value.
-        mItemPerScreen = 1;
-
         // Snap scroll is supported by the recyclerview if it shows a single item per screen. This
         // happens on phones or small windows on tablets.
         if (!isTablet) {
@@ -187,15 +192,17 @@
                     }
 
                     // Notifies the CirclePageIndicatorDecoration.
+                    int updatedStartMargin = mModuleDelegateHost.getStartMargin();
                     mPageIndicatorDecoration.onDisplayStyleChanged(
-                            mModuleDelegateHost.getStartMargin(), mItemPerScreen);
+                            updatedStartMargin, mItemPerScreen);
+                    mRecyclerView.onDisplayStyleChanged(updatedStartMargin, mItemPerScreen);
 
                     // Redraws the recyclerview when display style is changed on tablets.
                     mRecyclerView.invalidateItemDecorations();
                 };
         mUiConfig.addObserver(mDisplayStyleObserver);
-        mPageIndicatorDecoration.onDisplayStyleChanged(
-                mModuleDelegateHost.getStartMargin(), mItemPerScreen);
+        mPageIndicatorDecoration.onDisplayStyleChanged(startMargin, mItemPerScreen);
+        mRecyclerView.onDisplayStyleChanged(startMargin, mItemPerScreen);
     }
 
     /**
diff --git a/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesRecyclerView.java b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesRecyclerView.java
new file mode 100644
index 0000000..37dc977
--- /dev/null
+++ b/chrome/browser/magic_stack/android/java/src/org/chromium/chrome/browser/magic_stack/HomeModulesRecyclerView.java
@@ -0,0 +1,108 @@
+// Copyright 2024 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.magic_stack;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.RecyclerView;
+
+/** A custom RecyclerView implementation for the home modules. */
+public class HomeModulesRecyclerView extends RecyclerView {
+
+    /* Whether the activity is running on a tablet.*/
+    private boolean mIsTablet;
+
+    /** The value is updated for tablets when displayStyle is changed. */
+    private int mItemPerScreen;
+
+    /** The start margin of the recyclerview in pixel. */
+    private int mStartMarginPx;
+
+    /* The internal padding between two modules in pixel. */
+    private int mModuleInternalPaddingPx;
+
+    public HomeModulesRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Initializes the recyclerview.
+     *
+     * @param isTablet Whether the activity is running on a tablet.
+     * @param startMarginPx The start margin of the recyclerview in pixel.
+     * @param itemPerScreen The number of modules are shown per screen.
+     */
+    void initialize(boolean isTablet, int startMarginPx, int itemPerScreen) {
+        mIsTablet = isTablet;
+        mStartMarginPx = startMarginPx;
+
+        mItemPerScreen = itemPerScreen;
+        mModuleInternalPaddingPx =
+                getContext().getResources().getDimensionPixelSize(R.dimen.module_internal_padding);
+    }
+
+    @Override
+    public void onDraw(@NonNull Canvas c) {
+        super.onDraw(c);
+        // Don't need to change the width of a child view on phones since there is only one item
+        // shown per screen, and it never changes.
+        if (!mIsTablet) return;
+
+        int itemCount = getAdapter().getItemCount();
+        int measuredWidth = getMeasuredWidth();
+        for (int i = 0; i < getChildCount(); i++) {
+            onDrawImpl(getChildAt(i), itemCount, measuredWidth);
+        }
+    }
+
+    /** Called when the DisplayStyle is changed. */
+    void onDisplayStyleChanged(int startMarginPx, int itemPerScreen) {
+        mStartMarginPx = startMarginPx;
+        mItemPerScreen = itemPerScreen;
+    }
+
+    @VisibleForTesting
+    void onDrawImpl(View view, int totalChildCount, int measuredWidth) {
+        MarginLayoutParams marginLayoutParams = (MarginLayoutParams) view.getLayoutParams();
+        if (mItemPerScreen == 1 || totalChildCount == 1) {
+            // If showing one item per screen, the view's width should match the parent
+            // recyclerview.
+            if (marginLayoutParams.width == MATCH_PARENT) return;
+
+            marginLayoutParams.width = MATCH_PARENT;
+            if (mItemPerScreen == 1) {
+                updateMargin(view, marginLayoutParams);
+            }
+        } else {
+            // On a wide screen, we will show 2 cards instead of 1 on the magic stack.
+            // Updates the width of the view.
+            int width =
+                    (measuredWidth - mModuleInternalPaddingPx * (mItemPerScreen - 1))
+                            / mItemPerScreen;
+            if (marginLayoutParams.width == width) return;
+
+            marginLayoutParams.width = width;
+            updateMargin(view, marginLayoutParams);
+        }
+    }
+
+    private void updateMargin(View view, MarginLayoutParams marginLayoutParams) {
+        marginLayoutParams.setMarginEnd(mStartMarginPx);
+        marginLayoutParams.setMarginStart(mStartMarginPx);
+        view.setLayoutParams(marginLayoutParams);
+    }
+
+    void setStartMarginPxForTesting(int startMarginPx) {
+        mStartMarginPx = startMarginPx;
+    }
+}
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/CirclePagerIndicatorDecorationUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/CirclePagerIndicatorDecorationUnitTest.java
index d8a39c7..2de72f9c 100644
--- a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/CirclePagerIndicatorDecorationUnitTest.java
+++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/CirclePagerIndicatorDecorationUnitTest.java
@@ -4,12 +4,9 @@
 
 package org.chromium.chrome.browser.magic_stack;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -23,7 +20,6 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.view.View;
-import android.view.ViewGroup.MarginLayoutParams;
 
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -43,7 +39,6 @@
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.components.browser_ui.widget.displaystyle.HorizontalDisplayStyle;
-import org.chromium.components.browser_ui.widget.displaystyle.UiConfig;
 import org.chromium.components.browser_ui.widget.displaystyle.UiConfig.DisplayStyle;
 import org.chromium.components.browser_ui.widget.displaystyle.VerticalDisplayStyle;
 
@@ -52,7 +47,6 @@
 @Config(manifest = Config.NONE)
 public class CirclePagerIndicatorDecorationUnitTest {
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
-    @Mock private UiConfig mUiConfig;
     @Mock private Canvas mCanvas;
     @Mock private RecyclerView mRecyclerView;
     @Mock private RecyclerView.Adapter mAdapter;
@@ -66,10 +60,9 @@
     private float mIndicatorItemDiameter;
     private float mIndicatorRadius;
 
-    private float mIndicatorItemPadding;
+    private int mIndicatorItemPadding;
     private int mParentViewWidth;
     private int mParentHeight;
-    private int mModuleInternalPaddingPx;
     private CirclePagerIndicatorDecoration mDecoration;
     private Context mContext;
 
@@ -81,13 +74,12 @@
 
         Resources resources = mContext.getResources();
         mIndicatorItemPadding =
-                (float) resources.getDimensionPixelSize(R.dimen.page_indicator_internal_padding);
+                resources.getDimensionPixelSize(R.dimen.page_indicator_internal_padding);
         mIndicatorItemDiameter = resources.getDimensionPixelSize(R.dimen.page_indicator_dot_size);
         mIndicatorRadius = mIndicatorItemDiameter / 2f;
         mIndicatorHeight =
                 (int) mIndicatorItemDiameter
                         + resources.getDimensionPixelSize(R.dimen.page_indicator_top_margin);
-        mModuleInternalPaddingPx = resources.getDimensionPixelSize(R.dimen.module_internal_padding);
         mParentViewWidth = 800;
         mParentHeight = 400;
         when(mRecyclerView.getHeight()).thenReturn(mParentHeight);
@@ -299,102 +291,88 @@
         Rect rect = new Rect();
         View view = Mockito.mock(View.class);
         RecyclerView.State state = Mockito.mock(State.class);
-        when(mRecyclerView.getMeasuredWidth()).thenReturn(mParentViewWidth);
         when(mAdapter.getItemCount()).thenReturn(3);
+        when(mRecyclerView.getChildAdapterPosition(view)).thenReturn(1);
 
         mDecoration.getItemOffsetsImpl(rect, view, mRecyclerView, state);
-        // Verifies that the width is not updated for phones.
+        // Verifies that the page indicator is shown, but no extra padding is added to any view.
         assertEquals(mIndicatorHeight, rect.bottom);
         assertEquals(0, rect.left);
-        verify(view, never()).getLayoutParams();
     }
 
     @Test
     @SmallTest
-    public void testGetItemOffsetsOneItemOnly_Tablet() {
+    public void testGetItemOffsets_DoNotShowPageIndicator() {
         mDecoration = create(/* isTablet= */ true);
 
         Rect rect = new Rect();
         View view = Mockito.mock(View.class);
         RecyclerView.State state = Mockito.mock(State.class);
+
         // The recyclerview has only 1 item shown.
-        when(mAdapter.getItemCount()).thenReturn(1);
+        int itemCount = 1;
+        when(mAdapter.getItemCount()).thenReturn(itemCount);
 
         // Sets the tablet as a wide screen.
-        DisplayStyle displayStyle =
-                new DisplayStyle(HorizontalDisplayStyle.WIDE, VerticalDisplayStyle.REGULAR);
-        when(mUiConfig.getCurrentDisplayStyle()).thenReturn(displayStyle);
-        assertEquals(2, getItemPerScreen(mUiConfig.getCurrentDisplayStyle()));
-
-        MarginLayoutParams layoutParams = new MarginLayoutParams(0, 0);
-        when(view.getLayoutParams()).thenReturn(layoutParams);
-
-        mDecoration.onDisplayStyleChanged(0, getItemPerScreen(displayStyle));
+        int itemPerScreen = 2;
+        mDecoration.onDisplayStyleChanged(0, itemPerScreen);
         mDecoration.getItemOffsetsImpl(rect, view, mRecyclerView, state);
-        // Verifies that the space of the indicators are removed.
+        // Verifies that the space of the indicators are removed when the itemCount is less than the
+        // itemPerScreen.
         assertEquals(0, rect.bottom);
         assertEquals(0, rect.left);
-        assertEquals(MATCH_PARENT, layoutParams.width);
+
+        itemCount = 2;
+        when(mAdapter.getItemCount()).thenReturn(itemCount);
+        mDecoration.onDisplayStyleChanged(0, itemPerScreen);
+        mDecoration.getItemOffsetsImpl(rect, view, mRecyclerView, state);
+        // Verifies that the space of the indicators are removed when the itemCount equals to the
+        // itemPerScreen.
+        assertEquals(0, rect.bottom);
+        assertEquals(0, rect.left);
     }
 
     @Test
     @SmallTest
-    public void testGetItemOffsets_Tablet() {
+    public void testGetItemOffsets_ShowPageIndicator() {
         mDecoration = create(/* isTablet= */ true);
 
         Rect rect = new Rect();
         View view = Mockito.mock(View.class);
         RecyclerView.State state = Mockito.mock(State.class);
-        when(mRecyclerView.getMeasuredWidth()).thenReturn(mParentViewWidth);
-        int expectedItemWith = (mParentViewWidth - mModuleInternalPaddingPx) / 2;
         // The recyclerview has 3 items shown.
-        when(mAdapter.getItemCount()).thenReturn(3);
+        int itemCount = 3;
+        when(mAdapter.getItemCount()).thenReturn(itemCount);
 
         // Sets the tablet as a wide screen.
+        int itemPerScreen = 2;
+        mDecoration.onDisplayStyleChanged(0, itemPerScreen);
+        mDecoration.getItemOffsetsImpl(rect, view, mRecyclerView, state);
+        // Verifies that the page indicator is shown when all of the items can't fit in one screen.
+        assertEquals(mIndicatorHeight, rect.bottom);
+        // Verifies that no extra padding is added for the first child view.
+        assertEquals(0, rect.left);
+
+        // Sets the view not be the first child.
+        when(mRecyclerView.getChildAdapterPosition(view)).thenReturn(1);
+        mDecoration.getItemOffsetsImpl(rect, view, mRecyclerView, state);
+        // Verifies that an extra padding is added on the left side of the view.
+        assertEquals(mIndicatorHeight, rect.bottom);
+        assertEquals(mIndicatorItemPadding, rect.left);
+    }
+
+    @Test
+    @SmallTest
+    public void testGetItemPerScreen() {
+        // Sets the tablet as a wide screen.
         DisplayStyle displayStyle =
                 new DisplayStyle(HorizontalDisplayStyle.WIDE, VerticalDisplayStyle.REGULAR);
-        when(mUiConfig.getCurrentDisplayStyle()).thenReturn(displayStyle);
-        assertEquals(2, getItemPerScreen(mUiConfig.getCurrentDisplayStyle()));
+        assertEquals(2, getItemPerScreen(displayStyle));
 
-        MarginLayoutParams layoutParams = new MarginLayoutParams(0, 0);
-        when(view.getLayoutParams()).thenReturn(layoutParams);
-
-        mDecoration.onDisplayStyleChanged(0, getItemPerScreen(displayStyle));
-        mDecoration.getItemOffsetsImpl(rect, view, mRecyclerView, state);
-        // Verifies that the width is updated for the view when multiple items are shown per screen.
-        assertEquals(mIndicatorHeight, rect.bottom);
-        assertEquals(0, rect.left);
-        assertEquals(expectedItemWith, layoutParams.width);
-
-        // Changes the current screen to a narrow window on tablets.
+        // Sets the tablet as a small screen.
         displayStyle =
                 new DisplayStyle(HorizontalDisplayStyle.REGULAR, VerticalDisplayStyle.REGULAR);
-        when(mUiConfig.getCurrentDisplayStyle()).thenReturn(displayStyle);
-        MarginLayoutParams marginLayoutParams = Mockito.mock(MarginLayoutParams.class);
-        when(view.getLayoutParams()).thenReturn(marginLayoutParams);
-        assertEquals(1, getItemPerScreen(mUiConfig.getCurrentDisplayStyle()));
-        int startMargin = 20;
-        mDecoration.onDisplayStyleChanged(startMargin, getItemPerScreen(displayStyle));
-        mDecoration.getItemOffsetsImpl(rect, view, mRecyclerView, state);
-
-        // Verifies that the start margin is set to the view if a single item is shown per screen.
-        verify(marginLayoutParams).setMarginStart(eq(startMargin));
-        verify(marginLayoutParams).setMarginEnd(eq(startMargin));
-        verify(view).setLayoutParams(eq(marginLayoutParams));
-
-        // Set back to wide screen.
-        displayStyle = new DisplayStyle(HorizontalDisplayStyle.WIDE, VerticalDisplayStyle.REGULAR);
-        when(mUiConfig.getCurrentDisplayStyle()).thenReturn(displayStyle);
-        startMargin = 0;
-        assertEquals(2, getItemPerScreen(mUiConfig.getCurrentDisplayStyle()));
-
-        mDecoration.onDisplayStyleChanged(startMargin, getItemPerScreen(displayStyle));
-        mDecoration.getItemOffsetsImpl(rect, view, mRecyclerView, state);
-        // Verifies that the width is updated for the view when multiple items are shown per screen.
-        verify(marginLayoutParams).setMarginStart(eq(startMargin));
-        verify(marginLayoutParams).setMarginEnd(eq(startMargin));
-        verify(view, times(2)).setLayoutParams(eq(marginLayoutParams));
-        assertEquals(expectedItemWith, layoutParams.width);
+        assertEquals(1, getItemPerScreen(displayStyle));
     }
 
     private CirclePagerIndicatorDecoration create(boolean isTablet) {
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
index aa3f038..f6e0fc5 100644
--- a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
+++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesCoordinatorUnitTest.java
@@ -89,7 +89,7 @@
     @Mock private Resources mResources;
     @Mock private ModuleDelegateHost mModuleDelegateHost;
     @Mock private ViewGroup mView;
-    @Mock private RecyclerView mRecyclerView;
+    @Mock private HomeModulesRecyclerView mRecyclerView;
     @Mock private UiConfig mUiConfig;
     @Mock private Configuration mConfiguration;
     @Mock private ApplicationInfo mApplicationInfo;
diff --git a/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesRecyclerViewUnitTest.java b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesRecyclerViewUnitTest.java
new file mode 100644
index 0000000..42e08451
--- /dev/null
+++ b/chrome/browser/magic_stack/android/junit/src/org/chromium/chrome/browser/magic_stack/HomeModulesRecyclerViewUnitTest.java
@@ -0,0 +1,115 @@
+// Copyright 2024 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.magic_stack;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.view.View;
+import android.view.ViewGroup.MarginLayoutParams;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.R;
+
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class HomeModulesRecyclerViewUnitTest {
+    @Mock private View mView;
+
+    private Activity mActivity;
+    private HomeModulesRecyclerView mRecyclerView;
+    private int mModuleInternalPaddingPx;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mActivity = Robolectric.buildActivity(Activity.class).setup().get();
+        mActivity.setTheme(R.style.Theme_BrowserUI_DayNight);
+        mRecyclerView =
+                (HomeModulesRecyclerView)
+                        mActivity
+                                .getLayoutInflater()
+                                .inflate(R.layout.home_modules_recycler_view_layout, null);
+        mActivity.setContentView(mRecyclerView);
+
+        mModuleInternalPaddingPx =
+                ApplicationProvider.getApplicationContext()
+                        .getResources()
+                        .getDimensionPixelSize(R.dimen.module_internal_padding);
+    }
+
+    @Test
+    @SmallTest
+    public void testOnDraw_OneItermPerScreen() {
+        int itemPerScreen = 1;
+        int startMarginPx = 0;
+        int measuredWidth = 500;
+        mRecyclerView.initialize(/* isTablet= */ true, startMarginPx, itemPerScreen);
+
+        MarginLayoutParams marginLayoutParams = new MarginLayoutParams(100, 100);
+        when(mView.getLayoutParams()).thenReturn(marginLayoutParams);
+        startMarginPx = 5;
+        mRecyclerView.setStartMarginPxForTesting(startMarginPx);
+
+        // Verifies when there is one item per screen, the width is set to MATCH_PARENT.
+        mRecyclerView.onDrawImpl(mView, 3, measuredWidth);
+        assertEquals(MATCH_PARENT, marginLayoutParams.width);
+        assertEquals(startMarginPx, marginLayoutParams.getMarginStart());
+        assertEquals(startMarginPx, marginLayoutParams.getMarginEnd());
+        verify(mView).setLayoutParams(eq(marginLayoutParams));
+
+        // Verifies that setLayoutParams() isn't called again whether there isn't any change to the
+        // width of the view.
+        mRecyclerView.onDrawImpl(mView, 3, measuredWidth);
+        assertEquals(MATCH_PARENT, marginLayoutParams.width);
+        verify(mView).setLayoutParams(eq(marginLayoutParams));
+    }
+
+    @Test
+    @SmallTest
+    public void testOnDraw_MultipleItermsPerScreen() {
+        int itemPerScreen = 2;
+        int startMarginPx = 0;
+        int measuredWidth = 500;
+        mRecyclerView.initialize(/* isTablet= */ true, startMarginPx, itemPerScreen);
+
+        MarginLayoutParams marginLayoutParams = new MarginLayoutParams(100, 100);
+        when(mView.getLayoutParams()).thenReturn(marginLayoutParams);
+        startMarginPx = 10;
+        mRecyclerView.setStartMarginPxForTesting(startMarginPx);
+        int expectedWidth =
+                (measuredWidth - mModuleInternalPaddingPx * (itemPerScreen - 1)) / itemPerScreen;
+
+        // Verifies the width becomes the half of the parent's width.
+        mRecyclerView.onDrawImpl(mView, 3, measuredWidth);
+        assertEquals(expectedWidth, marginLayoutParams.width);
+        assertEquals(startMarginPx, marginLayoutParams.getMarginStart());
+        assertEquals(startMarginPx, marginLayoutParams.getMarginEnd());
+        verify(mView).setLayoutParams(eq(marginLayoutParams));
+
+        // Verifies that setLayoutParams() isn't called again whether there isn't any change to the
+        // width of the view.
+        mRecyclerView.onDrawImpl(mView, 3, measuredWidth);
+        assertEquals(expectedWidth, marginLayoutParams.width);
+        verify(mView).setLayoutParams(eq(marginLayoutParams));
+    }
+}
diff --git a/chrome/browser/metrics/chrome_metrics_services_manager_client.h b/chrome/browser/metrics/chrome_metrics_services_manager_client.h
index 1d8ca0f..96c2695 100644
--- a/chrome/browser/metrics/chrome_metrics_services_manager_client.h
+++ b/chrome/browser/metrics/chrome_metrics_services_manager_client.h
@@ -27,7 +27,6 @@
 
 // Used only for testing.
 namespace internal {
-// TODO(crbug.com/1068796): Replace kMetricsReportingFeature with a better name.
 BASE_DECLARE_FEATURE(kMetricsReportingFeature);
 #if BUILDFLAG(IS_ANDROID)
 BASE_DECLARE_FEATURE(kPostFREFixMetricsReportingFeature);
diff --git a/chrome/browser/metrics/ukm_browsertest.cc b/chrome/browser/metrics/ukm_browsertest.cc
index 61ffca7..c6263a0 100644
--- a/chrome/browser/metrics/ukm_browsertest.cc
+++ b/chrome/browser/metrics/ukm_browsertest.cc
@@ -232,8 +232,6 @@
 class UkmBrowserTestBase : public SyncTest {
  public:
   UkmBrowserTestBase() : SyncTest(SINGLE_CLIENT) {
-    // TODO(crbug.com/1068796): Replace kMetricsReportingFeature with a more
-    // apt name.
     // Explicitly enable UKM and disable metrics reporting. Disabling metrics
     // reporting should affect only UMA--not UKM.
     scoped_feature_list_.InitWithFeatures({ukm::kUkmFeature},
diff --git a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
index 4656937..c7504e0c 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
@@ -265,6 +265,8 @@
                             const std::u16string& credential) override {}
   void PreviewField(autofill::FieldRendererId field_id,
                     const std::u16string& value) override {}
+  void FillField(autofill::FieldRendererId field_id,
+                 const std::u16string& value) override {}
   void AnnotateFieldsWithParsingResult(
       const autofill::ParsingResult& parsing_result) override {}
 
diff --git a/chrome/browser/payments/android/service_worker_payment_app_bridge.cc b/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
index c8ff1580..74525f4 100644
--- a/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
+++ b/chrome/browser/payments/android/service_worker_payment_app_bridge.cc
@@ -15,7 +15,7 @@
 #include "chrome/android/chrome_jni_headers/ServiceWorkerPaymentAppBridge_jni.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/payments/content/android/payment_handler_host.h"
 #include "components/payments/content/payment_event_response_util.h"
 #include "components/payments/content/payment_handler_host.h"
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 1a8f483..bf1f775 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -2150,6 +2150,12 @@
     client_certificates::prefs::kProvisionManagedClientCertificateForUserPrefs,
     base::Value::Type::INTEGER },
 #endif  //
+
+#if !BUILDFLAG(IS_FUCHSIA)
+  { key::kProductSpecificationsEnabled,
+    commerce::kProductSpecificationsEnabledPrefName,
+    base::Value::Type::BOOLEAN},
+#endif  // !BUILDFLAG(IS_FUCHSIA)
 };
 // clang-format on
 
diff --git a/chrome/browser/profile_resetter/profile_resetter_test_base.cc b/chrome/browser/profile_resetter/profile_resetter_test_base.cc
index 723dfb4a..4695332 100644
--- a/chrome/browser/profile_resetter/profile_resetter_test_base.cc
+++ b/chrome/browser/profile_resetter/profile_resetter_test_base.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/search_engines/template_url_service_client.h"
diff --git a/chrome/browser/profile_resetter/profile_resetter_unittest.cc b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
index 4592edae..3a2cbeaa 100644
--- a/chrome/browser/profile_resetter/profile_resetter_unittest.cc
+++ b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
@@ -36,7 +36,7 @@
 #include "chrome/browser/themes/theme_service.h"
 #include "chrome/browser/themes/theme_service_factory.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "components/content_settings/core/browser/content_settings_info.h"
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 1afa4e1..d68150a 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -203,8 +203,8 @@
 #include "chrome/browser/updates/announcement_notification/announcement_notification_service_factory.h"
 #include "chrome/browser/usb/usb_chooser_context_factory.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_reader_registry_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
 #include "chrome/browser/webauthn/enclave_manager_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/browser/webid/federated_identity_api_permission_context_factory.h"
 #include "chrome/browser/webid/federated_identity_auto_reauthn_permission_context_factory.h"
 #include "chrome/browser/webid/federated_identity_permission_context_factory.h"
diff --git a/chrome/browser/profiles/profile_statistics_aggregator.cc b/chrome/browser/profiles/profile_statistics_aggregator.cc
index abbce1c..fb21307a 100644
--- a/chrome/browser/profiles/profile_statistics_aggregator.cc
+++ b/chrome/browser/profiles/profile_statistics_aggregator.cc
@@ -17,8 +17,8 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_statistics.h"
 #include "chrome/browser/profiles/profile_statistics_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
 #include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/browsing_data/core/counters/autofill_counter.h"
 #include "components/browsing_data/core/counters/bookmark_counter.h"
 #include "components/browsing_data/core/counters/history_counter.h"
diff --git a/chrome/browser/profiles/profile_statistics_unittest.cc b/chrome/browser/profiles/profile_statistics_unittest.cc
index f7e4b51..670d1f7 100644
--- a/chrome/browser/profiles/profile_statistics_unittest.cc
+++ b/chrome/browser/profiles/profile_statistics_unittest.cc
@@ -24,7 +24,7 @@
 #include "chrome/browser/sync/account_bookmark_sync_service_factory.h"
 #include "chrome/browser/sync/local_or_syncable_bookmark_sync_service_factory.h"
 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
index f7d2628..d6efc2ba 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudController.java
@@ -17,6 +17,7 @@
 import org.chromium.base.ApplicationState;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Log;
+import org.chromium.base.ObserverList;
 import org.chromium.base.Promise;
 import org.chromium.base.ResettersForTesting;
 import org.chromium.base.UserData;
@@ -78,8 +79,7 @@
     private static final Class<RestoreState> USER_DATA_KEY = RestoreState.class;
     private final Activity mActivity;
     private final ObservableSupplier<Profile> mProfileSupplier;
-    private final ObservableSupplierImpl<String> mReadabilitySupplier =
-            new ObservableSupplierImpl();
+    private final ObserverList<Runnable> mReadabilityUpdateObserverList = new ObserverList<>();
     private final Map<String, String> mSanitizedToFullUrlMap = new HashMap<>();
     private final Map<String, Boolean> mReadabilityMap = new HashMap<>();
     private final Map<String, Boolean> mTimepointsSupportedMap = new HashMap<>();
@@ -239,9 +239,45 @@
     private final ObservableSupplierImpl<String> mSelectedVoiceId;
     private final ActivityWindowAndroid mActivityWindowAndroid;
 
-    private long mTranslationObserverHandle;
-    private final TranslationObserver mTranslationObserver =
-            new TranslationObserver() {
+    /**
+     * Wrapper for TranslationObserver that keeps track of the tab it is observing and the pointer
+     * to the underlying native observer so that callers don't need to manage them.
+     */
+    private static class TranslationObserverImpl implements TranslationObserver {
+        private Tab mTab;
+        private long mHandle;
+
+        void observeTab(Tab tab) {
+            if (mTab != null) {
+                stopObservingTab(mTab);
+            }
+
+            WebContents webContents = tab.getWebContents();
+            if (webContents == null) {
+                return;
+            }
+
+            mHandle = TranslateBridge.addTranslationObserver(webContents, this);
+            mTab = tab;
+        }
+
+        void stopObservingTab(Tab tab) {
+            if (mTab == null || mTab != tab) {
+                return;
+            }
+
+            WebContents webContents = tab.getWebContents();
+            if (webContents != null) {
+                TranslateBridge.removeTranslationObserver(webContents, mHandle);
+            }
+
+            mTab = null;
+            mHandle = 0L;
+        }
+    }
+
+    private final TranslationObserverImpl mPlayingTabTranslationObserver =
+            new TranslationObserverImpl() {
                 @Override
                 public void onIsPageTranslatedChanged(WebContents webContents) {
                     if (mCurrentlyPlayingTab != null) {
@@ -258,6 +294,20 @@
                 }
             };
 
+    private final TranslationObserverImpl mCurrentTabTranslationObserver =
+            new TranslationObserverImpl() {
+                @Override
+                public void onIsPageTranslatedChanged(WebContents webContents) {
+                    notifyReadabilityMayHaveChanged();
+                }
+
+                @Override
+                public void onPageTranslated(
+                        String sourceLanguage, String translatedLanguage, int errorCode) {
+                    notifyReadabilityMayHaveChanged();
+                }
+            };
+
     /**
      * Kicks of readability check on a page load iff: the url is valid, no previous result is
      * available/pending and if a request has to be sent, the necessary conditions are satisfied.
@@ -283,7 +333,7 @@
                     mReadabilityMap.put(url, isReadable);
                     mTimepointsSupportedMap.put(url, timepointsSupported);
                     mPendingRequests.remove(url);
-                    mReadabilitySupplier.set(mSanitizedToFullUrlMap.get(url));
+                    notifyReadabilityMayHaveChanged();
                 }
 
                 @Override
@@ -332,9 +382,6 @@
         mActivityLifecycleDispatcher.register(this);
     }
 
-    public ObservableSupplier<String> getReadabilitySupplier() {
-        return mReadabilitySupplier;
-    }
     private void onProfileAvailable(Profile profile) {
         mProfile = profile;
         mReadabilityHooks =
@@ -360,6 +407,7 @@
                                 ReadAloudMetrics.recordIneligibilityReason(
                                         ReadAloudFeatures.getIneligibilityReason());
                             }
+                            mCurrentTabTranslationObserver.observeTab(tab);
                         }
 
                         @Override
@@ -378,10 +426,11 @@
                                 tab.getUserDataHost().setUserData(USER_DATA_KEY, state);
                             }
                             maybeStopPlayback(tab);
+                            mCurrentTabTranslationObserver.stopObservingTab(tab);
                         }
 
                         @Override
-                        protected void onTabSelected(Tab tab) {
+                        public void onTabSelected(Tab tab) {
                             super.onTabSelected(tab);
                             if (tab != null && tab.getUrl() != null) {
                                 Log.d(
@@ -414,6 +463,7 @@
                                     updatedRestored.restore();
                                     tab.getUserDataHost().removeUserData(USER_DATA_KEY);
                                 }
+                                mCurrentTabTranslationObserver.observeTab(tab);
                             }
                         }
 
@@ -421,6 +471,14 @@
                         public void willCloseTab(Tab tab) {
                             Log.d(TAG, "WillCloseTab");
                             maybeStopPlayback(tab);
+                            mPlayingTabTranslationObserver.stopObservingTab(tab);
+                            mCurrentTabTranslationObserver.stopObservingTab(tab);
+                        }
+
+                        @Override
+                        public void onDestroyed(Tab tab) {
+                            mPlayingTabTranslationObserver.stopObservingTab(tab);
+                            mCurrentTabTranslationObserver.stopObservingTab(tab);
                         }
                     };
 
@@ -523,6 +581,27 @@
         return false;
     }
 
+    /**
+     * Add a runnable to be called when new readability information is available for any page.
+     * Listeners can then call isReadable() to check a tab's readability.
+     *
+     * @param runnable Runnable called when a readability check succeeds or when a page is
+     *     translated.
+     */
+    public void addReadabilityUpdateListener(Runnable runnable) {
+        mReadabilityUpdateObserverList.addObserver(runnable);
+    }
+
+    /**
+     * Remove a runnable previously registered with addReadabilityUpdateListener. No effect if
+     * runnable was not added.
+     *
+     * @param runnable Runnable to remove.
+     */
+    public void removeReadabilityUpdateListener(Runnable runnable) {
+        mReadabilityUpdateObserverList.removeObserver(runnable);
+    }
+
     /** Returns true if the tab's current language is supported by the available voices. */
     private boolean isTabLanguageSupported(Tab tab) {
         if (mReadabilityHooks == null) {
@@ -597,11 +676,7 @@
 
         resetCurrentPlayback();
         mCurrentlyPlayingTab = tab;
-        mTranslationObserverHandle =
-                mCurrentlyPlayingTab.getWebContents() != null
-                        ? TranslateBridge.addTranslationObserver(
-                                mCurrentlyPlayingTab.getWebContents(), mTranslationObserver)
-                        : 0L;
+        mPlayingTabTranslationObserver.observeTab(mCurrentlyPlayingTab);
 
         if (!mPlaybackHooks.voicesInitialized()) {
             mPlaybackHooks.initVoices();
@@ -677,12 +752,7 @@
             mPlayback = null;
             mPlayerCoordinator.recordPlaybackDuration();
         }
-        if (mTranslationObserverHandle != 0L) {
-            assert mCurrentlyPlayingTab != null;
-            TranslateBridge.removeTranslationObserver(
-                    mCurrentlyPlayingTab.getWebContents(), mTranslationObserverHandle);
-            mTranslationObserverHandle = 0L;
-        }
+        mPlayingTabTranslationObserver.stopObservingTab(mCurrentlyPlayingTab);
         mCurrentlyPlayingTab = null;
         mGlobalRenderFrameId = null;
         mCurrentPlaybackData = null;
@@ -1154,6 +1224,12 @@
         mOnUserLeaveHint = true;
     }
 
+    private void notifyReadabilityMayHaveChanged() {
+        for (Runnable observer : mReadabilityUpdateObserverList) {
+            observer.run();
+        }
+    }
+
     // Tests.
     public void setHighlighterForTests(Highlighter highighter) {
         mHighlighter = highighter;
@@ -1172,7 +1248,11 @@
     }
 
     public TranslationObserver getTranslationObserverForTest() {
-        return mTranslationObserver;
+        return mPlayingTabTranslationObserver;
+    }
+
+    public TranslationObserver getCurrentTabTranslationObserverForTest() {
+        return mCurrentTabTranslationObserver;
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
index b377499..726b3ca3 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudControllerUnitTest.java
@@ -214,6 +214,10 @@
                 HistogramWatcher.newSingleRecordWatcher(
                         "ReadAloud.HighlightingEnabled.OnStartup", true);
 
+        mTab = mTabModelSelector.getCurrentTab();
+        mTab.setGurlOverrideForTesting(sTestGURL);
+        mTab.setWebContentsOverrideForTesting(mWebContents);
+
         mController =
                 new ReadAloudController(
                         mActivity,
@@ -227,10 +231,6 @@
                         mActivityLifecycleDispatcher);
         ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
 
-        mTab = mTabModelSelector.getCurrentTab();
-        mTab.setGurlOverrideForTesting(sTestGURL);
-        mTab.setWebContentsOverrideForTesting(mWebContents);
-
         when(mMetadata.languageCode()).thenReturn("en");
         when(mPlayback.getMetadata()).thenReturn(mMetadata);
         when(mWebContents.getMainFrame()).thenReturn(mRenderFrameHost);
@@ -487,6 +487,7 @@
 
     @Test
     public void checkReadabilityOnPageLoad_URLnotReadAloudSupported() {
+        reset(mHooksImpl);
         checkURLNotReadAloudSupported(new GURL("invalid"));
         checkURLNotReadAloudSupported(GURL.emptyGURL());
         checkURLNotReadAloudSupported(new GURL("chrome://history/"));
@@ -1328,24 +1329,66 @@
         // Play tab.
         requestAndStartPlayback();
 
-        assertEquals(1, mFakeTranslateBridge.getObserverCount());
+        // One observer should be registered on the playing tab to stop playback if translated, and
+        // one is registered regardless of playback for refreshing the entrypoint.
+        assertEquals(2, mFakeTranslateBridge.getObserverCount());
 
-        // stopping playback should unregister a listener
+        // stopping playback should unregister the listener that stops playback
         mController.maybeStopPlayback(mTab);
-        assertEquals(0, mFakeTranslateBridge.getObserverCount());
+        assertEquals(1, mFakeTranslateBridge.getObserverCount());
     }
 
     @Test
     public void testTranslationListenerRegistration_nullWebContents() {
+        assertEquals(1, mFakeTranslateBridge.getObserverCount());
+
         // Play tab.
         when(mTab.getWebContents()).thenReturn(null);
         requestAndStartPlayback();
 
+        assertEquals(1, mFakeTranslateBridge.getObserverCount());
+
+        mController.maybeStopPlayback(mTab);
+        assertEquals(1, mFakeTranslateBridge.getObserverCount());
+    }
+
+    @Test
+    public void testTranslationListenersUnregisteredOnTabDestroyed() {
+        // Play tab.
+        requestAndStartPlayback();
+        // One observer should be registered on the playing tab to stop playback if translated, and
+        // one is registered regardless of playback for refreshing the entrypoint.
+        assertEquals(2, mFakeTranslateBridge.getObserverCount());
+
+        // Both should be removed if the tab is destroyed.
+        mController.getTabModelTabObserverforTests().onDestroyed(mTab);
+        assertEquals(0, mFakeTranslateBridge.getObserverCount());
+    }
+
+    @Test
+    public void testTranslationListenerRegisteredOnPageLoad() {
+        // Listener should be registered already because onTabSelected() is called when
+        // TabModelTabObserver is created.
+        assertEquals(1, mFakeTranslateBridge.getObserverCount());
+
+        // Destroying tab should remove the observer.
+        mController.getTabModelTabObserverforTests().onDestroyed(mTab);
         assertEquals(0, mFakeTranslateBridge.getObserverCount());
 
-        // stopping playback should not a listener
-        mController.maybeStopPlayback(mTab);
-        assertEquals(0, mFakeTranslateBridge.getObserverCount());
+        // Observer should register again on page load. To keep the test simple we'll reuse the same
+        // tab even though it was used with onDestroyed().
+        mController.getTabModelTabObserverforTests().onPageLoadStarted(mTab, sTestGURL);
+        assertEquals(1, mFakeTranslateBridge.getObserverCount());
+    }
+
+    @Test
+    public void testTranslationListenersUnregistered_nullWebContents() {
+        assertEquals(1, mFakeTranslateBridge.getObserverCount());
+
+        // If tab has null web contents, we should not try to remove translation observers.
+        doReturn(null).when(mTab).getWebContents();
+        mController.getTabModelTabObserverforTests().onDestroyed(mTab);
+        assertEquals(1, mFakeTranslateBridge.getObserverCount());
     }
 
     @Test
@@ -1396,6 +1439,19 @@
     }
 
     @Test
+    public void testPageTranslatedNotifiesReadabilityChanged() {
+        Runnable runnable = Mockito.mock(Runnable.class);
+        mController.addReadabilityUpdateListener(runnable);
+
+        var translationObserver = mController.getCurrentTabTranslationObserverForTest();
+        translationObserver.onPageTranslated("en", "es", 1);
+        verify(runnable, times(1)).run();
+
+        translationObserver.onIsPageTranslatedChanged(null);
+        verify(runnable, times(2)).run();
+    }
+
+    @Test
     public void testStoppingAnyPlayback() {
         // Play tab.
         requestAndStartPlayback();
@@ -1448,13 +1504,14 @@
     public void testReadabilitySupplier() {
         String testUrl = "https://en.wikipedia.org/wiki/Google";
 
+        Runnable runnable = Mockito.mock(Runnable.class);
+        mController.addReadabilityUpdateListener(runnable);
         mController.maybeCheckReadability(new GURL(testUrl));
 
         verify(mHooksImpl, times(1)).isPageReadable(eq(testUrl), mCallbackCaptor.capture());
 
         mCallbackCaptor.getValue().onSuccess(testUrl, true, false);
-
-        assertEquals(mController.getReadabilitySupplier().get(), testUrl);
+        verify(runnable).run();
     }
 
     @Test
@@ -1898,6 +1955,7 @@
     // TODO(b/322052505): This test won't be necessary if we keep track of profile changes.
     @Test
     public void testNoRequestsIfProfileDestroyed() {
+        reset(mHooksImpl);
         doReturn(false).when(mMockProfile).isNativeInitialized();
         mController =
                 new ReadAloudController(
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHController.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHController.java
index 2b2f115..732a141 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHController.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHController.java
@@ -73,23 +73,22 @@
     /**
      * If the current tab is readable, requests to show a "Listen to this page" IPH for the app menu
      * and turns on the highlight for the ReadAloud item in the menu.
-     *
-     * @param url URL the readability check returns
      */
-    public void maybeShowReadAloudAppMenuIPH(String url) {
-        if (shouldShowIPH(url)) {
-            mUserEducationHelper.requestShowIPH(
-                    new IPHCommandBuilder(
-                                    mToolbarMenuButton.getContext().getResources(),
-                                    FeatureConstants.READ_ALOUD_APP_MENU_FEATURE,
-                                    R.string.menu_listen_to_this_page,
-                                    R.string.menu_listen_to_this_page)
-                            .setAnchorView(mToolbarMenuButton)
-                            .setOnShowCallback(
-                                    () -> turnOnHighlightForMenuItem(R.id.readaloud_menu_id))
-                            .setOnDismissCallback(this::turnOffHighlightForMenuItem)
-                            .build());
+    public void maybeShowReadAloudAppMenuIPH() {
+        if (!shouldShowIPH()) {
+            return;
         }
+
+        mUserEducationHelper.requestShowIPH(
+                new IPHCommandBuilder(
+                                mToolbarMenuButton.getContext().getResources(),
+                                FeatureConstants.READ_ALOUD_APP_MENU_FEATURE,
+                                R.string.menu_listen_to_this_page,
+                                R.string.menu_listen_to_this_page)
+                        .setAnchorView(mToolbarMenuButton)
+                        .setOnShowCallback(() -> turnOnHighlightForMenuItem(R.id.readaloud_menu_id))
+                        .setOnDismissCallback(this::turnOffHighlightForMenuItem)
+                        .build());
     }
 
     private void turnOnHighlightForMenuItem(int highlightMenuItemId) {
@@ -100,28 +99,24 @@
         mAppMenuHandler.clearMenuHighlight();
     }
 
-    protected boolean shouldShowIPH(String url) {
-        if (mCurrentTabSupplier.get() == null
-                || !mCurrentTabSupplier.get().getUrl().isValid()
-                || mReadAloudControllerSupplier.get() == null) {
+    protected boolean shouldShowIPH() {
+        if (mCurrentTabSupplier.get() == null || !mCurrentTabSupplier.get().getUrl().isValid()) {
             return false;
         }
-        if (mCurrentTabSupplier.get().getUrl().getSpec().equals(url)) {
-            return mReadAloudControllerSupplier.get().isReadable(mCurrentTabSupplier.get());
-        }
-        return false;
+        return mReadAloudControllerSupplier.get().isReadable(mCurrentTabSupplier.get());
     }
 
     void readAloudControllerReady(@Nullable ReadAloudController readAloudController) {
         if (readAloudController != null) {
-            mReadAloudReadabilitySupplier = readAloudController.getReadabilitySupplier();
-            mReadAloudReadabilitySupplier.addObserver(this::maybeShowReadAloudAppMenuIPH);
+            readAloudController.addReadabilityUpdateListener(this::maybeShowReadAloudAppMenuIPH);
         }
     }
 
     public void destroy() {
-        if (mReadAloudReadabilitySupplier != null) {
-            mReadAloudReadabilitySupplier.removeObserver(this::maybeShowReadAloudAppMenuIPH);
+        if (mReadAloudControllerSupplier.get() != null) {
+            mReadAloudControllerSupplier
+                    .get()
+                    .removeReadabilityUpdateListener(this::maybeShowReadAloudAppMenuIPH);
         }
     }
 }
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHControllerUnitTest.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHControllerUnitTest.java
index db854e46..9787ba9 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHControllerUnitTest.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/ReadAloudIPHControllerUnitTest.java
@@ -90,7 +90,7 @@
     @Test
     @SmallTest
     public void maybeShowReadAloudAppMenuIPH() {
-        mController.maybeShowReadAloudAppMenuIPH(sTestGURL.getSpec());
+        mController.maybeShowReadAloudAppMenuIPH();
         verify(mUserEducationHelper).requestShowIPH(mIPHCommandCaptor.capture());
 
         IPHCommand command = mIPHCommandCaptor.getValue();
@@ -106,22 +106,16 @@
     public void maybeShowReadAloudAppMenuIPH_false() {
         doReturn(false).when(mReadAloudController).isReadable(mTab);
 
-        mController.maybeShowReadAloudAppMenuIPH(sTestGURL.getSpec());
+        mController.maybeShowReadAloudAppMenuIPH();
         verify(mUserEducationHelper, never()).requestShowIPH(mIPHCommandCaptor.capture());
     }
 
     @Test
     @SmallTest
     public void maybeShowReadAloudAppMenuIPH_invalid() {
-        // mismatched urls
-        mController.maybeShowReadAloudAppMenuIPH("https://en.wikipedia.org/wiki/Google");
-        verify(mUserEducationHelper, never()).requestShowIPH(mIPHCommandCaptor.capture());
-        // invalid url
-        mController.maybeShowReadAloudAppMenuIPH("http://0x100.0/");
-        verify(mUserEducationHelper, never()).requestShowIPH(mIPHCommandCaptor.capture());
         // null tab
         doReturn(null).when(mMockTabProvider).get();
-        mController.maybeShowReadAloudAppMenuIPH(sTestGURL.getSpec());
+        mController.maybeShowReadAloudAppMenuIPH();
         verify(mUserEducationHelper, never()).requestShowIPH(mIPHCommandCaptor.capture());
     }
 }
diff --git a/chrome/browser/resources/ash/settings/device_page/display.html b/chrome/browser/resources/ash/settings/device_page/display.html
index a13adea..cb40244 100644
--- a/chrome/browser/resources/ash/settings/device_page/display.html
+++ b/chrome/browser/resources/ash/settings/device_page/display.html
@@ -46,6 +46,20 @@
 
 </style>
 
+<!-- Display Shiny Performance Mode Controller -->
+<div id="displayPerformanceModeSubsection" class="settings-box"
+    hidden="[[!isDisplayPerformanceSupported_]]">
+  <div id="displayPerformanceModeLabel"
+      class="start text-area" aria-hidden="true">
+      <!-- TODO(b/326270858): Translate the label -->
+      $i18n{displayShinyPerformanceLabel}
+  </div>
+  <cr-toggle id="displayPerformanceModeToggle"
+      aria-labelledby="displayPerformanceModeLabel"
+      on-change="toggleDisplayPerformanceEnabled_">
+  </cr-toggle>
+</div>
+
 <!-- Night Light Settings -->
 <template is="dom-if"
     if="[[isRevampWayfindingEnabled_]]" restamp>
diff --git a/chrome/browser/resources/ash/settings/device_page/display.ts b/chrome/browser/resources/ash/settings/device_page/display.ts
index 5df5ead..3212605d9 100644
--- a/chrome/browser/resources/ash/settings/device_page/display.ts
+++ b/chrome/browser/resources/ash/settings/device_page/display.ts
@@ -166,6 +166,13 @@
         },
       },
 
+      isDisplayPerformanceSupported_: {
+        type: Boolean,
+        value() {
+          return loadTimeData.getBoolean('isDisplayPerformanceSupported');
+        },
+      },
+
       ambientColorAvailable_: {
         type: Boolean,
         value() {
@@ -190,6 +197,11 @@
         value: false,
       },
 
+      isDisplayPerformanceEnabled_: {
+        type: Boolean,
+        value: false,
+      },
+
       selectedParentModePref_: {
         type: Object,
         value: function() {
@@ -263,6 +275,7 @@
   private displaySettingsProvider: DisplaySettingsProviderInterface;
   private displayTabNames_: string[];
   private invalidDisplayId_: string;
+  private isDisplayPerformanceEnabled_: boolean;
   private readonly isRevampWayfindingEnabled_: boolean;
   private isTabletMode_: boolean;
   private listAllDisplayModes_: boolean;
@@ -1375,6 +1388,11 @@
     }
   }
 
+  private toggleDisplayPerformanceEnabled_(): void {
+    this.isDisplayPerformanceEnabled_ = !this.isDisplayPerformanceEnabled_;
+    // TODO(b/320526769): Create a mojom call to Ash
+  }
+
   getInvalidDisplayId(): string {
     return this.invalidDisplayId_;
   }
diff --git a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.ts b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.ts
index 8aaac67..41957c5 100644
--- a/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.ts
+++ b/chrome/browser/resources/settings/clear_browsing_data_dialog/clear_browsing_data_dialog.ts
@@ -537,18 +537,6 @@
   private async clearBrowsingData_() {
     this.clearingInProgress_ = true;
     this.clearingDataAlertString_ = loadTimeData.getString('clearingData');
-    const tab = this.$.tabs.selectedItem as HTMLElement;
-    const dataTypes = this.getSelectedDataTypes_(tab);
-    const dropdownMenu =
-        tab.querySelector<SettingsDropdownMenuElement>('.time-range-select');
-    assert(dropdownMenu);
-    const timePeriod = dropdownMenu.pref!.value;
-
-    if (tab.id === 'basic-tab') {
-      chrome.metricsPrivate.recordUserAction('ClearBrowsingData_BasicTab');
-    } else {
-      chrome.metricsPrivate.recordUserAction('ClearBrowsingData_AdvancedTab');
-    }
 
     this.setPrefValue(
         'browser.last_clear_browsing_data_tab', this.selectedTabIndex_);
@@ -563,6 +551,19 @@
             'settings-dropdown-menu[no-set-pref]')
         .forEach(dropdown => dropdown.sendPrefChange());
 
+    const tab = this.$.tabs.selectedItem as HTMLElement;
+    const dataTypes = this.getSelectedDataTypes_(tab);
+    const dropdownMenu =
+        tab.querySelector<SettingsDropdownMenuElement>('.time-range-select');
+    assert(dropdownMenu);
+    const timePeriod = dropdownMenu.pref!.value;
+
+    if (tab.id === 'basic-tab') {
+      chrome.metricsPrivate.recordUserAction('ClearBrowsingData_BasicTab');
+    } else {
+      chrome.metricsPrivate.recordUserAction('ClearBrowsingData_AdvancedTab');
+    }
+
     const {showHistoryNotice, showPasswordsNotice} =
         await this.browserProxy_.clearBrowsingData(dataTypes, timePeriod);
     this.clearingInProgress_ = false;
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.html b/chrome/browser/resources/settings/privacy_page/privacy_page.html
index d014f08..da791bad 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.html
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.html
@@ -1288,40 +1288,38 @@
           </category-setting-exceptions>
         </settings-subpage>
       </template>
-      <template is="dom-if" if="[[enablePermissionStorageAccessApi_]]">
-        <template is="dom-if" route-path="/content/storageAccess" no-search>
-          <settings-subpage page-title="$i18n{siteSettingsStorageAccess}"
-              search-label="$i18n{siteSettingsAllSitesSearch}"
-              search-term="{{searchFilter_}}">
-            <div class="content-settings-header secondary">
-              $i18n{storageAccessDescription}
+      <template is="dom-if" route-path="/content/storageAccess" no-search>
+        <settings-subpage page-title="$i18n{siteSettingsStorageAccess}"
+            search-label="$i18n{siteSettingsAllSitesSearch}"
+            search-term="{{searchFilter_}}">
+          <div class="content-settings-header secondary">
+            $i18n{storageAccessDescription}
+          </div>
+          <settings-category-default-radio-group
+              category="[[contentSettingsTypesEnum_.STORAGE_ACCESS]]"
+              allow-option-label="$i18n{storageAccessAllowed}"
+              allow-option-icon="settings:storage-access"
+              block-option-label="$i18n{storageAccessBlocked}"
+              block-option-icon="settings:storage-access-off">
+          </settings-category-default-radio-group>
+          <!-- Exceptions section. -->
+          <div class="content-settings-header">
+            <h2>$i18n{siteSettingsCustomizedBehaviors}</h2>
+            <div class="cr-secondary-text">
+              $i18n{siteSettingsCustomizedBehaviorsDescription}
             </div>
-            <settings-category-default-radio-group
-                category="[[contentSettingsTypesEnum_.STORAGE_ACCESS]]"
-                allow-option-label="$i18n{storageAccessAllowed}"
-                allow-option-icon="settings:storage-access"
-                block-option-label="$i18n{storageAccessBlocked}"
-                block-option-icon="settings:storage-access-off">
-            </settings-category-default-radio-group>
-            <!-- Exceptions section. -->
-            <div class="content-settings-header">
-              <h2>$i18n{siteSettingsCustomizedBehaviors}</h2>
-              <div class="cr-secondary-text">
-                $i18n{siteSettingsCustomizedBehaviorsDescription}
-              </div>
-            </div>
-            <storage-access-site-list
-                category-subtype="[[contentSettingEnum_.BLOCK]]"
-                category-header="$i18n{storageAccessBlockedExceptions}"
-                search-filter="[[searchFilter_]]">
-            </storage-access-site-list>
-            <storage-access-site-list
-                category-subtype="[[contentSettingEnum_.ALLOW]]"
-                category-header="$i18n{storageAccessAllowedExceptions}"
-                search-filter="[[searchFilter_]]">
-            </storage-access-site-list>
-          </settings-subpage>
-        </template>
+          </div>
+          <storage-access-site-list
+              category-subtype="[[contentSettingEnum_.BLOCK]]"
+              category-header="$i18n{storageAccessBlockedExceptions}"
+              search-filter="[[searchFilter_]]">
+          </storage-access-site-list>
+          <storage-access-site-list
+              category-subtype="[[contentSettingEnum_.ALLOW]]"
+              category-header="$i18n{storageAccessAllowedExceptions}"
+              search-filter="[[searchFilter_]]">
+          </storage-access-site-list>
+        </settings-subpage>
       </template>
       <template is="dom-if" if="[[autoPictureInPictureEnabled_]]">
         <template is="dom-if" route-path="/content/autoPictureInPicture" no-search>
diff --git a/chrome/browser/resources/settings/privacy_page/privacy_page.ts b/chrome/browser/resources/settings/privacy_page/privacy_page.ts
index 483404b..d3b8b4d 100644
--- a/chrome/browser/resources/settings/privacy_page/privacy_page.ts
+++ b/chrome/browser/resources/settings/privacy_page/privacy_page.ts
@@ -188,12 +188,6 @@
         value: () => loadTimeData.getBoolean('privateStateTokensEnabled'),
       },
 
-      enablePermissionStorageAccessApi_: {
-        type: Boolean,
-        value: () =>
-            loadTimeData.getBoolean('enablePermissionStorageAccessApi'),
-      },
-
       autoPictureInPictureEnabled_: {
         type: Boolean,
         value: () => loadTimeData.getBoolean('autoPictureInPictureEnabled'),
@@ -354,7 +348,6 @@
   private privateStateTokensEnabled_: boolean;
   private autoPictureInPictureEnabled_: boolean;
   private safetyCheckNotificationPermissionsEnabled_: boolean;
-  private enablePermissionStorageAccessApi_: boolean;
   private enableSafetyHub_: boolean;
   private focusConfig_: FocusConfig;
   private searchFilter_: string;
diff --git a/chrome/browser/resources/settings/route.ts b/chrome/browser/resources/settings/route.ts
index d9e4f7b..0bf8676 100644
--- a/chrome/browser/resources/settings/route.ts
+++ b/chrome/browser/resources/settings/route.ts
@@ -138,11 +138,8 @@
   r.SITE_SETTINGS_FILE_SYSTEM_WRITE_DETAILS =
       r.SITE_SETTINGS_FILE_SYSTEM_WRITE.createChild('siteDetails');
   r.SITE_SETTINGS_LOCAL_FONTS = r.SITE_SETTINGS.createChild('localFonts');
+  r.SITE_SETTINGS_STORAGE_ACCESS = r.SITE_SETTINGS.createChild('storageAccess');
 
-  if (loadTimeData.getBoolean('enablePermissionStorageAccessApi')) {
-    r.SITE_SETTINGS_STORAGE_ACCESS =
-        r.SITE_SETTINGS.createChild('storageAccess');
-  }
   if (loadTimeData.getBoolean('enableAutomaticFullscreenContentSetting')) {
     r.SITE_SETTINGS_AUTOMATIC_FULLSCREEN =
         r.SITE_SETTINGS.createChild('automaticFullscreen');
diff --git a/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts b/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
index e2135e18..87626786 100644
--- a/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
+++ b/chrome/browser/resources/settings/site_settings_page/site_settings_page.ts
@@ -339,8 +339,6 @@
       icon: 'settings:storage-access',
       enabledLabel: 'storageAccessAllowed',
       disabledLabel: 'storageAccessBlocked',
-      shouldShow: () =>
-          loadTimeData.getBoolean('enablePermissionStorageAccessApi'),
     },
     {
       route: routes.SITE_SETTINGS_USB_DEVICES,
@@ -449,45 +447,36 @@
       lists_: {
         type: Object,
         value: function() {
-          // Move `BACKGROUND_SYNC` to the sixth position under the fold if
-          // `STORAGE_ACCESS` is present.
-          const enablePermissionStorageAccessApi =
-              loadTimeData.getBoolean('enablePermissionStorageAccessApi');
-          const basic = enablePermissionStorageAccessApi ? Id.STORAGE_ACCESS :
-                                                           Id.BACKGROUND_SYNC;
-          const advanced: ContentSettingsTypes[] =
-              enablePermissionStorageAccessApi ? [Id.BACKGROUND_SYNC] : [];
-
           return {
             permissionsBasic: buildItemListFromIds([
               Id.GEOLOCATION,
               Id.CAMERA,
               Id.MIC,
               Id.NOTIFICATIONS,
-              basic,
+              Id.STORAGE_ACCESS,
             ]),
             permissionsAdvanced: buildItemListFromIds([
-              ...advanced,
-              ...[Id.SENSORS,
-                  Id.AUTOMATIC_DOWNLOADS,
-                  Id.PROTOCOL_HANDLERS,
-                  Id.MIDI_DEVICES,
-                  Id.USB_DEVICES,
-                  Id.SERIAL_PORTS,
-                  Id.BLUETOOTH_DEVICES,
-                  Id.FILE_SYSTEM_WRITE,
-                  Id.HID_DEVICES,
-                  Id.CLIPBOARD,
-                  Id.PAYMENT_HANDLER,
-                  Id.BLUETOOTH_SCANNING,
-                  Id.AR,
-                  Id.VR,
-                  Id.IDLE_DETECTION,
-                  Id.WEB_PRINTING,
-                  Id.WINDOW_MANAGEMENT,
-                  Id.LOCAL_FONTS,
-                  Id.AUTO_PICTURE_IN_PICTURE,
-          ],
+              Id.BACKGROUND_SYNC,
+              Id.SENSORS,
+              Id.AUTOMATIC_DOWNLOADS,
+              Id.PROTOCOL_HANDLERS,
+              Id.MIDI_DEVICES,
+              Id.USB_DEVICES,
+              Id.SERIAL_PORTS,
+              Id.BLUETOOTH_DEVICES,
+              Id.FILE_SYSTEM_WRITE,
+              Id.HID_DEVICES,
+              Id.CLIPBOARD,
+              Id.PAYMENT_HANDLER,
+              Id.BLUETOOTH_SCANNING,
+              Id.AR,
+              Id.VR,
+              Id.IDLE_DETECTION,
+              Id.WEB_PRINTING,
+              Id.WINDOW_MANAGEMENT,
+              Id.LOCAL_FONTS,
+              Id.AUTO_PICTURE_IN_PICTURE,
+
             ]),
             contentBasic: buildItemListFromIds([
               Id.COOKIES,
diff --git a/chrome/browser/search_engines/template_url_service_factory.cc b/chrome/browser/search_engines/template_url_service_factory.cc
index b0b827b..9adb3ee 100644
--- a/chrome/browser/search_engines/template_url_service_factory.cc
+++ b/chrome/browser/search_engines/template_url_service_factory.cc
@@ -18,7 +18,7 @@
 #include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
 #include "chrome/browser/search_engines/chrome_template_url_service_client.h"
 #include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/pref_registry/pref_registry_syncable.h"
diff --git a/chrome/browser/signin/identity_manager_factory.cc b/chrome/browser/signin/identity_manager_factory.cc
index 1519d08..969620b 100644
--- a/chrome/browser/signin/identity_manager_factory.cc
+++ b/chrome/browser/signin/identity_manager_factory.cc
@@ -33,7 +33,7 @@
 #endif
 
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/keyed_service/core/service_access_type.h"
 #if BUILDFLAG(ENABLE_BOUND_SESSION_CREDENTIALS)
 #include "chrome/browser/signin/bound_session_credentials/unexportable_key_service_factory.h"
diff --git a/chrome/browser/storage_access_api/api_browsertest.cc b/chrome/browser/storage_access_api/api_browsertest.cc
index 9906b12..e195ee8d 100644
--- a/chrome/browser/storage_access_api/api_browsertest.cc
+++ b/chrome/browser/storage_access_api/api_browsertest.cc
@@ -480,58 +480,16 @@
   std::unique_ptr<permissions::MockPermissionPromptFactory> prompt_factory_;
 };
 
-struct TestCase {
-  std::string test_name;
-  bool permission_saa_feature_enabled;
-};
-
 // Test fixture for core Storage Access API functionality, guaranteed by spec.
 // This fixture should use the minimal set of features/params.
-class StorageAccessAPIBrowserTest
-    : public StorageAccessAPIBaseBrowserTest,
-      public testing::WithParamInterface<TestCase> {
+class StorageAccessAPIBrowserTest : public StorageAccessAPIBaseBrowserTest {
  public:
   StorageAccessAPIBrowserTest()
       : StorageAccessAPIBaseBrowserTest(/*is_storage_partitioned=*/false) {}
-
- protected:
-  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
-    std::vector<base::test::FeatureRefAndParams> enabled;
-
-    if (GetParam().permission_saa_feature_enabled) {
-      enabled.push_back(
-          {permissions::features::kPermissionStorageAccessAPI, {}});
-    }
-
-    return enabled;
-  }
-
-  std::vector<base::test::FeatureRef> GetDisabledFeatures() override {
-    std::vector<base::test::FeatureRef> disabled =
-        StorageAccessAPIBaseBrowserTest::GetDisabledFeatures();
-
-    if (!GetParam().permission_saa_feature_enabled) {
-      disabled.push_back(permissions::features::kPermissionStorageAccessAPI);
-    }
-
-    return disabled;
-  }
-};
-
-class StorageAccessAPIWithPromptsBrowserTest
-    : public StorageAccessAPIBaseBrowserTest {
- public:
-  StorageAccessAPIWithPromptsBrowserTest()
-      : StorageAccessAPIBaseBrowserTest(/* is_storage_partitioned=*/true) {}
-
- protected:
-  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
-    return {{permissions::features::kPermissionStorageAccessAPI, {}}};
-  }
 };
 
 // Check default values for permissions.query on storage-access.
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest, PermissionQueryDefault) {
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryDefault) {
   SetBlockThirdPartyCookies(true);
 
   NavigateToPageWithFrame(kHostA);
@@ -543,7 +501,7 @@
 
 // Check default values for permissions.query on storage-access when 3p cookie
 // is allowed.
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        PermissionQueryDefault_AllowCrossSiteCookie) {
   SetBlockThirdPartyCookies(false);
 
@@ -556,8 +514,7 @@
 
 // Test that permissions.query changes to "granted" when a storage access
 // request was successful.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
-                       PermissionQueryGranted) {
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryGranted) {
   SetBlockThirdPartyCookies(true);
 
   NavigateToPageWithFrame(kHostA);
@@ -607,8 +564,7 @@
 
 // Test that permissions.query changes to "denied" when a storage access
 // request was denied.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
-                       PermissionQueryDenied) {
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryDenied) {
   SetBlockThirdPartyCookies(true);
   EnsureUserInteractionOn(kHostB);
 
@@ -656,8 +612,7 @@
       UnorderedElementsAre(Pair(net::SchemefulSite(GURL(kOriginB)), false)));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
-                       PermissionQueryCrossSite) {
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, PermissionQueryCrossSite) {
   SetBlockThirdPartyCookies(true);
 
   EnsureUserInteractionOn(kHostA);
@@ -684,7 +639,7 @@
   EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
 }
 
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        Permission_Denied_WithoutInteraction) {
   base::HistogramTester histogram_tester;
   SetBlockThirdPartyCookies(true);
@@ -715,7 +670,7 @@
 
 // Validate that a cross-site iframe can bypass third-party cookie blocking via
 // the Storage Access API.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesIFrameRequestsAccess_CrossSiteIframe) {
   SetBlockThirdPartyCookies(true);
 
@@ -732,7 +687,7 @@
   EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        AccessGranted_NoSubsequentUserInteraction) {
   SetBlockThirdPartyCookies(true);
   prompt_factory()->set_response_type(
@@ -758,7 +713,7 @@
 // just that top-level/third-party combination and are still blocked for other
 // combinations.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameRequestsAccess_CrossSiteIframe_UnrelatedSites) {
   SetBlockThirdPartyCookies(true);
   base::HistogramTester histogram_tester;
@@ -790,7 +745,7 @@
 // Validate that a nested A(B(B)) iframe can obtain cookie access, and that that
 // access is not shared with the "middle" B iframe.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_InnerRequestsAccess) {
   SetBlockThirdPartyCookies(true);
 
@@ -819,7 +774,7 @@
 
 // Validate that in a A(B) frame tree, the iframe can make credentialed
 // same-site requests, even if the requests are cross-origin.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesIFrameRequestsAccess_CrossOriginFetch) {
   SetBlockThirdPartyCookies(true);
 
@@ -841,7 +796,7 @@
 // Validate that in a A(B(B)) frame tree, the middle B iframe can obtain access,
 // and that access is not shared with the leaf B iframe.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_MiddleRequestsAccess) {
   SetBlockThirdPartyCookies(true);
 
@@ -870,7 +825,7 @@
 // Validate that in a A(B(C)) frame tree, the C leaf iframe can obtain cookie
 // access.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameRequestsAccess_NestedCrossSiteIframe_DistinctSites) {
   SetBlockThirdPartyCookies(true);
   EnsureUserInteractionOn(kHostC);
@@ -894,7 +849,7 @@
 
 // Validate that cross-site sibling iframes cannot take advantage of each
 // other's granted permission.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesCrossSiteSiblingIFrameRequestsAccess) {
   EnsureUserInteractionOn(kHostC);
 
@@ -956,7 +911,7 @@
 
 // Validate that the Storage Access API does not override any explicit user
 // settings to block storage access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ThirdPartyCookiesIFrameThirdPartyExceptions) {
   SetBlockThirdPartyCookies(true);
   BlockAllCookiesOnHost(kHostB);
@@ -975,7 +930,7 @@
 // Validate that user settings take precedence for the leaf in a A(B(B)) frame
 // tree.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameThirdPartyExceptions_NestedSameSite) {
   SetBlockThirdPartyCookies(true);
   BlockAllCookiesOnHost(kHostB);
@@ -998,7 +953,7 @@
 // Validate that user settings take precedence for the leaf in a A(B(C)) frame
 // tree.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameThirdPartyExceptions_NestedCrossSite) {
   EnsureUserInteractionOn(kHostC);
 
@@ -1023,7 +978,7 @@
 // Validate that user settings take precedence for the leaf in a A(B(A)) frame
 // tree.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameThirdPartyExceptions_CrossSiteAncestorChain) {
   EnsureUserInteractionOn(kHostA);
   SetBlockThirdPartyCookies(true);
@@ -1048,7 +1003,7 @@
 // Validate that user settings take precedence for the leaf in a A(A) frame
 // tree.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     ThirdPartyCookiesIFrameThirdPartyExceptions_SameSiteAncestorChain) {
   EnsureUserInteractionOn(kHostA);
   SetBlockThirdPartyCookies(true);
@@ -1068,7 +1023,7 @@
 }
 
 // Validates that once a grant is removed access is also removed.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ThirdPartyGrantsDeletedAccess) {
   SetBlockThirdPartyCookies(true);
 
@@ -1086,6 +1041,15 @@
   HostContentSettingsMap* settings_map =
       HostContentSettingsMapFactory::GetForProfile(browser()->profile());
   settings_map->ClearSettingsForOneType(ContentSettingsType::STORAGE_ACCESS);
+
+  // Try to ensure that the pref observer is triggered and the updated settings
+  // are propagated to the network service.
+  base::RunLoop().RunUntilIdle();
+  browser()
+      ->profile()
+      ->GetDefaultStoragePartition()
+      ->FlushNetworkInterfaceForTesting();
+
   // Verify cookie cannot be accessed.
   EXPECT_EQ(ReadCookies(GetFrame(), kHostB), NoCookies());
 
@@ -1096,7 +1060,7 @@
 
 // Validates that if the user explicitly blocks cookies, cookie access is
 // blocked even with the existing grant.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        ExplicitUserSettingsBlockThirdPartyGrantsAccess) {
   SetBlockThirdPartyCookies(true);
 
@@ -1111,6 +1075,13 @@
   EXPECT_EQ(ReadCookies(GetFrame(), kHostB), CookieBundle("cross-site=b.test"));
 
   BlockAllCookiesOnHost(kHostB);
+  // Try to ensure that the pref observer is triggered and the updated settings
+  // are propagated to the network service.
+  base::RunLoop().RunUntilIdle();
+  browser()
+      ->profile()
+      ->GetDefaultStoragePartition()
+      ->FlushNetworkInterfaceForTesting();
 
   // Access change should be reflected immediately.
   EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostB), NoCookiesWithContent());
@@ -1122,8 +1093,7 @@
 
 // Validate that if the iframe's origin is opaque, it cannot obtain storage
 // access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
-                       OpaqueOriginRejects) {
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, OpaqueOriginRejects) {
   SetBlockThirdPartyCookies(true);
 
   NavigateToPageWithFrame(kHostA);
@@ -1141,7 +1111,7 @@
 
 // Validate that if the iframe is sandboxed and allows scripts but is missing
 // the Storage Access sandbox tag, the iframe cannot obtain storage access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        MissingSandboxTokenRejects) {
   SetBlockThirdPartyCookies(true);
 
@@ -1161,8 +1131,7 @@
 
 // Validate that if the iframe is sandboxed and has the Storage Access sandbox
 // tag, the iframe can obtain storage access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
-                       SandboxTokenResolves) {
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, SandboxTokenResolves) {
   SetBlockThirdPartyCookies(true);
 
   NavigateToPageWithFrame(kHostA);
@@ -1180,8 +1149,7 @@
 }
 
 // Validates that expired grants don't get reused.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
-                       ThirdPartyGrantsExpiry) {
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, ThirdPartyGrantsExpiry) {
   base::HistogramTester histogram_tester;
   SetBlockThirdPartyCookies(true);
 
@@ -1240,7 +1208,7 @@
 // Validate that if an iframe navigates itself to a same-origin endpoint, and
 // that navigation does not include any cross-origin redirects, the new document
 // can inherit storage access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        Navigation_SelfInitiated_SameOrigin_Preserves) {
   SetBlockThirdPartyCookies(true);
 
@@ -1266,7 +1234,7 @@
 // same-origin endpoint, and that navigation does not include any cross-origin
 // redirects, the new document cannot inherit storage access.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     Navigation_NonSelfInitiated_SameOriginDestination_CrossSiteInitiator) {
   SetBlockThirdPartyCookies(true);
 
@@ -1293,7 +1261,7 @@
 // same-origin endpoint (even if the navigation does not include any
 // cross-origin redirects), the new document cannot inherit storage access.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     Navigation_NonSelfInitiated_SameOriginDestination_SameSiteInitiator) {
   SetBlockThirdPartyCookies(true);
 
@@ -1325,7 +1293,7 @@
 // cross-origin redirects, and the navigated frame has obtained storage access
 // already), the new document cannot inherit storage access.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     Navigation_NonSelfInitiated_SameOriginDestination_SameSiteInitiator_TargetHasStorageAccess) {
   SetBlockThirdPartyCookies(true);
 
@@ -1357,7 +1325,7 @@
 // Validate that if an iframe navigates itself to a same-site cross-origin
 // endpoint, and that navigation does not include any cross-origin redirects,
 // the new document cannot inherit storage access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        Navigation_SelfInitiated_SameSiteCrossOrigin) {
   SetBlockThirdPartyCookies(true);
 
@@ -1384,7 +1352,7 @@
 // Validate that if an iframe navigates itself to a cross-site endpoint, and
 // that navigation does not include any cross-origin redirects, the new document
 // cannot inherit storage access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        Navigation_SelfInitiated_CrossSite) {
   SetBlockThirdPartyCookies(true);
 
@@ -1409,7 +1377,7 @@
 // that navigation include a cross-origin redirect, the new document
 // cannot inherit storage access.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     Navigation_SelfInitiated_SameOrigin_CrossOriginRedirect) {
   SetBlockThirdPartyCookies(true);
 
@@ -1442,7 +1410,7 @@
 // subsequent same-origin redirect), the new document cannot inherit storage
 // access.
 IN_PROC_BROWSER_TEST_F(
-    StorageAccessAPIWithPromptsBrowserTest,
+    StorageAccessAPIBrowserTest,
     Navigation_SelfInitiated_SameOrigin_CrossSiteAndSameSiteRedirects) {
   SetBlockThirdPartyCookies(true);
 
@@ -1472,7 +1440,7 @@
 
 // Validate that in a A(A) frame tree, the inner A iframe can obtain cookie
 // access by default.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        EmbeddedSameOriginCookieAccess) {
   SetBlockThirdPartyCookies(true);
 
@@ -1490,7 +1458,7 @@
 
 // Validate that in a A(sub.A) frame tree, the inner A iframe can obtain cookie
 // access by default.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        EmbeddedSameSiteCookieAccess) {
   SetBlockThirdPartyCookies(true);
 
@@ -1510,7 +1478,7 @@
 
 // Validate that in a A(B(A)) frame tree, the inner A iframe can obtain cookie
 // access after requesting access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        NestedSameOriginCookieAccess_CrossSiteAncestorChain) {
   base::HistogramTester histogram_tester;
   SetBlockThirdPartyCookies(true);
@@ -1537,7 +1505,7 @@
 
 // Validate that in a A(B(sub.A)) frame tree, the inner iframe can obtain cookie
 // access after requesting access.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        NestedSameSiteCookieAccess_CrossSiteAncestorChain) {
   SetBlockThirdPartyCookies(true);
 
@@ -1558,7 +1526,7 @@
             CookieBundle("cross-site=a.test"));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        DedicatedWorker_InheritsStorageAccessFromDocument) {
   SetBlockThirdPartyCookies(true);
   prompt_factory()->set_response_type(
@@ -1584,7 +1552,7 @@
       "cross-site=b.test");
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        WebsocketRequestsUseStorageAccessGrants) {
   net::SpawnedTestServer wss_server(
       net::SpawnedTestServer::TYPE_WSS,
@@ -1628,20 +1596,9 @@
   }
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    /* no prefix */,
-    StorageAccessAPIBrowserTest,
-    testing::ValuesIn<TestCase>({
-        {"enable_prompts", true},
-        {"disable_prompts", false},
-    }),
-    [](const testing::TestParamInfo<::TestCase>& info) {
-      return info.param.test_name;
-    });
-
 // Validate that in a A(B) frame tree, the embedded B iframe can obtain cookie
 // access if requested and got accepted.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        EmbeddedCrossSiteCookieAccess_Accept) {
   SetBlockThirdPartyCookies(true);
 
@@ -1659,7 +1616,7 @@
 
 // Validate that in a A(B) frame tree, the embedded B iframe can not obtain
 // cookie access if requested and got denied.
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        EmbeddedCrossSiteCookieAccess_Deny) {
   SetBlockThirdPartyCookies(true);
 
@@ -1843,8 +1800,7 @@
                                           testing::Bool()));
 
 class StorageAccessAPIWithFirstPartySetsBrowserTest
-    : public StorageAccessAPIBaseBrowserTest,
-      public testing::WithParamInterface<bool> {
+    : public StorageAccessAPIBaseBrowserTest {
  public:
   StorageAccessAPIWithFirstPartySetsBrowserTest()
       : StorageAccessAPIBaseBrowserTest(false) {}
@@ -1857,30 +1813,9 @@
                       R"(", "associatedSites": ["https://)", kHostB, R"("])",
                       R"(, "serviceSites": ["https://)", kHostD, R"("]})"}));
   }
-
- protected:
-  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
-    std::vector<base::test::FeatureRefAndParams> enabled = {};
-
-    if (PermissionStorageAccessAPIFeatureEnabled()) {
-      enabled.push_back(
-          {permissions::features::kPermissionStorageAccessAPI, {}});
-    }
-    return enabled;
-  }
-
-  std::vector<base::test::FeatureRef> GetDisabledFeatures() override {
-    std::vector<base::test::FeatureRef> disabled;
-    if (!PermissionStorageAccessAPIFeatureEnabled()) {
-      disabled.push_back(permissions::features::kPermissionStorageAccessAPI);
-    }
-    return disabled;
-  }
-
-  bool PermissionStorageAccessAPIFeatureEnabled() { return GetParam(); }
 };
 
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIWithFirstPartySetsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                        Permission_AutograntedWithinFirstPartySet) {
   base::HistogramTester histogram_tester;
   // Note: kHostA and kHostB are considered same-party due to the use of
@@ -1908,7 +1843,7 @@
       Gt(0));
 }
 
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIWithFirstPartySetsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                        Permission_PromptOrDenyUnderServiceDomain) {
   prompt_factory()->set_response_type(
       permissions::PermissionRequestManager::DENY_ALL);
@@ -1927,8 +1862,7 @@
   // The promise should be rejected; `kHostD` is a service domain.
   EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
   EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
-  EXPECT_EQ(prompt_factory()->TotalRequestCount(),
-            PermissionStorageAccessAPIFeatureEnabled() ? 1 : 0);
+  EXPECT_EQ(prompt_factory()->TotalRequestCount(), 1);
 
   NavigateFrameTo(EchoCookiesURL(kHostA));
 
@@ -1936,17 +1870,14 @@
   EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
 
   content::FetchHistogramsFromChildProcesses();
-  EXPECT_THAT(histogram_tester.GetBucketCount(
-                  kRequestOutcomeHistogram,
-                  PermissionStorageAccessAPIFeatureEnabled()
-                      ? RequestOutcome::kDeniedByUser
-                      : RequestOutcome::kDeniedByFirstPartySet),
+  EXPECT_THAT(histogram_tester.GetBucketCount(kRequestOutcomeHistogram,
+                                              RequestOutcome::kDeniedByUser),
               Gt(0));
   // Ensure that the denied state is not exposed to developers, per the spec.
   EXPECT_EQ(QueryPermission(GetFrame()), "prompt");
 }
 
-IN_PROC_BROWSER_TEST_P(
+IN_PROC_BROWSER_TEST_F(
     StorageAccessAPIWithFirstPartySetsBrowserTest,
     Permission_AutograntedForServiceDomainWithExistingGrant) {
   SetBlockThirdPartyCookies(true);
@@ -1976,48 +1907,8 @@
   EXPECT_EQ(QueryPermission(GetFrame()), "granted");
 }
 
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIWithFirstPartySetsBrowserTest,
-                       Permission_AutodeniedOutsideFirstPartySet) {
-  // If enabled, this will override and disable the autodenial, which does not
-  // belong in this test.
-  if (PermissionStorageAccessAPIFeatureEnabled()) {
-    return;
-  }
-
-  base::HistogramTester histogram_tester;
-  // Note: kHostA and kHostC are considered cross-party, since kHostA's set does
-  // not include kHostC.
-  SetBlockThirdPartyCookies(true);
-
-  NavigateToPageWithFrame(kHostA);
-  NavigateFrameTo(EchoCookiesURL(kHostC));
-
-  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), NoCookiesWithContent());
-  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
-  // kHostC cannot request storage access.
-  EXPECT_FALSE(content::ExecJs(GetFrame(), "document.requestStorageAccess()"));
-  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
-  NavigateFrameTo(EchoCookiesURL(kHostC));
-
-  EXPECT_EQ(ReadCookiesAndContent(GetFrame(), kHostC), NoCookiesWithContent());
-  EXPECT_FALSE(storage::test::HasStorageAccessForFrame(GetFrame()));
-
-  content::FetchHistogramsFromChildProcesses();
-
-  EXPECT_THAT(
-      histogram_tester.GetBucketCount(kRequestOutcomeHistogram,
-                                      RequestOutcome::kDeniedByFirstPartySet),
-      Gt(0));
-}
-
-// Verifies kPermissionStorageAccessAPI feature turns off the autodenial.
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIWithFirstPartySetsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                        Permission_AutodeniedOutsideFirstPartySet_Overridden) {
-  if (!PermissionStorageAccessAPIFeatureEnabled()) {
-    return;
-  }
   base::HistogramTester histogram_tester;
   // Note: kHostA and kHostC are considered cross-party, since kHostA's set does
   // not include kHostC.
@@ -2043,7 +1934,7 @@
               Gt(0));
 }
 
-IN_PROC_BROWSER_TEST_P(
+IN_PROC_BROWSER_TEST_F(
     StorageAccessAPIWithFirstPartySetsBrowserTest,
     Permission_AutodeniedInsideFirstPartySet_WithoutInteraction) {
   base::HistogramTester histogram_tester;
@@ -2073,7 +1964,7 @@
       Gt(0));
 }
 
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIWithFirstPartySetsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                        PRE_PermissionGrantsResetAfterRestart) {
   SetBlockThirdPartyCookies(true);
 
@@ -2084,7 +1975,7 @@
   ASSERT_EQ("granted", QueryPermission(GetFrame()));
 }
 
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIWithFirstPartySetsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                        PermissionGrantsResetAfterRestart) {
   SetBlockThirdPartyCookies(true);
 
@@ -2094,26 +1985,7 @@
   EXPECT_EQ("prompt", QueryPermission(GetFrame()));
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    /* no prefix */,
-    StorageAccessAPIWithFirstPartySetsBrowserTest,
-    testing::Bool());
-
-class StorageAccessAPIWithFirstPartySetsAndPromptsBrowserTest
-    : public StorageAccessAPIWithFirstPartySetsBrowserTest {
- protected:
-  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
-    std::vector<base::test::FeatureRefAndParams> enabled = {};
-
-    if (PermissionStorageAccessAPIFeatureEnabled()) {
-      enabled.push_back(
-          {permissions::features::kPermissionStorageAccessAPI, {}});
-    }
-    return enabled;
-  }
-};
-
-IN_PROC_BROWSER_TEST_P(StorageAccessAPIWithFirstPartySetsAndPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithFirstPartySetsBrowserTest,
                        Permission_GrantedForServiceDomain) {
   SetBlockThirdPartyCookies(true);
   base::HistogramTester histogram_tester;
@@ -2136,11 +2008,6 @@
       Gt(0));
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    /* no prefix */,
-    StorageAccessAPIWithFirstPartySetsAndPromptsBrowserTest,
-    testing::Bool());
-
 class StorageAccessAPIWithFirstPartySetsAndImplicitGrantsBrowserTest
     : public StorageAccessAPIBaseBrowserTest {
  public:
@@ -2148,11 +2015,6 @@
       : StorageAccessAPIBaseBrowserTest(false) {
     StorageAccessGrantPermissionContext::SetImplicitGrantLimitForTesting(5);
   }
-
- protected:
-  std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures() override {
-    return {};
-  }
 };
 
 // Validate that when auto-deny-outside-fps is disabled (but auto-grant is
@@ -2182,7 +2044,7 @@
   EXPECT_TRUE(storage::test::RequestAndCheckStorageAccessForFrame(GetFrame()));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        RequestStorageAccess_CoexistsWithPartitionedCookies) {
   SetBlockThirdPartyCookies(true);
 
@@ -2293,7 +2155,7 @@
   storage::test::ExpectStorageForFrame(GetFrame(), !ExpectPartitionedStorage());
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        EnsureOnePromptDenialSuffices) {
   SetBlockThirdPartyCookies(true);
   NavigateToPageWithFrame(kHostA);
@@ -2327,7 +2189,7 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        DismissalAllowsFuturePrompts) {
   SetBlockThirdPartyCookies(true);
   NavigateToPageWithFrame(kHostA);
@@ -2365,7 +2227,7 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        TopLevelUserInteractionRequired) {
   SetBlockThirdPartyCookies(true);
 
@@ -2396,7 +2258,7 @@
   EXPECT_EQ(ReadCookies(GetFrame(), kHostA), CookieBundle("cross-site=a.test"));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest,
                        IncognitoDoesntUseRegularInteractionsOrPermission) {
   NavigateToPageWithFrame(kHostA);
   NavigateFrameTo(EchoCookiesURL(kHostB));
@@ -2422,8 +2284,7 @@
                                "document.requestStorageAccess()"));
 }
 
-IN_PROC_BROWSER_TEST_F(StorageAccessAPIWithPromptsBrowserTest,
-                       IncognitoCanUseAPI) {
+IN_PROC_BROWSER_TEST_F(StorageAccessAPIBrowserTest, IncognitoCanUseAPI) {
   Browser* incognito_browser = Browser::Create(Browser::CreateParams(
       browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
       /*user_gesture=*/true));
diff --git a/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc b/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
index ce39e22..566b8c2 100644
--- a/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
+++ b/chrome/browser/storage_access_api/storage_access_grant_permission_context.cc
@@ -53,14 +53,6 @@
 constexpr base::TimeDelta kStorageAccessAPITopLevelUserInteractionBound =
     base::Days(30);
 
-// `kPermissionStorageAccessAPI` enables StorageAccessAPIwithPrompts
-// (https://chromestatus.com/feature/5085655327047680), which should not
-// auto-deny if FPS is irrelevant.
-bool ShouldAutoDenyOutsideFPS() {
-  return !base::FeatureList::IsEnabled(
-      permissions::features::kPermissionStorageAccessAPI);
-}
-
 // Returns true if the request wasn't answered by the user explicitly.
 bool IsImplicitOutcome(RequestOutcome outcome) {
   switch (outcome) {
@@ -343,14 +335,6 @@
         break;
     }
   }
-  if (ShouldAutoDenyOutsideFPS()) {
-    NotifyPermissionSetInternal(request_data.id, request_data.requesting_origin,
-                                request_data.embedding_origin,
-                                std::move(callback),
-                                /*persist=*/true, CONTENT_SETTING_BLOCK,
-                                RequestOutcome::kDeniedByFirstPartySet);
-    return;
-  }
 
   // Get all of our implicit grants and see which ones apply to our
   // |requesting_origin|.
diff --git a/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc b/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
index 59f8a35d..7c1109b8 100644
--- a/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
+++ b/chrome/browser/storage_access_api/storage_access_grant_permission_context_unittest.cc
@@ -82,27 +82,16 @@
               base::NumberToString(dummy_id) + ".com");
 }
 
-struct TestCase {
-  std::string test_name;
-  bool permission_saa_feature_enabled;
-};
-
 }  // namespace
 
-class StorageAccessGrantPermissionContextTestBase
+class StorageAccessGrantPermissionContextTest
     : public ChromeRenderViewHostTestHarness {
  public:
-  StorageAccessGrantPermissionContextTestBase() = default;
+  StorageAccessGrantPermissionContextTest() = default;
 
   void SetUp() override {
     std::vector<base::test::FeatureRefAndParams> enabled;
     std::vector<base::test::FeatureRef> disabled;
-    if (PermissionStorageAccessAPIFeatureEnabled()) {
-      enabled.push_back(
-          {permissions::features::kPermissionStorageAccessAPI, {}});
-    } else {
-      disabled.push_back(permissions::features::kPermissionStorageAccessAPI);
-    }
     features_.InitWithFeaturesAndParameters(enabled, disabled);
     ChromeRenderViewHostTestHarness::SetUp();
 
@@ -147,8 +136,6 @@
     ChromeRenderViewHostTestHarness::TearDown();
   }
 
-  virtual bool PermissionStorageAccessAPIFeatureEnabled() const = 0;
-
   std::unique_ptr<base::test::TestFuture<ContentSetting>> DecidePermission(
       bool user_gesture) {
     auto future = std::make_unique<base::test::TestFuture<ContentSetting>>();
@@ -239,13 +226,6 @@
     return *mock_permission_prompt_factory_;
   }
 
-  struct PrintToStringParamName {
-    std::string operator()(
-        const testing::TestParamInfo<::TestCase>& info) const {
-      return info.param.test_name;
-    }
-  };
-
   base::HistogramTester& histogram_tester() { return histogram_tester_; }
 
  private:
@@ -259,28 +239,7 @@
   first_party_sets::ScopedMockFirstPartySetsHandler first_party_sets_handler_;
 };
 
-class StorageAccessGrantPermissionContextTest
-    : public StorageAccessGrantPermissionContextTestBase,
-      public testing::WithParamInterface<TestCase> {
- public:
-  StorageAccessGrantPermissionContextTest() = default;
-
-  bool PermissionStorageAccessAPIFeatureEnabled() const override {
-    return GetParam().permission_saa_feature_enabled;
-  }
-};
-
-class StorageAccessGrantPermissionContextWithPromptsTest
-    : public StorageAccessGrantPermissionContextTestBase {
- public:
-  StorageAccessGrantPermissionContextWithPromptsTest() = default;
-
-  bool PermissionStorageAccessAPIFeatureEnabled() const override {
-    return true;
-  }
-};
-
-TEST_P(StorageAccessGrantPermissionContextTest, InsecureOriginsDisallowed) {
+TEST_F(StorageAccessGrantPermissionContextTest, InsecureOriginsDisallowed) {
   GURL insecure_url = GURL("http://www.example.com");
   EXPECT_FALSE(permission_context()->IsPermissionAvailableToOrigins(
       insecure_url, insecure_url));
@@ -294,7 +253,7 @@
 
 // Test that after a successful explicit storage access grant, there's a content
 // setting that applies on an (embedded site, top-level site) scope.
-TEST_F(StorageAccessGrantPermissionContextWithPromptsTest,
+TEST_F(StorageAccessGrantPermissionContextTest,
        ExplicitGrantAcceptCrossSiteContentSettings) {
   // Assert that all content settings are in their initial state.
   CheckCrossSiteContentSettings(ContentSetting::CONTENT_SETTING_ASK);
@@ -326,7 +285,7 @@
 
 // When the Storage Access API feature is enabled and we have a user gesture we
 // should get a decision.
-TEST_F(StorageAccessGrantPermissionContextWithPromptsTest, PermissionDecided) {
+TEST_F(StorageAccessGrantPermissionContextTest, PermissionDecided) {
   auto future = DecidePermission(/*user_gesture=*/true);
   WaitUntilPrompt();
 
@@ -349,7 +308,7 @@
 }
 
 // No user gesture should force a permission rejection.
-TEST_P(StorageAccessGrantPermissionContextTest,
+TEST_F(StorageAccessGrantPermissionContextTest,
        PermissionDeniedWithoutUserGesture) {
   EXPECT_EQ(CONTENT_SETTING_BLOCK,
             DecidePermissionSync(/*user_gesture=*/false));
@@ -361,7 +320,7 @@
               IsEmpty());
 }
 
-TEST_P(StorageAccessGrantPermissionContextTest, PermissionGrantReused) {
+TEST_F(StorageAccessGrantPermissionContextTest, PermissionGrantReused) {
   auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
   map->SetContentSettingDefaultScope(GetRequesterURL(), GetTopLevelURL(),
                                      ContentSettingsType::STORAGE_ACCESS,
@@ -374,7 +333,7 @@
               UnorderedElementsAre(Pair(GetRequesterSite(), true)));
 }
 
-TEST_P(StorageAccessGrantPermissionContextTest, BlockReused) {
+TEST_F(StorageAccessGrantPermissionContextTest, BlockReused) {
   auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
   map->SetContentSettingDefaultScope(GetRequesterURL(), GetTopLevelURL(),
                                      ContentSettingsType::STORAGE_ACCESS,
@@ -387,7 +346,7 @@
               UnorderedElementsAre(Pair(GetRequesterSite(), true)));
 }
 
-TEST_P(StorageAccessGrantPermissionContextTest, FpsGrantReused) {
+TEST_F(StorageAccessGrantPermissionContextTest, FpsGrantReused) {
   auto* map = HostContentSettingsMapFactory::GetForProfile(profile());
   content_settings::ContentSettingConstraints constraint;
   constraint.set_session_model(
@@ -404,7 +363,7 @@
               IsEmpty());
 }
 
-TEST_P(StorageAccessGrantPermissionContextTest,
+TEST_F(StorageAccessGrantPermissionContextTest,
        PermissionStatusAsksWhenFeatureEnabled) {
   EXPECT_EQ(PermissionStatus::ASK,
             permission_context()
@@ -416,7 +375,7 @@
 // When 3p cookie access is already allowed by user-agent-specific cookie
 // settings, request should be allowed without granting an explicit storage
 // access permission.
-TEST_P(StorageAccessGrantPermissionContextTest, AllowedByCookieSettings) {
+TEST_F(StorageAccessGrantPermissionContextTest, AllowedByCookieSettings) {
   // Allow 3p cookies.
   profile()->GetPrefs()->SetInteger(
       prefs::kCookieControlsMode,
@@ -435,7 +394,7 @@
 
 // When 3p cookie access is blocked by user explicitly, request should be denied
 // without prompting.
-TEST_P(StorageAccessGrantPermissionContextTest, DeniedByCookieSettings) {
+TEST_F(StorageAccessGrantPermissionContextTest, DeniedByCookieSettings) {
   HostContentSettingsMap* settings_map =
       HostContentSettingsMapFactory::GetForProfile(profile());
   settings_map->SetContentSettingDefaultScope(
@@ -453,17 +412,8 @@
               IsEmpty());
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    /* no prefix */,
-    StorageAccessGrantPermissionContextTest,
-    testing::ValuesIn<TestCase>({
-        {"enable_prompts", true},
-        {"disable_prompts", false},
-    }),
-    StorageAccessGrantPermissionContextTest::PrintToStringParamName());
-
 class StorageAccessGrantPermissionContextAPIWithImplicitGrantsTest
-    : public StorageAccessGrantPermissionContextWithPromptsTest {
+    : public StorageAccessGrantPermissionContextTest {
  public:
   StorageAccessGrantPermissionContextAPIWithImplicitGrantsTest() {
     StorageAccessGrantPermissionContext::SetImplicitGrantLimitForTesting(5);
@@ -593,8 +543,7 @@
               IsEmpty());
 }
 
-TEST_F(StorageAccessGrantPermissionContextWithPromptsTest,
-       ExplicitGrantDenial) {
+TEST_F(StorageAccessGrantPermissionContextTest, ExplicitGrantDenial) {
   histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 0);
   histogram_tester().ExpectTotalCount(kPromptResultHistogram, 0);
 
@@ -618,7 +567,7 @@
               UnorderedElementsAre(Pair(GetRequesterSite(), false)));
 }
 
-TEST_F(StorageAccessGrantPermissionContextWithPromptsTest,
+TEST_F(StorageAccessGrantPermissionContextTest,
        ExplicitGrantDenialNotExposedViaQuery) {
   // Set the content setting to blocked, mimicking a prompt rejection by the
   // user.
@@ -649,8 +598,7 @@
               UnorderedElementsAre(Pair(GetRequesterSite(), false)));
 }
 
-TEST_F(StorageAccessGrantPermissionContextWithPromptsTest,
-       ExplicitGrantAccept) {
+TEST_F(StorageAccessGrantPermissionContextTest, ExplicitGrantAccept) {
   histogram_tester().ExpectTotalCount(kGrantIsImplicitHistogram, 0);
   histogram_tester().ExpectTotalCount(kPromptResultHistogram, 0);
 
@@ -675,16 +623,12 @@
 }
 
 class StorageAccessGrantPermissionContextAPIWithFirstPartySetsTest
-    : public StorageAccessGrantPermissionContextTestBase {
+    : public StorageAccessGrantPermissionContextTest {
  public:
   StorageAccessGrantPermissionContextAPIWithFirstPartySetsTest() = default;
 
-  bool PermissionStorageAccessAPIFeatureEnabled() const override {
-    return false;
-  }
-
   void SetUp() override {
-    StorageAccessGrantPermissionContextTestBase::SetUp();
+    StorageAccessGrantPermissionContextTest::SetUp();
 
     // Create a FPS with https://requester.example.com as the member and
     // https://embedder.com as the primary.
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index 5b12a9e..7ad5b6e 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -48,7 +48,7 @@
 #include "chrome/browser/trusted_vault/trusted_vault_service_factory.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/chrome_paths.h"
diff --git a/chrome/browser/sync/sync_service_factory.cc b/chrome/browser/sync/sync_service_factory.cc
index 5a60daf..ee8cebe 100644
--- a/chrome/browser/sync/sync_service_factory.cc
+++ b/chrome/browser/sync/sync_service_factory.cc
@@ -48,7 +48,7 @@
 #include "chrome/browser/trusted_vault/trusted_vault_service_factory.h"
 #include "chrome/browser/undo/bookmark_undo_service_factory.h"
 #include "chrome/browser/web_applications/web_app_provider_factory.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/channel_info.h"
 #include "components/password_manager/core/browser/sharing/password_receiver_service.h"
diff --git a/chrome/browser/sync/sync_service_factory_unittest.cc b/chrome/browser/sync/sync_service_factory_unittest.cc
index 9b21479d..2222174 100644
--- a/chrome/browser/sync/sync_service_factory_unittest.cc
+++ b/chrome/browser/sync/sync_service_factory_unittest.cc
@@ -14,7 +14,7 @@
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/trusted_vault/trusted_vault_service_factory.h"
 #include "chrome/browser/ui/ui_features.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/data_sharing/public/features.h"
diff --git a/chrome/browser/sync/sync_service_util_browsertest.cc b/chrome/browser/sync/sync_service_util_browsertest.cc
deleted file mode 100644
index b10f383f..0000000
--- a/chrome/browser/sync/sync_service_util_browsertest.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/sync/sync_service_util.h"
-
-#include "base/test/scoped_feature_list.h"
-#include "build/build_config.h"
-#include "chrome/browser/browser_process.h"
-#include "chrome/test/base/in_process_browser_test.h"
-#include "chrome/test/base/scoped_browser_locale.h"
-#include "components/page_image_service/features.h"
-#include "components/variations/service/variations_service.h"
-#include "content/public/test/browser_test.h"
-
-class SyncSeviceUtilBrowserTest : public InProcessBrowserTest {
- public:
-  SyncSeviceUtilBrowserTest() {
-    // The following feature does not affect the tests but it resets the
-    // experiment from field trial testing configuration. This is required
-    // because the tests verify the default behavior.
-    scoped_feature_list_.InitAndEnableFeature(
-        page_image_service::kImageServiceObserveSyncDownloadStatus);
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(SyncSeviceUtilBrowserTest,
-                       SyncPollEnabledBasedOnPlatformAndCountryLocale) {
-  auto locale = std::make_unique<ScopedBrowserLocale>("en-US");
-  g_browser_process->variations_service()->OverrideStoredPermanentCountry("us");
-#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || \
-    BUILDFLAG(IS_WIN)
-  EXPECT_TRUE(IsDesktopEnUSLocaleOnlySyncPollFeatureEnabled());
-#else
-  EXPECT_FALSE(IsDesktopEnUSLocaleOnlySyncPollFeatureEnabled());
-#endif
-}
-
-IN_PROC_BROWSER_TEST_F(SyncSeviceUtilBrowserTest,
-                       SyncPollDisabledBasedOnPlatformAndCountryLocale) {
-  auto locale = std::make_unique<ScopedBrowserLocale>("en-US");
-  g_browser_process->variations_service()->OverrideStoredPermanentCountry("ca");
-  EXPECT_FALSE(IsDesktopEnUSLocaleOnlySyncPollFeatureEnabled());
-}
diff --git a/chrome/browser/sync/test/integration/apps_helper.cc b/chrome/browser/sync/test/integration/apps_helper.cc
index abf286d..24b31e8 100644
--- a/chrome/browser/sync/test/integration/apps_helper.cc
+++ b/chrome/browser/sync/test/integration/apps_helper.cc
@@ -250,7 +250,7 @@
   base::RunLoop run_loop;
   webapps::AppId app_id;
   auto* provider = web_app::WebAppProvider::GetForTest(profile);
-  provider->scheduler().InstallFromInfo(
+  provider->scheduler().InstallFromInfoNoIntegrationForTesting(
       std::make_unique<web_app::WebAppInstallInfo>(info.Clone()),
       /*overwrite_existing_manifest_fields=*/true,
       webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
diff --git a/chrome/browser/sync/test/integration/autofill_helper.cc b/chrome/browser/sync/test/integration/autofill_helper.cc
index 00684b9..aee95e6 100644
--- a/chrome/browser/sync/test/integration/autofill_helper.cc
+++ b/chrome/browser/sync/test/integration/autofill_helper.cc
@@ -21,7 +21,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/autofill/core/browser/autofill_type.h"
 #include "components/autofill/core/browser/country_type.h"
diff --git a/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc b/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
index 4bb7e14..1769f7a3 100644
--- a/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_wallet_sync_test.cc
@@ -19,7 +19,7 @@
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/browser/sync/test/integration/updated_progress_marker_checker.h"
 #include "chrome/browser/sync/test/integration/wallet_helper.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/autofill/core/browser/autofill_data_util.h"
 #include "components/autofill/core/browser/data_model/autofill_metadata.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
index 8ddafc3..00328294 100644
--- a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
@@ -149,7 +149,7 @@
     base::RunLoop run_loop;
     webapps::AppId app_id;
     auto* provider = WebAppProvider::GetForTest(profile);
-    provider->scheduler().InstallFromInfo(
+    provider->scheduler().InstallFromInfoNoIntegrationForTesting(
         std::make_unique<web_app::WebAppInstallInfo>(info.Clone()),
         /*overwrite_existing_manifest_fields=*/true, source,
         base::BindLambdaForTesting(
diff --git a/chrome/browser/sync/test/integration/wallet_helper.cc b/chrome/browser/sync/test/integration/wallet_helper.cc
index fc80a8d..d52c538 100644
--- a/chrome/browser/sync/test/integration/wallet_helper.cc
+++ b/chrome/browser/sync/test/integration/wallet_helper.cc
@@ -13,7 +13,7 @@
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
 #include "chrome/browser/sync/test/integration/sync_datatype_helper.h"
 #include "chrome/browser/sync/test/integration/sync_test.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "components/autofill/core/browser/autofill_data_util.h"
 #include "components/autofill/core/browser/data_model/autofill_metadata.h"
 #include "components/autofill/core/browser/payments/payments_customer_data.h"
diff --git a/chrome/browser/tab_group/BUILD.gn b/chrome/browser/tab_group/BUILD.gn
index a4d40cf0..22788aeb 100644
--- a/chrome/browser/tab_group/BUILD.gn
+++ b/chrome/browser/tab_group/BUILD.gn
@@ -9,6 +9,7 @@
 android_library("java") {
   sources = [
     "java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroup.java",
+    "java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupColorUtils.java",
     "java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java",
     "java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterObserver.java",
     "java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupTitleUtils.java",
@@ -20,12 +21,15 @@
     "//chrome/browser/flags:java",
     "//chrome/browser/tab:java",
     "//chrome/browser/tabmodel:java",
+    "//components/tab_groups:tab_groups_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_collection_collection_java",
   ]
 }
 
 robolectric_library("junit") {
   sources = [
+    "junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupColorUtilsUnitTest.java",
     "junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java",
     "junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupTitleUtilsUnitTest.java",
   ]
@@ -41,6 +45,7 @@
     "//chrome/test/android:chrome_java_unit_test_support",
     "//third_party/android_deps:espresso_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_collection_collection_java",
     "//third_party/hamcrest:hamcrest_core_java",
     "//third_party/jni_zero:jni_zero_java",
     "//third_party/junit",
diff --git a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupColorUtils.java b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupColorUtils.java
new file mode 100644
index 0000000..0457d57
--- /dev/null
+++ b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupColorUtils.java
@@ -0,0 +1,169 @@
+// Copyright 2024 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.tasks.tab_groups;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.collection.ArrayMap;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.components.tab_groups.TabGroupColorId;
+
+import java.util.Map;
+import java.util.Set;
+
+/** Helper class to handle tab group color related utilities. */
+public class TabGroupColorUtils {
+    private static final String TAB_GROUP_COLORS_FILE_NAME = "tab_group_colors";
+    private static final String MIGRATION_CHECK = "migration_check";
+    private static final int INVALID_COLOR_ID = -1;
+    private static final int MIGRATION_NOT_DONE = 0;
+    private static final int MIGRATION_DONE = 1;
+
+    /**
+     * This method stores tab group colors with reference to {@code tabRootId}.
+     *
+     * @param tabRootId The tab root ID which is used as a reference to store group colors.
+     * @param color The tab group color {@link TabGroupColorId} to store.
+     */
+    public static void storeTabGroupColor(int tabRootId, int color) {
+        assert tabRootId != Tab.INVALID_TAB_ID;
+        getSharedPreferences().edit().putInt(String.valueOf(tabRootId), color).apply();
+    }
+
+    /**
+     * This method deletes a specific stored tab group color with reference to {@code tabRootId}.
+     *
+     * @param tabRootId The tab root ID whose related tab group color will be deleted.
+     */
+    public static void deleteTabGroupColor(int tabRootId) {
+        assert tabRootId != Tab.INVALID_TAB_ID;
+        getSharedPreferences().edit().remove(String.valueOf(tabRootId)).apply();
+    }
+
+    /**
+     * This method fetches tab group colors for the related tab group root ID.
+     *
+     * @param tabRootId The tab root ID whose related tab group color will be fetched.
+     * @return The stored color of the target tab group, default value is -1 (INVALID_COLOR_ID).
+     */
+    public static int getTabGroupColor(int tabRootId) {
+        assert tabRootId != Tab.INVALID_TAB_ID;
+        return getSharedPreferences().getInt(String.valueOf(tabRootId), INVALID_COLOR_ID);
+    }
+
+    /**
+     * This method assigns a color to all tab groups which do not have an assigned tab color at
+     * startup. If a migration for all existing tabs has already been performed, skip this logic.
+     *
+     * @param tabGroupModelFilter The {@TabGroupModelFilter} that governs the current tab groups.
+     */
+    public static void assignTabGroupColorsIfApplicable(TabGroupModelFilter tabGroupModelFilter) {
+        // TODO(b/41490324): Consider removing the migration logic when tab group colors are
+        // launched. There may be an argument to keep this around in case the color info is somehow
+        // lost between startups, in which case this will at least set some default colors up. In
+        // theory, once the migrations have been applied to everyone there won't be a need for this.
+        //
+        // If the migration is already done, skip the below logic.
+        if (getSharedPreferences().getInt(MIGRATION_CHECK, MIGRATION_NOT_DONE) == MIGRATION_DONE) {
+            return;
+        }
+
+        Map<Integer, Integer> currentColorCountMap = getCurrentColorCountMap(tabGroupModelFilter);
+        Set<Integer> rootIds = tabGroupModelFilter.getAllTabGroupRootIds();
+
+        // Assign a color to all tab groups that don't have a color.
+        for (Integer rootId : rootIds) {
+            int colorId = getTabGroupColor(rootId);
+
+            // Retrieve the next suggested colorId if the current tab group does not have a color.
+            if (colorId == INVALID_COLOR_ID) {
+                int suggestedColorId = getNextSuggestedColorId(currentColorCountMap);
+                storeTabGroupColor(rootId, suggestedColorId);
+                currentColorCountMap.put(
+                        suggestedColorId, currentColorCountMap.get(suggestedColorId) + 1);
+            }
+        }
+
+        // Mark that the initial migration of tab colors is complete.
+        getSharedPreferences().edit().putInt(MIGRATION_CHECK, MIGRATION_DONE).apply();
+    }
+
+    /**
+     * This method returns the next suggested colorId to be assigned to a tab group if that tab
+     * group has no color assigned to it. This algorithm uses a key-value map to store all usage
+     * counts of the current list of colors in other tab groups. It will select the least used color
+     * that appears first in the color list. The suggested color value should be a color id of type
+     * {@link TabGroupColorId}.
+     *
+     * @param tabGroupModelFilter The {@link TabGroupModelFilter} that governs all tab groups.
+     */
+    public static int getNextSuggestedColorId(TabGroupModelFilter tabGroupModelFilter) {
+        // Generate the currentColorCountMap.
+        Map<Integer, Integer> currentColorCountMap = getCurrentColorCountMap(tabGroupModelFilter);
+        return getNextSuggestedColorId(currentColorCountMap);
+    }
+
+    /**
+     * This method removes the shared preference file. TODO(b/41490324): Consider removing this when
+     * the feature is launched.
+     */
+    public static void clearTabGroupColorInfo() {
+        ContextUtils.getApplicationContext().deleteSharedPreferences(TAB_GROUP_COLORS_FILE_NAME);
+    }
+
+    private static SharedPreferences getSharedPreferences() {
+        return ContextUtils.getApplicationContext()
+                .getSharedPreferences(TAB_GROUP_COLORS_FILE_NAME, Context.MODE_PRIVATE);
+    }
+
+    /** Get a map that indicates the current usage count of each tab group color. */
+    private static Map<Integer, Integer> getCurrentColorCountMap(
+            TabGroupModelFilter tabGroupModelFilter) {
+        int colorListSize = TabGroupColorId.NUM_ENTRIES;
+        Map<Integer, Integer> colorCountMap = new ArrayMap<>(colorListSize);
+        for (int i = 0; i < colorListSize; i++) {
+            colorCountMap.put(i, 0);
+        }
+
+        Set<Integer> rootIds = tabGroupModelFilter.getAllTabGroupRootIds();
+
+        // Filter all tab groups for ones that already have a color assigned.
+        for (Integer rootId : rootIds) {
+            int colorId = getTabGroupColor(rootId);
+
+            // If the tab group has a color stored on shared prefs, increment the colorId map count.
+            if (colorId != INVALID_COLOR_ID) {
+                colorCountMap.put(colorId, colorCountMap.get(colorId) + 1);
+            }
+        }
+
+        return colorCountMap;
+    }
+
+    /** Impl of getNextSuggestedColorId which assumes a currentColorCountMap has been created. */
+    private static int getNextSuggestedColorId(Map<Integer, Integer> currentColorCountMap) {
+        int colorId = Integer.MAX_VALUE;
+        int colorCount = Integer.MAX_VALUE;
+
+        for (Map.Entry<Integer, Integer> entry : currentColorCountMap.entrySet()) {
+            if (entry.getValue() < colorCount) {
+                colorCount = entry.getValue();
+                colorId = entry.getKey();
+            } else if (entry.getValue() == colorCount) {
+                if (entry.getKey() < colorId) {
+                    colorId = entry.getKey();
+                }
+            }
+        }
+
+        // Assert that the current color count map exists and sets a valid colorId on loop
+        // iteration, otherwise default to an invalid colorId.
+        assert colorId != Integer.MAX_VALUE;
+        return colorId != Integer.MAX_VALUE ? colorId : INVALID_COLOR_ID;
+    }
+}
diff --git a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java
index 6688c35..f8ac830 100644
--- a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java
+++ b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java
@@ -9,6 +9,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.collection.ArraySet;
 
 import org.chromium.base.MathUtils;
 import org.chromium.base.ObserverList;
@@ -42,6 +43,8 @@
  * https://crbug.com/1523745.
  */
 public class TabGroupModelFilter extends TabModelFilter {
+    private static final int INVALID_COLOR_ID = -1;
+
     private ObserverList<TabGroupModelFilterObserver> mGroupFilterObserver = new ObserverList<>();
     private Map<Integer, Integer> mRootIdToGroupIndexMap = new HashMap<>();
     private Map<Integer, TabGroup> mRootIdToGroupMap = new HashMap<>();
@@ -136,7 +139,8 @@
                         Collections.singletonList(index),
                         Collections.singletonList(tab.getRootId()),
                         Collections.singletonList(null),
-                        null);
+                        null,
+                        INVALID_COLOR_ID);
             }
         }
     }
@@ -185,6 +189,7 @@
             List<Integer> originalRootIds = new ArrayList<>();
             List<Token> originalTabGroupIds = new ArrayList<>();
             String destinationGroupTitle = TabGroupTitleUtils.getTabGroupTitle(destinationRootId);
+            int destinationGroupColorId = TabGroupColorUtils.getTabGroupColor(destinationRootId);
             boolean didCreateNewGroup =
                     !isTabInTabGroup(sourceTab) && !isTabInTabGroup(destinationTab);
 
@@ -239,7 +244,8 @@
                             originalIndexes,
                             originalRootIds,
                             originalTabGroupIds,
-                            destinationGroupTitle);
+                            destinationGroupTitle,
+                            destinationGroupColorId);
                 }
             }
         }
@@ -292,6 +298,7 @@
         }
         int destinationIndexInTabModel = getTabModelDestinationIndex(destinationTab);
         String destinationGroupTitle = TabGroupTitleUtils.getTabGroupTitle(destinationRootId);
+        int destinationGroupColorId = TabGroupColorUtils.getTabGroupColor(destinationRootId);
 
         for (int i = 0; i < tabs.size(); i++) {
             Tab tab = tabs.get(i);
@@ -355,7 +362,8 @@
                         originalIndexes,
                         originalRootIds,
                         originalTabGroupIds,
-                        destinationGroupTitle);
+                        destinationGroupTitle,
+                        destinationGroupColorId);
             }
         }
     }
@@ -1021,6 +1029,20 @@
         super.didMoveTab(tab, newIndex, curIndex);
     }
 
+    /** Get all tab group root ids that are associated with tab groups greater than size 1. */
+    public Set<Integer> getAllTabGroupRootIds() {
+        Set<Integer> uniqueTabGroupRootIds = new ArraySet<>();
+        TabList tabList = getTabModel();
+
+        for (int i = 0; i < tabList.getCount(); i++) {
+            Tab tab = tabList.getTabAt(i);
+            if (isTabInTabGroup(tab)) {
+                uniqueTabGroupRootIds.add(tab.getRootId());
+            }
+        }
+        return uniqueTabGroupRootIds;
+    }
+
     private boolean isMoveTabOutOfGroup(Tab movedTab) {
         return !mRootIdToGroupMap.containsKey(movedTab.getRootId());
     }
diff --git a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterObserver.java b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterObserver.java
index b6ac970..6fec905 100644
--- a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterObserver.java
+++ b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterObserver.java
@@ -73,13 +73,15 @@
      * @param tabOriginalRootId The original root id for each modified tab.
      * @param tabOriginalTabGroupId The original tab group id for each modified tab.
      * @param destinationGroupTitle The original destination group title.
+     * @param destinationGroupColorId The original destination group color id.
      */
     default void didCreateGroup(
             List<Tab> tabs,
             List<Integer> tabOriginalIndex,
             List<Integer> tabOriginalRootId,
             List<Token> tabOriginalTabGroupId,
-            String destinationGroupTitle) {}
+            String destinationGroupTitle,
+            int destinationGroupColorId) {}
 
     /**
      * This method is called after a new tab group is created, either through drag and drop, the tab
diff --git a/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupColorUtilsUnitTest.java b/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupColorUtilsUnitTest.java
new file mode 100644
index 0000000..cfda4da
--- /dev/null
+++ b/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupColorUtilsUnitTest.java
@@ -0,0 +1,273 @@
+// Copyright 2024 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.tasks.tab_groups;
+
+import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.collection.ArraySet;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Features;
+
+import java.util.Set;
+
+/** Tests for {@link TabGroupColorUtils}. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class TabGroupColorUtilsUnitTest {
+    @Rule public TestRule mProcessor = new Features.JUnitProcessor();
+
+    private static final String TAB_GROUP_COLORS_FILE_NAME = "tab_group_colors";
+    private static final String MIGRATION_CHECK = "migration_check";
+    private static final int INVALID_COLOR_ID = -1;
+    private static final int MIGRATION_DONE = 1;
+
+    private static final int ROOT_ID_1 = 123;
+    private static final int ROOT_ID_2 = 456;
+    private static final int ROOT_ID_3 = 789;
+    private static final int ROOT_ID_4 = 147;
+    private static final int ROOT_ID_5 = 258;
+    private static final int ROOT_ID_6 = 369;
+    private static final int ROOT_ID_7 = 159;
+    private static final int ROOT_ID_8 = 160;
+    private static final int ROOT_ID_9 = 161;
+    private static final int ROOT_ID_10 = 162;
+    private static final int COLOR_1 = 0;
+    private static final int COLOR_2 = 1;
+    private static final int COLOR_3 = 2;
+    private static final int COLOR_4 = 3;
+    private static final int COLOR_5 = 4;
+    private static final int COLOR_6 = 5;
+    private static final int COLOR_7 = 6;
+    private static final int COLOR_8 = 7;
+    private static final int COLOR_9 = 8;
+
+    @Mock Context mContext;
+    @Mock TabGroupModelFilter mFilter;
+    @Mock SharedPreferences mSharedPreferences;
+    @Mock SharedPreferences.Editor mEditor;
+    @Mock SharedPreferences.Editor mPutIntEditor;
+    @Mock SharedPreferences.Editor mRemoveEditor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(mSharedPreferences)
+                .when(mContext)
+                .getSharedPreferences(TAB_GROUP_COLORS_FILE_NAME, Context.MODE_PRIVATE);
+        doReturn(mEditor).when(mSharedPreferences).edit();
+        doReturn(mRemoveEditor).when(mEditor).remove(any(String.class));
+        doReturn(mPutIntEditor).when(mEditor).putInt(any(String.class), any(Integer.class));
+        ContextUtils.initApplicationContextForTests(mContext);
+    }
+
+    @Test
+    public void testDeleteTabGroupColor() {
+        TabGroupColorUtils.deleteTabGroupColor(ROOT_ID_1);
+
+        verify(mEditor).remove(eq(String.valueOf(ROOT_ID_1)));
+        verify(mRemoveEditor).apply();
+    }
+
+    @Test
+    public void testGetTabGroupColor() {
+        // Mock that we have a stored tab group color with reference to ROOT_ID.
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_1), INVALID_COLOR_ID))
+                .thenReturn(COLOR_1);
+
+        assertThat(TabGroupColorUtils.getTabGroupColor(ROOT_ID_1), equalTo(COLOR_1));
+    }
+
+    @Test
+    public void testStoreTabGroupColor() {
+        TabGroupColorUtils.storeTabGroupColor(ROOT_ID_1, COLOR_1);
+
+        verify(mEditor).putInt(eq(String.valueOf(ROOT_ID_1)), eq(COLOR_1));
+        verify(mPutIntEditor).apply();
+    }
+
+    @Test
+    public void testAssignDefaultTabGroupColors() {
+        Set<Integer> rootIdsSet = new ArraySet<>();
+        rootIdsSet.add(ROOT_ID_1);
+        rootIdsSet.add(ROOT_ID_2);
+        rootIdsSet.add(ROOT_ID_3);
+
+        when(mFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        // Mock that there is no stored tab group color for these root ids.
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_1), INVALID_COLOR_ID))
+                .thenReturn(INVALID_COLOR_ID);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_2), INVALID_COLOR_ID))
+                .thenReturn(INVALID_COLOR_ID);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_3), INVALID_COLOR_ID))
+                .thenReturn(INVALID_COLOR_ID);
+
+        TabGroupColorUtils.assignTabGroupColorsIfApplicable(mFilter);
+
+        // Test the scenario where no tab groups have colors so the first colors in order are
+        // assigned.
+        verify(mEditor).putInt(eq(String.valueOf(ROOT_ID_1)), eq(COLOR_1));
+        verify(mEditor).putInt(eq(String.valueOf(ROOT_ID_2)), eq(COLOR_2));
+        verify(mEditor).putInt(eq(String.valueOf(ROOT_ID_3)), eq(COLOR_3));
+        verify(mEditor).putInt(eq(MIGRATION_CHECK), eq(MIGRATION_DONE));
+        verify(mPutIntEditor, times(4)).apply();
+    }
+
+    @Test
+    public void testNextSuggestedColorFirstAndThird() {
+        Set<Integer> rootIdsSet = new ArraySet<>();
+        rootIdsSet.add(ROOT_ID_1);
+        rootIdsSet.add(ROOT_ID_2);
+
+        when(mFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        // Mock that the first and third colors already exist.
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_1), INVALID_COLOR_ID))
+                .thenReturn(COLOR_1);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_2), INVALID_COLOR_ID))
+                .thenReturn(COLOR_3);
+
+        assertEquals(COLOR_2, TabGroupColorUtils.getNextSuggestedColorId(mFilter));
+    }
+
+    @Test
+    public void testNextSuggestedColorDoubleFirstAndSecond() {
+        Set<Integer> rootIdsSet = new ArraySet<>();
+        rootIdsSet.add(ROOT_ID_1);
+        rootIdsSet.add(ROOT_ID_2);
+        rootIdsSet.add(ROOT_ID_3);
+
+        when(mFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        // Mock that the first and second colors already exist.
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_1), INVALID_COLOR_ID))
+                .thenReturn(COLOR_1);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_2), INVALID_COLOR_ID))
+                .thenReturn(COLOR_1);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_3), INVALID_COLOR_ID))
+                .thenReturn(COLOR_2);
+
+        assertEquals(COLOR_3, TabGroupColorUtils.getNextSuggestedColorId(mFilter));
+    }
+
+    @Test
+    public void testNextSuggestedColorSecondColor() {
+        Set<Integer> rootIdsSet = new ArraySet<>();
+        rootIdsSet.add(ROOT_ID_1);
+
+        when(mFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        // Mock that only the second color already exists.
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_1), INVALID_COLOR_ID))
+                .thenReturn(COLOR_2);
+
+        assertEquals(COLOR_1, TabGroupColorUtils.getNextSuggestedColorId(mFilter));
+    }
+
+    @Test
+    public void testNextSuggestedColorAllColorsUsed() {
+        Set<Integer> rootIdsSet = new ArraySet<>();
+        rootIdsSet.add(ROOT_ID_1);
+        rootIdsSet.add(ROOT_ID_2);
+        rootIdsSet.add(ROOT_ID_3);
+        rootIdsSet.add(ROOT_ID_4);
+        rootIdsSet.add(ROOT_ID_5);
+        rootIdsSet.add(ROOT_ID_6);
+        rootIdsSet.add(ROOT_ID_7);
+        rootIdsSet.add(ROOT_ID_8);
+        rootIdsSet.add(ROOT_ID_9);
+
+        when(mFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        // Mock that all colors are used.
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_1), INVALID_COLOR_ID))
+                .thenReturn(COLOR_1);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_2), INVALID_COLOR_ID))
+                .thenReturn(COLOR_2);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_3), INVALID_COLOR_ID))
+                .thenReturn(COLOR_3);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_4), INVALID_COLOR_ID))
+                .thenReturn(COLOR_4);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_5), INVALID_COLOR_ID))
+                .thenReturn(COLOR_5);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_6), INVALID_COLOR_ID))
+                .thenReturn(COLOR_6);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_7), INVALID_COLOR_ID))
+                .thenReturn(COLOR_7);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_8), INVALID_COLOR_ID))
+                .thenReturn(COLOR_8);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_9), INVALID_COLOR_ID))
+                .thenReturn(COLOR_9);
+
+        assertEquals(COLOR_1, TabGroupColorUtils.getNextSuggestedColorId(mFilter));
+    }
+
+    @Test
+    public void testNextSuggestedColorContinuousSuggestion() {
+        Set<Integer> rootIdsSet = new ArraySet<>();
+        rootIdsSet.add(ROOT_ID_1);
+        rootIdsSet.add(ROOT_ID_2);
+        rootIdsSet.add(ROOT_ID_3);
+        rootIdsSet.add(ROOT_ID_4);
+        rootIdsSet.add(ROOT_ID_5);
+        rootIdsSet.add(ROOT_ID_6);
+        rootIdsSet.add(ROOT_ID_7);
+        rootIdsSet.add(ROOT_ID_8);
+
+        when(mFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        // Mock that all colors are used except for COLOR_8.
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_1), INVALID_COLOR_ID))
+                .thenReturn(COLOR_1);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_2), INVALID_COLOR_ID))
+                .thenReturn(COLOR_2);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_3), INVALID_COLOR_ID))
+                .thenReturn(COLOR_3);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_4), INVALID_COLOR_ID))
+                .thenReturn(COLOR_4);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_5), INVALID_COLOR_ID))
+                .thenReturn(COLOR_5);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_6), INVALID_COLOR_ID))
+                .thenReturn(COLOR_6);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_7), INVALID_COLOR_ID))
+                .thenReturn(COLOR_7);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_8), INVALID_COLOR_ID))
+                .thenReturn(COLOR_9);
+
+        assertEquals(COLOR_8, TabGroupColorUtils.getNextSuggestedColorId(mFilter));
+
+        // Mock that subsequent addition of the missing color directs the suggestion to COLOR_1.
+        rootIdsSet.add(ROOT_ID_9);
+        when(mFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_9), INVALID_COLOR_ID))
+                .thenReturn(COLOR_8);
+        assertEquals(COLOR_1, TabGroupColorUtils.getNextSuggestedColorId(mFilter));
+
+        // Mock that subsequent addition of the first color directs the suggestion to COLOR_2.
+        rootIdsSet.add(ROOT_ID_10);
+        when(mFilter.getAllTabGroupRootIds()).thenReturn(rootIdsSet);
+        when(mSharedPreferences.getInt(String.valueOf(ROOT_ID_10), INVALID_COLOR_ID))
+                .thenReturn(COLOR_1);
+        assertEquals(COLOR_2, TabGroupColorUtils.getNextSuggestedColorId(mFilter));
+    }
+}
diff --git a/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java b/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java
index af846e8f..fd96105 100644
--- a/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java
+++ b/chrome/browser/tab_group/junit/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilterUnitTest.java
@@ -30,6 +30,7 @@
 import android.content.SharedPreferences;
 
 import androidx.annotation.Nullable;
+import androidx.collection.ArraySet;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -63,6 +64,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 /** Tests for {@link TabGroupModelFilter}. */
 @SuppressWarnings("ResultOfMethodCallIgnored")
@@ -108,6 +110,10 @@
     private static final String TAB_GROUP_TITLES_FILE_NAME = "tab_group_titles";
     private static final String TAB_TITLE = "Tab";
 
+    private static final String TAB_GROUP_COLORS_FILE_NAME = "tab_group_colors";
+    private static final int INVALID_COLOR_ID = -1;
+    private static final int COLOR_ID = 0;
+
     @Rule public TestRule mProcessor = new Features.JUnitProcessor();
 
     @Rule public JniMocker mJniMocker = new JniMocker();
@@ -120,7 +126,9 @@
 
     @Mock Context mContext;
 
-    @Mock SharedPreferences mSharedPreferences;
+    @Mock SharedPreferences mSharedPreferencesTitle;
+
+    @Mock SharedPreferences mSharedPreferencesColor;
 
     @Captor ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
 
@@ -320,11 +328,15 @@
             assertTrue(mTabGroupModelFilter.isTabModelRestored());
         }
 
-        doReturn(mSharedPreferences)
+        doReturn(mSharedPreferencesTitle)
                 .when(mContext)
                 .getSharedPreferences(TAB_GROUP_TITLES_FILE_NAME, Context.MODE_PRIVATE);
+        doReturn(mSharedPreferencesColor)
+                .when(mContext)
+                .getSharedPreferences(TAB_GROUP_COLORS_FILE_NAME, Context.MODE_PRIVATE);
         ContextUtils.initApplicationContextForTests(mContext);
-        when(mSharedPreferences.getString(anyString(), any())).thenReturn(TAB_TITLE);
+        when(mSharedPreferencesTitle.getString(anyString(), any())).thenReturn(TAB_TITLE);
+        when(mSharedPreferencesColor.getInt(anyString(), anyInt())).thenReturn(COLOR_ID);
     }
 
     @Before
@@ -785,7 +797,8 @@
                         Collections.singletonList(0),
                         Collections.singletonList(mTab1.getRootId()),
                         Collections.singletonList(null),
-                        null);
+                        null,
+                        INVALID_COLOR_ID);
         assertTrue(mTabGroupModelFilter.isTabInTabGroup(mTab1));
 
         mTabGroupModelFilter.moveTabOutOfGroup(TAB1_ID);
@@ -1332,7 +1345,7 @@
                 .didCreateNewGroup(mTab4.getRootId(), mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver).didMergeTabToGroup(mTab4, mTab4.getId());
         verify(mTabGroupModelFilterObserver, never())
-                .didCreateGroup(any(), any(), any(), any(), any());
+                .didCreateGroup(any(), any(), any(), any(), any(), anyInt());
 
         assertThat(mTab4.getTabGroupId(), equalTo(tabGroupId));
 
@@ -1592,7 +1605,8 @@
                         originalIndexes,
                         originalRootIds,
                         originalTabGroupIds,
-                        TAB_TITLE);
+                        TAB_TITLE,
+                        COLOR_ID);
         assertArrayEquals(
                 mTabGroupModelFilter.getRelatedTabList(mTab2.getId()).toArray(),
                 expectedGroup.toArray());
@@ -1635,7 +1649,8 @@
                         originalIndexes,
                         originalRootIds,
                         originalTabGroupIds,
-                        TAB_TITLE);
+                        TAB_TITLE,
+                        COLOR_ID);
         assertArrayEquals(
                 mTabGroupModelFilter.getRelatedTabList(mTab2.getId()).toArray(),
                 expectedGroup.toArray());
@@ -1671,7 +1686,8 @@
                         originalIndexes,
                         originalRootIds,
                         originalTabGroupIds,
-                        TAB_TITLE);
+                        TAB_TITLE,
+                        COLOR_ID);
         assertArrayEquals(
                 mTabGroupModelFilter.getRelatedTabList(mTab1.getId()).toArray(),
                 expectedGroup.toArray());
@@ -1689,7 +1705,7 @@
         verify(mTabGroupModelFilterObserver)
                 .didCreateNewGroup(mTab4.getRootId(), mTabGroupModelFilter);
         verify(mTabGroupModelFilterObserver, never())
-                .didCreateGroup(any(), any(), any(), any(), any());
+                .didCreateGroup(any(), any(), any(), any(), any(), anyInt());
 
         assertEquals(mTab1.getTabGroupId(), tabGroupId);
         assertEquals(mTab4.getTabGroupId(), tabGroupId);
@@ -1765,4 +1781,15 @@
         assertTrue(mTabGroupModelFilter.tabGroupExistsForRootId(mTab3.getRootId()));
         assertTrue(mTabGroupModelFilter.tabGroupExistsForRootId(mTab5.getRootId()));
     }
+
+    @Test
+    public void testGetAllTabGroupRootIds() {
+        // With the given setup, mTab2 and mTab3 are in a group and mTab5 and mTab6 are in another
+        // group.
+        Set<Integer> rootIds = new ArraySet<>();
+        rootIds.add(mTab2.getRootId());
+        rootIds.add(mTab5.getRootId());
+
+        assertEquals(rootIds, mTabGroupModelFilter.getAllTabGroupRootIds());
+    }
 }
diff --git a/chrome/browser/tab_resumption/java/src/org/chromium/chrome/browser/tab_resumption/TabResumptionModuleView.java b/chrome/browser/tab_resumption/java/src/org/chromium/chrome/browser/tab_resumption/TabResumptionModuleView.java
index 83285e35..cbf9005 100644
--- a/chrome/browser/tab_resumption/java/src/org/chromium/chrome/browser/tab_resumption/TabResumptionModuleView.java
+++ b/chrome/browser/tab_resumption/java/src/org/chromium/chrome/browser/tab_resumption/TabResumptionModuleView.java
@@ -23,6 +23,8 @@
     private SuggestionBundle mBundle;
 
     private boolean mIsSuggestionBundleReady;
+    private String mTitle;
+    private String mAllTilesTexts;
 
     public TabResumptionModuleView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -56,7 +58,9 @@
     }
 
     void setTitle(@Nullable String title) {
-        ((TextView) findViewById(R.id.tab_resumption_title_description)).setText(title);
+        mTitle = title;
+        ((TextView) findViewById(R.id.tab_resumption_title_description)).setText(mTitle);
+        setContentDescriptionOfTabResumption();
     }
 
     TabResumptionTileContainerView getTileContainerViewForTesting() {
@@ -67,9 +71,22 @@
         if (mIsSuggestionBundleReady && mUrlImageProvider != null && mClickCallback != null) {
             if (mBundle == null) {
                 mTileContainerView.removeAllViews();
+                mAllTilesTexts = null;
             } else {
-                mTileContainerView.renderAllTiles(mBundle, mUrlImageProvider, mClickCallback);
+                mAllTilesTexts =
+                        mTileContainerView.renderAllTiles(
+                                mBundle, mUrlImageProvider, mClickCallback);
             }
+            setContentDescriptionOfTabResumption();
+        }
+    }
+
+    /** Sets the content description for the tab resumption module. */
+    private void setContentDescriptionOfTabResumption() {
+        if (mTitle != null && mAllTilesTexts != null) {
+            setContentDescription(mTitle + ". " + mAllTilesTexts);
+        } else {
+            setContentDescription(null);
         }
     }
 }
diff --git a/chrome/browser/tab_resumption/java/src/org/chromium/chrome/browser/tab_resumption/TabResumptionTileContainerView.java b/chrome/browser/tab_resumption/java/src/org/chromium/chrome/browser/tab_resumption/TabResumptionTileContainerView.java
index b26582d..47a8ebe 100644
--- a/chrome/browser/tab_resumption/java/src/org/chromium/chrome/browser/tab_resumption/TabResumptionTileContainerView.java
+++ b/chrome/browser/tab_resumption/java/src/org/chromium/chrome/browser/tab_resumption/TabResumptionTileContainerView.java
@@ -41,13 +41,17 @@
         removeAllViews();
     }
 
-    /** Adds all new {@link TabResumptionTileView} instances, after removing existing ones. */
-    public void renderAllTiles(
+    /**
+     * Adds all new {@link TabResumptionTileView} instances, after removing existing ones and
+     * returns the text of all instances.
+     */
+    public String renderAllTiles(
             SuggestionBundle bundle,
             UrlImageProvider urlImageProvider,
             SuggestionClickCallback suggestionClickCallback) {
         removeAllViews();
 
+        String allTilesTexts = "";
         boolean isSingle = bundle.entries.size() == 1;
         for (SuggestionEntry entry : bundle.entries) {
             // Add divider if some tile already exists.
@@ -68,15 +72,17 @@
             TabResumptionTileView tileView =
                     (TabResumptionTileView)
                             LayoutInflater.from(getContext()).inflate(layoutId, this, false);
-            loadTileTexts(entry, bundle.referenceTimeMs, isSingle, tileView);
+            allTilesTexts +=
+                    loadTileTexts(entry, bundle.referenceTimeMs, isSingle, tileView) + ". ";
             loadTileUrlImage(entry, urlImageProvider, tileView);
             tileView.bindSuggestionClickCallback(suggestionClickCallback, entry.url);
             addView(tileView);
         }
+        return allTilesTexts;
     }
 
-    /** Renders the texts of a {@link TabResumptionTileView}. */
-    private void loadTileTexts(
+    /** Renders and returns the texts of a {@link TabResumptionTileView}. */
+    private String loadTileTexts(
             SuggestionEntry entry,
             long referenceTimeMs,
             boolean isSingle,
@@ -94,6 +100,7 @@
                             recencyString,
                             entry.url.getHost());
             tileView.setSuggestionTextsSingle(preInfoText, entry.title, postInfoText);
+            return preInfoText + ", " + entry.title + ", " + postInfoText;
         } else {
             String infoText =
                     res.getString(
@@ -101,6 +108,7 @@
                             recencyString,
                             entry.sourceName);
             tileView.setSuggestionTextsMulti(entry.title, infoText);
+            return entry.title + ", " + infoText;
         }
     }
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 1ca0be7..a69834a 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1657,6 +1657,8 @@
       "webui/cr_components/customize_color_scheme_mode/customize_color_scheme_mode_handler.h",
       "webui/cr_components/history_clusters/history_clusters_util.cc",
       "webui/cr_components/history_clusters/history_clusters_util.h",
+      "webui/cr_components/history_embeddings/history_embeddings_handler.cc",
+      "webui/cr_components/history_embeddings/history_embeddings_handler.h",
       "webui/cr_components/most_visited/most_visited_handler.cc",
       "webui/cr_components/most_visited/most_visited_handler.h",
       "webui/cr_components/theme_color_picker/customize_chrome_colors.cc",
@@ -2112,6 +2114,7 @@
       "//ui/webui/resources/cr_components/commerce:mojo_bindings",
       "//ui/webui/resources/cr_components/help_bubble:mojo_bindings",
       "//ui/webui/resources/cr_components/history_clusters:mojo_bindings",
+      "//ui/webui/resources/cr_components/history_embeddings:mojo_bindings",
       "//ui/webui/resources/js/browser_command:mojo_bindings",
       "//ui/webui/resources/js/metrics_reporter:mojo_bindings",
     ]
diff --git a/chrome/browser/ui/android/signin/BUILD.gn b/chrome/browser/ui/android/signin/BUILD.gn
index 9504c09b..975cbff 100644
--- a/chrome/browser/ui/android/signin/BUILD.gn
+++ b/chrome/browser/ui/android/signin/BUILD.gn
@@ -127,7 +127,8 @@
     "java/res/layout/account_row.xml",
     "java/res/layout/confirm_import_sync_data.xml",
     "java/res/layout/fre_uma_dialog.xml",
-    "java/res/layout/history_sync_view.xml",
+    "java/res/layout/history_sync_landscape_view.xml",
+    "java/res/layout/history_sync_portrait_view.xml",
     "java/res/layout/signin_first_run_bottom_group_view.xml",
     "java/res/layout/signin_first_run_landscape_view.xml",
     "java/res/layout/signin_first_run_portrait_view.xml",
diff --git a/chrome/browser/ui/android/signin/java/res/layout/history_sync_landscape_view.xml b/chrome/browser/ui/android/signin/java/res/layout/history_sync_landscape_view.xml
new file mode 100644
index 0000000..a275bfc1
--- /dev/null
+++ b/chrome/browser/ui/android/signin/java/res/layout/history_sync_landscape_view.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2024 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.chrome.browser.ui.signin.history_sync.HistorySyncView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingStart="14dp"
+    android:paddingEnd="24dp"
+    android:gravity="fill"
+    android:orientation="horizontal">
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_alignParentStart="true"
+        android:gravity="center_vertical"
+        android:importantForAccessibility="no">
+        <ImageView
+            android:id="@+id/history_sync_illustration"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:scaleType="center"
+            app:srcCompat="@drawable/history_sync_illustration"
+            android:importantForAccessibility="no" />
+        <ImageView
+            android:id="@+id/account_image"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_centerInParent="true"
+            android:importantForAccessibility="no" />
+    </RelativeLayout>
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingStart="24dp"
+        android:gravity="center_vertical">
+
+        <ScrollView
+            android:id="@+id/sync_consent_scroll_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:fadeScrollbars="false"
+            android:fillViewport="true">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_vertical"
+                android:orientation="vertical">
+
+                <TextView
+                    android:id="@+id/sync_consent_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="start"
+                    android:textAppearance="@style/TextAppearance.Headline.Primary"/>
+
+                <org.chromium.ui.widget.TextViewWithLeading
+                    android:id="@+id/sync_consent_subtitle"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginEnd="105dp"
+                    android:paddingVertical="8dp"
+                    android:gravity="start"
+                    android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
+                    app:leading="@dimen/text_size_medium_leading" />
+
+
+                <RelativeLayout
+                    android:id="@+id/button_bar"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginBottom="16dp">
+
+                    <org.chromium.ui.widget.ButtonCompat
+                        android:id="@+id/negative_button"
+                        style="@style/TextButton"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_alignParentStart="true" />
+
+                    <View
+                        android:layout_width="wrap_content"
+                        android:layout_height="0dp"
+                        android:layout_toEndOf="@id/negative_button"
+                        android:layout_toStartOf="@id/positive_button"
+                        android:visibility="invisible" />
+
+                    <org.chromium.ui.widget.ButtonCompat
+                        android:id="@+id/positive_button"
+                        style="@style/FilledButton.Flat"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_alignParentEnd="true" />
+
+                    <org.chromium.ui.widget.ButtonCompat
+                        android:id="@+id/more_button"
+                        style="@style/FilledButton.Flat"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_centerHorizontal="true"
+                        android:drawableEnd="@drawable/down_arrow"
+                        android:drawableTint="?attr/globalFilledButtonTextColor"
+                        android:drawablePadding="8dp"
+                        android:visibility="gone" />
+                </RelativeLayout>
+            </LinearLayout>
+        </ScrollView>
+
+        <org.chromium.ui.widget.TextViewWithLeading
+            android:id="@+id/sync_consent_details_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginVertical="16dp"
+            android:layout_alignParentBottom="true"
+            android:gravity="start"
+            android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
+            app:leading="@dimen/text_size_small_leading" />
+    </RelativeLayout>
+</org.chromium.chrome.browser.ui.signin.history_sync.HistorySyncView>
\ No newline at end of file
diff --git a/chrome/browser/ui/android/signin/java/res/layout/history_sync_view.xml b/chrome/browser/ui/android/signin/java/res/layout/history_sync_portrait_view.xml
similarity index 100%
rename from chrome/browser/ui/android/signin/java/res/layout/history_sync_view.xml
rename to chrome/browser/ui/android/signin/java/res/layout/history_sync_portrait_view.xml
diff --git a/chrome/browser/ui/android/signin/java/res/layout/signin_first_run_landscape_view.xml b/chrome/browser/ui/android/signin/java/res/layout/signin_first_run_landscape_view.xml
index d897d2a..d072128 100644
--- a/chrome/browser/ui/android/signin/java/res/layout/signin_first_run_landscape_view.xml
+++ b/chrome/browser/ui/android/signin/java/res/layout/signin_first_run_landscape_view.xml
@@ -55,9 +55,9 @@
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:gravity="start"
-                    android:paddingBottom="8dp"
+                    android:paddingBottom="14dp"
                     android:paddingStart="24dp"
-                    android:paddingTop="8dp"
+                    android:paddingTop="4dp"
                     android:text="@string/signin_fre_subtitle"
                     android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
                     android:visibility="gone"
@@ -110,7 +110,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginHorizontal="24dp"
-            android:layout_marginVertical="12dp"
+            android:layout_marginVertical="16dp"
             android:gravity="start"
             android:textAppearance="@style/TextAppearance.TextSmall.Secondary"
             android:text="@string/signin_fre_footer"
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncCoordinator.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncCoordinator.java
index 20a6dc8..b6e06eb 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncCoordinator.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncCoordinator.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.ui.signin.history_sync;
 
+import android.content.res.Configuration;
 import android.view.LayoutInflater;
 import android.view.View;
 
@@ -17,6 +18,8 @@
     /*Delegate for the History Sync MVC */
     public interface HistorySyncDelegate {
         void dismiss();
+
+        boolean canUseLandscapeLayout();
     }
 
     private final HistorySyncMediator mMediator;
@@ -29,8 +32,8 @@
             HistorySyncDelegate delegate,
             Profile profile,
             @SigninAccessPoint int accessPoint) {
-        mView = (HistorySyncView) inflater.inflate(R.layout.history_sync_view, null, false);
         mMediator = new HistorySyncMediator(inflater.getContext(), delegate, profile, accessPoint);
+        mView = inflateView(inflater, delegate);
         mPropertyModelChangeProcessor =
                 PropertyModelChangeProcessor.create(
                         mMediator.getModel(), mView, HistorySyncViewBinder::bind);
@@ -48,4 +51,19 @@
     public View getView() {
         return mView;
     }
+
+    private HistorySyncView inflateView(LayoutInflater inflater, HistorySyncDelegate delegate) {
+        Configuration configuration = inflater.getContext().getResources().getConfiguration();
+        boolean useLandscapeLayout =
+                delegate.canUseLandscapeLayout()
+                        && configuration.orientation == Configuration.ORIENTATION_LANDSCAPE;
+
+        return (HistorySyncView)
+                inflater.inflate(
+                        useLandscapeLayout
+                                ? R.layout.history_sync_landscape_view
+                                : R.layout.history_sync_portrait_view,
+                        null,
+                        false);
+    }
 }
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncRenderTest.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncRenderTest.java
index 6a2f75f..a45927b 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncRenderTest.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncRenderTest.java
@@ -8,6 +8,7 @@
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 
 import static org.hamcrest.Matchers.allOf;
+import static org.mockito.Mockito.when;
 
 import android.content.res.Configuration;
 import android.view.LayoutInflater;
@@ -101,9 +102,12 @@
     public final RenderTestRule mRenderTestRule =
             RenderTestRule.Builder.withPublicCorpus()
                     .setBugComponent(RenderTestRule.Component.SERVICES_SIGN_IN)
+                    .setRevision(1)
+                    .setDescription("New landscape layout")
                     .build();
 
     @Mock private SyncService mSyncServiceMock;
+    @Mock private HistorySyncCoordinator.HistorySyncDelegate mHistorySyncDelegateMock;
 
     private HistorySyncCoordinator mHistorySyncCoordinator;
 
@@ -125,6 +129,7 @@
     @Before
     public void setUp() {
         NativeLibraryTestUtils.loadNativeLibraryAndInitBrowserProcess();
+        when(mHistorySyncDelegateMock.canUseLandscapeLayout()).thenReturn(true);
         mActivityTestRule.launchActivity(null);
         mSigninTestRule.addTestAccountThenSignin();
         SyncServiceFactory.setInstanceForTesting(mSyncServiceMock);
@@ -148,7 +153,7 @@
                     mHistorySyncCoordinator =
                             new HistorySyncCoordinator(
                                     LayoutInflater.from(mActivityTestRule.getActivity()),
-                                    () -> {},
+                                    mHistorySyncDelegateMock,
                                     ProfileManager.getLastUsedRegularProfile(),
                                     SigninAccessPoint.UNKNOWN);
                     mActivityTestRule
diff --git a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncTest.java b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncTest.java
index 6ba00d52..240d464 100644
--- a/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncTest.java
+++ b/chrome/browser/ui/android/signin/java/src/org/chromium/chrome/browser/ui/signin/history_sync/HistorySyncTest.java
@@ -19,7 +19,6 @@
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -54,28 +53,6 @@
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @DoNotBatch(reason = "This test relies on native initialization")
 public class HistorySyncTest {
-    /** Stub implementation of the {@link HistorySyncDelegate} for testing */
-    public class HistorySyncTestDelegate implements HistorySyncCoordinator.HistorySyncDelegate {
-        private HistorySyncCoordinator mCoordinator;
-
-        @Override
-        public void dismiss() {
-            if (isCoordinatorDestroyed()) {
-                return;
-            }
-            mCoordinator.destroy();
-            mCoordinator = null;
-        }
-
-        public void setCoordinator(HistorySyncCoordinator coordinator) {
-            mCoordinator = coordinator;
-        }
-
-        public boolean isCoordinatorDestroyed() {
-            return mCoordinator == null;
-        }
-    }
-
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS);
 
@@ -88,9 +65,9 @@
     private static final @SigninAccessPoint int SIGNIN_ACCESS_POINT = SigninAccessPoint.UNKNOWN;
 
     @Mock private SyncService mSyncServiceMock;
+    @Mock private HistorySyncCoordinator.HistorySyncDelegate mHistorySyncDelegateMock;
 
     private HistorySyncCoordinator mHistorySyncCoordinator;
-    private HistorySyncTestDelegate mDelegate;
 
     @Before
     public void setUp() {
@@ -102,13 +79,6 @@
 
     @After
     public void tearDown() {
-        TestThreadUtils.runOnUiThreadBlocking(
-                () -> {
-                    if (mDelegate.isCoordinatorDestroyed()) {
-                        mDelegate.dismiss();
-                    }
-                });
-
         mSigninTestRule.forceSignOut();
     }
 
@@ -144,7 +114,7 @@
         histogramWatcher.assertExpected();
         verify(mSyncServiceMock).setSelectedType(UserSelectableType.HISTORY, true);
         verify(mSyncServiceMock).setSelectedType(UserSelectableType.TABS, true);
-        Assert.assertTrue(mDelegate.isCoordinatorDestroyed());
+        verify(mHistorySyncDelegateMock).dismiss();
     }
 
     @Test
@@ -159,7 +129,7 @@
 
         histogramWatcher.assertExpected();
         verifyNoInteractions(mSyncServiceMock);
-        Assert.assertTrue(mDelegate.isCoordinatorDestroyed());
+        verify(mHistorySyncDelegateMock).dismiss();
     }
 
     @Test
@@ -175,24 +145,22 @@
                 () -> mSigninTestRule.getPrimaryAccount(ConsentLevel.SIGNIN) == null);
 
         histogramWatcher.assertExpected();
-        Assert.assertTrue(mDelegate.isCoordinatorDestroyed());
+        verify(mHistorySyncDelegateMock).dismiss();
     }
 
     private void buildHistorySyncCoordinator() {
-        mDelegate = new HistorySyncTestDelegate();
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> {
                     mHistorySyncCoordinator =
                             new HistorySyncCoordinator(
                                     LayoutInflater.from(mActivityTestRule.getActivity()),
-                                    mDelegate,
+                                    mHistorySyncDelegateMock,
                                     ProfileManager.getLastUsedRegularProfile(),
                                     SIGNIN_ACCESS_POINT);
                     mActivityTestRule
                             .getActivity()
                             .setContentView(mHistorySyncCoordinator.getView());
                 });
-        mDelegate.setCoordinator(mHistorySyncCoordinator);
         ViewUtils.waitForVisibleView(allOf(withId(R.id.sync_consent_title), isDisplayed()));
     }
 }
diff --git a/chrome/browser/ui/ash/birch/birch_calendar_fetcher.cc b/chrome/browser/ui/ash/birch/birch_calendar_fetcher.cc
index 96e853b..4c17585 100644
--- a/chrome/browser/ui/ash/birch/birch_calendar_fetcher.cc
+++ b/chrome/browser/ui/ash/birch/birch_calendar_fetcher.cc
@@ -126,7 +126,7 @@
   sender_->StartRequestWithAuthRetry(
       std::make_unique<google_apis::calendar::CalendarApiEventsRequest>(
           sender_.get(), url_generator_, std::move(callback_), start_time_,
-          end_time_));
+          end_time_, /*include_attachments=*/true));
 }
 
 void BirchCalendarFetcher::SetSenderForTest(
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.cc b/chrome/browser/ui/ash/chrome_shell_delegate.cc
index e8a51e3..8833628 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.cc
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.cc
@@ -127,6 +127,8 @@
       return chrome::FeedbackSource::kFeedbackSourceFocusMode;
     case ash::ShellDelegate::FeedbackSource::kGameDashboard:
       return chrome::FeedbackSource::kFeedbackSourceGameDashboard;
+    case ash::ShellDelegate::FeedbackSource::kOverview:
+      return chrome::FeedbackSource::kFeedbackSourceOverview;
     case ash::ShellDelegate::FeedbackSource::kWindowLayoutMenu:
       return chrome::FeedbackSource::kFeedbackSourceWindowLayoutMenu;
   }
@@ -395,10 +397,11 @@
 
 void ChromeShellDelegate::OpenFeedbackDialog(
     ShellDelegate::FeedbackSource source,
-    const std::string& description_template) {
+    const std::string& description_template,
+    const std::string& category_tag) {
   chrome::OpenFeedbackDialog(/*browser=*/nullptr,
                              ToChromeFeedbackSource(source),
-                             description_template);
+                             description_template, category_tag);
 }
 
 void ChromeShellDelegate::OpenProfileManager() {
diff --git a/chrome/browser/ui/ash/chrome_shell_delegate.h b/chrome/browser/ui/ash/chrome_shell_delegate.h
index 96b2fbf..cf69d6f 100644
--- a/chrome/browser/ui/ash/chrome_shell_delegate.h
+++ b/chrome/browser/ui/ash/chrome_shell_delegate.h
@@ -79,7 +79,8 @@
   bool IsLoggingRedirectDisabled() const override;
   base::FilePath GetPrimaryUserDownloadsFolder() const override;
   void OpenFeedbackDialog(ShellDelegate::FeedbackSource source,
-                          const std::string& description_template) override;
+                          const std::string& description_template,
+                          const std::string& category_tag) override;
   void OpenProfileManager() override;
   static void SetDisableLoggingRedirectForTesting(bool value);
   static void ResetDisableLoggingRedirectForTesting();
diff --git a/chrome/browser/ui/ash/download_status/display_manager.cc b/chrome/browser/ui/ash/download_status/display_manager.cc
index d616477..77008b1 100644
--- a/chrome/browser/ui/ash/download_status/display_manager.cc
+++ b/chrome/browser/ui/ash/download_status/display_manager.cc
@@ -25,6 +25,12 @@
 #include "chrome/browser/ui/ash/download_status/notification_display_client.h"
 #include "chromeos/crosapi/mojom/download_controller.mojom.h"
 #include "chromeos/crosapi/mojom/download_status_updater.mojom.h"
+#include "net/base/mime_util.h"
+#include "third_party/blink/public/common/mime_util/mime_util.h"
+#include "ui/base/clipboard/clipboard_buffer.h"
+#include "ui/base/clipboard/file_info.h"
+#include "ui/base/clipboard/scoped_clipboard_writer.h"
+#include "ui/gfx/image/image_skia.h"
 
 namespace ash::download_status {
 
@@ -134,6 +140,15 @@
   return file_path.get().BaseName().LossyDisplayName();
 }
 
+// Returns true if the file referred to by `file_path` is of an image MIME type.
+bool HasSupportedImageMimeType(const base::FilePath& file_path) {
+  std::string mime_type;
+  if (net::GetMimeTypeFromFile(file_path, &mime_type)) {
+    return blink::IsSupportedImageMimeType(mime_type);
+  }
+  return false;
+}
+
 // Opens the download file specified by `file_path` under the file system
 // associated with `profile`.
 void OpenFile(Profile* profile, const base::FilePath& file_path) {
@@ -240,22 +255,38 @@
         &kResumeIcon, IDS_ASH_DOWNLOAD_COMMAND_TEXT_RESUME,
         CommandType::kResume);
   }
+  const base::FilePath& full_path = *download_status.full_path;
   switch (download_status.state) {
     case crosapi::mojom::DownloadState::kComplete:
       // NOTE: `kOpenFile` is not shown so it doesn't require an icon/text_id.
       command_infos.emplace_back(
-          base::BindRepeating(
-              &DisplayManager::PerformCommand, weak_ptr_factory_.GetWeakPtr(),
-              CommandType::kOpenFile, *download_status.full_path),
+          base::BindRepeating(&DisplayManager::PerformCommand,
+                              weak_ptr_factory_.GetWeakPtr(),
+                              CommandType::kOpenFile, full_path),
           /*icon=*/nullptr, /*text_id=*/-1, CommandType::kOpenFile);
 
       // NOTE: The `kShowInFolder` button does not have an icon.
       command_infos.emplace_back(
-          base::BindRepeating(
-              &DisplayManager::PerformCommand, weak_ptr_factory_.GetWeakPtr(),
-              CommandType::kShowInFolder, *download_status.full_path),
+          base::BindRepeating(&DisplayManager::PerformCommand,
+                              weak_ptr_factory_.GetWeakPtr(),
+                              CommandType::kShowInFolder, full_path),
           /*icon=*/nullptr, IDS_ASH_DOWNLOAD_COMMAND_TEXT_SHOW_IN_FOLDER,
           CommandType::kShowInFolder);
+
+      // Add a command to copy the download file to clipboard if:
+      // 1. `download_status` has a valid image; AND
+      // 2. The download file is an image.
+      // NOTE: The `kCopyToClipboard` button does not require an icon.
+      if (const gfx::ImageSkia& image = download_status.image;
+          !image.isNull() && !image.size().IsEmpty() &&
+          HasSupportedImageMimeType(full_path)) {
+        command_infos.emplace_back(
+            base::BindRepeating(&DisplayManager::PerformCommand,
+                                weak_ptr_factory_.GetWeakPtr(),
+                                CommandType::kCopyToClipboard, full_path),
+            /*icon=*/nullptr, IDS_ASH_DOWNLOAD_COMMAND_TEXT_COPY_TO_CLIPBOARD,
+            CommandType::kCopyToClipboard);
+      }
       break;
     case crosapi::mojom::DownloadState::kInProgress:
       // NOTE: `kShowInBrowser` is not shown so doesn't require an icon/text_id.
@@ -284,7 +315,7 @@
   }
   display_metadata.command_infos = std::move(command_infos);
 
-  display_metadata.file_path = *download_status.full_path;
+  display_metadata.file_path = full_path;
   display_metadata.image = download_status.image;
   display_metadata.progress = GetProgress(download_status);
   display_metadata.secondary_text = download_status.status_text;
@@ -301,6 +332,13 @@
       download_status_updater_->Cancel(/*guid=*/std::get<std::string>(param),
                                        /*callback=*/base::DoNothing());
       break;
+    case CommandType::kCopyToClipboard: {
+      ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
+      scw.WriteFilenames(ui::FileInfosToURIList(
+          /*filenames=*/{ui::FileInfo(std::get<base::FilePath>(param),
+                                      /*display_name=*/base::FilePath())}));
+      break;
+    }
     case CommandType::kOpenFile:
       OpenFile(profile_, std::get<base::FilePath>(param));
       break;
diff --git a/chrome/browser/ui/ash/download_status/display_metadata.h b/chrome/browser/ui/ash/download_status/display_metadata.h
index 64bd130..4d6b8f1 100644
--- a/chrome/browser/ui/ash/download_status/display_metadata.h
+++ b/chrome/browser/ui/ash/download_status/display_metadata.h
@@ -22,6 +22,7 @@
 // Lists the types of commands that can be performed on a displayed download.
 enum class CommandType {
   kCancel,
+  kCopyToClipboard,
   kOpenFile,
   kPause,
   kResume,
diff --git a/chrome/browser/ui/ash/download_status/display_test_util.cc b/chrome/browser/ui/ash/download_status/display_test_util.cc
index 00d548f..715f1e4b 100644
--- a/chrome/browser/ui/ash/download_status/display_test_util.cc
+++ b/chrome/browser/ui/ash/download_status/display_test_util.cc
@@ -22,11 +22,12 @@
 
 crosapi::mojom::DownloadStatusPtr CreateDownloadStatus(
     Profile* profile,
+    std::string_view extension,
     crosapi::mojom::DownloadState state,
     crosapi::mojom::DownloadProgressPtr progress) {
   crosapi::mojom::DownloadStatusPtr download_status =
       crosapi::mojom::DownloadStatus::New();
-  download_status->full_path = test::CreateFile(profile);
+  download_status->full_path = test::CreateFile(profile, extension);
   download_status->guid = base::UnguessableToken::Create().ToString();
   download_status->progress = std::move(progress);
   download_status->state = state;
@@ -36,10 +37,11 @@
 
 crosapi::mojom::DownloadStatusPtr CreateInProgressDownloadStatus(
     Profile* profile,
+    std::string_view extension,
     int64_t received_bytes,
     const std::optional<int64_t>& total_bytes) {
   return CreateDownloadStatus(
-      profile, crosapi::mojom::DownloadState::kInProgress,
+      profile, extension, crosapi::mojom::DownloadState::kInProgress,
       crosapi::mojom::DownloadProgress::New(
           /*loop=*/false, received_bytes,
           total_bytes.value_or(kUnknownTotalBytes), /*visible=*/true));
diff --git a/chrome/browser/ui/ash/download_status/display_test_util.h b/chrome/browser/ui/ash/download_status/display_test_util.h
index 8e947f1..dee4bfd 100644
--- a/chrome/browser/ui/ash/download_status/display_test_util.h
+++ b/chrome/browser/ui/ash/download_status/display_test_util.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_ASH_DOWNLOAD_STATUS_DISPLAY_TEST_UTIL_H_
 
 #include <optional>
+#include <string_view>
 
 #include "chromeos/crosapi/mojom/download_status_updater.mojom.h"
 
@@ -13,10 +14,11 @@
 
 namespace ash::download_status {
 
-// Creates a download status associated with a file under the downloads
-// directory of `profile`.
+// Creates a download status associated with a file with the specified
+// `extension` under the downloads directory of `profile`.
 crosapi::mojom::DownloadStatusPtr CreateDownloadStatus(
     Profile* profile,
+    std::string_view extension,
     crosapi::mojom::DownloadState state,
     crosapi::mojom::DownloadProgressPtr progress);
 
@@ -24,6 +26,7 @@
 // with a file under the downloads directory of `profile`.
 crosapi::mojom::DownloadStatusPtr CreateInProgressDownloadStatus(
     Profile* profile,
+    std::string_view extension,
     int64_t received_bytes,
     const std::optional<int64_t>& total_bytes = std::nullopt);
 
diff --git a/chrome/browser/ui/ash/download_status/holding_space_display_client.cc b/chrome/browser/ui/ash/download_status/holding_space_display_client.cc
index 0b2641e3..190ce7ec 100644
--- a/chrome/browser/ui/ash/download_status/holding_space_display_client.cc
+++ b/chrome/browser/ui/ash/download_status/holding_space_display_client.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ui/ash/download_status/holding_space_display_client.h"
 
+#include <optional>
 #include <utility>
 #include <vector>
 
@@ -28,14 +29,17 @@
 
 namespace {
 
-// Returns the command ID corresponding to the given command type.
+// Returns the command ID corresponding to the given command type if any. If
+// there is no such command ID, returns `std::nullopt`.
 // NOTE: It is fine to map both `CommandType::kOpenFile` and
 // `CommandType::kShowInBrowser` to `kOpenItem`, because `kOpenItem` is not
 // accessible from a holding space chip's context menu.
-HoldingSpaceCommandId ConvertCommandTypeToId(CommandType type) {
+std::optional<HoldingSpaceCommandId> ConvertCommandTypeToId(CommandType type) {
   switch (type) {
     case CommandType::kCancel:
       return HoldingSpaceCommandId::kCancelItem;
+    case CommandType::kCopyToClipboard:
+      return std::nullopt;
     case CommandType::kOpenFile:
       return HoldingSpaceCommandId::kOpenItem;
     case CommandType::kPause:
@@ -51,12 +55,16 @@
   }
 }
 
-// Returns the holding space item action corresponding to `type`.
-holding_space_metrics::ItemAction ConvertCommandTypeToAction(CommandType type) {
+// Returns the holding space item action corresponding to `type` if any. If
+// there is no such action, returns `std::nullopt`.
+std::optional<holding_space_metrics::ItemAction> ConvertCommandTypeToAction(
+    CommandType type) {
   using ItemAction = holding_space_metrics::ItemAction;
   switch (type) {
     case CommandType::kCancel:
       return ItemAction::kCancel;
+    case CommandType::kCopyToClipboard:
+      return std::nullopt;
     case CommandType::kOpenFile:
       return ItemAction::kLaunch;
     case CommandType::kPause:
@@ -121,23 +129,31 @@
   // Generate in-progress commands from `display_metadata`.
   std::vector<HoldingSpaceItem::InProgressCommand> in_progress_commands;
   for (const auto& command_info : display_metadata.command_infos) {
-    if (const HoldingSpaceCommandId id =
-            ConvertCommandTypeToId(command_info.type);
-        holding_space_util::IsInProgressCommand(id)) {
-      in_progress_commands.emplace_back(
-          id, command_info.text_id, command_info.icon,
-          base::BindRepeating(
-              [](holding_space_metrics::ItemAction action,
-                 const base::RepeatingClosure& command_callback,
-                 const HoldingSpaceItem* item, HoldingSpaceCommandId command_id,
-                 holding_space_metrics::EventSource event_source) {
-                command_callback.Run();
-                holding_space_metrics::RecordItemAction(
-                    /*items=*/{item}, action, event_source);
-              },
-              ConvertCommandTypeToAction(command_info.type),
-              command_info.command_callback));
+    const std::optional<HoldingSpaceCommandId> id =
+        ConvertCommandTypeToId(command_info.type);
+    const std::optional<holding_space_metrics::ItemAction> item_action =
+        ConvertCommandTypeToAction(command_info.type);
+
+    // Skip `command_info` if:
+    // 1. It does not have a corresponding ID; OR
+    // 2. Its corresponding ID is not for an in-progress command; OR
+    // 3. It does not have a corresponding item action.
+    if (!id || !holding_space_util::IsInProgressCommand(*id) || !item_action) {
+      continue;
     }
+
+    in_progress_commands.emplace_back(
+        *id, command_info.text_id, command_info.icon,
+        base::BindRepeating(
+            [](holding_space_metrics::ItemAction action,
+               const base::RepeatingClosure& command_callback,
+               const HoldingSpaceItem* item, HoldingSpaceCommandId command_id,
+               holding_space_metrics::EventSource event_source) {
+              command_callback.Run();
+              holding_space_metrics::RecordItemAction(
+                  /*items=*/{item}, action, event_source);
+            },
+            *item_action, command_info.command_callback));
   }
 
   // Specify the backing file.
diff --git a/chrome/browser/ui/ash/download_status/holding_space_display_client_browsertest.cc b/chrome/browser/ui/ash/download_status/holding_space_display_client_browsertest.cc
index 5a1f215..6ddf84c 100644
--- a/chrome/browser/ui/ash/download_status/holding_space_display_client_browsertest.cc
+++ b/chrome/browser/ui/ash/download_status/holding_space_display_client_browsertest.cc
@@ -137,17 +137,18 @@
   Profile* const profile = ProfileManager::GetActiveUserProfile();
   crosapi::mojom::DownloadStatusPtr in_progress_download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   in_progress_download->cancellable = true;
   Update(in_progress_download->Clone());
-  crosapi::mojom::DownloadStatusPtr completed_download =
-      CreateDownloadStatus(profile, crosapi::mojom::DownloadState::kComplete,
-                           crosapi::mojom::DownloadProgress::New(
-                               /*loop=*/false,
-                               /*received_bytes=*/1024,
-                               /*total_bytes=*/1024,
-                               /*visible=*/false));
+  crosapi::mojom::DownloadStatusPtr completed_download = CreateDownloadStatus(
+      profile, /*extension=*/"txt", crosapi::mojom::DownloadState::kComplete,
+      crosapi::mojom::DownloadProgress::New(
+          /*loop=*/false,
+          /*received_bytes=*/1024,
+          /*total_bytes=*/1024,
+          /*visible=*/false));
   Update(completed_download->Clone());
   test_api().Show();
 
@@ -258,12 +259,13 @@
   Profile* const profile = ProfileManager::GetActiveUserProfile();
   crosapi::mojom::DownloadStatusPtr in_progress_download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   in_progress_download->cancellable = true;
   Update(in_progress_download->Clone());
   crosapi::mojom::DownloadStatusPtr completed_download = CreateDownloadStatus(
-      profile, crosapi::mojom::DownloadState::kComplete,
+      profile, /*extension=*/"txt", crosapi::mojom::DownloadState::kComplete,
       crosapi::mojom::DownloadProgress::New(
           /*loop=*/false,
           /*received_bytes=*/1024, /*total_bytes=*/1024, /*visible=*/false));
@@ -367,14 +369,14 @@
 IN_PROC_BROWSER_TEST_F(HoldingSpaceDisplayClientBrowserTest,
                        ClickCompletedDownloadChip) {
   // Add a completed download.
-  crosapi::mojom::DownloadStatusPtr download =
-      CreateDownloadStatus(ProfileManager::GetActiveUserProfile(),
-                           crosapi::mojom::DownloadState::kComplete,
-                           crosapi::mojom::DownloadProgress::New(
-                               /*loop=*/false,
-                               /*received_bytes=*/1024,
-                               /*total_bytes=*/1024,
-                               /*visible=*/false));
+  crosapi::mojom::DownloadStatusPtr download = CreateDownloadStatus(
+      ProfileManager::GetActiveUserProfile(),
+      /*extension=*/"txt", crosapi::mojom::DownloadState::kComplete,
+      crosapi::mojom::DownloadProgress::New(
+          /*loop=*/false,
+          /*received_bytes=*/1024,
+          /*total_bytes=*/1024,
+          /*visible=*/false));
   Update(download->Clone());
   test_api().Show();
 
@@ -416,6 +418,7 @@
   // Add an in-progress download.
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -456,6 +459,7 @@
   Profile* const active_profile = ProfileManager::GetActiveUserProfile();
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(active_profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -535,6 +539,7 @@
   // Add a new in-progress download with the duplicate download guid.
   crosapi::mojom::DownloadStatusPtr duplicate_download =
       CreateInProgressDownloadStatus(active_profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   duplicate_download->guid = download->guid;
@@ -548,8 +553,9 @@
 IN_PROC_BROWSER_TEST_F(HoldingSpaceDisplayClientBrowserTest,
                        IndeterminateDownload) {
   // Create a download with an unknown total bytes count.
-  crosapi::mojom::DownloadStatusPtr download = CreateInProgressDownloadStatus(
-      ProfileManager::GetActiveUserProfile(), /*received_bytes=*/0);
+  crosapi::mojom::DownloadStatusPtr download =
+      CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt", /*received_bytes=*/0);
   Update(download->Clone());
   test_api().Show();
 
@@ -568,6 +574,7 @@
   // could happen when a download is blocked.
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   download->progress->visible = false;
@@ -592,6 +599,7 @@
                        InterruptDownload) {
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -610,6 +618,7 @@
                        PauseAndResumeDownloadViaContextMenu) {
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   download->pausable = true;
@@ -689,6 +698,7 @@
                        PauseAndResumeDownloadViaSecondaryAction) {
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   download->pausable = true;
@@ -773,6 +783,7 @@
 IN_PROC_BROWSER_TEST_F(HoldingSpaceDisplayClientBrowserTest, SecondaryLabel) {
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -806,6 +817,7 @@
                        ServiceSuspendedDuringDownload) {
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -849,6 +861,7 @@
   // Create an in-progress download that can be canceled and paused.
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   download->cancellable = true;
diff --git a/chrome/browser/ui/ash/download_status/notification_display_client.cc b/chrome/browser/ui/ash/download_status/notification_display_client.cc
index a655207..1502f2fa 100644
--- a/chrome/browser/ui/ash/download_status/notification_display_client.cc
+++ b/chrome/browser/ui/ash/download_status/notification_display_client.cc
@@ -144,6 +144,8 @@
   switch (command) {
     case CommandType::kCancel:
       return "DownloadNotificationV2.Button_Cancel";
+    case CommandType::kCopyToClipboard:
+      return "DownloadNotificationV2.Button_CopyToClipboard";
     case CommandType::kOpenFile:
       return "DownloadNotificationV2.Click_Completed";
     case CommandType::kPause:
@@ -167,6 +169,7 @@
     case CommandType::kShowInBrowser:
       return true;
     case CommandType::kCancel:
+    case CommandType::kCopyToClipboard:
     case CommandType::kPause:
     case CommandType::kResume:
     case CommandType::kShowInFolder:
@@ -180,6 +183,7 @@
 bool IsButtonClickCommandType(CommandType command) {
   switch (command) {
     case CommandType::kCancel:
+    case CommandType::kCopyToClipboard:
     case CommandType::kPause:
     case CommandType::kResume:
     case CommandType::kShowInFolder:
diff --git a/chrome/browser/ui/ash/download_status/notification_display_client_browsertest.cc b/chrome/browser/ui/ash/download_status/notification_display_client_browsertest.cc
index 6e8f758..1200f68 100644
--- a/chrome/browser/ui/ash/download_status/notification_display_client_browsertest.cc
+++ b/chrome/browser/ui/ash/download_status/notification_display_client_browsertest.cc
@@ -53,6 +53,8 @@
 #include "ui/aura/env_observer.h"
 #include "ui/aura/test/find_window.h"
 #include "ui/aura/window.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/clipboard_buffer.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/image/image_unittest_util.h"
 #include "ui/message_center/public/cpp/notification.h"
@@ -75,7 +77,9 @@
 using ::testing::AllOf;
 using ::testing::Contains;
 using ::testing::Each;
+using ::testing::ElementsAre;
 using ::testing::Eq;
+using ::testing::Field;
 using ::testing::Mock;
 using ::testing::NiceMock;
 using ::testing::Not;
@@ -123,6 +127,8 @@
   switch (command_type) {
     case CommandType::kCancel:
       return IDS_ASH_DOWNLOAD_COMMAND_TEXT_CANCEL;
+    case CommandType::kCopyToClipboard:
+      return IDS_ASH_DOWNLOAD_COMMAND_TEXT_COPY_TO_CLIPBOARD;
     case CommandType::kOpenFile:
       NOTREACHED_NORETURN();
     case CommandType::kPause:
@@ -251,6 +257,7 @@
           }));
   crosapi::mojom::DownloadStatusPtr uncancellable_download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   uncancellable_download->cancellable = false;
@@ -278,6 +285,7 @@
           }));
   crosapi::mojom::DownloadStatusPtr cancellable_download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   cancellable_download->cancellable = true;
@@ -335,13 +343,13 @@
           [&notification_id](const message_center::Notification& notification) {
             notification_id = notification.id();
           }));
-  crosapi::mojom::DownloadStatusPtr download =
-      CreateDownloadStatus(profile, crosapi::mojom::DownloadState::kComplete,
-                           crosapi::mojom::DownloadProgress::New(
-                               /*loop=*/false,
-                               /*received_bytes=*/1024,
-                               /*total_bytes=*/1024,
-                               /*visible=*/false));
+  crosapi::mojom::DownloadStatusPtr download = CreateDownloadStatus(
+      profile, /*extension=*/"txt", crosapi::mojom::DownloadState::kComplete,
+      crosapi::mojom::DownloadProgress::New(
+          /*loop=*/false,
+          /*received_bytes=*/1024,
+          /*total_bytes=*/1024,
+          /*visible=*/false));
   Update(download->Clone());
   Mock::VerifyAndClearExpectations(&service_observer());
 
@@ -384,6 +392,7 @@
           }));
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -418,9 +427,10 @@
 // still show.
 IN_PROC_BROWSER_TEST_F(NotificationDisplayClientBrowserTest, CompleteDownload) {
   Profile* const profile = ProfileManager::GetActiveUserProfile();
-  crosapi::mojom::DownloadStatusPtr download =
-      CreateDownloadStatus(profile, crosapi::mojom::DownloadState::kInProgress,
-                           /*progress=*/nullptr);
+  crosapi::mojom::DownloadStatusPtr download = CreateDownloadStatus(
+      profile,
+      /*extension=*/"txt", crosapi::mojom::DownloadState::kInProgress,
+      /*progress=*/nullptr);
   EXPECT_FALSE(download->target_file_path);
   std::string notification_id;
 
@@ -544,6 +554,7 @@
   Profile* const profile = ProfileManager::GetActiveUserProfile();
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -580,12 +591,14 @@
             notification_id = notification.id();
           }));
 
-  // Create a download.
+  // Create an image download.
   Profile* const profile = ProfileManager::GetActiveUserProfile();
-  crosapi::mojom::DownloadStatusPtr download =
-      CreateInProgressDownloadStatus(profile,
-                                     /*received_bytes=*/0,
-                                     /*total_bytes=*/1024);
+  crosapi::mojom::DownloadStatusPtr download = CreateDownloadStatus(
+      profile,
+      /*extension=*/"png", crosapi::mojom::DownloadState::kInProgress,
+      crosapi::mojom::DownloadProgress::New(
+          /*loop=*/false, /*received_bytes=*/0,
+          /*total_bytes=*/1024, /*visible=*/true));
   Update(download->Clone());
   Mock::VerifyAndClearExpectations(&service_observer());
 
@@ -614,6 +627,49 @@
       *large_image_view->original_image().bitmap(),
       gfx::test::CreateBitmap(/*width=*/360,
                               /*height=*/240, image_color)));
+
+  // An in-progress image download's notification should not have a 'Copy to
+  // clipboard' button.
+  const std::u16string copy_to_clipboard_button_text =
+      l10n_util::GetStringUTF16(
+          GetCommandTextId(CommandType::kCopyToClipboard));
+  EXPECT_THAT(
+      popup_view->GetActionButtonsForTest(),
+      Not(Contains(Pointee(Property(&views::LabelButton::GetText,
+                                    Eq(copy_to_clipboard_button_text))))));
+
+  // Complete `download`. Then check action buttons.
+  MarkDownloadStatusCompleted(*download);
+  Update(download->Clone());
+  const std::vector<raw_ptr<views::LabelButton, VectorExperimental>>
+      action_buttons = popup_view->GetActionButtonsForTest();
+  EXPECT_THAT(
+      action_buttons,
+      ElementsAre(
+          Pointee(Property(&views::LabelButton::GetText,
+                           Eq(l10n_util::GetStringUTF16(
+                               GetCommandTextId(CommandType::kShowInFolder))))),
+          Pointee(Property(&views::LabelButton::GetText,
+                           Eq(copy_to_clipboard_button_text)))));
+
+  // Click the 'Copy to clipboard' button. Then verify the click is recorded.
+  base::UserActionTester tester;
+  auto copy_to_clipboard_button_iter =
+      base::ranges::find(action_buttons, copy_to_clipboard_button_text,
+                         &views::LabelButton::GetText);
+  ASSERT_NE(copy_to_clipboard_button_iter, action_buttons.cend());
+  test::Click(*copy_to_clipboard_button_iter, ui::EF_NONE);
+  EXPECT_EQ(
+      tester.GetActionCount("DownloadNotificationV2.Button_CopyToClipboard"),
+      1);
+
+  // Verify the filename in the clipboard as expected.
+  base::test::TestFuture<std::vector<ui::FileInfo>> test_future;
+  ui::Clipboard::GetForCurrentThread()->ReadFilenames(
+      ui::ClipboardBuffer::kCopyPaste,
+      /*data_dst=*/nullptr, test_future.GetCallback());
+  EXPECT_THAT(test_future.Get(),
+              ElementsAre(Field(&ui::FileInfo::path, *download->full_path)));
 }
 
 // Verifies that the notification of a download with an unknown total bytes
@@ -630,6 +686,7 @@
   Profile* const profile = ProfileManager::GetActiveUserProfile();
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0);
 
   Update(download->Clone());
@@ -668,6 +725,7 @@
           }));
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(ProfileManager::GetActiveUserProfile(),
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -691,6 +749,7 @@
           }));
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   download->pausable = true;
@@ -803,6 +862,7 @@
           }));
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   Update(download->Clone());
@@ -860,6 +920,7 @@
   Profile* const profile = ProfileManager::GetActiveUserProfile();
   crosapi::mojom::DownloadStatusPtr download =
       CreateInProgressDownloadStatus(profile,
+                                     /*extension=*/"txt",
                                      /*received_bytes=*/0,
                                      /*total_bytes=*/1024);
   download->cancellable = true;
diff --git a/chrome/browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc b/chrome/browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc
index f0ee274..f22d9d0 100644
--- a/chrome/browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc
+++ b/chrome/browser/ui/aura/accessibility/automation_manager_aura_browsertest.cc
@@ -253,19 +253,22 @@
 
   views::View* view1 = new views::View();
   view1->GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-  view1->GetViewAccessibility().OverrideName("view1");
+  view1->GetViewAccessibility().SetName("view1",
+                                        ax::mojom::NameFrom::kAttribute);
   view1->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
   widget->GetRootView()->AddChildView(view1);
   views::AXAuraObjWrapper* wrapper1 = cache_ptr->GetOrCreate(view1);
   views::View* view2 = new views::View();
   view2->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
   view2->GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-  view2->GetViewAccessibility().OverrideName("view2");
+  view2->GetViewAccessibility().SetName("view2",
+                                        ax::mojom::NameFrom::kAttribute);
   widget->GetRootView()->AddChildView(view2);
   views::AXAuraObjWrapper* wrapper2 = cache_ptr->GetOrCreate(view2);
   views::View* view3 = new views::View();
   view3->GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-  view3->GetViewAccessibility().OverrideName("view3");
+  view3->GetViewAccessibility().SetName("view3",
+                                        ax::mojom::NameFrom::kAttribute);
   view3->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
   widget->GetRootView()->AddChildView(view3);
   views::AXAuraObjWrapper* wrapper3 = cache_ptr->GetOrCreate(view3);
@@ -528,13 +531,15 @@
 
   views::View* view1 = new views::View();
   view1->GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-  view1->GetViewAccessibility().OverrideName("view1");
+  view1->GetViewAccessibility().SetName("view1",
+                                        ax::mojom::NameFrom::kAttribute);
   view1->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
   widget->GetRootView()->AddChildView(view1);
   views::View* view2 = new views::View();
   view2->SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
   view2->GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-  view2->GetViewAccessibility().OverrideName("view2");
+  view2->GetViewAccessibility().SetName("view2",
+                                        ax::mojom::NameFrom::kAttribute);
   widget->GetRootView()->AddChildView(view2);
   views::AXAuraObjWrapper* wrapper2 = cache_ptr->GetOrCreate(view2);
 
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 92d0ea40..41a59c48 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -58,7 +58,7 @@
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
 #include "chrome/browser/ui/passwords/ui_utils.h"
 #include "chrome/browser/ui/plus_addresses/plus_address_creation_controller.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/url_constants.h"
 #include "components/autofill/content/browser/autofill_log_router_factory.h"
@@ -1211,11 +1211,12 @@
 }
 
 void ChromeAutofillClient::ShowAutofillErrorDialog(
-    const AutofillErrorDialogContext& context) {
-  autofill_error_dialog_controller_.Show(
-      context,
+    AutofillErrorDialogContext context) {
+  autofill_error_dialog_controller_ =
+      std::make_unique<AutofillErrorDialogControllerImpl>(std::move(context));
+  autofill_error_dialog_controller_->Show(
       base::BindOnce(&CreateAndShowAutofillErrorDialog,
-                     base::Unretained(&autofill_error_dialog_controller_),
+                     base::Unretained(autofill_error_dialog_controller_.get()),
                      base::Unretained(web_contents())));
 }
 
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index fd73d42..6685cc4 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -248,8 +248,7 @@
   void DismissOfferNotification() override;
   void OnVirtualCardDataAvailable(
       const VirtualCardManualFallbackBubbleOptions& options) override;
-  void ShowAutofillErrorDialog(
-      const AutofillErrorDialogContext& context) override;
+  void ShowAutofillErrorDialog(AutofillErrorDialogContext context) override;
   void TriggerUserPerceptionOfAutofillSurvey(
       const std::map<std::string, std::string>& field_filling_stats_data)
       override;
@@ -344,7 +343,8 @@
       autofill_cvc_save_message_delegate_;
 #endif
   std::unique_ptr<CardUnmaskPromptControllerImpl> unmask_controller_;
-  AutofillErrorDialogControllerImpl autofill_error_dialog_controller_;
+  std::unique_ptr<AutofillErrorDialogControllerImpl>
+      autofill_error_dialog_controller_;
   std::unique_ptr<CardUnmaskOtpInputDialogControllerImpl>
       card_unmask_otp_input_dialog_controller_;
 };
diff --git a/chrome/browser/ui/autofill/payments/desktop_payments_window_manager.cc b/chrome/browser/ui/autofill/payments/desktop_payments_window_manager.cc
index fe762b54..c985149 100644
--- a/chrome/browser/ui/autofill/payments/desktop_payments_window_manager.cc
+++ b/chrome/browser/ui/autofill/payments/desktop_payments_window_manager.cc
@@ -58,28 +58,40 @@
   params.source_contents = &source_contents;
   params.is_tab_modal_popup = true;
 
-  // TODO(crbug.com/1517762): Handle the case where the pop-up is not shown by
-  // displaying an error message.
   if (base::WeakPtr<content::NavigationHandle> navigation_handle =
           Navigate(&params)) {
     content::WebContentsObserver::Observe(navigation_handle->GetWebContents());
+  } else {
+    client_->ShowAutofillErrorDialog(
+        AutofillErrorDialogContext::WithVirtualCardPermanentOrTemporaryError(
+            /*is_permanent_error=*/false));
   }
 }
 
 void DesktopPaymentsWindowManager::OnWebContentsDestroyedForVcn3ds() {
   flow_type_ = FlowType::kNoFlow;
-  if (base::expected<PaymentsWindowManager::RedirectCompletionProof,
-                     PaymentsWindowManager::Vcn3dsAuthenticationPopupErrorType>
-          result = ParseFinalUrlForVcn3ds(web_contents()->GetVisibleURL());
-      result.has_value()) {
+  base::expected<RedirectCompletionProof, Vcn3dsAuthenticationPopupErrorType>
+      result = ParseFinalUrlForVcn3ds(web_contents()->GetVisibleURL());
+  if (result.has_value()) {
     CHECK(!result.value()->empty());
     return client_->GetPaymentsAutofillClient()->LoadRiskData(base::BindOnce(
         &DesktopPaymentsWindowManager::OnDidLoadRiskDataForVcn3ds,
         weak_ptr_factory_.GetWeakPtr(), std::move(result.value())));
   }
 
-  // TODO(crbug.com/1517762): Trigger an error dialog if `result` is
-  // `kAuthenticationFailed`.
+  switch (result.error()) {
+    case Vcn3dsAuthenticationPopupErrorType::kAuthenticationFailed:
+    case Vcn3dsAuthenticationPopupErrorType::kInvalidQueryParams:
+      client_->ShowAutofillErrorDialog(
+          AutofillErrorDialogContext::WithVirtualCardPermanentOrTemporaryError(
+              /*is_permanent_error=*/true));
+      break;
+    case Vcn3dsAuthenticationPopupErrorType::kAuthenticationNotCompleted:
+      break;
+  }
+
+  // In the case of an error, we show the user an error dialog but still run the
+  // callback to let the caller know failure occurred.
   std::move(vcn_3ds_context_->completion_callback)
       .Run(Vcn3dsAuthenticationResponse());
   vcn_3ds_context_.reset();
@@ -109,8 +121,12 @@
   client_->GetPaymentsAutofillClient()->CloseAutofillProgressDialog(
       /*show_confirmation_before_closing=*/response.card.has_value(),
       /*no_interactive_authentication_callback=*/base::OnceClosure());
-  // TODO(crbug.com/1517762): Trigger an error dialog if no card is present in
-  // `response`.
+  if (!response.card.has_value()) {
+    client_->ShowAutofillErrorDialog(
+        AutofillErrorDialogContext::WithVirtualCardPermanentOrTemporaryError(
+            /*is_permanent_error=*/true));
+  }
+
   std::move(vcn_3ds_context_->completion_callback).Run(std::move(response));
   vcn_3ds_context_.reset();
 }
diff --git a/chrome/browser/ui/autofill/payments/desktop_payments_window_manager_interactive_uitest.cc b/chrome/browser/ui/autofill/payments/desktop_payments_window_manager_interactive_uitest.cc
index 23f30fa..26ec4bc 100644
--- a/chrome/browser/ui/autofill/payments/desktop_payments_window_manager_interactive_uitest.cc
+++ b/chrome/browser/ui/autofill/payments/desktop_payments_window_manager_interactive_uitest.cc
@@ -234,6 +234,7 @@
   EXPECT_EQ(response->card->expiration_year(), expiration_year);
   EXPECT_EQ(response->card->record_type(),
             CreditCard::RecordType::kVirtualCard);
+  EXPECT_FALSE(client()->autofill_error_dialog_shown());
 }
 
 // Test that the VCN 3DS pop-up is shown correctly, and on close an
@@ -264,6 +265,7 @@
       authentication_response();
   ASSERT_TRUE(response.has_value());
   EXPECT_FALSE(response->card.has_value());
+  EXPECT_TRUE(client()->autofill_error_dialog_shown());
 }
 
 // Test that the VCN 3DS pop-up is shown correctly, and on close an
@@ -285,6 +287,7 @@
       authentication_response();
   ASSERT_TRUE(response.has_value());
   EXPECT_FALSE(response->card.has_value());
+  EXPECT_FALSE(client()->autofill_error_dialog_shown());
 }
 
 // Test that the VCN 3DS pop-up is shown correctly, and on close an
@@ -314,6 +317,7 @@
       authentication_response();
   ASSERT_TRUE(response.has_value());
   EXPECT_FALSE(response->card.has_value());
+  EXPECT_TRUE(client()->autofill_error_dialog_shown());
 }
 
 // Test that the VCN 3DS pop-up is shown correctly, and when the user cancels
diff --git a/chrome/browser/ui/browser_commands.cc b/chrome/browser/ui/browser_commands.cc
index c2a9e1d3..32e77c6 100644
--- a/chrome/browser/ui/browser_commands.cc
+++ b/chrome/browser/ui/browser_commands.cc
@@ -1889,12 +1889,12 @@
 
 void OpenFeedbackDialog(Browser* browser,
                         FeedbackSource source,
-                        const std::string& description_template) {
+                        const std::string& description_template,
+                        const std::string& category_tag) {
   base::RecordAction(UserMetricsAction("Feedback"));
   chrome::ShowFeedbackPage(browser, source, description_template,
                            std::string() /* description_placeholder_text */,
-                           std::string() /* category_tag */,
-                           std::string() /* extra_diagnostics */);
+                           category_tag, std::string() /* extra_diagnostics */);
 }
 
 void ToggleBookmarkBar(Browser* browser) {
diff --git a/chrome/browser/ui/browser_commands.h b/chrome/browser/ui/browser_commands.h
index c4aff62..7f52ce0 100644
--- a/chrome/browser/ui/browser_commands.h
+++ b/chrome/browser/ui/browser_commands.h
@@ -223,10 +223,10 @@
 bool CanOpenTaskManager();
 // Opens task manager UI. Note that |browser| can be nullptr as input.
 void OpenTaskManager(Browser* browser);
-void OpenFeedbackDialog(
-    Browser* browser,
-    FeedbackSource source,
-    const std::string& description_template = std::string());
+void OpenFeedbackDialog(Browser* browser,
+                        FeedbackSource source,
+                        const std::string& description_template = std::string(),
+                        const std::string& category_tag = std::string());
 void ToggleBookmarkBar(Browser* browser);
 void ToggleShowFullURLs(Browser* browser);
 void ShowAppMenu(Browser* browser);
diff --git a/chrome/browser/ui/chrome_pages.h b/chrome/browser/ui/chrome_pages.h
index 227fcba8..86fb34c3 100644
--- a/chrome/browser/ui/chrome_pages.h
+++ b/chrome/browser/ui/chrome_pages.h
@@ -127,6 +127,7 @@
   kFeedbackSourceLogin,
   kFeedbackSourceAI,
   kFeedbackSourceFocusMode,
+  kFeedbackSourceOverview,
 
   // ATTENTION: Before making any changes or adding to feedback collection,
   // please ensure the teams that operationalize feedback are aware and
diff --git a/chrome/browser/ui/color/chrome_color_id.h b/chrome/browser/ui/color/chrome_color_id.h
index 6d8f25c..86443a5d 100644
--- a/chrome/browser/ui/color/chrome_color_id.h
+++ b/chrome/browser/ui/color/chrome_color_id.h
@@ -337,6 +337,7 @@
   E_CPONLY(kColorPageInfoChosenObjectDeleteButtonIconDisabled) \
   E_CPONLY(kColorPageInfoIconHover) \
   E_CPONLY(kColorPageInfoIconPressed) \
+  E_CPONLY(kColorPageInfoPermissionUsedIcon) \
   /* Payments colors. */ \
   E_CPONLY(kColorPaymentsFeedbackTipBackground) \
   E_CPONLY(kColorPaymentsFeedbackTipBorder) \
diff --git a/chrome/browser/ui/color/material_chrome_color_mixer.cc b/chrome/browser/ui/color/material_chrome_color_mixer.cc
index 4c69799..7668f21b 100644
--- a/chrome/browser/ui/color/material_chrome_color_mixer.cc
+++ b/chrome/browser/ui/color/material_chrome_color_mixer.cc
@@ -70,6 +70,9 @@
   mixer[kColorExtensionsMenuText] = {ui::kColorSysOnSurface};
   mixer[kColorExtensionsMenuSecondaryText] = {ui::kColorSysOnSurfaceSubtle};
 
+  // PageInfo colors.
+  mixer[kColorPageInfoPermissionUsedIcon] = {ui::kColorSysPrimary};
+
   // Permission Prompt colors.
   mixer[kColorPermissionPromptRequestText] = {ui::kColorSysOnSurfaceSubtle};
 
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index e5d226e..fc33584 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -589,6 +589,12 @@
 
   const GURL popup_url = extension_action_->GetPopupUrl(tab_id);
 
+  // Skip popup if there is an open security UI that would be covered by it,
+  // mitigation occlusion/spoofing risks.
+  if (extensions_container_->HasBlockingSecurityUI()) {
+    return;
+  }
+
   std::unique_ptr<extensions::ExtensionViewHost> host =
       extensions::ExtensionViewHostFactory::CreatePopupHost(popup_url,
                                                             browser_);
diff --git a/chrome/browser/ui/extensions/extensions_container.h b/chrome/browser/ui/extensions/extensions_container.h
index 9bc14c7..3c79588 100644
--- a/chrome/browser/ui/extensions/extensions_container.h
+++ b/chrome/browser/ui/extensions/extensions_container.h
@@ -77,6 +77,10 @@
   // Whether there are any Extensions registered with the ExtensionsContainer.
   virtual bool HasAnyExtensions() const = 0;
 
+  // Whether there is any security UI in the browser window that would
+  // overlap with the extensions popup.
+  virtual bool HasBlockingSecurityUI() const = 0;
+
   // Updates the hover card for `action_view` based on `update_type`.
   virtual void UpdateToolbarActionHoverCard(
       ToolbarActionView* action_view,
diff --git a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java
index a529ede..ebeab406 100644
--- a/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java
+++ b/chrome/browser/ui/messages/android/java/src/org/chromium/chrome/browser/ui/messages/snackbar/Snackbar.java
@@ -115,6 +115,7 @@
     public static final int UMA_QUICK_DELETE = 60;
     public static final int UMA_AUTO_TRANSLATE = 61;
     public static final int UMA_BOOKMARK_MOVED = 62;
+    public static final int UMA_CLEAR_BROWSING_DATA = 63;
 
     private @Nullable SnackbarController mController;
     private CharSequence mText;
diff --git a/chrome/browser/ui/page_info/page_info_unittest.cc b/chrome/browser/ui/page_info/page_info_unittest.cc
index acb633e..5691c6f 100644
--- a/chrome/browser/ui/page_info/page_info_unittest.cc
+++ b/chrome/browser/ui/page_info/page_info_unittest.cc
@@ -152,7 +152,6 @@
     // TODO(crbug.com/1344787): Fix tests and enable the feature.
     scoped_feature_list_.InitWithFeatures(
         {
-            permissions::features::kPermissionStorageAccessAPI,
 #if !BUILDFLAG(IS_ANDROID)
             features::kFileSystemAccessPersistentPermissions,
 #endif
diff --git a/chrome/browser/ui/views/autofill/payments/autofill_error_dialog_view_native_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/autofill_error_dialog_view_native_views_browsertest.cc
index b4102bb..4831b7a 100644
--- a/chrome/browser/ui/views/autofill/payments/autofill_error_dialog_view_native_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/autofill_error_dialog_view_native_views_browsertest.cc
@@ -26,10 +26,7 @@
     : public DialogBrowserTest,
       public testing::WithParamInterface<std::tuple<bool, bool>> {
  public:
-  AutofillErrorDialogViewNativeViewsBrowserTest() {
-    autofill_error_dialog_controller_ =
-        std::make_unique<AutofillErrorDialogControllerImpl>();
-  }
+  AutofillErrorDialogViewNativeViewsBrowserTest() = default;
 
   ~AutofillErrorDialogViewNativeViewsBrowserTest() override = default;
 
@@ -61,8 +58,10 @@
           AutofillErrorDialogType::kVirtualCardNotEligibleError;
     }
 
+    autofill_error_dialog_controller_ =
+        std::make_unique<AutofillErrorDialogControllerImpl>(
+            autofill_error_dialog_context);
     autofill_error_dialog_controller_->Show(
-        autofill_error_dialog_context,
         base::BindOnce(&CreateAndShowAutofillErrorDialog,
                        base::Unretained(controller()),
                        base::Unretained(contents())));
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_uitest.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_uitest.cc
index 2314f03d..de84c84 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_uitest.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_uitest.cc
@@ -45,7 +45,7 @@
 #include "chrome/browser/ui/views/page_action/page_action_icon_loading_indicator_view.h"
 #include "chrome/browser/ui/views/page_action/page_action_icon_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
diff --git a/chrome/browser/ui/views/autofill/popup/popup_pixel_test.h b/chrome/browser/ui/views/autofill/popup/popup_pixel_test.h
index 3008e33..54b1333 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_pixel_test.h
+++ b/chrome/browser/ui/views/autofill/popup/popup_pixel_test.h
@@ -37,36 +37,37 @@
 // By default, the test class has two parameters: Dark vs light mode and RTL vs
 // LTR for the text direction of the browser language.
 template <std::derived_from<PopupBaseView> View,
-          std::derived_from<AutofillPopupViewDelegate> Controller>
+          std::derived_from<AutofillPopupViewDelegate> Controller,
+          typename ParameterType = TestParameterType>
 class PopupPixelTest : public UiBrowserTest,
-                       public testing::WithParamInterface<TestParameterType> {
+                       public testing::WithParamInterface<ParameterType> {
  public:
   PopupPixelTest() = default;
   ~PopupPixelTest() override = default;
 
-  static bool IsDarkModeOn(const TestParameterType& param) {
+  static bool IsDarkModeOn(const ParameterType& param) {
     return std::get<0>(param);
   }
-  static bool IsBrowserLanguageRTL(const TestParameterType& param) {
+  static bool IsBrowserLanguageRTL(const ParameterType& param) {
     return std::get<1>(param);
   }
 
   static std::string GetTestSuffix(
-      const testing::TestParamInfo<TestParameterType>& param_info) {
+      const testing::TestParamInfo<ParameterType>& param_info) {
     return base::StrCat(
         {IsDarkModeOn(param_info.param) ? "Dark" : "Light",
          IsBrowserLanguageRTL(param_info.param) ? "BrowserRTL" : "BrowserLTR"});
   }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
-    if (IsDarkModeOn(GetParam())) {
+    if (IsDarkModeOn(this->GetParam())) {
       command_line->AppendSwitch(switches::kForceDarkMode);
     }
   }
 
   void SetUpOnMainThread() override {
     UiBrowserTest::SetUpOnMainThread();
-    base::i18n::SetRTLForTesting(IsBrowserLanguageRTL(GetParam()));
+    base::i18n::SetRTLForTesting(IsBrowserLanguageRTL(this->GetParam()));
 
     content::WebContents* web_contents =
         browser()->tab_strip_model()->GetActiveWebContents();
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_view.cc b/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
index 3e0c511..53c5ebd 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
@@ -245,9 +245,11 @@
   content_view_->AddObserver(this);
   content_view_->GetViewAccessibility().SetRole(
       ax::mojom::Role::kListBoxOption);
-  content_view_->GetViewAccessibility().OverrideName(GetSuggestionA11yString(
-      suggestion,
-      /*add_call_to_action_if_expandable=*/suggestion.is_acceptable));
+  content_view_->GetViewAccessibility().SetName(
+      GetSuggestionA11yString(
+          suggestion,
+          /*add_call_to_action_if_expandable=*/suggestion.is_acceptable),
+      ax::mojom::NameFrom::kAttribute);
   auto [position, set_size] = ComputePositionInSet(controller_, line_number);
   content_view_->GetViewAccessibility().SetPosInSet(position);
   content_view_->GetViewAccessibility().SetSetSize(set_size);
diff --git a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_unittest.cc b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_unittest.cc
index 0b4c6b84..c23ca9b 100644
--- a/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_unittest.cc
+++ b/chrome/browser/ui/views/bookmarks/bookmark_bubble_view_unittest.cc
@@ -14,8 +14,11 @@
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
 #include "chrome/browser/commerce/shopping_service_factory.h"
 #include "chrome/browser/feature_engagement/tracker_factory.h"
+#include "chrome/browser/signin/chrome_signin_client_factory.h"
+#include "chrome/browser/signin/chrome_signin_client_test_util.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/browser/signin/identity_test_environment_profile_adaptor.h"
+#include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/commerce/mock_commerce_ui_tab_helper.h"
 #include "chrome/browser/ui/signin/bubble_signin_promo_delegate.h"
@@ -32,6 +35,7 @@
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/feature_engagement/test/mock_tracker.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
+#include "components/sync/test/test_sync_service.h"
 #include "ui/base/interaction/element_identifier.h"
 #include "ui/base/interaction/element_tracker.h"
 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
@@ -100,6 +104,16 @@
         {commerce::ShoppingServiceFactory::GetInstance(),
          base::BindRepeating([](content::BrowserContext* context) {
            return commerce::MockShoppingService::Build();
+         })},
+        // Used by IdentityTestEnvironmentProfileAdaptor.
+        {ChromeSigninClientFactory::GetInstance(),
+         base::BindRepeating(&BuildChromeSigninClientWithURLLoader,
+                             test_url_loader_factory())},
+        // Used by ImageService.
+        {SyncServiceFactory::GetInstance(),
+         base::BindRepeating([](content::BrowserContext*) {
+           return static_cast<std::unique_ptr<KeyedService>>(
+               std::make_unique<syncer::TestSyncService>());
          })}};
     IdentityTestEnvironmentProfileAdaptor::
         AppendIdentityTestEnvironmentFactories(&factories);
diff --git a/chrome/browser/ui/views/constrained_window_views_browsertest.cc b/chrome/browser/ui/views/constrained_window_views_browsertest.cc
index 86ec6c6..a5c8516 100644
--- a/chrome/browser/ui/views/constrained_window_views_browsertest.cc
+++ b/chrome/browser/ui/views/constrained_window_views_browsertest.cc
@@ -35,7 +35,8 @@
     // Dialogs that take focus must have a name and role to pass accessibility
     // checks.
     GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-    GetViewAccessibility().OverrideName("Test dialog");
+    GetViewAccessibility().SetName("Test dialog",
+                                   ax::mojom::NameFrom::kAttribute);
   }
 
   TestDialog(const TestDialog&) = delete;
diff --git a/chrome/browser/ui/views/desktop_capture/desktop_media_tab_list.cc b/chrome/browser/ui/views/desktop_capture/desktop_media_tab_list.cc
index 48ee4181..7d304b66 100644
--- a/chrome/browser/ui/views/desktop_capture/desktop_media_tab_list.cc
+++ b/chrome/browser/ui/views/desktop_capture/desktop_media_tab_list.cc
@@ -244,7 +244,8 @@
       model_.get(), std::vector<ui::TableColumn>(1),
       views::TableType::kIconAndText, true);
   table->set_observer(view_observer_.get());
-  table->GetViewAccessibility().OverrideName(accessible_name);
+  table->GetViewAccessibility().SetName(accessible_name,
+                                        ax::mojom::NameFrom::kAttribute);
   table_ = table.get();
 
   AddChildView(BuildUI(std::move(table)));
diff --git a/chrome/browser/ui/views/device_chooser_content_view.cc b/chrome/browser/ui/views/device_chooser_content_view.cc
index 4419375..3975dc89 100644
--- a/chrome/browser/ui/views/device_chooser_content_view.cc
+++ b/chrome/browser/ui/views/device_chooser_content_view.cc
@@ -73,8 +73,10 @@
   table_view_ = table_view.get();
   table_view->SetSelectOnRemove(false);
   table_view->set_observer(table_view_observer);
-  table_view->GetViewAccessibility().OverrideName(l10n_util::GetStringUTF16(
-      IDS_DEVICE_CHOOSER_ACCNAME_COMPATIBLE_DEVICES_LIST));
+  table_view->GetViewAccessibility().SetName(
+      l10n_util::GetStringUTF16(
+          IDS_DEVICE_CHOOSER_ACCNAME_COMPATIBLE_DEVICES_LIST),
+      ax::mojom::NameFrom::kAttribute);
 
   table_parent_ = AddChildView(
       views::TableView::CreateScrollViewWithTable(std::move(table_view)));
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
index 5f29316..690ee0c 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
@@ -40,6 +40,7 @@
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/autofill/content/browser/content_autofill_client.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/safe_browsing_policy_handler.h"
@@ -693,6 +694,7 @@
     button_click_time_ = base::TimeTicks();
   }
 
+  CloseAutofillPopup();
   if (ShouldShowBubbleAsInactive()) {
     bubble_delegate_->GetWidget()->ShowInactive();
     bubble_closer_ = std::make_unique<BubbleCloser>(this);
@@ -873,6 +875,19 @@
   return is_primary_partial_view_;
 }
 
+void DownloadToolbarButtonView::CloseAutofillPopup() {
+  content::WebContents* web_contents =
+      browser_->tab_strip_model()->GetActiveWebContents();
+  if (!web_contents) {
+    return;
+  }
+  if (auto* autofill_client =
+          autofill::ContentAutofillClient::FromWebContents(web_contents)) {
+    autofill_client->HideAutofillPopup(
+        autofill::PopupHidingReason::kOverlappingWithAnotherPrompt);
+  }
+}
+
 SkColor DownloadToolbarButtonView::GetIconColor() const {
   if (is_dormant_) {
     return GetColorProvider()->GetColor(kColorDownloadToolbarButtonInactive);
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
index 03a51218..89afe66 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
@@ -230,6 +230,8 @@
 
   bool ShouldShowBubbleAsInactive() const;
 
+  void CloseAutofillPopup();
+
   // Whether to show the progress ring as a continuously spinning ring, during
   // deep scanning or if the progress is indeterminate.
   bool ShouldShowScanningAnimation() const;
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc
index 3328c5c..3900acc 100644
--- a/chrome/browser/ui/views/download/download_item_view.cc
+++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -929,7 +929,7 @@
   views::ViewAccessibility& ax = accessible_alert_->GetViewAccessibility();
   ax.SetRole(ax::mojom::Role::kAlert);
   if (!accessible_alert_text.empty())
-    ax.OverrideName(accessible_alert_text);
+    ax.SetName(accessible_alert_text, ax::mojom::NameFrom::kAttribute);
   if (announce_accessible_alert_soon_ || !accessible_alert_timer_.IsRunning()) {
     AnnounceAccessibleAlert();
     accessible_alert_timer_.Reset();
diff --git a/chrome/browser/ui/views/download/download_shelf_view.cc b/chrome/browser/ui/views/download/download_shelf_view.cc
index e90cef0..7db2277 100644
--- a/chrome/browser/ui/views/download/download_shelf_view.cc
+++ b/chrome/browser/ui/views/download/download_shelf_view.cc
@@ -94,8 +94,8 @@
   }
 
   views::ViewAccessibility& accessibility = GetViewAccessibility();
-  accessibility.OverrideName(
-      l10n_util::GetStringUTF16(IDS_ACCNAME_DOWNLOADS_BAR));
+  accessibility.SetName(l10n_util::GetStringUTF16(IDS_ACCNAME_DOWNLOADS_BAR),
+                        ax::mojom::NameFrom::kAttribute);
   accessibility.SetRole(ax::mojom::Role::kGroup);
 
   // Delay 5 seconds if the mouse leaves the shelf by way of entering another
diff --git a/chrome/browser/ui/views/extensions/extensions_menu_view.cc b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
index 0a2fcf2..17b8fca 100644
--- a/chrome/browser/ui/views/extensions/extensions_menu_view.cc
+++ b/chrome/browser/ui/views/extensions/extensions_menu_view.cc
@@ -110,7 +110,7 @@
       GetAccessibleWindowTitle().empty()
           ? ax::mojom::NameFrom::kAttributeExplicitlyEmpty
           : ax::mojom::NameFrom::kAttribute;
-  GetViewAccessibility().OverrideName(GetAccessibleWindowTitle(), name_from);
+  GetViewAccessibility().SetName(GetAccessibleWindowTitle(), name_from);
 
   SetEnableArrowKeyTraversal(true);
 
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
index cca5874..d5af9dc 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.cc
@@ -64,22 +64,6 @@
   return *callback;
 }
 
-// Check if there's any security UI that might be spoofable because of
-// overlapping with the extension popup. The media picker dialog has been
-// identified to be susceptible. See crbug.com/1300006.
-bool HasPossiblyOverlappingSecurityUI(Browser* browser) {
-  views::ElementTrackerViews::ViewList media_picker_dialogs =
-      views::ElementTrackerViews::GetInstance()->GetAllMatchingViews(
-          DesktopMediaPickerDialogView::kDesktopMediaPickerDialogViewIdentifier,
-          browser->window()->GetElementContext());
-
-  return std::any_of(media_picker_dialogs.begin(), media_picker_dialogs.end(),
-                     [](views::View* dialog_view) {
-                       views::Widget* dialog_widget = dialog_view->GetWidget();
-                       return dialog_widget && dialog_widget->IsVisible();
-                     });
-}
-
 }  // namespace
 
 void ExtensionsToolbarContainer::SetOnVisibleCallbackForTesting(
@@ -643,10 +627,6 @@
   if (popped_out_action_ || !browser_->window()->IsActive())
     return false;
 
-  // Don't draw over security UIs.
-  if (HasPossiblyOverlappingSecurityUI(browser_))
-    return false;
-
   ToolbarActionViewController* action = GetActionForId(action_id);
   DCHECK(action);
   action->TriggerPopupForAPI(std::move(callback));
@@ -676,6 +656,28 @@
   return !actions_.empty();
 }
 
+bool ExtensionsToolbarContainer::HasBlockingSecurityUI() const {
+  // Check if there's any security UI that might be spoofable because of
+  // overlapping with the extension popup. The media picker dialog has been
+  // identified to be susceptible. See crbug.com/40058873.
+  // Not all security UIs are blocking. Non-blocking security UIs can avoid
+  // being occluded by setting a higher z-order (sub)level. In contrast,
+  // blocking security UIs cannot leverage z-ordering because they share the
+  // same rendering layer with the browser window.
+  // TODO(crbug.com/326681253): block on other possibly overlapping security
+  // UIs.
+  views::ElementTrackerViews::ViewList media_picker_dialogs =
+      views::ElementTrackerViews::GetInstance()->GetAllMatchingViews(
+          DesktopMediaPickerDialogView::kDesktopMediaPickerDialogViewIdentifier,
+          browser_->window()->GetElementContext());
+
+  return std::any_of(media_picker_dialogs.begin(), media_picker_dialogs.end(),
+                     [](views::View* dialog_view) {
+                       views::Widget* dialog_widget = dialog_view->GetWidget();
+                       return dialog_widget && dialog_widget->IsVisible();
+                     });
+}
+
 void ExtensionsToolbarContainer::ReorderViews() {
   const auto& pinned_action_ids = model_->pinned_action_ids();
   for (size_t i = 0; i < pinned_action_ids.size(); ++i)
diff --git a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
index 087febe7..42dd240 100644
--- a/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
+++ b/chrome/browser/ui/views/extensions/extensions_toolbar_container.h
@@ -203,6 +203,7 @@
       std::unique_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
   void ToggleExtensionsMenu() override;
   bool HasAnyExtensions() const override;
+  bool HasBlockingSecurityUI() const override;
   void UpdateToolbarActionHoverCard(
       ToolbarActionView* action_view,
       ToolbarActionHoverCardUpdateType update_type) override;
diff --git a/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm b/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm
index e0a0ff2..6a59095 100644
--- a/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm
+++ b/chrome/browser/ui/views/frame/immersive_mode_controller_mac.mm
@@ -96,9 +96,8 @@
                                 browser_view_->frame()->GetFrameView())
                                 ->GetTopInset(false);
 
-      browser_view_->tab_overlay_widget()->SetBounds(
-          gfx::Rect(0, 0, browser_view_->top_container()->size().width(),
-                    tab_widget_height_));
+      browser_view_->tab_overlay_widget()->SetSize(gfx::Size(
+          browser_view_->top_container()->size().width(), tab_widget_height_));
       browser_view_->tab_overlay_widget()->Show();
 
       // Move the tab strip to the `tab_overlay_widget`, the host of the
@@ -345,7 +344,7 @@
         new_size.width(), browser_view_->tab_strip_region_view()->height()));
     overlay_height_ += tab_widget_height_;
   }
-  browser_view_->overlay_widget()->SetBounds(bounds);
+  browser_view_->overlay_widget()->SetSize(bounds.size());
   if (auto* window = GetNSWindowMojo()) {
     window->OnTopContainerViewBoundsChanged(bounds);
   }
diff --git a/chrome/browser/ui/views/frame/test_with_browser_view.cc b/chrome/browser/ui/views/frame/test_with_browser_view.cc
index 8433f81..ffc6fac 100644
--- a/chrome/browser/ui/views/frame/test_with_browser_view.cc
+++ b/chrome/browser/ui/views/frame/test_with_browser_view.cc
@@ -23,7 +23,7 @@
 #include "chrome/browser/signin/chrome_signin_client_test_util.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 #include "chrome/test/base/browser_with_test_window_test.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "components/omnibox/browser/autocomplete_classifier.h"
diff --git a/chrome/browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc b/chrome/browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc
index 98cc0ac..7f3c240 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/location_bar/content_setting_bubble_dialog_browsertest.cc
@@ -114,8 +114,7 @@
                       GetPopupNavigationDelegateFactoryForTesting(),
                   &CreateTestPopupNavigationDelegate) {
     scoped_feature_list_.InitWithFeatures(
-        {features::kQuietNotificationPrompts,
-         permissions::features::kPermissionStorageAccessAPI},
+        {features::kQuietNotificationPrompts},
         // Cookies icon intentionally does not show when 3PC are blocked.
         {content_settings::features::kTrackingProtection3pcd});
   }
diff --git a/chrome/browser/ui/views/page_info/page_info_view_factory.cc b/chrome/browser/ui/views/page_info/page_info_view_factory.cc
index d2230dc..ce0c0d7 100644
--- a/chrome/browser/ui/views/page_info/page_info_view_factory.cc
+++ b/chrome/browser/ui/views/page_info/page_info_view_factory.cc
@@ -15,6 +15,7 @@
 #include "build/chromeos_buildflags.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/page_info/page_info_features.h"
+#include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/page_info/chrome_page_info_ui_delegate.h"
 #include "chrome/browser/ui/view_ids.h"
@@ -407,6 +408,10 @@
     // If there is no ChromeRefreshIcon currently defined, continue to the rest
     // of the function.
     if (icon != nullptr) {
+      if (info.is_in_use && !show_blocked_badge) {
+        return ui::ImageModel::FromVectorIcon(
+            *icon, kColorPageInfoPermissionUsedIcon, GetIconSize());
+      }
       return ui::ImageModel::FromVectorIcon(*icon, ui::kColorIcon,
                                             GetIconSize());
     }
diff --git a/chrome/browser/ui/views/page_info/permission_toggle_row_view.cc b/chrome/browser/ui/views/page_info/permission_toggle_row_view.cc
index d8a0145..63fe4f1c 100644
--- a/chrome/browser/ui/views/page_info/permission_toggle_row_view.cc
+++ b/chrome/browser/ui/views/page_info/permission_toggle_row_view.cc
@@ -270,6 +270,7 @@
 void PermissionToggleRowView::ResetPermission() {
   permission_.setting = CONTENT_SETTING_DEFAULT;
   permission_.is_one_time = false;
+  permission_.is_in_use = false;
   PermissionChanged();
 }
 
diff --git a/chrome/browser/ui/views/passwords/password_generation_popup_view_views_browsertest.cc b/chrome/browser/ui/views/passwords/password_generation_popup_view_views_browsertest.cc
index 8d3f75b3..a540b59 100644
--- a/chrome/browser/ui/views/passwords/password_generation_popup_view_views_browsertest.cc
+++ b/chrome/browser/ui/views/passwords/password_generation_popup_view_views_browsertest.cc
@@ -7,12 +7,15 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
+#include "base/strings/strcat.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ui/passwords/password_generation_popup_controller.h"
 #include "chrome/browser/ui/views/autofill/popup/popup_pixel_test.h"
 #include "chrome/browser/ui/views/passwords/password_generation_popup_view_views.h"
 #include "chrome/grit/branded_strings.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "components/password_manager/core/browser/features/password_features.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -24,6 +27,7 @@
 using ::testing::Combine;
 using ::testing::Return;
 using ::testing::ReturnRef;
+using ::testing::Values;
 
 constexpr char16_t kSampleEmail[] = u"test-account@gmail.com";
 
@@ -76,7 +80,11 @@
     : public autofill::PopupPixelTest<PasswordGenerationPopupViewViews,
                                       MockPasswordGenerationPopupController> {
  public:
-  PasswordGenerationPopupViewBrowsertest() = default;
+  PasswordGenerationPopupViewBrowsertest() {
+    // TODO(crbug.com/326949412): Remove once the experiment concludes.
+    feature_list_.InitAndDisableFeature(
+        password_manager::features::kPasswordGenerationExperiment);
+  }
   ~PasswordGenerationPopupViewBrowsertest() override = default;
 
   void SetUpOnMainThread() override {
@@ -90,11 +98,13 @@
     ON_CALL(controller(), password).WillByDefault(ReturnRef(password_));
   }
 
-  void PrepareOfferGenerationState(const std::u16string& suggested_text) {
+  void PrepareOfferGenerationState() {
     ON_CALL(controller(), state)
         .WillByDefault(Return(PasswordGenerationPopupController::
                                   GenerationUIState::kOfferGeneration));
-    ON_CALL(controller(), SuggestedText).WillByDefault(Return(suggested_text));
+    ON_CALL(controller(), SuggestedText)
+        .WillByDefault(Return(
+            l10n_util::GetStringUTF16(IDS_PASSWORD_GENERATION_SUGGESTION_GPM)));
   }
 
   void PrepareEditingSuggestionState() {
@@ -132,52 +142,23 @@
  private:
   static constexpr gfx::RectF kElementBounds{100, 100, 250, 50};
   const std::u16string password_{u"123!-scfFGamFD"};
+  base::test::ScopedFeatureList feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_P(PasswordGenerationPopupViewBrowsertest,
                        OfferPasswordGeneration) {
-  PrepareOfferGenerationState(
-      l10n_util::GetStringUTF16(IDS_PASSWORD_GENERATION_SUGGESTION_GPM));
+  PrepareOfferGenerationState();
   ShowAndVerifyUi();
 }
 
 IN_PROC_BROWSER_TEST_P(PasswordGenerationPopupViewBrowsertest,
                        OfferPasswordGenerationHovered) {
-  PrepareOfferGenerationState(
-      l10n_util::GetStringUTF16(IDS_PASSWORD_GENERATION_SUGGESTION_GPM));
+  PrepareOfferGenerationState();
   SetSelected(true);
   ShowAndVerifyUi();
 }
 
 IN_PROC_BROWSER_TEST_P(PasswordGenerationPopupViewBrowsertest,
-                       OfferPasswordGenerationWithTrustedAdvice) {
-  PrepareOfferGenerationState(l10n_util::GetStringUTF16(
-      IDS_PASSWORD_GENERATION_SUGGESTION_TRUSTED_ADVICE));
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_P(PasswordGenerationPopupViewBrowsertest,
-                       OfferPasswordGenerationWithSafetyFirst) {
-  PrepareOfferGenerationState(l10n_util::GetStringUTF16(
-      IDS_PASSWORD_GENERATION_SUGGESTION_SAFETY_FIRST));
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_P(PasswordGenerationPopupViewBrowsertest,
-                       OfferPasswordGenerationWithTrySomethingNew) {
-  PrepareOfferGenerationState(l10n_util::GetStringUTF16(
-      IDS_PASSWORD_GENERATION_SUGGESTION_TRY_SOMETHING_NEW));
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_P(PasswordGenerationPopupViewBrowsertest,
-                       OfferPasswordGenerationWithConvenience) {
-  PrepareOfferGenerationState(l10n_util::GetStringUTF16(
-      IDS_PASSWORD_GENERATION_SUGGESTION_CONVENIENCE));
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_P(PasswordGenerationPopupViewBrowsertest,
                        EditingSuggestionState) {
   PrepareEditingSuggestionState();
   ShowAndVerifyUi();
@@ -194,3 +175,90 @@
                          PasswordGenerationPopupViewBrowsertest,
                          Combine(Bool(), Bool()),
                          PasswordGenerationPopupViewBrowsertest::GetTestSuffix);
+
+using ExperimentParameterType = std::tuple<bool, bool, std::string>;
+
+// TODO(crbug.com/326949412): Remove once the experiments conclude.
+class PasswordGenerationPopupViewWithExperimentsBrowsertest
+    : public autofill::PopupPixelTest<PasswordGenerationPopupViewViews,
+                                      MockPasswordGenerationPopupController,
+                                      ExperimentParameterType> {
+ public:
+  PasswordGenerationPopupViewWithExperimentsBrowsertest() {
+    feature_list_.InitWithFeaturesAndParameters(
+        /*enabled_features=*/{{password_manager::features::
+                                   kPasswordGenerationExperiment,
+                               {{"password_generation_variation",
+                                 std::get<2>(GetParam())}}}},
+        /*disabled_features=*/{});
+  }
+  ~PasswordGenerationPopupViewWithExperimentsBrowsertest() override = default;
+
+  static std::string GetExperimentTestSuffix(
+      const testing::TestParamInfo<ExperimentParameterType>& param_info) {
+    return base::StrCat(
+        {std::get<0>(param_info.param) ? "Dark" : "Light",
+         std::get<1>(param_info.param) ? "BrowserRTL" : "BrowserLTR",
+         std::get<2>(param_info.param)});
+  }
+
+  void SetUpOnMainThread() override {
+    PopupPixelTest::SetUpOnMainThread();
+
+    ON_CALL(controller(), element_bounds())
+        .WillByDefault(ReturnRef(kElementBounds));
+
+    ON_CALL(controller(), GetPrimaryAccountEmail)
+        .WillByDefault(Return(kSampleEmail));
+    ON_CALL(controller(), password).WillByDefault(ReturnRef(password_));
+  }
+
+  void PrepareOfferGenerationState() {
+    ON_CALL(controller(), state)
+        .WillByDefault(Return(PasswordGenerationPopupController::
+                                  GenerationUIState::kOfferGeneration));
+    ON_CALL(controller(), SuggestedText)
+        .WillByDefault(Return(
+            l10n_util::GetStringUTF16(IDS_PASSWORD_GENERATION_SUGGESTION_GPM)));
+  }
+
+  void ShowUi(const std::string& name) override {
+    PopupPixelTest::ShowUi(name);
+    ASSERT_TRUE(view()->Show());
+  }
+
+ protected:
+  // autofill::PopupPixelTest:
+  PasswordGenerationPopupViewViews* CreateView(
+      MockPasswordGenerationPopupController& controller) override {
+    return new PasswordGenerationPopupViewViews(
+        controller.GetWeakPtr(), views::Widget::GetWidgetForNativeWindow(
+                                     browser()->window()->GetNativeWindow()));
+  }
+
+ private:
+  static constexpr gfx::RectF kElementBounds{100, 100, 250, 50};
+  const std::u16string password_{u"123!-scfFGamFD"};
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(PasswordGenerationPopupViewWithExperimentsBrowsertest,
+                       OfferPasswordGeneration) {
+  PrepareOfferGenerationState();
+  ShowAndVerifyUi();
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         PasswordGenerationPopupViewWithExperimentsBrowsertest,
+                         Combine(Bool(),
+                                 Bool(),
+                                 Values("trusted_advice",
+                                        "safety_first",
+                                        "try_something_new",
+                                        "convenience",
+                                        "cross_device",
+                                        "edit_password",
+                                        "chunk_password",
+                                        "nudge_password")),
+                         PasswordGenerationPopupViewWithExperimentsBrowsertest::
+                             GetExperimentTestSuffix);
diff --git a/chrome/browser/ui/views/passwords/password_save_update_view.cc b/chrome/browser/ui/views/passwords/password_save_update_view.cc
index f0c53e2..90120f5 100644
--- a/chrome/browser/ui/views/passwords/password_save_update_view.cc
+++ b/chrome/browser/ui/views/passwords/password_save_update_view.cc
@@ -454,7 +454,7 @@
 
   views::ViewAccessibility& ax = accessibility_alert_->GetViewAccessibility();
   ax.SetRole(ax::mojom::Role::kAlert);
-  ax.OverrideName(accessibility_alert_text);
+  ax.SetName(accessibility_alert_text, ax::mojom::NameFrom::kAttribute);
   accessibility_alert_->NotifyAccessibilityEvent(ax::mojom::Event::kAlert,
                                                  true);
 }
diff --git a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view_browsertest.cc b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view_browsertest.cc
index da7381a..9dee962 100644
--- a/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view_browsertest.cc
+++ b/chrome/browser/ui/views/permissions/permission_prompt_bubble_base_view_browsertest.cc
@@ -98,11 +98,6 @@
 
 class PermissionPromptBubbleBaseViewBrowserTest : public DialogBrowserTest {
  public:
-  PermissionPromptBubbleBaseViewBrowserTest() {
-    feature_list_.InitWithFeatures(
-        {}, {permissions::features::kPermissionStorageAccessAPI});
-  }
-
   // DialogBrowserTest:
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
@@ -399,35 +394,8 @@
   ShowAndVerifyUi();
 }
 
-// Test fixture to test the Storage Access prompt with the new Google UI.
-//
-// We have created a new test fixture for the new Google UI so we can have a
-// test for the new and old prompt UI and avoid adding unnecessary Gold images.
-// If were to add a new parameter to |PermissionPromptBubbleBaseViewBrowserTest|
-// to toggle the PermissionStorageAccessAPI, we would have to add extra Gold
-// images for each of the other eleven tests, even though this flag only affects
-// the Storage Access prompt.
-class StorageAccessEnabledPermissionPromptBubbleViewBrowserTest
-    : public PermissionPromptBubbleBaseViewBrowserTest {
- public:
-  StorageAccessEnabledPermissionPromptBubbleViewBrowserTest() {
-    feature_list_.InitWithFeatures(
-        {permissions::features::kPermissionStorageAccessAPI}, {});
-  }
-  base::test::ScopedFeatureList feature_list_;
-};
-
-// Host wants to access storage from the site in which it's embedded. Prompt
-// with new Google UI.
-IN_PROC_BROWSER_TEST_F(
-    StorageAccessEnabledPermissionPromptBubbleViewBrowserTest,
-    InvokeUi_storage_access) {
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_F(
-    StorageAccessEnabledPermissionPromptBubbleViewBrowserTest,
-    OpenHelpCenterLinkInNewTab) {
+IN_PROC_BROWSER_TEST_F(PermissionPromptBubbleBaseViewBrowserTest,
+                       OpenHelpCenterLinkInNewTab) {
   ShowUi("storage_access");
 
   // Get link widget from the prompt.
diff --git a/chrome/browser/ui/views/profiles/incognito_menu_view.cc b/chrome/browser/ui/views/profiles/incognito_menu_view.cc
index e2dc1635..791ab794 100644
--- a/chrome/browser/ui/views/profiles/incognito_menu_view.cc
+++ b/chrome/browser/ui/views/profiles/incognito_menu_view.cc
@@ -37,7 +37,8 @@
                                      Browser* browser)
     : ProfileMenuViewBase(anchor_button, browser) {
   DCHECK(browser->profile()->IsIncognitoProfile());
-  GetViewAccessibility().OverrideName(GetAccessibleWindowTitle());
+  GetViewAccessibility().SetName(GetAccessibleWindowTitle(),
+                                 ax::mojom::NameFrom::kAttribute);
 
   base::RecordAction(base::UserMetricsAction("IncognitoMenu_Show"));
 }
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
index c892377..6f6ca760 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_base.cc
@@ -708,7 +708,8 @@
   // crbug.com/1161166: Orca does not read the accessible window title of the
   // bubble, so we duplicate it in the top-level menu item. To be revisited
   // after considering other options, including fixes on the AT side.
-  GetViewAccessibility().OverrideName(GetAccessibleWindowTitle());
+  GetViewAccessibility().SetName(GetAccessibleWindowTitle(),
+                                 ax::mojom::NameFrom::kAttribute);
 #endif
 
   std::unique_ptr<views::Label> heading_label;
@@ -835,7 +836,8 @@
   // accessibility tools can read it together with the button text. The role
   // change is required by Windows ATs.
   sync_info_container_->GetViewAccessibility().SetRole(ax::mojom::Role::kGroup);
-  sync_info_container_->GetViewAccessibility().OverrideName(description);
+  sync_info_container_->GetViewAccessibility().SetName(
+      description, ax::mojom::NameFrom::kAttribute);
 
   // Add the prominent button at the bottom.
   auto* button =
@@ -970,8 +972,8 @@
     // ATs.
     selectable_profiles_container_->GetViewAccessibility().SetRole(
         ax::mojom::Role::kGroup);
-    selectable_profiles_container_->GetViewAccessibility().OverrideName(
-        profile_mgmt_heading_);
+    selectable_profiles_container_->GetViewAccessibility().SetName(
+        profile_mgmt_heading_, ax::mojom::NameFrom::kAttribute);
   }
 
   DCHECK(!image_model.IsEmpty());
diff --git a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_action_button.cc b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_action_button.cc
index a9c9a50b..13bacad5 100644
--- a/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_action_button.cc
+++ b/chrome/browser/ui/views/sharing_hub/sharing_hub_bubble_action_button.cc
@@ -80,7 +80,8 @@
       action_info.title, views::style::CONTEXT_MENU));
   title_->SetCanProcessEventsWithinSubtree(false);
 
-  GetViewAccessibility().OverrideName(title_->GetText());
+  GetViewAccessibility().SetName(title_->GetText(),
+                                 ax::mojom::NameFrom::kAttribute);
 }
 
 SharingHubBubbleActionButton::~SharingHubBubbleActionButton() = default;
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index f32dcbce..39567f5 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -1890,7 +1890,8 @@
     // Dialogs that take focus must have a name and role to pass accessibility
     // checks.
     GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-    GetViewAccessibility().OverrideName("Test dialog");
+    GetViewAccessibility().SetName("Test dialog",
+                                   ax::mojom::NameFrom::kAttribute);
   }
 
   TestDialog(const TestDialog&) = delete;
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc
index 4af6f06..e799bb40 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller_interactive_uitest.cc
@@ -751,8 +751,14 @@
   EXPECT_FALSE(footer_view->GetVisible());
 }
 
+#if BUILDFLAG(IS_WIN)
+// https://crbug.com/327245883
+#define MAYBE_BackgroundTabHoverCardContentsHaveCorrectDimensions DISABLED_BackgroundTabHoverCardContentsHaveCorrectDimensions
+#else
+#define MAYBE_BackgroundTabHoverCardContentsHaveCorrectDimensions BackgroundTabHoverCardContentsHaveCorrectDimensions
+#endif
 IN_PROC_BROWSER_TEST_P(TabHoverCardFadeFooterInteractiveUiTest,
-                       BackgroundTabHoverCardContentsHaveCorrectDimensions) {
+                       MAYBE_BackgroundTabHoverCardContentsHaveCorrectDimensions) {
   TabStrip* const tab_strip = GetTabStrip(browser());
   ASSERT_TRUE(
       AddTabAtIndex(1, GURL(url::kAboutBlankURL), ui::PAGE_TRANSITION_TYPED));
diff --git a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
index 17d814f..dc1812a4 100644
--- a/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
+++ b/chrome/browser/ui/views/toolbar/chrome_labs_item_view.cc
@@ -159,7 +159,8 @@
   experiment_description->GetViewAccessibility().OverrideIsIgnored(true);
   GetViewAccessibility().SetRole(ax::mojom::Role::kGroup);
   if (!lab.visible_name.empty())
-    GetViewAccessibility().OverrideName(lab.visible_name);
+    GetViewAccessibility().SetName(lab.visible_name,
+                                   ax::mojom::NameFrom::kAttribute);
 
     // There is currently a MacOS VoiceOver screen reader bug where VoiceOver
     // does not announce the accessible description for groups
diff --git a/chrome/browser/ui/web_applications/sub_apps_service_impl.cc b/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
index 58d3bf4a..b16ca01 100644
--- a/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
+++ b/chrome/browser/ui/web_applications/sub_apps_service_impl.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
+#include "chrome/browser/web_applications/web_app_install_params.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/browser/web_applications/web_app_tab_helper.h"
@@ -433,7 +434,7 @@
   base::ConcurrentCallbacks<SubAppInstallResult> concurrent;
   for (auto& install_info : add_call_info.install_infos) {
     webapps::ManifestId manifest_id = install_info->manifest_id;
-    provider->scheduler().InstallFromInfo(
+    provider->scheduler().InstallFromInfoWithParams(
         std::move(install_info), /*overwrite_existing_manifest_fields=*/false,
         webapps::WebappInstallSource::SUB_APP,
         base::BindOnce(
@@ -442,7 +443,8 @@
               return SubAppInstallResult(manifest_id, app_id, result_code);
             },
             manifest_id)
-            .Then(concurrent.CreateCallback()));
+            .Then(concurrent.CreateCallback()),
+        WebAppInstallParams());
   }
   std::move(concurrent)
       .Done(base::BindOnce(&SubAppsServiceImpl::FinishAddCall,
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.cc b/chrome/browser/ui/webid/identity_dialog_controller.cc
index 78f13db0..2e49d0a 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.cc
+++ b/chrome/browser/ui/webid/identity_dialog_controller.cc
@@ -103,11 +103,6 @@
   std::move(on_accounts_displayed_).Run();
 }
 
-void IdentityDialogController::ShowIdpSigninFailureDialog(
-    base::OnceClosure user_notified_callback) {
-  NOTIMPLEMENTED();
-}
-
 std::string IdentityDialogController::GetTitle() const {
   return account_view_->GetTitle();
 }
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.h b/chrome/browser/ui/webid/identity_dialog_controller.h
index 028d844f..9670dbe2 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.h
+++ b/chrome/browser/ui/webid/identity_dialog_controller.h
@@ -67,7 +67,6 @@
                        const std::optional<TokenError>& error,
                        DismissCallback dismiss_callback,
                        MoreDetailsCallback more_details_callback) override;
-  void ShowIdpSigninFailureDialog(base::OnceClosure dismiss_callback) override;
 
   std::string GetTitle() const override;
   std::optional<std::string> GetSubtitle() const override;
diff --git a/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc b/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
index ffd8417..17f9beb 100644
--- a/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
+++ b/chrome/browser/ui/webui/ash/settings/pages/device/device_section.cc
@@ -704,6 +704,10 @@
       ::switches::kEnableUnifiedDesktop);
 }
 
+bool IsDisplayPerformanceSupported() {
+  return ash::features::IsDisplayPerformanceModeEnabled();
+}
+
 bool DoesDeviceSupportAmbientColor() {
   return ash::features::IsAllowAmbientEQEnabled();
 }
@@ -1867,6 +1871,8 @@
       {"displayScreenExtended", IDS_SETTINGS_DISPLAY_SCREEN_EXTENDED},
       {"displayScreenPrimary", IDS_SETTINGS_DISPLAY_SCREEN_PRIMARY},
       {"displayScreenTitle", IDS_SETTINGS_DISPLAY_SCREEN},
+      {"displayShinyPerformanceLabel",
+       IDS_SETTINGS_DISPLAY_SHINY_PERFORMANCE_LABEL},
       {"displaySizeSliderMaxLabel", IDS_SETTINGS_DISPLAY_ZOOM_SLIDER_MAXIMUM},
       {"displaySizeSliderMinLabel", IDS_SETTINGS_DISPLAY_ZOOM_SLIDER_MINIMUM},
       {"displayTitle", kIsRevampEnabled ? IDS_OS_SETTINGS_REVAMP_DISPLAY_TITLE
@@ -1927,6 +1933,9 @@
   html_source->AddBoolean(
       "allowDisplayAlignmentApi",
       base::FeatureList::IsEnabled(ash::features::kDisplayAlignAssist));
+
+  html_source->AddBoolean("isDisplayPerformanceSupported",
+                          IsDisplayPerformanceSupported());
 }
 
 }  // namespace ash::settings
diff --git a/chrome/browser/ui/webui/cr_components/history_embeddings/history_embeddings_handler.cc b/chrome/browser/ui/webui/cr_components/history_embeddings/history_embeddings_handler.cc
new file mode 100644
index 0000000..806639d
--- /dev/null
+++ b/chrome/browser/ui/webui/cr_components/history_embeddings/history_embeddings_handler.cc
@@ -0,0 +1,16 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/cr_components/history_embeddings/history_embeddings_handler.h"
+
+HistoryEmbeddingsHandler::HistoryEmbeddingsHandler(
+    mojo::PendingReceiver<history_embeddings::mojom::PageHandler>
+        pending_page_handler)
+    : page_handler_(this, std::move(pending_page_handler)) {}
+
+HistoryEmbeddingsHandler::~HistoryEmbeddingsHandler() = default;
+
+void HistoryEmbeddingsHandler::DoSomething(DoSomethingCallback callback) {
+  std::move(callback).Run(true);
+}
diff --git a/chrome/browser/ui/webui/cr_components/history_embeddings/history_embeddings_handler.h b/chrome/browser/ui/webui/cr_components/history_embeddings/history_embeddings_handler.h
new file mode 100644
index 0000000..b62f85d
--- /dev/null
+++ b/chrome/browser/ui/webui/cr_components/history_embeddings/history_embeddings_handler.h
@@ -0,0 +1,32 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_CR_COMPONENTS_HISTORY_EMBEDDINGS_HISTORY_EMBEDDINGS_HANDLER_H_
+#define CHROME_BROWSER_UI_WEBUI_CR_COMPONENTS_HISTORY_EMBEDDINGS_HISTORY_EMBEDDINGS_HANDLER_H_
+
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "ui/webui/mojo_bubble_web_ui_controller.h"
+#include "ui/webui/resources/cr_components/history_embeddings/history_embeddings.mojom.h"
+
+class HistoryEmbeddingsHandler : public history_embeddings::mojom::PageHandler {
+ public:
+  explicit HistoryEmbeddingsHandler(
+      mojo::PendingReceiver<history_embeddings::mojom::PageHandler>
+          pending_page_handler);
+  HistoryEmbeddingsHandler(const HistoryEmbeddingsHandler&) = delete;
+  HistoryEmbeddingsHandler& operator=(const HistoryEmbeddingsHandler&) = delete;
+  ~HistoryEmbeddingsHandler() override;
+
+  // history_embeddings::mojom::PageHandler:
+  void DoSomething(DoSomethingCallback callback) override;
+
+ private:
+  mojo::Receiver<history_embeddings::mojom::PageHandler> page_handler_;
+  base::WeakPtrFactory<HistoryEmbeddingsHandler> weak_ptr_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CR_COMPONENTS_HISTORY_EMBEDDINGS_HISTORY_EMBEDDINGS_HANDLER_H_
diff --git a/chrome/browser/ui/webui/history/history_ui.cc b/chrome/browser/ui/webui/history/history_ui.cc
index 4818fd8..91508b3 100644
--- a/chrome/browser/ui/webui/history/history_ui.cc
+++ b/chrome/browser/ui/webui/history/history_ui.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/signin/signin_ui_util.h"
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/webui/cr_components/history_clusters/history_clusters_util.h"
+#include "chrome/browser/ui/webui/cr_components/history_embeddings/history_embeddings_handler.h"
 #include "chrome/browser/ui/webui/favicon_source.h"
 #include "chrome/browser/ui/webui/history/browsing_history_handler.h"
 #include "chrome/browser/ui/webui/history/foreign_session_handler.h"
@@ -242,6 +243,13 @@
 }
 
 void HistoryUI::BindInterface(
+    mojo::PendingReceiver<history_embeddings::mojom::PageHandler>
+        pending_page_handler) {
+  history_embeddings_handler_ = std::make_unique<HistoryEmbeddingsHandler>(
+      std::move(pending_page_handler));
+}
+
+void HistoryUI::BindInterface(
     mojo::PendingReceiver<history_clusters::mojom::PageHandler>
         pending_page_handler) {
   history_clusters_handler_ =
diff --git a/chrome/browser/ui/webui/history/history_ui.h b/chrome/browser/ui/webui/history/history_ui.h
index c6e9530..43d178b1 100644
--- a/chrome/browser/ui/webui/history/history_ui.h
+++ b/chrome/browser/ui/webui/history/history_ui.h
@@ -15,6 +15,7 @@
 #include "ui/base/resource/resource_scale_factor.h"
 #include "ui/webui/mojo_web_ui_controller.h"
 #include "ui/webui/resources/cr_components/history_clusters/history_clusters.mojom-forward.h"
+#include "ui/webui/resources/cr_components/history_embeddings/history_embeddings.mojom.h"
 
 namespace base {
 class RefCountedMemory;
@@ -24,6 +25,8 @@
 class HistoryClustersHandler;
 }
 
+class HistoryEmbeddingsHandler;
+
 namespace page_image_service {
 class ImageServiceHandler;
 }
@@ -50,6 +53,9 @@
       ui::ResourceScaleFactor scale_factor);
 
   // Instantiates the implementors of mojom interfaces.
+  void BindInterface(
+      mojo::PendingReceiver<history_embeddings::mojom::PageHandler>
+          pending_page_handler);
   void BindInterface(mojo::PendingReceiver<history_clusters::mojom::PageHandler>
                          pending_page_handler);
   void BindInterface(
@@ -63,6 +69,7 @@
   }
 
  private:
+  std::unique_ptr<HistoryEmbeddingsHandler> history_embeddings_handler_;
   std::unique_ptr<history_clusters::HistoryClustersHandler>
       history_clusters_handler_;
   std::unique_ptr<page_image_service::ImageServiceHandler>
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index 17c1647..03c34e2 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -551,11 +551,6 @@
       performance_manager::user_tuning::IsBatterySaverModeManagedByOS());
 
   html_source->AddBoolean(
-      "enablePermissionStorageAccessApi",
-      base::FeatureList::IsEnabled(
-          permissions::features::kPermissionStorageAccessAPI));
-
-  html_source->AddBoolean(
       "autoPictureInPictureEnabled",
       base::FeatureList::IsEnabled(
           blink::features::kMediaSessionEnterPictureInPicture));
diff --git a/chrome/browser/ui/webui/signin/ash/inline_login_dialog_browsertest.cc b/chrome/browser/ui/webui/signin/ash/inline_login_dialog_browsertest.cc
index 8e98fbb..a60c2a2 100644
--- a/chrome/browser/ui/webui/signin/ash/inline_login_dialog_browsertest.cc
+++ b/chrome/browser/ui/webui/signin/ash/inline_login_dialog_browsertest.cc
@@ -39,7 +39,8 @@
     // Dialogs that take focus must have a name and role to pass accessibility
     // checks.
     GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
-    GetViewAccessibility().OverrideName("Test dialog");
+    GetViewAccessibility().SetName("Test dialog",
+                                   ax::mojom::NameFrom::kAttribute);
   }
   ChildModalDialogDelegate(const ChildModalDialogDelegate&) = delete;
   ChildModalDialogDelegate& operator=(const ChildModalDialogDelegate&) = delete;
diff --git a/chrome/browser/web_applications/commands/install_from_info_command_browsertest.cc b/chrome/browser/web_applications/commands/install_from_info_command_browsertest.cc
index 178f419..7524379 100644
--- a/chrome/browser/web_applications/commands/install_from_info_command_browsertest.cc
+++ b/chrome/browser/web_applications/commands/install_from_info_command_browsertest.cc
@@ -74,7 +74,7 @@
 
   base::RunLoop loop;
   webapps::AppId result_app_id;
-  provider().scheduler().InstallFromInfo(
+  provider().scheduler().InstallFromInfoNoIntegrationForTesting(
       std::move(info),
       /*overwrite_existing_manifest_fields=*/false, install_source,
       base::BindLambdaForTesting(
diff --git a/chrome/browser/web_applications/commands/os_integration_synchronize_command_unittest.cc b/chrome/browser/web_applications/commands/os_integration_synchronize_command_unittest.cc
index 024f91b..bd118aa 100644
--- a/chrome/browser/web_applications/commands/os_integration_synchronize_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/os_integration_synchronize_command_unittest.cc
@@ -87,7 +87,7 @@
           webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON) {
     base::test::TestFuture<const webapps::AppId&, webapps::InstallResultCode>
         result;
-    provider()->scheduler().InstallFromInfo(
+    provider()->scheduler().InstallFromInfoNoIntegrationForTesting(
         std::move(install_info), /*overwrite_existing_manifest_fields=*/true,
         source, result.GetCallback());
     bool success = result.Wait();
diff --git a/chrome/browser/web_applications/os_integration/shortcut_sub_manager_unittest.cc b/chrome/browser/web_applications/os_integration/shortcut_sub_manager_unittest.cc
index 4b2ef615..dbd6a0d 100644
--- a/chrome/browser/web_applications/os_integration/shortcut_sub_manager_unittest.cc
+++ b/chrome/browser/web_applications/os_integration/shortcut_sub_manager_unittest.cc
@@ -292,8 +292,8 @@
     info->icon_bitmaps.any = std::move(icon_map);
     base::test::TestFuture<const webapps::AppId&, webapps::InstallResultCode>
         result;
-    // InstallFromInfo() does not trigger OS integration.
-    provider().scheduler().InstallFromInfo(
+    // InstallFromInfoNoIntegrationForTesting() does not trigger OS integration.
+    provider().scheduler().InstallFromInfoNoIntegrationForTesting(
         std::move(info), /*overwrite_existing_manifest_fields=*/true,
         webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
         result.GetCallback());
diff --git a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
index f87b5942..7f4a4fd 100644
--- a/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
+++ b/chrome/browser/web_applications/policy/web_app_policy_manager_browsertest.cc
@@ -264,7 +264,7 @@
                                install_info.get());
 
   auto* provider = WebAppProvider::GetForTest(profile());
-  provider->scheduler().InstallFromInfo(
+  provider->scheduler().InstallFromInfoNoIntegrationForTesting(
       std::move(install_info),
       /*overwrite_existing_manifest_fields=*/true,
       webapps::WebappInstallSource::EXTERNAL_POLICY, base::DoNothing());
diff --git a/chrome/browser/web_applications/test/web_app_install_test_utils.cc b/chrome/browser/web_applications/test/web_app_install_test_utils.cc
index 629788e..dfa86dc 100644
--- a/chrome/browser/web_applications/test/web_app_install_test_utils.cc
+++ b/chrome/browser/web_applications/test/web_app_install_test_utils.cc
@@ -114,9 +114,9 @@
   // In unit tests, we do not have Browser or WebContents instances. Hence we
   // use `InstallFromInfoCommand` instead of `FetchManifestAndInstallCommand` or
   // `WebAppInstallCommand` to install the web app.
-  provider->scheduler().InstallFromInfo(std::move(web_app_info),
-                                        overwrite_existing_manifest_fields,
-                                        install_source, future.GetCallback());
+  provider->scheduler().InstallFromInfoNoIntegrationForTesting(
+      std::move(web_app_info), overwrite_existing_manifest_fields,
+      install_source, future.GetCallback());
 
   EXPECT_EQ(webapps::InstallResultCode::kSuccessNewInstall,
             future.Get<webapps::InstallResultCode>());
@@ -155,7 +155,7 @@
   // In unit tests, we do not have Browser or WebContents instances. Hence we
   // use `InstallFromInfoCommand` instead of `FetchManifestAndInstallCommand` or
   // `WebAppInstallCommand` to install the web app.
-  provider->scheduler().InstallFromInfo(
+  provider->scheduler().InstallFromInfoNoIntegrationForTesting(
       std::move(web_app_info), /*overwrite_existing_manifest_fields =*/true,
       is_policy_install ? webapps::WebappInstallSource::EXTERNAL_POLICY
                         : webapps::WebappInstallSource::MENU_CREATE_SHORTCUT,
diff --git a/chrome/browser/web_applications/web_app_command_scheduler.cc b/chrome/browser/web_applications/web_app_command_scheduler.cc
index 5bd0feb9..6f6b5d4 100644
--- a/chrome/browser/web_applications/web_app_command_scheduler.cc
+++ b/chrome/browser/web_applications/web_app_command_scheduler.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <optional>
 
+#include "base/check_is_test.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
@@ -122,12 +123,13 @@
           std::move(parent_manifest_id), std::move(callback)));
 }
 
-void WebAppCommandScheduler::InstallFromInfo(
+void WebAppCommandScheduler::InstallFromInfoNoIntegrationForTesting(
     std::unique_ptr<WebAppInstallInfo> install_info,
     bool overwrite_existing_manifest_fields,
     webapps::WebappInstallSource install_surface,
     OnceInstallCallback install_callback,
     const base::Location& location) {
+  CHECK_IS_TEST();
   provider_->command_manager().ScheduleCommand(
       std::make_unique<InstallFromInfoCommand>(
           &profile_.get(), std::move(install_info),
diff --git a/chrome/browser/web_applications/web_app_command_scheduler.h b/chrome/browser/web_applications/web_app_command_scheduler.h
index 2765773f..8aab44d 100644
--- a/chrome/browser/web_applications/web_app_command_scheduler.h
+++ b/chrome/browser/web_applications/web_app_command_scheduler.h
@@ -114,11 +114,12 @@
   // manifest.
   // `InstallFromInfo` doesn't install OS hooks. `InstallFromInfoWithParams`
   // install OS hooks when they are set in `install_params`.
-  void InstallFromInfo(std::unique_ptr<WebAppInstallInfo> install_info,
-                       bool overwrite_existing_manifest_fields,
-                       webapps::WebappInstallSource install_surface,
-                       OnceInstallCallback install_callback,
-                       const base::Location& location = FROM_HERE);
+  void InstallFromInfoNoIntegrationForTesting(
+      std::unique_ptr<WebAppInstallInfo> install_info,
+      bool overwrite_existing_manifest_fields,
+      webapps::WebappInstallSource install_surface,
+      OnceInstallCallback install_callback,
+      const base::Location& location = FROM_HERE);
 
   void InstallFromInfoWithParams(
       std::unique_ptr<WebAppInstallInfo> install_info,
diff --git a/chrome/browser/web_applications/web_app_icon_manager_browsertest.cc b/chrome/browser/web_applications/web_app_icon_manager_browsertest.cc
index 3f2af7b0..d03191f 100644
--- a/chrome/browser/web_applications/web_app_icon_manager_browsertest.cc
+++ b/chrome/browser/web_applications/web_app_icon_manager_browsertest.cc
@@ -91,7 +91,7 @@
     base::RunLoop run_loop;
 
     auto* provider = WebAppProvider::GetForTest(browser()->profile());
-    provider->scheduler().InstallFromInfo(
+    provider->scheduler().InstallFromInfoNoIntegrationForTesting(
         std::move(install_info),
         /*overwrite_existing_manifest_fields=*/false,
         webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
diff --git a/chrome/browser/webauthn/enclave_manager.cc b/chrome/browser/webauthn/enclave_manager.cc
index 49167a1a..1fe29e9 100644
--- a/chrome/browser/webauthn/enclave_manager.cc
+++ b/chrome/browser/webauthn/enclave_manager.cc
@@ -766,6 +766,9 @@
   void Process(Event event) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+    CHECK(!processing_) << ToString(state_);
+    processing_ = true;
+
     const State initial_state = state_;
     const std::string event_str = ToString(event);
 
@@ -849,6 +852,7 @@
     // The only internal state transition (i.e. where one state moves to another
     // without waiting for an external event) allowed is to `kNextAction`.
     if (state_ != State::kNextAction) {
+      processing_ = false;
       return;
     }
 
@@ -859,7 +863,10 @@
     if (state_ == State::kStop) {
       manager_->Stopped();
       // `this` has been deleted now.
+      return;
     }
+
+    processing_ = false;
   }
 
   static std::string ToString(State state) {
@@ -1522,6 +1529,7 @@
   const std::unique_ptr<CoreAccountInfo> primary_account_info_;
 
   State state_ = State::kInit;
+  bool processing_ = false;
 
   std::unique_ptr<StoreKeysArgs> store_keys_args_;
   std::string pending_pin_;
diff --git a/chrome/browser/webdata_services/DIR_METADATA b/chrome/browser/webdata_services/DIR_METADATA
new file mode 100644
index 0000000..84f5b819
--- /dev/null
+++ b/chrome/browser/webdata_services/DIR_METADATA
@@ -0,0 +1,7 @@
+monorail: {
+  component: "Internals"
+}
+team_email: "chromium-reviews@chromium.org"
+buganizer_public: {
+  component_id: 1456292
+}
diff --git a/chrome/browser/webdata_services/OWNERS b/chrome/browser/webdata_services/OWNERS
new file mode 100644
index 0000000..05fbb74
--- /dev/null
+++ b/chrome/browser/webdata_services/OWNERS
@@ -0,0 +1 @@
+file://components/webdata_services/OWNERS
diff --git a/chrome/browser/web_data_service_factory.cc b/chrome/browser/webdata_services/web_data_service_factory.cc
similarity index 98%
rename from chrome/browser/web_data_service_factory.cc
rename to chrome/browser/webdata_services/web_data_service_factory.cc
index 49bdba1..71238e9 100644
--- a/chrome/browser/web_data_service_factory.cc
+++ b/chrome/browser/webdata_services/web_data_service_factory.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/web_data_service_factory.h"
+#include "chrome/browser/webdata_services/web_data_service_factory.h"
 
 #include "base/files/file_path.h"
 #include "base/functional/bind.h"
diff --git a/chrome/browser/web_data_service_factory.h b/chrome/browser/webdata_services/web_data_service_factory.h
similarity index 92%
rename from chrome/browser/web_data_service_factory.h
rename to chrome/browser/webdata_services/web_data_service_factory.h
index faac2d4..5c8a5a2 100644
--- a/chrome/browser/web_data_service_factory.h
+++ b/chrome/browser/webdata_services/web_data_service_factory.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_WEB_DATA_SERVICE_FACTORY_H_
-#define CHROME_BROWSER_WEB_DATA_SERVICE_FACTORY_H_
+#ifndef CHROME_BROWSER_WEBDATA_SERVICES_WEB_DATA_SERVICE_FACTORY_H_
+#define CHROME_BROWSER_WEBDATA_SERVICES_WEB_DATA_SERVICE_FACTORY_H_
 
 #include "base/memory/ref_counted.h"
 #include "build/build_config.h"
@@ -78,4 +78,4 @@
   bool ServiceIsNULLWhileTesting() const override;
 };
 
-#endif  // CHROME_BROWSER_WEB_DATA_SERVICE_FACTORY_H_
+#endif  // CHROME_BROWSER_WEBDATA_SERVICES_WEB_DATA_SERVICE_FACTORY_H_
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 3501b53..189bcfc 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1709034969-8c8f1bb05aea5f39fc381bb65fc70625e79b398a-a44e4a5e326ed73137c943c645157d3d58bca36d.profdata
+chrome-android32-main-1709056799-5f5189e825f1404c0057f96d91ccb2dbcd3e5f44-d6b7729d8cbb7ade6b97baf90bac574cf6efc744.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 292bd62..8f901b7 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1709034969-aae66e2a5d3e205794fab7a85c937517e697a18c-a44e4a5e326ed73137c943c645157d3d58bca36d.profdata
+chrome-linux-main-1709056799-aae34d88ca80a9a1b0bdd8a5b7dbe051d1403586-d6b7729d8cbb7ade6b97baf90bac574cf6efc744.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 007c7af4..19a39c9 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1709049366-639836269488162b48e688b482f47efdcf6620e7-3de8aede4f38380836d650db46578baf59e6da3c.profdata
+chrome-mac-arm-main-1709056799-7400e0edda683bbe8ce2388f45e2b364a9393d14-d6b7729d8cbb7ade6b97baf90bac574cf6efc744.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 1ce971b..5b9754b 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1709034969-98a9fe11f3c290c0cc36e776e0c0669373fb99d6-a44e4a5e326ed73137c943c645157d3d58bca36d.profdata
+chrome-win-arm64-main-1709056799-d3a45c7322b38a7afb3881ec1db28754ba7deda0-d6b7729d8cbb7ade6b97baf90bac574cf6efc744.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index b783f0c..266ea142 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1709034969-8c10f18d5fdbfd55d35ed90dd47ec64e762591b6-a44e4a5e326ed73137c943c645157d3d58bca36d.profdata
+chrome-win32-main-1709045678-a87f50c015123fa7ef8618cda12b48b4048a0247-36e3f3ba78470906cf717bd5c6104b2054d42886.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 0855203b..f6c0a4e 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1709034969-47e6e28aa7831ef373fbdcf429ce034b2c52303d-a44e4a5e326ed73137c943c645157d3d58bca36d.profdata
+chrome-win64-main-1709056799-57eff0e35bf841127d162da05dfc07cf73bb0798-d6b7729d8cbb7ade6b97baf90bac574cf6efc744.profdata
diff --git a/chrome/common/controlled_frame/controlled_frame.cc b/chrome/common/controlled_frame/controlled_frame.cc
index 6954019..8f4a52f 100644
--- a/chrome/common/controlled_frame/controlled_frame.cc
+++ b/chrome/common/controlled_frame/controlled_frame.cc
@@ -9,8 +9,10 @@
 #include "base/containers/contains.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/initialize_extensions_client.h"
+#include "components/version_info/version_info.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/features/feature.h"
+#include "extensions/common/features/feature_channel.h"
 #include "extensions/common/mojom/context_type.mojom.h"
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -69,7 +71,8 @@
   // Also allow API exposure in ChromeOS Kiosk mode for web apps.
   if (base::FeatureList::IsEnabled(features::kWebKioskEnableIwaApis) &&
       IsRunningInKioskMode() && url.SchemeIs(url::kHttpsScheme)) {
-    is_allowed_for_scheme = true;
+    is_allowed_for_scheme =
+        extensions::GetCurrentChannel() != version_info::Channel::STABLE;
   }
 #endif
 
diff --git a/chrome/common/logging_chrome.h b/chrome/common/logging_chrome.h
index 6679f79..1363926b 100644
--- a/chrome/common/logging_chrome.h
+++ b/chrome/common/logging_chrome.h
@@ -22,14 +22,8 @@
 // setting levels in the future.
 //
 // The main process might want to delete any old log files on startup by
-// setting delete_old_log_file, but the renderer processes should not, or
-// they will delete each others' logs.
-//
-// XXX
-// Setting suppress_error_dialogs to true disables any dialogs that would
-// normally appear for assertions and crashes, and makes any catchable
-// errors (namely assertions) available via GetSilencedErrorCount()
-// and GetSilencedError().
+// setting `delete_old_log_file`, but child processes should not, or they
+// will delete each others' logs.
 void InitChromeLogging(const base::CommandLine& command_line,
                        OldFileDeletionState delete_old_log_file);
 
@@ -37,7 +31,7 @@
     const base::CommandLine& command_line);
 
 #if BUILDFLAG(IS_CHROMEOS)
-// Prepare the log file. If |new_log| is true, rotate the previous log file to
+// Prepare the log file. If `new_log` is true, rotate the previous log file to
 // write new logs to the latest log file. Otherwise, we reuse the existing file
 // if exists.
 base::FilePath SetUpLogFile(const base::FilePath& target_path, bool new_log);
diff --git a/chrome/renderer/accessibility/read_anything_app_controller.cc b/chrome/renderer/accessibility/read_anything_app_controller.cc
index 69aec830..6dda0d3 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller.cc
@@ -1097,7 +1097,7 @@
   web_ui_connected_time_ms_ = base::TimeTicks::Now();
   base::UmaHistogramLongTimes(
       "Accessibility.ReadAnything.TimeFromEntryTriggeredToWebUIConnected",
-      base::TimeTicks::Now() - web_ui_connected_time_ms_);
+      base::TimeTicks::Now() - renderer_load_triggered_time_ms_);
   mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandlerFactory>
       page_handler_factory_receiver =
           page_handler_factory_.BindNewPipeAndPassReceiver();
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index 96d51079..7bf7cc06 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -2343,6 +2343,80 @@
   CheckTextFieldsDOMState(std::string(), false, "TextToFill", true);
 }
 
+// Tests that `FillInfoField` doesn't fill read-only text fields.
+TEST_F(PasswordAutofillAgentTest, FillIntoReadonlyTextField) {
+  // Neither field should be autocompleted.
+  CheckTextFieldsDOMState(
+      /*username=*/std::string(), /*username_autofilled=*/false,
+      /*password=*/std::string(), /*password_autofilled=*/false);
+
+  // If the field is readonly, it should not be affected.
+  SetElementReadOnly(username_element_, true);
+  password_autofill_agent_->FillField(
+      form_util::GetFieldRendererId(username_element_), kAliceUsername16);
+  CheckTextFieldsDOMState(
+      /*username=*/std::string(), /*username_autofilled=*/false,
+      /*password=*/std::string(), /*password_autofilled=*/false);
+}
+
+// Tests that `FillInfoField` correctly fills the username field.
+TEST_F(PasswordAutofillAgentTest, FillIntoUsernameField) {
+  // Neither field should be autocompleted.
+  CheckTextFieldsDOMState(
+      /*username=*/std::string(), /*username_autofilled=*/false,
+      /*password=*/std::string(), /*password_autofilled=*/false);
+
+  password_autofill_agent_->FillField(
+      form_util::GetFieldRendererId(username_element_), kAliceUsername16);
+  CheckTextFieldsDOMState(
+      /*username=*/kAliceUsername, /*username_autofilled=*/true,
+      /*password=*/std::string(), /*password_autofilled=*/false);
+}
+
+// Tests that `FillInfoField` correctly fills the password field.
+TEST_F(PasswordAutofillAgentTest, FillIntoPasswordField) {
+  // Neither field should be autocompleted.
+  CheckTextFieldsDOMState(
+      /*username=*/std::string(), /*username_autofilled=*/false,
+      /*password=*/std::string(), /*password_autofilled=*/false);
+
+  password_autofill_agent_->FillField(
+      form_util::GetFieldRendererId(password_element_), kAlicePassword16);
+  CheckTextFieldsDOMState(
+      /*username=*/std::string(), /*username_autofilled=*/false,
+      /*password=*/kAlicePassword, /*password_autofilled=*/true);
+}
+
+// Tests that `FillInfoField` can fill into a random field.
+TEST_F(PasswordAutofillAgentTest, FillIntoRandomField) {
+  WebInputElement random_element = GetInputElementByID("random_field");
+
+  // The field should not be autocompleted.
+  EXPECT_EQ(std::string(), random_element.Value().Utf8());
+
+  password_autofill_agent_->FillField(
+      form_util::GetFieldRendererId(random_element), kAliceUsername16);
+  EXPECT_EQ(kAliceUsername, random_element.Value().Utf8());
+}
+
+// Tests that `FillInfoField` doesn't fill non-existent fields.
+TEST_F(PasswordAutofillAgentTest, FillIntoNonExistingField) {
+  WebInputElement random_element = GetInputElementByID("random_field");
+
+  // Neither field should be autocompleted.
+  CheckTextFieldsDOMState(
+      /*username=*/std::string(), /*username_autofilled=*/false,
+      /*password=*/std::string(), /*password_autofilled=*/false);
+  EXPECT_EQ(std::string(), random_element.Value().Utf8());
+
+  password_autofill_agent_->FillField(FieldRendererId(), kAliceUsername16);
+  // Neither field should be autocompleted.
+  CheckTextFieldsDOMState(
+      /*username=*/std::string(), /*username_autofilled=*/false,
+      /*password=*/std::string(), /*password_autofilled=*/false);
+  EXPECT_EQ(std::string(), random_element.Value().Utf8());
+}
+
 // Tests that `ClearPreview` properly clears previewed username and password
 // with neither username nor password being previously autofilled.
 TEST_F(PasswordAutofillAgentTest,
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 22042b0..f06e731 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2647,7 +2647,6 @@
       "../browser/support_tool/screenshot_data_collector_browsertest.cc",
       "../browser/support_tool/support_tool_util_browsertest.cc",
       "../browser/sync/sessions/sync_sessions_router_tab_helper_browsertest.cc",
-      "../browser/sync/sync_service_util_browsertest.cc",
       "../browser/sync_file_system/mock_local_change_processor.cc",
       "../browser/sync_file_system/mock_local_change_processor.h",
       "../browser/sync_file_system/mock_remote_file_sync_service.cc",
diff --git a/chrome/test/chromeos/standalone_browser_test_controller.cc b/chrome/test/chromeos/standalone_browser_test_controller.cc
index c7b49f7..d52dd7e 100644
--- a/chrome/test/chromeos/standalone_browser_test_controller.cc
+++ b/chrome/test/chromeos/standalone_browser_test_controller.cc
@@ -165,7 +165,7 @@
   info->user_display_mode = WindowModeToUserDisplayMode(window_mode);
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
   auto* provider = web_app::WebAppProvider::GetForWebApps(profile);
-  provider->scheduler().InstallFromInfo(
+  provider->scheduler().InstallFromInfoNoIntegrationForTesting(
       std::move(info),
       /*overwrite_existing_manifest_fields=*/false,
       webapps::WebappInstallSource::SYNC,
@@ -291,7 +291,7 @@
 
   Profile* profile = ProfileManager::GetPrimaryUserProfile();
   auto* provider = web_app::WebAppProvider::GetForWebApps(profile);
-  provider->scheduler().InstallFromInfo(
+  provider->scheduler().InstallFromInfoNoIntegrationForTesting(
       std::move(info),
       /*overwrite_existing_manifest_fields=*/false,
       webapps::WebappInstallSource::SUB_APP,
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn b/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn
index 19c5ec7..68e59f6b 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/BUILD.gn
@@ -15,6 +15,7 @@
   ]
 
   files = [
+    "fake_print_preview_page_handler_test.ts",
     "print_preview_cros_app_test.ts",
     "print_ticket_manager_test.ts",
     "summary_panel_controller_test.ts",
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/fake_print_preview_page_handler_test.ts b/chrome/test/data/webui/chromeos/print_preview_cros/fake_print_preview_page_handler_test.ts
new file mode 100644
index 0000000..f9eaad48
--- /dev/null
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/fake_print_preview_page_handler_test.ts
@@ -0,0 +1,41 @@
+// Copyright 2024 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-print/js/fakes/fake_print_preview_page_handler.js';
+
+import {FAKE_PRINT_REQUEST_FAILURE_INVALID_SETTINGS_ERROR, FAKE_PRINT_REQUEST_SUCCESSFUL, FakePrintPreviewPageHandler} from 'chrome://os-print/js/fakes/fake_print_preview_page_handler.js';
+import {assert} from 'chrome://resources/js/assert.js';
+import {assertEquals} from 'chrome://webui-test/chromeos/chai_assert.js';
+
+suite('PrintPreviewCrosApp', () => {
+  let printPreviewPageHandler: FakePrintPreviewPageHandler;
+
+  setup(() => {
+    printPreviewPageHandler = new FakePrintPreviewPageHandler();
+    assert(printPreviewPageHandler);
+  });
+
+  // Verify initial call count for tracked methods is zero.
+  test('call count zero', () => {
+    assertEquals(0, printPreviewPageHandler.getCallCount('print'));
+  });
+
+  // Verify the fake PrintPreviewPageHandler returns a successful response by
+  // default.
+  test('default fake print request result return successful', async () => {
+    const result = await printPreviewPageHandler.print();
+    assertEquals(
+        FAKE_PRINT_REQUEST_SUCCESSFUL, result,
+        `Print request should be successful`);
+  });
+
+  // Verify the fake PrintPreviewPageHandler can set expected print request
+  // result to false and resolve.
+  test('can set print request result', async () => {
+    printPreviewPageHandler.setPrintResult(
+        FAKE_PRINT_REQUEST_FAILURE_INVALID_SETTINGS_ERROR);
+    const result = await printPreviewPageHandler.print();
+    assertEquals(FAKE_PRINT_REQUEST_FAILURE_INVALID_SETTINGS_ERROR, result);
+  });
+});
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc
index c8b25a0..6894bd0 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/print_preview_cros_browsertest.cc
@@ -36,6 +36,11 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
+IN_PROC_BROWSER_TEST_F(PrintPreviewCrosBrowserTest,
+                       FakePrintPreviewPageHandlerTest) {
+  RunTestAtPath("fake_print_preview_page_handler_test.js");
+}
+
 IN_PROC_BROWSER_TEST_F(PrintPreviewCrosBrowserTest, PrintPreviewCrosAppTest) {
   RunTestAtPath("print_preview_cros_app_test.js");
 }
diff --git a/chrome/test/data/webui/chromeos/print_preview_cros/print_ticket_manager_test.ts b/chrome/test/data/webui/chromeos/print_preview_cros/print_ticket_manager_test.ts
index 0947a67a..95a0d40 100644
--- a/chrome/test/data/webui/chromeos/print_preview_cros/print_ticket_manager_test.ts
+++ b/chrome/test/data/webui/chromeos/print_preview_cros/print_ticket_manager_test.ts
@@ -5,9 +5,21 @@
 import 'chrome://os-print/js/data/print_ticket_manager.js';
 
 import {PrintTicketManager} from 'chrome://os-print/js/data/print_ticket_manager.js';
+import {FakePrintPreviewPageHandler} from 'chrome://os-print/js/fakes/fake_print_preview_page_handler.js';
+import {setPrintPreviewPageHandlerForTesting} from 'chrome://os-print/js/utils/mojo_data_providers.js';
 import {assertEquals, assertTrue} from 'chrome://webui-test/chromeos/chai_assert.js';
 
 suite('PrintTicketManager', () => {
+  let printPreviewPageHandler: FakePrintPreviewPageHandler;
+
+  setup(() => {
+    PrintTicketManager.resetInstanceForTesting();
+
+    // Setup fakes for testing.
+    printPreviewPageHandler = new FakePrintPreviewPageHandler();
+    setPrintPreviewPageHandlerForTesting(printPreviewPageHandler);
+  });
+
   test('is a singleton', () => {
     const instance1 = PrintTicketManager.getInstance();
     const instance2 = PrintTicketManager.getInstance();
@@ -20,4 +32,12 @@
     const instance2 = PrintTicketManager.getInstance();
     assertTrue(instance1 !== instance2);
   });
+
+  // Verify PrintPreviewPageHandler called when sentPrintRequest triggered.
+  test('sendPrintRequest calls PrintPreviewPageHandler.print', () => {
+    const instance = PrintTicketManager.getInstance();
+    assertEquals(0, printPreviewPageHandler.getCallCount('print'));
+    instance.sendPrintRequest();
+    assertEquals(1, printPreviewPageHandler.getCallCount('print'));
+  });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/device_page/fake_display_settings_provider.ts b/chrome/test/data/webui/settings/chromeos/device_page/fake_display_settings_provider.ts
index 4e359bb..af4dda5 100644
--- a/chrome/test/data/webui/settings/chromeos/device_page/fake_display_settings_provider.ts
+++ b/chrome/test/data/webui/settings/chromeos/device_page/fake_display_settings_provider.ts
@@ -29,6 +29,7 @@
   private displayConfigurationObservers:
       DisplayConfigurationObserverInterface[] = [];
   private isTabletMode: boolean = false;
+  private performanceSettingEnabled: boolean = false;
   private internalDisplayHistogram = new Map<DisplaySettingsType, number>();
   private externalDisplayHistogram = new Map<DisplaySettingsType, number>();
   private displayHistogram = new Map<DisplaySettingsType, number>();
@@ -153,6 +154,15 @@
     }
   }
 
+  // Implement DisplaySettingsProviderInterface.
+  setShinyPerformance(enabled: boolean): void {
+    this.performanceSettingEnabled = enabled;
+  }
+
+  getShinyPerformance(): boolean {
+    return this.performanceSettingEnabled;
+  }
+
   getInternalDisplayHistogram(): Map<DisplaySettingsType, number> {
     return this.internalDisplayHistogram;
   }
diff --git a/chrome/test/data/webui/settings/clear_browsing_data_test.ts b/chrome/test/data/webui/settings/clear_browsing_data_test.ts
index b3052617..0fd229a 100644
--- a/chrome/test/data/webui/settings/clear_browsing_data_test.ts
+++ b/chrome/test/data/webui/settings/clear_browsing_data_test.ts
@@ -429,8 +429,13 @@
   });
 
   async function assertDropdownSelectionPersisted(
-      tabName: string, prefName: string) {
+      tabIndex: number, tabName: string, prefName: string) {
     assertTrue(element.$.clearBrowsingDataDialog.open);
+    // The user selects the tab of interest.
+    const crTabs = element.shadowRoot!.querySelector('cr-tabs');
+    assertTrue(!!crTabs);
+    crTabs.selected = tabIndex;
+
     const timePeriodDropdown = getTimePeriodDropdown(tabName, element);
     const selectElement =
         timePeriodDropdown.shadowRoot!.querySelector('select');
@@ -452,22 +457,26 @@
     assertTrue(!!element.$.cookiesCheckboxBasic);
     element.$.cookiesCheckboxBasic.$.checkbox.click();
     await element.$.cookiesCheckboxBasic.$.checkbox.updateComplete;
-    // Confirming the deletion persists the dropdown selection to the pref.
+    // Confirming the deletion persists the dropdown selection to the pref and
+    // sends the time range for clearing.
     const actionButton =
         element.shadowRoot!.querySelector<CrButtonElement>('.action-button');
     assertTrue(!!actionButton);
     actionButton.click();
     assertEquals(TimePeriod.LAST_WEEK, element.getPref(prefName).value);
+    const args = await testBrowserProxy.whenCalled('clearBrowsingData');
+    const timeRange = args[1];
+    assertEquals(TimePeriod.LAST_WEEK, timeRange);
   }
 
   test('dropdownSelectionPersisted_Basic', function() {
     return assertDropdownSelectionPersisted(
-        'basic-tab', 'browser.clear_data.time_period_basic');
+        /*tabIndex*/ 0, 'basic-tab', 'browser.clear_data.time_period_basic');
   });
 
   test('dropdownSelectionPersisted_Advanced', function() {
     return assertDropdownSelectionPersisted(
-        'advanced-tab', 'browser.clear_data.time_period');
+        /*tabIndex*/ 1, 'advanced-tab', 'browser.clear_data.time_period');
   });
 
   test('tabSelection', async function() {
diff --git a/chrome/test/data/webui/settings/settings_browsertest.cc b/chrome/test/data/webui/settings/settings_browsertest.cc
index 2cd4616..dbf5a44e 100644
--- a/chrome/test/data/webui/settings/settings_browsertest.cc
+++ b/chrome/test/data/webui/settings/settings_browsertest.cc
@@ -884,11 +884,12 @@
  protected:
   SettingsPrivacyPageTest() {
     scoped_feature_list1_.InitWithFeatures(
-        {permissions::features::kPermissionStorageAccessAPI,
+        {
 #if BUILDFLAG(IS_CHROMEOS)
-         blink::features::kWebPrinting,
+            blink::features::kWebPrinting,
 #endif
-         features::kSafetyCheckNotificationPermissions, features::kSafetyHub},
+            features::kSafetyCheckNotificationPermissions,
+            features::kSafetyHub},
         {});
     scoped_feature_list2_.InitAndEnableFeatureWithParameters(
         features::kFedCm, {
@@ -1303,7 +1304,6 @@
     scoped_feature_list_.InitWithFeatures(
         {
             content_settings::features::kSafetyCheckUnusedSitePermissions,
-            permissions::features::kPermissionStorageAccessAPI,
             features::kAutomaticFullscreenContentSetting,
             features::kSafetyHub,
         },
@@ -1357,12 +1357,6 @@
           "runMochaSuite('UnusedSitePermissionsReviewSafetyHubDisabled')");
 }
 
-IN_PROC_BROWSER_TEST_F(SettingsSiteSettingsPageTest,
-                       PermissionStorageAccessApiDisabled) {
-  RunTest("settings/site_settings_page_test.js",
-          "runMochaSuite('PermissionStorageAccessApiDisabled')");
-}
-
 IN_PROC_BROWSER_TEST_F(SettingsSiteSettingsPageTest, SafetyHubDisabled) {
   RunTest("settings/site_settings_page_test.js",
           "runMochaSuite('SafetyHubDisabled')");
diff --git a/chrome/test/data/webui/settings/site_settings_page_test.ts b/chrome/test/data/webui/settings/site_settings_page_test.ts
index 103535a..94580c2 100644
--- a/chrome/test/data/webui/settings/site_settings_page_test.ts
+++ b/chrome/test/data/webui/settings/site_settings_page_test.ts
@@ -454,33 +454,6 @@
   });
 });
 
-suite('PermissionStorageAccessApiDisabled', function() {
-  let page: SettingsSiteSettingsPageElement;
-
-  suiteSetup(function() {
-    loadTimeData.overrideValues({
-      enablePermissionStorageAccessApi: false,
-    });
-  });
-
-  setup(function() {
-    document.body.innerHTML = window.trustedTypes!.emptyHTML;
-    page = document.createElement('settings-site-settings-page');
-    document.body.appendChild(page);
-    flush();
-  });
-
-  teardown(function() {
-    page.remove();
-  });
-
-  test('StorageAccessLinkRow', function() {
-    assertFalse(isChildVisible(
-        page.shadowRoot!.querySelector('#basicPermissionsList')!,
-        '#storage-access'));
-  });
-});
-
 // TODO(crbug/1443466): Remove after SafetyHub is launched.
 suite('SafetyHubDisabled', function() {
   let page: SettingsSiteSettingsPageElement;
diff --git a/chrome/test/enterprise/e2e/policy/encrypted_reporting/report_cbcm_events.py b/chrome/test/enterprise/e2e/policy/encrypted_reporting/report_cbcm_events.py
index 0da34500..75459c9 100644
--- a/chrome/test/enterprise/e2e/policy/encrypted_reporting/report_cbcm_events.py
+++ b/chrome/test/enterprise/e2e/policy/encrypted_reporting/report_cbcm_events.py
@@ -20,8 +20,8 @@
 @environment(file="../policy_test.asset.textpb")
 class ReportCbcmEvents(ChromeReportingConnectorTestCase):
   """
-  This test verifies that CBCM enrolled browsers can send events
-  to the encrypted reporting server.
+  This test verifies that chrome browsers that are commercially managed
+  (CBCM browsers) can send events to the ChromeOS encrypted reporting server.
   """
 
   @before_all
@@ -69,7 +69,7 @@
     return url
 
   @test
-  def test_ReportExtensionInstallEvent(self):
+  def test_ReportCbcmEvent(self):
     test_start_time_in_microseconds = round(time.time() * 1000000)
 
     # Enroll browser to managedchrome.com domain
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index a1e35b2..87f1bb0 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -604,6 +604,8 @@
         "//chrome/updater/app/server/win:winver",
         "//chrome/updater/win:wrl_strict",
       ]
+
+      libs = [ "wtsapi32.lib" ]
     }
 
     if (is_linux) {
diff --git a/chrome/updater/util/win_util.cc b/chrome/updater/util/win_util.cc
index 7a000f77..cd90e763 100644
--- a/chrome/updater/util/win_util.cc
+++ b/chrome/updater/util/win_util.cc
@@ -13,6 +13,7 @@
 #include <windows.h>
 #include <winhttp.h>
 #include <wrl/client.h>
+#include <wtsapi32.h>
 
 #include <algorithm>
 #include <cstdlib>
@@ -1276,4 +1277,140 @@
   return true;
 }
 
+namespace {
+
+struct ScopedWtsConnectStateCloseTraits {
+  static WTS_CONNECTSTATE_CLASS* InvalidValue() { return nullptr; }
+  static void Free(WTS_CONNECTSTATE_CLASS* memory) { ::WTSFreeMemory(memory); }
+};
+
+struct ScopedWtsSessionInfoCloseTraits {
+  static PWTS_SESSION_INFO InvalidValue() { return nullptr; }
+  static void Free(PWTS_SESSION_INFO memory) { ::WTSFreeMemory(memory); }
+};
+
+using ScopedWtsConnectState =
+    base::ScopedGeneric<WTS_CONNECTSTATE_CLASS*,
+                        ScopedWtsConnectStateCloseTraits>;
+using ScopedWtsSessionInfo =
+    base::ScopedGeneric<PWTS_SESSION_INFO, ScopedWtsSessionInfoCloseTraits>;
+
+// Returns `true` if there is a user logged on and active in the specified
+// session.
+bool IsSessionActive(std::optional<DWORD> session_id) {
+  if (!session_id) {
+    return false;
+  }
+
+  ScopedWtsConnectState wts_connect_state;
+  DWORD bytes_returned = 0;
+  if (::WTSQuerySessionInformation(
+          WTS_CURRENT_SERVER_HANDLE, *session_id, WTSConnectState,
+          reinterpret_cast<LPTSTR*>(
+              ScopedWtsConnectState::Receiver(wts_connect_state).get()),
+          &bytes_returned)) {
+    CHECK_EQ(bytes_returned, sizeof(WTS_CONNECTSTATE_CLASS));
+    return *wts_connect_state.get() == WTSActive;
+  }
+
+  return false;
+}
+
+// Returns the currently active session.
+// `WTSGetActiveConsoleSessionId` retrieves the Terminal Services session
+// currently attached to the physical console, so that is attempted first.
+// `WTSGetActiveConsoleSessionId` does not work for terminal servers where the
+// current active session is always the console. For those, an active session
+// is found by enumerating all the sessions that are present on the system, and
+// the first active session is returned.
+std::optional<DWORD> GetActiveSessionId() {
+  if (DWORD active_session_id = ::WTSGetActiveConsoleSessionId();
+      IsSessionActive(active_session_id)) {
+    return active_session_id;
+  }
+
+  ScopedWtsSessionInfo session_info;
+  DWORD num_sessions = 0;
+  if (::WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1,
+                             ScopedWtsSessionInfo::Receiver(session_info).get(),
+                             &num_sessions)) {
+    for (size_t i = 0; i < num_sessions; ++i) {
+      if (session_info.get()[i].State == WTSActive) {
+        return session_info.get()[i].SessionId;
+      }
+    }
+  }
+
+  return {};
+}
+
+std::vector<DWORD> FindProcesses(const std::wstring& process_name) {
+  base::NamedProcessIterator iter(process_name, nullptr);
+  std::vector<DWORD> pids;
+  while (const base::ProcessEntry* process_entry = iter.NextProcessEntry()) {
+    pids.push_back(process_entry->pid());
+  }
+  return pids;
+}
+
+// Returns processes running under `session_id`.
+std::vector<DWORD> FindProcessesInSession(const std::wstring& process_name,
+                                          std::optional<DWORD> session_id) {
+  if (!session_id) {
+    return {};
+  }
+  std::vector<DWORD> pids;
+  for (const auto pid : FindProcesses(process_name)) {
+    DWORD process_session = 0;
+    if (::ProcessIdToSessionId(pid, &process_session) &&
+        (process_session == *session_id)) {
+      pids.push_back(pid);
+    }
+  }
+  return pids;
+}
+
+// Returns the first instance found of explorer.exe.
+std::optional<DWORD> GetExplorerPid() {
+  std::vector<DWORD> pids =
+      FindProcessesInSession(L"EXPLORER.EXE", GetActiveSessionId());
+  if (pids.empty()) {
+    return {};
+  }
+  return pids[0];
+}
+
+// Returns an impersonation token for the user running process_id.
+HResultOr<ScopedKernelHANDLE> GetImpersonationToken(
+    std::optional<DWORD> process_id) {
+  if (!process_id) {
+    return base::unexpected(E_UNEXPECTED);
+  }
+  base::win::ScopedHandle process(::OpenProcess(
+      PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, TRUE, *process_id));
+  if (!process.IsValid()) {
+    return base::unexpected(HRESULTFromLastError());
+  }
+  ScopedKernelHANDLE process_token;
+  if (!::OpenProcessToken(process.Get(), TOKEN_DUPLICATE | TOKEN_QUERY,
+                          ScopedKernelHANDLE::Receiver(process_token).get())) {
+    return base::unexpected(HRESULTFromLastError());
+  }
+  ScopedKernelHANDLE user_token;
+  if (!::DuplicateTokenEx(process_token.get(),
+                          TOKEN_IMPERSONATE | TOKEN_QUERY |
+                              TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE,
+                          NULL, SecurityImpersonation, TokenPrimary,
+                          ScopedKernelHANDLE::Receiver(user_token).get())) {
+    return base::unexpected(HRESULTFromLastError());
+  }
+  return user_token;
+}
+
+}  // namespace
+
+HResultOr<ScopedKernelHANDLE> GetLoggedOnUserToken() {
+  return GetImpersonationToken(GetExplorerPid());
+}
+
 }  // namespace updater
diff --git a/chrome/updater/util/win_util.h b/chrome/updater/util/win_util.h
index cef0f44..4e65f84 100644
--- a/chrome/updater/util/win_util.h
+++ b/chrome/updater/util/win_util.h
@@ -36,6 +36,7 @@
 #include "base/win/win_util.h"
 #include "base/win/windows_types.h"
 #include "chrome/updater/updater_scope.h"
+#include "chrome/updater/win/scoped_handle.h"
 
 namespace base {
 class FilePath;
@@ -429,6 +430,10 @@
 // or it defaults to US English.
 std::wstring GetTextForSystemError(int error);
 
+// Retrieves the logged on user token for the active explorer process if one
+// exists.
+HResultOr<ScopedKernelHANDLE> GetLoggedOnUserToken();
+
 }  // namespace updater
 
 #endif  // CHROME_UPDATER_UTIL_WIN_UTIL_H_
diff --git a/chrome/updater/util/win_util_unittest.cc b/chrome/updater/util/win_util_unittest.cc
index de5edf7..5851686f 100644
--- a/chrome/updater/util/win_util_unittest.cc
+++ b/chrome/updater/util/win_util_unittest.cc
@@ -48,6 +48,7 @@
 #include "chrome/updater/updater_version.h"
 #include "chrome/updater/util/unit_test_util.h"
 #include "chrome/updater/util/unit_test_util_win.h"
+#include "chrome/updater/win/scoped_impersonation.h"
 #include "chrome/updater/win/test/test_executables.h"
 #include "chrome/updater/win/test/test_strings.h"
 #include "chrome/updater/win/win_constants.h"
@@ -576,4 +577,18 @@
       L"0x80040200");
 }
 
+TEST(WinUtil, GetLoggedOnUserToken) {
+  if (!::IsUserAnAdmin() || !IsUACOn()) {
+    return;
+  }
+
+  ASSERT_TRUE(::IsUserAnAdmin());
+  HResultOr<ScopedKernelHANDLE> token = GetLoggedOnUserToken();
+  ASSERT_TRUE(token.has_value());
+
+  ScopedImpersonation impersonate;
+  ASSERT_TRUE(SUCCEEDED(impersonate.Impersonate(token.value().get())));
+  ASSERT_FALSE(::IsUserAnAdmin());
+}
+
 }  // namespace updater
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index 9242ce7..04c8fd5e8 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -122,6 +122,10 @@
   "a11y.Smoke",
   "a11y.Smoke.lacros",
 
+  # b/326849541
+  "inputs.InputMethodManagement.guest",
+  "inputs.PhysicalKeyboardEmojiSuggestion.guest",
+
   # READ COMMENT AT TOP BEFORE ADDING NEW TESTS HERE.
 ]
 
diff --git a/clank b/clank
index 86871e6..15ea959 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 86871e647c038acdf24601d6955b545a59642446
+Subproject commit 15ea959101d2efe298a00841d9f8c015a387b57b
diff --git a/components/autofill/content/common/mojom/autofill_agent.mojom b/components/autofill/content/common/mojom/autofill_agent.mojom
index af94d41..c2ed1a5 100644
--- a/components/autofill/content/common/mojom/autofill_agent.mojom
+++ b/components/autofill/content/common/mojom/autofill_agent.mojom
@@ -124,6 +124,9 @@
   // Previews the given `value` into the field identified by `field_id`.
   PreviewField(FieldRendererId field_id, mojo_base.mojom.String16 value);
 
+  // Fills the given `value` into the field identified by `field_id`.
+  FillField(FieldRendererId field_id, mojo_base.mojom.String16 value);
+
   // Notification to start (`active` == true) or stop (`active` == false)
   // logging the decisions made about saving the password.
   SetLoggingState(bool active);
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index fa641b5..3442051 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -869,11 +869,11 @@
                           element.IsPasswordFieldForAutofill()) &&
       !(username.empty() && element.IsPasswordFieldForAutofill()) &&
       username_element.Value().Utf16() != username) {
-    FillField(&username_element, username);
+    DoFillField(username_element, username);
   }
 
   if (!password_element.IsNull()) {
-    FillPasswordFieldAndSave(&password_element, password);
+    FillPasswordFieldAndSave(password_element, password);
 
     // TODO(crbug.com/1319364): As Touch-To-Fill and auto-submission don't
     // currently support filling single username fields, the code below is
@@ -903,12 +903,12 @@
     return;
   }
   if (!is_password) {
-    FillField(&focused_input_element, credential);
+    DoFillField(focused_input_element, credential);
   }
   if (!focused_input_element.IsPasswordFieldForAutofill()) {
     return;
   }
-  FillPasswordFieldAndSave(&focused_input_element, credential);
+  FillPasswordFieldAndSave(focused_input_element, credential);
 }
 
 void PasswordAutofillAgent::PreviewField(FieldRendererId field_id,
@@ -922,6 +922,18 @@
                  /*is_password=*/input.IsPasswordFieldForAutofill());
 }
 
+void PasswordAutofillAgent::FillField(FieldRendererId field_id,
+                                      const std::u16string& value) {
+  WebFormControlElement form_control =
+      form_util::GetFormControlByRendererId(field_id);
+  WebInputElement input_element = form_control.DynamicTo<WebInputElement>();
+  if (input_element.IsNull() || input_element.IsReadOnly()) {
+    // Early return for non-input fields such as textarea.
+    return;
+  }
+  DoFillField(input_element, value);
+}
+
 void PasswordAutofillAgent::DoPreviewField(WebInputElement& input,
                                            const std::u16string& credential,
                                            bool is_password) {
@@ -934,24 +946,22 @@
   input.SetSuggestedValue(WebString::FromUTF16(credential));
 }
 
-void PasswordAutofillAgent::FillField(WebInputElement* input,
-                                      const std::u16string& credential) {
-  DCHECK(input);
-  DCHECK(!input->IsNull());
-  input->SetAutofillValue(WebString::FromUTF16(credential));
+void PasswordAutofillAgent::DoFillField(WebInputElement& input,
+                                        const std::u16string& credential) {
+  CHECK(!input.IsNull());
+  input.SetAutofillValue(WebString::FromUTF16(credential));
   field_data_manager().UpdateFieldDataMap(
-      form_util::GetFieldRendererId(*input), credential,
+      form_util::GetFieldRendererId(input), credential,
       FieldPropertiesFlags::kAutofilledOnUserTrigger);
-  TrackAutofilledElement(*input);
+  TrackAutofilledElement(input);
 }
 
 void PasswordAutofillAgent::FillPasswordFieldAndSave(
-    WebInputElement* password_input,
+    WebInputElement& password_input,
     const std::u16string& credential) {
-  DCHECK(password_input);
-  DCHECK(password_input->IsPasswordFieldForAutofill());
-  FillField(password_input, credential);
-  InformBrowserAboutUserInput(GetFormElement(*password_input), *password_input);
+  CHECK(password_input.IsPasswordFieldForAutofill());
+  DoFillField(password_input, credential);
+  InformBrowserAboutUserInput(GetFormElement(password_input), password_input);
 }
 
 void PasswordAutofillAgent::PreviewSuggestion(
diff --git a/components/autofill/content/renderer/password_autofill_agent.h b/components/autofill/content/renderer/password_autofill_agent.h
index c2c16ce2..dd7ff91 100644
--- a/components/autofill/content/renderer/password_autofill_agent.h
+++ b/components/autofill/content/renderer/password_autofill_agent.h
@@ -140,6 +140,8 @@
                             const std::u16string& credential) override;
   void PreviewField(FieldRendererId field_id,
                     const std::u16string& value) override;
+  void FillField(FieldRendererId field_id,
+                 const std::u16string& value) override;
   void SetLoggingState(bool active) override;
   void AnnotateFieldsWithParsingResult(
       const ParsingResult& parsing_result) override;
@@ -394,12 +396,12 @@
 
   // Checks that a given input field is valid before filling the given `input`
   // with the given `credential` and marking the field as auto-filled.
-  void FillField(blink::WebInputElement* input,
-                 const std::u16string& credential);
+  void DoFillField(blink::WebInputElement& input,
+                   const std::u16string& credential);
 
   // Uses `FillField` to fill the given `credential` into the `password_input`.
   // Saves the password for its associated form.
-  void FillPasswordFieldAndSave(blink::WebInputElement* password_input,
+  void FillPasswordFieldAndSave(blink::WebInputElement& password_input,
                                 const std::u16string& credential);
 
   // `form` and `input` are the elements user has just been interacting with
diff --git a/components/autofill/core/browser/autofill_client.cc b/components/autofill/core/browser/autofill_client.cc
index ab40594..a6972c7 100644
--- a/components/autofill/core/browser/autofill_client.cc
+++ b/components/autofill/core/browser/autofill_client.cc
@@ -257,8 +257,7 @@
 }
 
 void AutofillClient::ShowAutofillErrorDialog(
-    const AutofillErrorDialogContext& context) {
-}
+    AutofillErrorDialogContext context) {}
 
 LogManager* AutofillClient::GetLogManager() const {
   return nullptr;
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 3cb039cc..ca3e1a1 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -801,8 +801,7 @@
   // `server_returned_title` and `server_returned_description` in `context` are
   // both set, the error dialog that is displayed will have these fields
   // displayed for the title and description, respectively.
-  virtual void ShowAutofillErrorDialog(
-      const AutofillErrorDialogContext& context);
+  virtual void ShowAutofillErrorDialog(AutofillErrorDialogContext context);
 
   // Maybe triggers a hats survey that measures the user's perception of
   // Autofill. When triggering happens, the survey dialog will be displayed with
diff --git a/components/autofill/core/browser/payments/autofill_error_dialog_context.cc b/components/autofill/core/browser/payments/autofill_error_dialog_context.cc
index a8879ef..2973d86 100644
--- a/components/autofill/core/browser/payments/autofill_error_dialog_context.cc
+++ b/components/autofill/core/browser/payments/autofill_error_dialog_context.cc
@@ -22,9 +22,18 @@
 AutofillErrorDialogContext::AutofillErrorDialogContext(
     const AutofillErrorDialogContext& other) = default;
 
+AutofillErrorDialogContext::AutofillErrorDialogContext(
+    AutofillErrorDialogContext&& other) = default;
+
 AutofillErrorDialogContext& AutofillErrorDialogContext::operator=(
     const AutofillErrorDialogContext&) = default;
 
+AutofillErrorDialogContext& AutofillErrorDialogContext::operator=(
+    AutofillErrorDialogContext&&) = default;
+
 AutofillErrorDialogContext::~AutofillErrorDialogContext() = default;
 
+bool AutofillErrorDialogContext::operator==(
+    const AutofillErrorDialogContext& other_context) const = default;
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/payments/autofill_error_dialog_context.h b/components/autofill/core/browser/payments/autofill_error_dialog_context.h
index 8a58a21..0fcdc82 100644
--- a/components/autofill/core/browser/payments/autofill_error_dialog_context.h
+++ b/components/autofill/core/browser/payments/autofill_error_dialog_context.h
@@ -48,9 +48,13 @@
 
   AutofillErrorDialogContext();
   AutofillErrorDialogContext(const AutofillErrorDialogContext& other);
+  AutofillErrorDialogContext(AutofillErrorDialogContext&& other);
   AutofillErrorDialogContext& operator=(const AutofillErrorDialogContext&);
+  AutofillErrorDialogContext& operator=(AutofillErrorDialogContext&&);
   ~AutofillErrorDialogContext();
 
+  bool operator==(const AutofillErrorDialogContext& other_context) const;
+
   // The type of autofill error dialog that will be displayed.
   AutofillErrorDialogType type = AutofillErrorDialogType::kTypeUnknown;
 
diff --git a/components/autofill/core/browser/payments/payments_window_manager.h b/components/autofill/core/browser/payments/payments_window_manager.h
index d546f7cd..627e86a 100644
--- a/components/autofill/core/browser/payments/payments_window_manager.h
+++ b/components/autofill/core/browser/payments/payments_window_manager.h
@@ -58,8 +58,17 @@
 
   // The error type of the 3DS authentication inside of the pop-up.
   enum class Vcn3dsAuthenticationPopupErrorType {
+    // The authentication inside of the 3DS pop-up was a failure. The reason for
+    // the failure is unknown to Chrome, and can be due to any of several
+    // possible reasons. Some reasons can be that the user failed to
+    // authenticate, or there is a server error.
     kAuthenticationFailed = 0,
+    // The authentication inside of the 3DS pop-up did not complete. This occurs
+    // if the user closes the pop-up before finishing the authentication, and
+    // there are no query params.
     kAuthenticationNotCompleted = 1,
+    // The query params are invalid. This should not happen, but since Chrome
+    // has no control over this it is handled gracefully.
     kInvalidQueryParams = 2,
   };
 
diff --git a/components/autofill/core/browser/test_autofill_client.h b/components/autofill/core/browser/test_autofill_client.h
index d5778759..11e2204 100644
--- a/components/autofill/core/browser/test_autofill_client.h
+++ b/components/autofill/core/browser/test_autofill_client.h
@@ -467,10 +467,9 @@
 
   PopupHidingReason popup_hiding_reason() { return popup_hidden_reason_; }
 
-  void ShowAutofillErrorDialog(
-      const AutofillErrorDialogContext& context) override {
+  void ShowAutofillErrorDialog(AutofillErrorDialogContext context) override {
     autofill_error_dialog_shown_ = true;
-    autofill_error_dialog_context_ = context;
+    autofill_error_dialog_context_ = std::move(context);
   }
 
   bool IsAutocompleteEnabled() const override { return true; }
diff --git a/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl.cc b/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl.cc
index ca112ba..b6c5456c6 100644
--- a/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl.cc
+++ b/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl.cc
@@ -14,35 +14,30 @@
 
 namespace autofill {
 
-AutofillErrorDialogControllerImpl::AutofillErrorDialogControllerImpl() =
-    default;
+AutofillErrorDialogControllerImpl::AutofillErrorDialogControllerImpl(
+    AutofillErrorDialogContext error_dialog_context)
+    : error_dialog_context_(std::move(error_dialog_context)) {}
 
 AutofillErrorDialogControllerImpl::~AutofillErrorDialogControllerImpl() {
-  Dismiss();
+  DismissIfApplicable();
 }
 
 void AutofillErrorDialogControllerImpl::Show(
-    const AutofillErrorDialogContext& autofill_error_dialog_context,
     base::OnceCallback<base::WeakPtr<AutofillErrorDialogView>()>
         view_creation_callback) {
-  if (autofill_error_dialog_view_) {
-    Dismiss();
-  }
-
   CHECK(!autofill_error_dialog_view_);
-  error_dialog_context_ = autofill_error_dialog_context;
   autofill_error_dialog_view_ = std::move(view_creation_callback).Run();
   CHECK(autofill_error_dialog_view_);
 
   base::UmaHistogramEnumeration("Autofill.ErrorDialogShown",
-                                autofill_error_dialog_context.type);
+                                error_dialog_context_.type);
 
   // If both |server_returned_title| and |server_returned_description| are
   // populated, then the error dialog was displayed with the server-driven text.
   if (error_dialog_context_.server_returned_title &&
       error_dialog_context_.server_returned_description) {
     base::UmaHistogramEnumeration("Autofill.ErrorDialogShown.WithServerText",
-                                  autofill_error_dialog_context.type);
+                                  error_dialog_context_.type);
   }
 }
 
@@ -135,7 +130,7 @@
       IDS_AUTOFILL_ERROR_DIALOG_NEGATIVE_BUTTON_LABEL);
 }
 
-void AutofillErrorDialogControllerImpl::Dismiss() {
+void AutofillErrorDialogControllerImpl::DismissIfApplicable() {
   if (autofill_error_dialog_view_) {
     autofill_error_dialog_view_->Dismiss();
   }
diff --git a/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl.h b/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl.h
index b711809..c87ddfd 100644
--- a/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl.h
+++ b/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl.h
@@ -21,7 +21,8 @@
 // The controller is destroyed once the view is dismissed.
 class AutofillErrorDialogControllerImpl : public AutofillErrorDialogController {
  public:
-  AutofillErrorDialogControllerImpl();
+  explicit AutofillErrorDialogControllerImpl(
+      AutofillErrorDialogContext error_dialog_context);
   ~AutofillErrorDialogControllerImpl() override;
 
   AutofillErrorDialogControllerImpl(const AutofillErrorDialogControllerImpl&) =
@@ -29,10 +30,8 @@
   AutofillErrorDialogControllerImpl& operator=(
       const AutofillErrorDialogControllerImpl&) = delete;
 
-  // Show the error dialog for the given `autofill_error_dialog_context` and the
-  // `view_creation_callback`.
-  void Show(const AutofillErrorDialogContext& autofill_error_dialog_context,
-            base::OnceCallback<base::WeakPtr<AutofillErrorDialogView>()>
+  // Provide the `view_creation_callback` and show the error dialog.
+  void Show(base::OnceCallback<base::WeakPtr<AutofillErrorDialogView>()>
                 view_creation_callback);
 
   // AutofillErrorDialogController.
@@ -48,7 +47,7 @@
 
  private:
   // Dismiss the error dialog if showing.
-  void Dismiss();
+  void DismissIfApplicable();
 
   // The context of the error dialog that is being displayed. Contains
   // information such as the type of the error dialog that is being displayed.
@@ -57,7 +56,7 @@
   // |error_dialog_context_| contains this information, the fields in
   // |error_dialog_context_| should be preferred when displaying the error
   // dialog.
-  AutofillErrorDialogContext error_dialog_context_;
+  const AutofillErrorDialogContext error_dialog_context_;
 
   // View that displays the error dialog.
   base::WeakPtr<AutofillErrorDialogView> autofill_error_dialog_view_;
diff --git a/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl_unittest.cc b/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl_unittest.cc
index 1c7d7d1..93b5d80 100644
--- a/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl_unittest.cc
+++ b/components/autofill/core/browser/ui/payments/autofill_error_dialog_controller_impl_unittest.cc
@@ -41,16 +41,11 @@
  public:
   AutofillErrorDialogControllerImplTest() = default;
 
-  void SetUp() override {
-    controller_ = std::make_unique<AutofillErrorDialogControllerImpl>();
-  }
-
   void ShowPrompt(const AutofillErrorDialogContext& context) {
-    controller()->Show(
-        context,
-        base::BindOnce(
-            &AutofillErrorDialogControllerImplTest::CreateErrorDialogView,
-            base::Unretained(this)));
+    controller_ = std::make_unique<AutofillErrorDialogControllerImpl>(context);
+    controller_->Show(base::BindOnce(
+        &AutofillErrorDialogControllerImplTest::CreateErrorDialogView,
+        base::Unretained(this)));
   }
 
   base::WeakPtr<AutofillErrorDialogView> CreateErrorDialogView() {
diff --git a/components/autofill/ios/browser/resources/suggestion_controller.ts b/components/autofill/ios/browser/resources/suggestion_controller.ts
index 71d1aea..0d12626 100644
--- a/components/autofill/ios/browser/resources/suggestion_controller.ts
+++ b/components/autofill/ios/browser/resources/suggestion_controller.ts
@@ -367,19 +367,24 @@
              currentElement, document.querySelectorAll('*')) !== null;
 }
 
+declare interface PreviousNextElements {
+  previous: boolean;
+  next: boolean;
+}
+
 /**
  * @param formName The name of the form containing the element.
  * @param fieldName The name of the field containing the element.
  * @return Whether there is an element in the sequential navigation before and
- *     after currently active element. The result is returned as a comma
- *     separated string of the strings `true` and `false`.
- *     TODO(crbug.com/893368): Return a dictionary with the values instead.
+ *     after currently active element. The result is returned as an object with
+ *     a boolean value for the keys `previous` and `next`.
  */
-function hasPreviousNextElements(formName: string, fieldName: string): string {
-  return [
-    hasPreviousElement(formName, fieldName),
-    hasNextElement(formName, fieldName),
-  ].toString();
+function hasPreviousNextElements(
+    formName: string, fieldName: string): PreviousNextElements {
+  return {
+    previous: hasPreviousElement(formName, fieldName),
+    next: hasNextElement(formName, fieldName),
+  };
 }
 
 gCrWeb.suggestion = {
diff --git a/components/autofill/ios/browser/suggestion_controller_java_script_feature.mm b/components/autofill/ios/browser/suggestion_controller_java_script_feature.mm
index 9f64988..5a1587a6b 100644
--- a/components/autofill/ios/browser/suggestion_controller_java_script_feature.mm
+++ b/components/autofill/ios/browser/suggestion_controller_java_script_feature.mm
@@ -26,31 +26,26 @@
 void ProcessPreviousAndNextElementsPresenceResult(
     base::OnceCallback<void(bool, bool)> completion_handler,
     const base::Value* res) {
-  NSString* result = nil;
-  if (res && res->is_string()) {
-    result = base::SysUTF8ToNSString(res->GetString());
-  }
-  // The result maybe an empty string here due to 2 reasons:
+  // The result may be invalid:
   // 1) When there is an exception running the JS
   // 2) There is a race when the page is changing due to which
   // SuggestionControllerJavaScriptFeature has not yet injected the
   // __gCrWeb.suggestion object.
-  // Handle this case gracefully. If a page has overridden
-  // Array.toString, the string returned may not contain a ",",
-  // hence this is a defensive measure to early return.
-  NSArray* components = [result componentsSeparatedByString:@","];
-  if (components.count != 2) {
+  // Handle this case gracefully.
+  if (!res || !res->is_dict() || res->GetDict().size() != 2) {
     std::move(completion_handler).Run(false, false);
     return;
   }
 
-  DCHECK([components[0] isEqualToString:@"true"] ||
-         [components[0] isEqualToString:@"false"]);
-  bool has_previous_element = [components[0] isEqualToString:@"true"];
-  DCHECK([components[1] isEqualToString:@"true"] ||
-         [components[1] isEqualToString:@"false"]);
-  bool has_next_element = [components[1] isEqualToString:@"true"];
-  std::move(completion_handler).Run(has_previous_element, has_next_element);
+  const base::Value::Dict& dict = res->GetDict();
+  std::optional<bool> previous = dict.FindBool("previous");
+  std::optional<bool> next = dict.FindBool("next");
+  if (!previous || !next) {
+    std::move(completion_handler).Run(false, false);
+    return;
+  }
+
+  std::move(completion_handler).Run(previous.value(), next.value());
 }
 
 }  // namespace
diff --git a/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc b/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc
index 33266cf0..bcee528 100644
--- a/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc
+++ b/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc
@@ -28,13 +28,6 @@
 
 namespace blocklist {
 
-// When enabled, prefer to use the new recovery module to recover the
-// `OptOutStoreSQL` database. See https://crbug.com/1385500 for details.
-// This is a kill switch and is not intended to be used in a field trial.
-BASE_FEATURE(kOptOutStoreSQLUseBuiltInRecoveryIfSupported,
-             "OptOutStoreSQLUseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 namespace {
 
 // Command line switch to change the entry per host DB size.
@@ -102,8 +95,7 @@
                            sql::Statement* stmt) {
   // Attempt to recover a corrupt database, if it is eligible to be recovered.
   if (sql::BuiltInRecovery::RecoverIfPossible(
-          db, extended_error, sql::BuiltInRecovery::Strategy::kRecoverOrRaze,
-          &kOptOutStoreSQLUseBuiltInRecoveryIfSupported)) {
+          db, extended_error, sql::BuiltInRecovery::Strategy::kRecoverOrRaze)) {
     // Recovery was attempted. The database handle has been poisoned and the
     // error callback has been reset.
 
diff --git a/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h b/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h
index dcbd120..d3d54b9 100644
--- a/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h
+++ b/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h
@@ -27,8 +27,6 @@
 
 namespace blocklist {
 
-BASE_DECLARE_FEATURE(kOptOutStoreSQLUseBuiltInRecoveryIfSupported);
-
 // OptOutStoreSQL is an instance of OptOutStore
 // which is implemented using a SQLite database.
 class OptOutStoreSQL : public OptOutStore {
diff --git a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/AccessibilitySettingsDelegate.java b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/AccessibilitySettingsDelegate.java
index 186b063..90382aa 100644
--- a/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/AccessibilitySettingsDelegate.java
+++ b/components/browser_ui/accessibility/android/java/src/org/chromium/components/browser_ui/accessibility/AccessibilitySettingsDelegate.java
@@ -11,15 +11,6 @@
  * embedder-specific logic.
  */
 public interface AccessibilitySettingsDelegate {
-    /** An interface to control a single boolean preference. */
-    interface BooleanPreferenceDelegate {
-        /** @return whether the preference is enabled. */
-        boolean isEnabled();
-
-        /** Called when the preference value is changed. */
-        void setEnabled(boolean value);
-    }
-
     /** An interface to control a single integer preference. */
     interface IntegerPreferenceDelegate {
         /** @return int - Current value of the preference of this instance. */
@@ -36,12 +27,6 @@
     BrowserContextHandle getBrowserContextHandle();
 
     /**
-     * @return the BooleanPreferenceDelegate instance that should be used when rendering the reader
-     * for accessibility preference. Return null to omit the preference.
-     */
-    BooleanPreferenceDelegate getReaderForAccessibilityDelegate();
-
-    /**
      * @return the InterPreferenceDelegate instance that should be used for reading and setting the
      * text size contrast value for accessibility settings. Return null to omit the preference.
      */
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
index f513839..ff672de 100644
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
@@ -16,8 +16,6 @@
 import org.chromium.base.CommandLine;
 import org.chromium.components.content_settings.ContentSettingValues;
 import org.chromium.components.content_settings.ContentSettingsType;
-import org.chromium.components.permissions.PermissionsAndroidFeatureList;
-import org.chromium.components.permissions.PermissionsAndroidFeatureMap;
 import org.chromium.content_public.browser.BrowserContextHandle;
 import org.chromium.content_public.browser.ContentFeatureList;
 import org.chromium.content_public.browser.ContentFeatureMap;
@@ -99,11 +97,7 @@
             case ContentSettingsType.VR:
                 return WebsitePermissionsType.PERMISSION_INFO;
             case ContentSettingsType.STORAGE_ACCESS:
-                if (PermissionsAndroidFeatureMap.isEnabled(
-                        PermissionsAndroidFeatureList.PERMISSION_STORAGE_ACCESS)) {
                     return WebsitePermissionsType.EMBEDDED_PERMISSION;
-                }
-                return null;
             case ContentSettingsType.BLUETOOTH_GUARD:
             case ContentSettingsType.USB_GUARD:
                 return WebsitePermissionsType.CHOSEN_OBJECT_INFO;
diff --git a/components/commerce/core/BUILD.gn b/components/commerce/core/BUILD.gn
index 0e3162e..900d14bd 100644
--- a/components/commerce/core/BUILD.gn
+++ b/components/commerce/core/BUILD.gn
@@ -34,6 +34,17 @@
   ]
 }
 
+source_set("feature_utils") {
+  sources = [
+    "feature_utils.cc",
+    "feature_utils.h",
+  ]
+  deps = [
+    ":account_checker",
+    ":feature_list",
+  ]
+}
+
 source_set("feature_list_unittests") {
   testonly = true
   sources = [ "commerce_feature_list_unittest.cc" ]
@@ -206,7 +217,6 @@
   ]
 
   deps = [
-    ":account_checker",
     ":commerce_constants",
     ":commerce_subscription_db_content_proto",
     ":discounts_db_content_proto",
@@ -242,7 +252,9 @@
   ]
 
   public_deps = [
+    ":account_checker",
     ":commerce_types",
+    ":feature_utils",
     "//components/commerce/core/subscriptions:subscriptions",
     "//services/data_decoder/public/cpp",
   ]
@@ -379,6 +391,7 @@
   deps = [
     ":account_checker",
     "//base",
+    "//components/prefs",
     "//services/network/public/cpp:cpp",
     "//testing/gmock",
   ]
diff --git a/components/commerce/core/account_checker.cc b/components/commerce/core/account_checker.cc
index ef8f6a6..5d906e09 100644
--- a/components/commerce/core/account_checker.cc
+++ b/components/commerce/core/account_checker.cc
@@ -37,11 +37,15 @@
     "https://memex-pa.googleapis.com/v1/notifications/preferences";
 
 AccountChecker::AccountChecker(
+    std::string country,
+    std::string locale,
     PrefService* pref_service,
     signin::IdentityManager* identity_manager,
     syncer::SyncService* sync_service,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
-    : pref_service_(pref_service),
+    : country_(country),
+      locale_(locale),
+      pref_service_(pref_service),
       identity_manager_(identity_manager),
       sync_service_(sync_service),
       url_loader_factory_(url_loader_factory),
@@ -113,6 +117,18 @@
          signin::Tribool::kTrue;
 }
 
+std::string AccountChecker::GetCountry() {
+  return country_;
+}
+
+std::string AccountChecker::GetLocale() {
+  return locale_;
+}
+
+PrefService* AccountChecker::GetPrefs() {
+  return pref_service_.get();
+}
+
 void AccountChecker::FetchPriceEmailPref() {
   if (!IsSignedIn()) {
     return;
diff --git a/components/commerce/core/account_checker.h b/components/commerce/core/account_checker.h
index 7989f2e1..4909fdbd 100644
--- a/components/commerce/core/account_checker.h
+++ b/components/commerce/core/account_checker.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_COMMERCE_CORE_ACCOUNT_CHECKER_H_
 #define COMPONENTS_COMMERCE_CORE_ACCOUNT_CHECKER_H_
 
+#include <string>
+
 #include "base/memory/scoped_refptr.h"
 #include "components/endpoint_fetcher/endpoint_fetcher.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
@@ -43,12 +45,22 @@
 
   virtual bool IsSubjectToParentalControls();
 
+  // Gets the user's country as determined at startup.
+  virtual std::string GetCountry();
+
+  // Gets the user's locale as determine at startup.
+  virtual std::string GetLocale();
+
+  virtual PrefService* GetPrefs();
+
  protected:
   friend class ShoppingService;
   friend class MockAccountChecker;
 
   // This class should only be initialized in ShoppingService.
   AccountChecker(
+      std::string country,
+      std::string locale,
       PrefService* pref_service,
       signin::IdentityManager* identity_manager,
       syncer::SyncService* sync_service,
@@ -96,6 +108,10 @@
   void OnFetchPriceEmailPrefJsonParsed(
       data_decoder::DataDecoder::ValueOrError result);
 
+  std::string country_;
+
+  std::string locale_;
+
   raw_ptr<PrefService> pref_service_;
 
   raw_ptr<signin::IdentityManager> identity_manager_;
diff --git a/components/commerce/core/account_checker_unittest.cc b/components/commerce/core/account_checker_unittest.cc
index 3338156..364fd67 100644
--- a/components/commerce/core/account_checker_unittest.cc
+++ b/components/commerce/core/account_checker_unittest.cc
@@ -53,7 +53,9 @@
       signin::IdentityManager* identity_manager,
       syncer::SyncService* sync_service,
       scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
-      : AccountChecker(pref_service,
+      : AccountChecker("us",
+                       "en-us",
+                       pref_service,
                        identity_manager,
                        sync_service,
                        std::move(url_loader_factory)) {}
diff --git a/components/commerce/core/feature_utils.cc b/components/commerce/core/feature_utils.cc
new file mode 100644
index 0000000..48b6ab6d
--- /dev/null
+++ b/components/commerce/core/feature_utils.cc
@@ -0,0 +1,36 @@
+// Copyright 2024 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/commerce/core/feature_utils.h"
+
+#include "components/commerce/core/account_checker.h"
+#include "components/commerce/core/commerce_feature_list.h"
+
+namespace commerce {
+
+bool IsShoppingListEligible(AccountChecker* account_checker) {
+  if (!commerce::IsRegionLockedFeatureEnabled(
+          kShoppingList, kShoppingListRegionLaunched,
+          account_checker->GetCountry(), account_checker->GetLocale())) {
+    return false;
+  }
+
+  if (!account_checker->GetPrefs() ||
+      !IsShoppingListAllowedForEnterprise(account_checker->GetPrefs())) {
+    return false;
+  }
+
+  // Make sure the user allows subscriptions to be made and that we can fetch
+  // store data.
+  if (!account_checker || !account_checker->IsSignedIn() ||
+      !account_checker->IsSyncingBookmarks() ||
+      !account_checker->IsAnonymizedUrlDataCollectionEnabled() ||
+      account_checker->IsSubjectToParentalControls()) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace commerce
diff --git a/components/commerce/core/feature_utils.h b/components/commerce/core/feature_utils.h
new file mode 100644
index 0000000..0e616d5
--- /dev/null
+++ b/components/commerce/core/feature_utils.h
@@ -0,0 +1,22 @@
+// Copyright 2024 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_COMMERCE_CORE_FEATURE_UTILS_H_
+#define COMPONENTS_COMMERCE_CORE_FEATURE_UTILS_H_
+
+namespace commerce {
+
+class AccountChecker;
+
+// This is a feature check for the "shopping list". This will only return true
+// if the user has the feature flag enabled, is signed-in, has MSBB enabled,
+// has webapp activity enabled, is allowed by enterprise policy, and (if
+// applicable) in an eligible country and locale. The value returned by this
+// method can change at runtime, so it should not be used when deciding
+// whether to create critical, feature-related infrastructure.
+bool IsShoppingListEligible(AccountChecker* account_checker);
+
+}  // namespace commerce
+
+#endif  // COMPONENTS_COMMERCE_CORE_FEATURE_UTILS_H_
diff --git a/components/commerce/core/mock_account_checker.cc b/components/commerce/core/mock_account_checker.cc
index 8c3c652..3ad8fde 100644
--- a/components/commerce/core/mock_account_checker.cc
+++ b/components/commerce/core/mock_account_checker.cc
@@ -3,16 +3,19 @@
 // found in the LICENSE file.
 
 #include "components/commerce/core/mock_account_checker.h"
+#include "components/prefs/pref_service.h"
 
 namespace commerce {
 
 MockAccountChecker::MockAccountChecker()
-    : AccountChecker(nullptr, nullptr, nullptr, nullptr) {
+    : AccountChecker("", "", nullptr, nullptr, nullptr, nullptr) {
   // Default to an account checker with the fewest restrictions.
   SetSignedIn(true);
   SetSyncingBookmarks(true);
   SetAnonymizedUrlDataCollectionEnabled(true);
   SetIsSubjectToParentalControls(false);
+  SetCountry("us");
+  SetLocale("en-us");
 }
 
 MockAccountChecker::~MockAccountChecker() = default;
@@ -36,4 +39,16 @@
       .WillByDefault(testing::Return(subject_to_parental_controls));
 }
 
+void MockAccountChecker::SetCountry(std::string country) {
+  ON_CALL(*this, GetCountry).WillByDefault(testing::Return(country));
+}
+
+void MockAccountChecker::SetLocale(std::string locale) {
+  ON_CALL(*this, GetLocale).WillByDefault(testing::Return(locale));
+}
+
+void MockAccountChecker::SetPrefs(PrefService* prefs) {
+  ON_CALL(*this, GetPrefs).WillByDefault(testing::Return(prefs));
+}
+
 }  // namespace commerce
diff --git a/components/commerce/core/mock_account_checker.h b/components/commerce/core/mock_account_checker.h
index 3ba7c2d..d8063023 100644
--- a/components/commerce/core/mock_account_checker.h
+++ b/components/commerce/core/mock_account_checker.h
@@ -5,9 +5,13 @@
 #ifndef COMPONENTS_COMMERCE_CORE_MOCK_ACCOUNT_CHECKER_H_
 #define COMPONENTS_COMMERCE_CORE_MOCK_ACCOUNT_CHECKER_H_
 
+#include <string>
+
 #include "components/commerce/core/account_checker.h"
 #include "testing/gmock/include/gmock/gmock.h"
 
+class PrefService;
+
 namespace commerce {
 
 // Used to mock user account status in tests.
@@ -26,6 +30,12 @@
 
   MOCK_METHOD(bool, IsSubjectToParentalControls, (), (override));
 
+  MOCK_METHOD(std::string, GetCountry, (), (override));
+
+  MOCK_METHOD(std::string, GetLocale, (), (override));
+
+  MOCK_METHOD(PrefService*, GetPrefs, (), (override));
+
   void SetSignedIn(bool signed_in);
 
   void SetSyncingBookmarks(bool syncing);
@@ -33,6 +43,12 @@
   void SetAnonymizedUrlDataCollectionEnabled(bool enabled);
 
   void SetIsSubjectToParentalControls(bool subject_to_parental_controls);
+
+  void SetCountry(std::string country);
+
+  void SetLocale(std::string locale);
+
+  void SetPrefs(PrefService* prefs);
 };
 
 }  // namespace commerce
diff --git a/components/commerce/core/mock_shopping_service.cc b/components/commerce/core/mock_shopping_service.cc
index a243000..dda4e31 100644
--- a/components/commerce/core/mock_shopping_service.cc
+++ b/components/commerce/core/mock_shopping_service.cc
@@ -58,6 +58,11 @@
   SetGetAllParcelStatusesCallbackValue(std::vector<ParcelTrackingStatus>());
 }
 
+void MockShoppingService::SetAccountChecker(AccountChecker* account_checker) {
+  ON_CALL(*this, GetAccountChecker)
+      .WillByDefault(testing::Return(account_checker));
+}
+
 void MockShoppingService::SetResponseForGetProductInfoForUrl(
     std::optional<commerce::ProductInfo> product_info) {
   ON_CALL(*this, GetProductInfoForUrl)
diff --git a/components/commerce/core/mock_shopping_service.h b/components/commerce/core/mock_shopping_service.h
index 8e78eeb3..b9237fd 100644
--- a/components/commerce/core/mock_shopping_service.h
+++ b/components/commerce/core/mock_shopping_service.h
@@ -21,6 +21,8 @@
 
 namespace commerce {
 
+class AccountChecker;
+
 // A mock ShoppingService that allows us to decide the response.
 class MockShoppingService : public commerce::ShoppingService {
  public:
@@ -31,6 +33,7 @@
   ~MockShoppingService() override;
 
   // commerce::ShoppingService overrides.
+  MOCK_METHOD(AccountChecker*, GetAccountChecker, (), (override));
   MOCK_METHOD(void,
               GetProductInfoForUrl,
               (const GURL& url, commerce::ProductInfoCallback callback),
@@ -123,6 +126,7 @@
   // data for all accessors of shopping data.
   void SetupPermissiveMock();
 
+  void SetAccountChecker(AccountChecker* account_checker);
   void SetResponseForGetProductInfoForUrl(
       std::optional<commerce::ProductInfo> product_info);
   void SetResponseForGetPriceInsightsInfoForUrl(
diff --git a/components/commerce/core/pref_names.cc b/components/commerce/core/pref_names.cc
index dfcfac87a..188246e 100644
--- a/components/commerce/core/pref_names.cc
+++ b/components/commerce/core/pref_names.cc
@@ -17,6 +17,7 @@
   registry->RegisterTimePref(kCommerceDailyMetricsLastUpdateTime, base::Time());
   registry->RegisterTimePref(kShoppingListBookmarkLastUpdateTime, base::Time());
 
+  registry->RegisterBooleanPref(kProductSpecificationsEnabledPrefName, true);
   registry->RegisterBooleanPref(kShoppingListEnabledPrefName, true);
 }
 
diff --git a/components/commerce/core/pref_names.h b/components/commerce/core/pref_names.h
index 43562e5..83c61d28 100644
--- a/components/commerce/core/pref_names.h
+++ b/components/commerce/core/pref_names.h
@@ -14,6 +14,11 @@
 inline constexpr char kShoppingListBookmarkLastUpdateTime[] =
     "shopping_list_bookmark_last_update_time";
 
+// This preference is used to enable or disable the product specifications
+// feature for enterprise policies.
+inline constexpr char kProductSpecificationsEnabledPrefName[] =
+    "product_specifications_enabled";
+
 // This setting is primarily for enabling or disabling the shopping list feature
 // in enterprise settings.
 inline constexpr char kShoppingListEnabledPrefName[] = "shopping_list_enabled";
diff --git a/components/commerce/core/shopping_service.cc b/components/commerce/core/shopping_service.cc
index 1907049..4fe32aee 100644
--- a/components/commerce/core/shopping_service.cc
+++ b/components/commerce/core/shopping_service.cc
@@ -24,6 +24,7 @@
 #include "components/commerce/core/commerce_feature_list.h"
 #include "components/commerce/core/commerce_utils.h"
 #include "components/commerce/core/discounts_storage.h"
+#include "components/commerce/core/feature_utils.h"
 #include "components/commerce/core/metrics/metrics_utils.h"
 #include "components/commerce/core/metrics/scheduled_metrics_manager.h"
 #include "components/commerce/core/parcel/parcels_manager.h"
@@ -146,7 +147,8 @@
 
   if (identity_manager) {
     account_checker_ = base::WrapUnique(new AccountChecker(
-        pref_service, identity_manager, sync_service, url_loader_factory));
+        country_on_startup_, locale_on_startup_, pref_service, identity_manager,
+        sync_service, url_loader_factory));
   }
 
   if (identity_manager && account_checker_) {
@@ -209,6 +211,10 @@
   }
 }
 
+AccountChecker* ShoppingService::GetAccountChecker() {
+  return account_checker_.get();
+}
+
 void ShoppingService::WebWrapperCreated(WebWrapper* web) {}
 
 void ShoppingService::DidNavigatePrimaryMainFrame(WebWrapper* web) {
@@ -1486,8 +1492,7 @@
 }
 
 bool ShoppingService::IsShoppingListEligible() {
-  return IsShoppingListEligible(account_checker_.get(), pref_service_,
-                                country_on_startup_, locale_on_startup_);
+  return commerce::IsShoppingListEligible(account_checker_.get());
 }
 
 void ShoppingService::WaitForReady(
@@ -1508,30 +1513,6 @@
       AsWeakPtr(), sync_service_, std::move(callback)));
 }
 
-bool ShoppingService::IsShoppingListEligible(AccountChecker* account_checker,
-                                             PrefService* prefs,
-                                             const std::string& country_code,
-                                             const std::string& locale) {
-  if (!commerce::IsRegionLockedFeatureEnabled(
-          kShoppingList, kShoppingListRegionLaunched, country_code, locale)) {
-    return false;
-  }
-
-  if (!prefs || !IsShoppingListAllowedForEnterprise(prefs))
-    return false;
-
-  // Make sure the user allows subscriptions to be made and that we can fetch
-  // store data.
-  if (!account_checker || !account_checker->IsSignedIn() ||
-      !account_checker->IsSyncingBookmarks() ||
-      !account_checker->IsAnonymizedUrlDataCollectionEnabled() ||
-      account_checker->IsSubjectToParentalControls()) {
-    return false;
-  }
-
-  return true;
-}
-
 void ShoppingService::StartTrackingParcels(
     const std::vector<std::pair<ParcelIdentifier::Carrier, std::string>>&
         parcel_identifiers,
diff --git a/components/commerce/core/shopping_service.h b/components/commerce/core/shopping_service.h
index 73ab7cfe..c1bce9f 100644
--- a/components/commerce/core/shopping_service.h
+++ b/components/commerce/core/shopping_service.h
@@ -247,6 +247,9 @@
   ShoppingService(const ShoppingService&) = delete;
   ShoppingService& operator=(const ShoppingService&) = delete;
 
+  // Gets an AccountChecker instance to aid in determining feature eligibility.
+  virtual AccountChecker* GetAccountChecker();
+
   // This API retrieves the product information for the provided |url| and
   // passes the payload back to the caller via |callback|. At minimum, this
   // API will wait for data from the backend but may provide a "partial" result
@@ -551,15 +554,6 @@
   static void MergeProductInfoData(ProductInfo* info,
                                    const base::Value::Dict& on_page_data_map);
 
-  // Check if the shopping list is eligible for use. This not only checks the
-  // feature flag, but whether the feature is allowed by enterprise policy and
-  // whether the user is signed in. The value returned here can change during
-  // runtime so it should not be used when deciding to build infrastructure.
-  static bool IsShoppingListEligible(AccountChecker* account_checker,
-                                     PrefService* prefs,
-                                     const std::string& country_code,
-                                     const std::string& locale);
-
   void HandleOptGuideMerchantInfoResponse(
       const GURL& url,
       MerchantInfoCallback callback,
diff --git a/components/commerce/core/shopping_service_unittest.cc b/components/commerce/core/shopping_service_unittest.cc
index b0ad1fb..c2546b7 100644
--- a/components/commerce/core/shopping_service_unittest.cc
+++ b/components/commerce/core/shopping_service_unittest.cc
@@ -10,6 +10,7 @@
 #include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_node.h"
 #include "components/commerce/core/commerce_feature_list.h"
+#include "components/commerce/core/feature_utils.h"
 #include "components/commerce/core/mock_account_checker.h"
 #include "components/commerce/core/mock_discounts_storage.h"
 #include "components/commerce/core/pref_names.h"
@@ -99,15 +100,6 @@
     ShoppingServiceTestBase::SetUp();
   }
 
-  // Expose the private feature check for testing.
-  static bool IsShoppingListEligible(AccountChecker* account_checker,
-                                     PrefService* prefs,
-                                     const std::string& country,
-                                     const std::string& locale) {
-    return ShoppingService::IsShoppingListEligible(account_checker, prefs,
-                                                   country, locale);
-  }
-
   bool ShouldEnableReplaceSyncPromosWithSignInPromos() const {
     return GetParam();
   }
@@ -643,13 +635,14 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                     kEligibleLocale));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
 
   SetShoppingListEnterprisePolicyPref(&prefs, false);
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                      kEligibleLocale));
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest, TestShoppingListEligible_FeatureFlagOff) {
@@ -661,9 +654,11 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                      kEligibleLocale));
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest, TestShoppingListEligible_MSBB) {
@@ -675,14 +670,15 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                     kEligibleLocale));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
 
   checker.SetAnonymizedUrlDataCollectionEnabled(false);
 
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                      kEligibleLocale));
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest, TestShoppingListEligible_SignIn) {
@@ -694,14 +690,15 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                     kEligibleLocale));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
 
   checker.SetSignedIn(false);
 
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                      kEligibleLocale));
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest, TestShoppingListEligible_ChildAccount) {
@@ -713,14 +710,15 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                     kEligibleLocale));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
 
   checker.SetIsSubjectToParentalControls(true);
 
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                      kEligibleLocale));
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest, TestShoppingListEligible_SyncState) {
@@ -732,14 +730,15 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                     kEligibleLocale));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
 
   checker.SetSyncingBookmarks(false);
 
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                      kEligibleLocale));
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest, TestShoppingListEligible_CountryAndLocale) {
@@ -751,13 +750,18 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                     kEligibleLocale));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
+
+  checker.SetCountry("ZZ");
+  checker.SetLocale("zz-zz");
 
   // This should continue to work since we can assume, for the sake of the test,
   // that the experiment config includes the ZZ country and zz-zz locale.
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, "ZZ", "zz-zz"));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest,
@@ -770,13 +774,18 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                     kEligibleLocale));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
+
+  checker.SetCountry("ZZ");
+  checker.SetLocale("zz-zz");
 
   // Same as the previous test, this should still work since, presumably, the
   // experiment config for "ShoppingList" includes these.
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, "ZZ", "zz-zz"));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest, TestShoppingListEligible_CountryAndLocale_NoFlags) {
@@ -789,10 +798,12 @@
 
   MockAccountChecker checker;
 
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                      kEligibleLocale));
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, "ZZ", "zz-zz"));
+  checker.SetCountry("ZZ");
+  checker.SetLocale("zz-zz");
+
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 }
 
 TEST_P(ShoppingServiceTest,
@@ -805,14 +816,19 @@
   SetShoppingListEnterprisePolicyPref(&prefs, true);
 
   MockAccountChecker checker;
+  checker.SetCountry(kEligibleCountry);
+  checker.SetLocale(kEligibleLocale);
+  checker.SetPrefs(&prefs);
 
-  ASSERT_TRUE(IsShoppingListEligible(&checker, &prefs, kEligibleCountry,
-                                     kEligibleLocale));
+  ASSERT_TRUE(IsShoppingListEligible(&checker));
+
+  checker.SetCountry("ZZ");
+  checker.SetLocale("zz-zz");
 
   // If we only have the region flag enabled, we should be restricted to
   // specific countries and locales. The fake country and locale below should
   // be blocked.
-  ASSERT_FALSE(IsShoppingListEligible(&checker, &prefs, "ZZ", "zz-zz"));
+  ASSERT_FALSE(IsShoppingListEligible(&checker));
 }
 
 class ShoppingServiceReadyTest : public ShoppingServiceTest {
diff --git a/components/exo/extended_drag_source.cc b/components/exo/extended_drag_source.cc
index ddf02c2..19450c0 100644
--- a/components/exo/extended_drag_source.cc
+++ b/components/exo/extended_drag_source.cc
@@ -122,6 +122,9 @@
   void OnSurfaceDestroying(Surface* surface) override {
     if (surface_ == surface) {
       surface_->RemoveSurfaceObserver(this);
+      if (surface_->window()->HasObserver(this)) {
+        surface_->window()->RemoveObserver(this);
+      }
       surface_ = nullptr;
     }
   }
diff --git a/components/exo/wayland/xdg_shell.cc b/components/exo/wayland/xdg_shell.cc
index 26220f24..b3e1a56 100644
--- a/components/exo/wayland/xdg_shell.cc
+++ b/components/exo/wayland/xdg_shell.cc
@@ -202,6 +202,7 @@
 
   // Overridden from aura::WindowObserver:
   void OnWindowDestroying(aura::Window* window) override {
+    window->RemoveObserver(this);
     shell_surface_data_ = nullptr;
   }
 
@@ -565,6 +566,7 @@
 
   // Overridden from aura::WindowObserver:
   void OnWindowDestroying(aura::Window* window) override {
+    window->RemoveObserver(this);
     shell_surface_data_ = nullptr;
   }
 
diff --git a/components/favicon/core/favicon_database.cc b/components/favicon/core/favicon_database.cc
index b1297ac..dd49f23 100644
--- a/components/favicon/core/favicon_database.cc
+++ b/components/favicon/core/favicon_database.cc
@@ -105,13 +105,6 @@
 const int kCompatibleVersionNumber = 8;
 const int kDeprecatedVersionNumber = 6;  // and earlier.
 
-// When enabled, prefer to use the new recovery module to recover the
-// `FaviconDatabase` database. See https://crbug.com/1385500 for details.
-// This is a kill switch and is not intended to be used in a field trial.
-BASE_FEATURE(kFaviconDatabaseUseBuiltInRecoveryIfSupported,
-             "FaviconDatabaseUseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 void FillIconMapping(const GURL& page_url,
                      sql::Statement& statement,
                      IconMapping* icon_mapping) {
@@ -204,8 +197,7 @@
   // Attempt to recover a corrupt database, if it is eligible to be recovered.
   if (sql::BuiltInRecovery::RecoverIfPossible(
           db, extended_error,
-          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
-          &kFaviconDatabaseUseBuiltInRecoveryIfSupported)) {
+          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
     // Recovery was attempted. The database handle has been poisoned and the
     // error callback has been reset.
 
diff --git a/components/favicon/core/favicon_database.h b/components/favicon/core/favicon_database.h
index 41480c9..d6dfb86 100644
--- a/components/favicon/core/favicon_database.h
+++ b/components/favicon/core/favicon_database.h
@@ -33,8 +33,6 @@
 // All earlier updates are ignored.
 static const int kFaviconUpdateLastRequestedAfterDays = 10;
 
-BASE_DECLARE_FEATURE(kFaviconDatabaseUseBuiltInRecoveryIfSupported);
-
 // This database interface is owned by the history backend and runs on the
 // history thread.
 class FaviconDatabase {
diff --git a/components/global_media_controls/public/media_session_notification_item.cc b/components/global_media_controls/public/media_session_notification_item.cc
index 4021abe..e934209 100644
--- a/components/global_media_controls/public/media_session_notification_item.cc
+++ b/components/global_media_controls/public/media_session_notification_item.cc
@@ -8,6 +8,7 @@
 #include "base/functional/bind.h"
 #include "base/i18n/rtl.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/ranges/algorithm.h"
 #include "base/time/time.h"
 #include "components/global_media_controls/public/constants.h"
 #include "components/media_message_center/media_notification_util.h"
@@ -180,6 +181,19 @@
     MaybeUnfreeze();
 }
 
+void MediaSessionNotificationItem::MediaControllerChapterImageChanged(
+    int chapter_index,
+    const SkBitmap& bitmap) {
+  chapter_artwork_[chapter_index] = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+
+  if (view_ && !frozen_) {
+    view_->UpdateWithChapterArtwork(chapter_index,
+                                    chapter_artwork_[chapter_index]);
+  } else if (frozen_with_chapter_artwork_[chapter_index]) {
+    MaybeUnfreeze();
+  }
+}
+
 void MediaSessionNotificationItem::SetView(
     media_message_center::MediaNotificationView* view) {
   DCHECK(view_ || view);
@@ -193,6 +207,9 @@
       view_->UpdateWithMediaPosition(*session_position_);
     if (session_artwork_.has_value())
       view_->UpdateWithMediaArtwork(*session_artwork_);
+    for (auto& item : chapter_artwork_) {
+      view_->UpdateWithChapterArtwork(item.first, item.second);
+    }
     if (session_favicon_.has_value())
       view_->UpdateWithFavicon(*session_favicon_);
   } else {
@@ -300,6 +317,9 @@
   frozen_ = true;
   frozen_with_actions_ = HasActions();
   frozen_with_artwork_ = HasArtwork();
+  for (auto& item : chapter_artwork_) {
+    frozen_with_chapter_artwork_[item.first] = HasChapterArtwork(item.first);
+  }
 
   freeze_timer_.Start(
       FROM_HERE, kFreezeTimerDelay,
@@ -390,8 +410,9 @@
 }
 
 void MediaSessionNotificationItem::MaybeUnfreeze() {
-  if (!frozen_ && !frozen_with_artwork_)
+  if (!frozen_ && !frozen_with_artwork_ && !FrozenWithChapterArtwork()) {
     return;
+  }
 
   if (waiting_for_actions_ && !HasActions())
     return;
@@ -417,6 +438,13 @@
     return;
   }
 
+  for (auto& item : chapter_artwork_) {
+    if (frozen_with_chapter_artwork_[item.first] &&
+        !HasChapterArtwork(item.first)) {
+      return;
+    }
+  }
+
   UnfreezeArtwork();
 }
 
@@ -424,8 +452,9 @@
   frozen_ = false;
   waiting_for_actions_ = false;
   frozen_with_actions_ = false;
-  if (!frozen_with_artwork_)
+  if (!frozen_with_artwork_ && !FrozenWithChapterArtwork()) {
     freeze_timer_.Stop();
+  }
 
   // When we unfreeze, we want to fully update |view_| with any changes that
   // we've avoided sending during the freeze.
@@ -445,12 +474,18 @@
 // would be slow and unresponsive when trying to skip ahead multiple tracks.
 void MediaSessionNotificationItem::UnfreezeArtwork() {
   frozen_with_artwork_ = false;
+  for (auto& item : chapter_artwork_) {
+    frozen_with_chapter_artwork_[item.first] = false;
+  }
   freeze_timer_.Stop();
   if (view_) {
     if (session_artwork_.has_value())
       view_->UpdateWithMediaArtwork(*session_artwork_);
     if (session_favicon_.has_value())
       view_->UpdateWithFavicon(*session_favicon_);
+    for (auto& item : chapter_artwork_) {
+      view_->UpdateWithChapterArtwork(item.first, item.second);
+    }
   }
 }
 
@@ -462,8 +497,13 @@
   return session_artwork_.has_value() && !session_artwork_->isNull();
 }
 
+bool MediaSessionNotificationItem::HasChapterArtwork(int index) const {
+  auto it = chapter_artwork_.find(index);
+  return it != chapter_artwork_.end() && !it->second.isNull();
+}
+
 void MediaSessionNotificationItem::OnFreezeTimerFired() {
-  DCHECK(frozen_ || frozen_with_artwork_);
+  DCHECK(frozen_ || frozen_with_artwork_ || FrozenWithChapterArtwork());
 
   // If we've just been waiting for actions or artwork, stop waiting and just
   // show what we have.
@@ -471,8 +511,9 @@
     if (frozen_)
       UnfreezeNonArtwork();
 
-    if (frozen_with_artwork_)
+    if (frozen_with_artwork_ || FrozenWithChapterArtwork()) {
       UnfreezeArtwork();
+    }
 
     return;
   }
@@ -510,4 +551,10 @@
                                            : nullptr);
 }
 
+bool MediaSessionNotificationItem::FrozenWithChapterArtwork() {
+  auto it =
+      base::ranges::find_if(frozen_with_chapter_artwork_,
+                            [](const auto& it) { return it.second == true; });
+  return it != frozen_with_chapter_artwork_.end();
+}
 }  // namespace global_media_controls
diff --git a/components/global_media_controls/public/media_session_notification_item.h b/components/global_media_controls/public/media_session_notification_item.h
index 88f92dc..98c0618 100644
--- a/components/global_media_controls/public/media_session_notification_item.h
+++ b/components/global_media_controls/public/media_session_notification_item.h
@@ -100,7 +100,7 @@
       media_session::mojom::MediaSessionImageType type,
       const SkBitmap& bitmap) override;
   void MediaControllerChapterImageChanged(int chapter_index,
-                                          const SkBitmap& bitmap) override {}
+                                          const SkBitmap& bitmap) override;
 
   // media_message_center::MediaNotificationItem:
   void SetView(media_message_center::MediaNotificationView* view) override;
@@ -174,12 +174,19 @@
 
   bool HasArtwork() const;
 
+  // Returns true if there's an image at the chapter `index`.
+  bool HasChapterArtwork(int index) const;
+
   void OnFreezeTimerFired();
 
   void MaybeHideOrShowNotification();
 
   void UpdateViewCommon();
 
+  // Returns true if we're currently frozen and the frozen view contains
+  // non-null artwork at any chapter index.
+  bool FrozenWithChapterArtwork();
+
   const raw_ptr<Delegate> delegate_;
 
   bool is_bound_ = true;
@@ -221,6 +228,10 @@
 
   std::optional<gfx::ImageSkia> session_favicon_;
 
+  // This map carries the index as the key and the image at this chapter index
+  // as the value.
+  base::flat_map<int, gfx::ImageSkia> chapter_artwork_;
+
   // True if the metadata needs to be updated on |view_|. Used to prevent
   // updating |view_|'s metadata twice on a single change.
   bool view_needs_metadata_update_ = false;
@@ -241,6 +252,10 @@
   // artwork.
   bool frozen_with_artwork_ = false;
 
+  // The value is true if we're currently frozen and the frozen view contains
+  // non-null artwork at the chapter index.
+  base::flat_map<int, bool> frozen_with_chapter_artwork_;
+
   // The timer that will notify the controller to destroy this item after it
   // has been frozen for a certain period of time.
   base::OneShotTimer freeze_timer_;
diff --git a/components/global_media_controls/public/media_session_notification_item_unittest.cc b/components/global_media_controls/public/media_session_notification_item_unittest.cc
index edcebbd..0282237e 100644
--- a/components/global_media_controls/public/media_session_notification_item_unittest.cc
+++ b/components/global_media_controls/public/media_session_notification_item_unittest.cc
@@ -84,6 +84,15 @@
   metadata.artist = u"artist2";
   metadata.album = u"album";
 
+  std::vector<media_session::ChapterInformation> expected_chapters;
+  media_session::MediaImage test_image_1;
+  test_image_1.src = GURL("https://www.google.com");
+  media_session::ChapterInformation test_chapter_1(
+      /*title=*/u"chapter1", /*start_time=*/base::Seconds(10),
+      /*artwork=*/{test_image_1});
+  expected_chapters.push_back(test_chapter_1);
+  metadata.chapters = expected_chapters;
+
   EXPECT_CALL(view(), UpdateWithMediaMetadata(_)).Times(0);
   item().Freeze(base::DoNothing());
   item().MediaSessionMetadataChanged(metadata);
@@ -125,6 +134,16 @@
       media_session::mojom::MediaSessionImageType::kArtwork, image);
 }
 
+TEST_F(MediaSessionNotificationItemTest, Freezing_DoNotUpdateChapterImage) {
+  SkBitmap image;
+  image.allocN32Pixels(10, 10);
+  image.eraseColor(SK_ColorMAGENTA);
+
+  EXPECT_CALL(view(), UpdateWithChapterArtwork(_, _)).Times(0);
+  item().Freeze(base::DoNothing());
+  item().MediaControllerChapterImageChanged(0, image);
+}
+
 TEST_F(MediaSessionNotificationItemTest, Freezing_DoNotUpdatePlaybackState) {
   EXPECT_CALL(view(), UpdateWithMediaSessionInfo(_)).Times(0);
 
@@ -228,17 +247,27 @@
       media_session::mojom::MediaSessionImageType::kArtwork, initial_image);
   testing::Mock::VerifyAndClearExpectations(&view());
 
+  // Set an chapter image before freezing.
+  EXPECT_CALL(view(), UpdateWithChapterArtwork(_, _));
+  SkBitmap initial_chapter_image;
+  initial_chapter_image.allocN32Pixels(10, 10);
+  initial_chapter_image.eraseColor(SK_ColorMAGENTA);
+  item().MediaControllerChapterImageChanged(0, initial_chapter_image);
+  testing::Mock::VerifyAndClearExpectations(&view());
+
   // Freeze the item and clear the metadata.
   base::MockOnceClosure unfrozen_callback;
   EXPECT_CALL(unfrozen_callback, Run).Times(0);
   EXPECT_CALL(view(), UpdateWithMediaSessionInfo(_)).Times(0);
   EXPECT_CALL(view(), UpdateWithMediaMetadata(_)).Times(0);
   EXPECT_CALL(view(), UpdateWithMediaArtwork(_)).Times(0);
+  EXPECT_CALL(view(), UpdateWithChapterArtwork(_, _)).Times(0);
   item().Freeze(unfrozen_callback.Get());
   item().MediaSessionInfoChanged(nullptr);
   item().MediaSessionMetadataChanged(std::nullopt);
   item().MediaControllerImageChanged(
       media_session::mojom::MediaSessionImageType::kArtwork, SkBitmap());
+  item().MediaControllerChapterImageChanged(0, SkBitmap());
 
   // The item should be frozen and the view should contain the old data.
   EXPECT_TRUE(item().frozen());
@@ -264,6 +293,7 @@
   EXPECT_CALL(view(), UpdateWithMediaSessionInfo(_));
   EXPECT_CALL(view(), UpdateWithMediaMetadata(_));
   EXPECT_CALL(view(), UpdateWithMediaArtwork(_)).Times(0);
+  EXPECT_CALL(view(), UpdateWithChapterArtwork(_, _)).Times(0);
   media_session::MediaMetadata metadata;
   metadata.title = u"title2";
   metadata.artist = u"artist2";
diff --git a/components/global_media_controls/public/views/media_item_ui_detailed_view.cc b/components/global_media_controls/public/views/media_item_ui_detailed_view.cc
index 1d85e3a..3aeada5 100644
--- a/components/global_media_controls/public/views/media_item_ui_detailed_view.cc
+++ b/components/global_media_controls/public/views/media_item_ui_detailed_view.cc
@@ -483,6 +483,13 @@
   SchedulePaint();
 }
 
+void MediaItemUIDetailedView::UpdateWithChapterArtwork(
+    int index,
+    const gfx::ImageSkia& image) {
+  // TODO(b/325142008) Update the chapter list view, which is not implemented
+  // yet.
+}
+
 void MediaItemUIDetailedView::UpdateDeviceSelectorAvailability(
     bool has_devices) {
   CHECK(start_casting_button_);
diff --git a/components/global_media_controls/public/views/media_item_ui_detailed_view.h b/components/global_media_controls/public/views/media_item_ui_detailed_view.h
index 1ea912c..16e3ba0 100644
--- a/components/global_media_controls/public/views/media_item_ui_detailed_view.h
+++ b/components/global_media_controls/public/views/media_item_ui_detailed_view.h
@@ -95,6 +95,8 @@
   void UpdateWithMediaPosition(
       const media_session::MediaPosition& position) override;
   void UpdateWithMediaArtwork(const gfx::ImageSkia& image) override;
+  void UpdateWithChapterArtwork(int index,
+                                const gfx::ImageSkia& image) override;
   void UpdateWithFavicon(const gfx::ImageSkia& icon) override {}
   void UpdateWithVectorIcon(const gfx::VectorIcon* vector_icon) override {}
   void UpdateWithMuteStatus(bool mute) override {}
diff --git a/components/history/core/browser/features.cc b/components/history/core/browser/features.cc
index e61f66c..77990c1 100644
--- a/components/history/core/browser/features.cc
+++ b/components/history/core/browser/features.cc
@@ -84,13 +84,6 @@
              "SyncSegmentsData",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
-// When enabled, prefer to use the new recovery module to recover the
-// `TopSitesDatabase` database. See https://crbug.com/1385500 for details.
-// This is a kill switch and is not intended to be used in a field trial.
-BASE_FEATURE(kTopSitesDatabaseUseBuiltInRecoveryIfSupported,
-             "TopSitesDatabaseUseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // The maximum number of New Tab Page displays to show with synced segments
 // data.
 const base::FeatureParam<int> kMaxNumNewTabPageDisplays(
diff --git a/components/history/core/browser/features.h b/components/history/core/browser/features.h
index 7b3b151..08c5ac1 100644
--- a/components/history/core/browser/features.h
+++ b/components/history/core/browser/features.h
@@ -29,9 +29,6 @@
 // is enabled; do not check `kSyncSegmentsData` directly.
 BASE_DECLARE_FEATURE(kSyncSegmentsData);
 
-// Kill switch for use of the new SQL recovery module in `TopSitesDatabase`.
-BASE_DECLARE_FEATURE(kTopSitesDatabaseUseBuiltInRecoveryIfSupported);
-
 // Returns true when both full history sync and synced segments data are
 // enabled.
 bool IsSyncSegmentsDataEnabled();
diff --git a/components/history/core/browser/top_sites_database.cc b/components/history/core/browser/top_sites_database.cc
index 433e8f4..963d4742 100644
--- a/components/history/core/browser/top_sites_database.cc
+++ b/components/history/core/browser/top_sites_database.cc
@@ -105,49 +105,6 @@
   }
 }
 
-// Recover the database to the extent possible, then fixup any broken
-// constraints.
-void RecoverAndFixup(sql::Database* db, const base::FilePath& db_path) {
-  // NOTE(shess): If the version changes, review this code.
-  static_assert(kVersionNumber == 5);
-
-  std::unique_ptr<sql::Recovery> recovery =
-      sql::Recovery::BeginRecoverDatabase(db, db_path);
-  if (!recovery) {
-    return;
-  }
-
-  // If the [meta] table does not exist, or the [version] key cannot be found,
-  // then the schema is indeterminate.  The only plausible approach would be to
-  // validate that the schema contains all of the tables and indices and columns
-  // expected, but that complexity may not be warranted, this case has only been
-  // seen for a few thousand database files.
-  int version = 0;
-  if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) {
-    sql::Recovery::Unrecoverable(std::move(recovery));
-    return;
-  }
-
-  // In this case the next open will clear the database anyhow.
-  if (version <= kDeprecatedVersionNumber) {
-    sql::Recovery::Unrecoverable(std::move(recovery));
-    return;
-  }
-
-  // TODO(shess): Consider marking corrupt databases from the future
-  // Unrecoverable(), since this histogram value has never been seen.  OTOH,
-  // this may be too risky, because if future code was correlated with
-  // corruption then rollback would be a sensible response.
-  if (version > kVersionNumber) {
-    sql::Recovery::Rollback(std::move(recovery));
-    return;
-  }
-
-  FixTopSitesTable(*recovery->db());
-
-  std::ignore = sql::Recovery::Recovered(std::move(recovery));
-}
-
 }  // namespace
 
 void TopSitesDatabase::DatabaseErrorCallback(const base::FilePath& db_path,
@@ -156,21 +113,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(db_);
 
-  // TODO(https://crbug.com/1513464): Simplify this code as soon as we're
-  // confident that we can remove sql::Recovery.
-  if (base::FeatureList::IsEnabled(
-          kTopSitesDatabaseUseBuiltInRecoveryIfSupported) &&
-      sql::BuiltInRecovery::ShouldAttemptRecovery(db_.get(), extended_error)) {
-    bool recovery_was_attempted = sql::BuiltInRecovery::RecoverIfPossible(
-        db_.get(), extended_error,
-        sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
-        &kTopSitesDatabaseUseBuiltInRecoveryIfSupported);
-    if (!recovery_was_attempted) {
-      LOG(ERROR)
-          << "Recovery should have been attempted if the checks above passed.";
-      base::debug::DumpWithoutCrashing();
-      return;
-    }
+  if (sql::BuiltInRecovery::RecoverIfPossible(
+          db_.get(), extended_error,
+          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
     // Recovery was attempted. The database handle has been poisoned and the
     // error callback has been reset.
 
@@ -189,31 +134,10 @@
     // `FixTopSitesTable()` every time the database is opened, but in most cases
     // that would be a (possibly expensive) no-op.
     needs_fixing_up_ = true;
-
-    // Signal the test-expectation framework that the error was handled.
-    std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
-    return;
   }
 
-  if (sql::Recovery::ShouldRecover(extended_error)) {
-    // Prevent reentrant calls.
-    db_->reset_error_callback();
-
-    // After this call, the `db` handle is poisoned so that future calls will
-    // return errors until the handle is re-opened.
-    RecoverAndFixup(db_.get(), db_path);
-
-    // The DLOG(FATAL) below is intended to draw immediate attention to errors
-    // in newly-written code.  Database corruption is generally a result of OS
-    // or hardware issues, not coding errors at the client level, so displaying
-    // the error would probably lead to confusion.  The ignored call signals the
-    // test-expectation framework that the error was handled.
-    std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
-    return;
-  }
-
-  if (!sql::Database::IsExpectedSqliteError(extended_error))
-    DLOG(FATAL) << db_->GetErrorMessage();
+  // Signal the test-expectation framework that the error was handled.
+  std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
 }
 
 // static
@@ -333,9 +257,6 @@
 
   // Attempt to fix up the table if recovery was attempted when opening.
   if (needs_fixing_up_) {
-    CHECK(base::FeatureList::IsEnabled(
-        kTopSitesDatabaseUseBuiltInRecoveryIfSupported));
-
     FixTopSitesTable(*db_);
     needs_fixing_up_ = false;
   }
diff --git a/components/history/core/browser/top_sites_database_unittest.cc b/components/history/core/browser/top_sites_database_unittest.cc
index 4f411b85..7b86817fc 100644
--- a/components/history/core/browser/top_sites_database_unittest.cc
+++ b/components/history/core/browser/top_sites_database_unittest.cc
@@ -78,20 +78,6 @@
   base::FilePath file_name_;
 };
 
-// Tests both the legacy `sql::Recovery` interface and the newer
-// `sql::BuiltInRecovery` interface, if it's supported.
-class TopSitesDatabaseRecoveryTest : public TopSitesDatabaseTest,
-                                     public testing::WithParamInterface<bool> {
- public:
-  TopSitesDatabaseRecoveryTest() {
-    scoped_feature_list_.InitWithFeatureState(
-        kTopSitesDatabaseUseBuiltInRecoveryIfSupported, GetParam());
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
 // Version 1 is deprecated, the resulting schema should be current,
 // with no data.
 TEST_F(TopSitesDatabaseTest, Version1) {
@@ -171,7 +157,7 @@
 
 // Version 1 is deprecated, the resulting schema should be current, with no
 // data.
-TEST_P(TopSitesDatabaseRecoveryTest, Recovery1) {
+TEST_F(TopSitesDatabaseTest, Recovery1) {
   // Create an example database.
   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v1.sql"));
 
@@ -204,7 +190,7 @@
 
 // Version 2 is deprecated, the resulting schema should be current, with no
 // data.
-TEST_P(TopSitesDatabaseRecoveryTest, Recovery2) {
+TEST_F(TopSitesDatabaseTest, Recovery2) {
   // Create an example database.
   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v2.sql"));
 
@@ -235,7 +221,7 @@
   VerifyDatabaseEmpty(db.db_for_testing());
 }
 
-TEST_P(TopSitesDatabaseRecoveryTest, Recovery4_CorruptHeader) {
+TEST_F(TopSitesDatabaseTest, Recovery4_CorruptHeader) {
   // Create an example database.
   EXPECT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v4.sql"));
 
@@ -277,7 +263,7 @@
   }
 }
 
-TEST_P(TopSitesDatabaseRecoveryTest, Recovery5_CorruptIndex) {
+TEST_F(TopSitesDatabaseTest, Recovery5_CorruptIndex) {
   // Create an example database.
   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v5.sql"));
 
@@ -336,7 +322,7 @@
   EXPECT_EQ(kUrl2, urls[2].url);  // [2] because of url_rank.
 }
 
-TEST_P(TopSitesDatabaseRecoveryTest, Recovery5_CorruptIndexAndLostRow) {
+TEST_F(TopSitesDatabaseTest, Recovery5_CorruptIndexAndLostRow) {
   // Create an example database.
   ASSERT_TRUE(CreateDatabaseFromSQL(file_name_, "TopSites.v5.sql"));
 
@@ -550,6 +536,4 @@
   }
 }
 
-INSTANTIATE_TEST_SUITE_P(All, TopSitesDatabaseRecoveryTest, testing::Bool());
-
 }  // namespace history
diff --git a/components/media_device_salt/media_device_salt_database.cc b/components/media_device_salt/media_device_salt_database.cc
index 5b52976..5f5058e2 100644
--- a/components/media_device_salt/media_device_salt_database.cc
+++ b/components/media_device_salt/media_device_salt_database.cc
@@ -18,10 +18,6 @@
 
 namespace media_device_salt {
 
-BASE_FEATURE(kMediaDeviceSaltDatabaseUseBuiltInRecoveryIfSupported,
-             "MediaDeviceSaltDatabaseUseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 namespace {
 // The current version of the database schema.
 constexpr int kCurrentVersion = 1;
@@ -226,8 +222,7 @@
   sql::UmaHistogramSqliteResult("Media.MediaDevices.SaltDatabaseErrors", error);
   std::ignore = sql::BuiltInRecovery::RecoverIfPossible(
       &db_, error,
-      sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
-      &kMediaDeviceSaltDatabaseUseBuiltInRecoveryIfSupported);
+      sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze);
 }
 
 }  // namespace media_device_salt
diff --git a/components/media_message_center/media_notification_view.h b/components/media_message_center/media_notification_view.h
index 956d1a10..e630d3e 100644
--- a/components/media_message_center/media_notification_view.h
+++ b/components/media_message_center/media_notification_view.h
@@ -46,6 +46,8 @@
   virtual void UpdateWithMediaPosition(
       const media_session::MediaPosition& position) = 0;
   virtual void UpdateWithMediaArtwork(const gfx::ImageSkia& image) = 0;
+  virtual void UpdateWithChapterArtwork(int index,
+                                        const gfx::ImageSkia& image) = 0;
   // Updates the background color to match that of the favicon.
   virtual void UpdateWithFavicon(const gfx::ImageSkia& icon) = 0;
   // Sets the icon to be displayed in the notification's header section.
diff --git a/components/media_message_center/media_notification_view_impl.h b/components/media_message_center/media_notification_view_impl.h
index 07f5b6d..9102267a 100644
--- a/components/media_message_center/media_notification_view_impl.h
+++ b/components/media_message_center/media_notification_view_impl.h
@@ -79,6 +79,11 @@
   void UpdateWithMediaPosition(
       const media_session::MediaPosition& position) override {}
   void UpdateWithMediaArtwork(const gfx::ImageSkia& image) override;
+  // This view is used before `media::kGlobalMediaControlsCrOSUpdatedUI` is
+  // enabled in the `MediaItemUIView`. So we don't need to implement this method
+  // since there's no chapter images in this view.
+  void UpdateWithChapterArtwork(int index,
+                                const gfx::ImageSkia& image) override {}
   void UpdateWithFavicon(const gfx::ImageSkia& icon) override;
   void UpdateWithVectorIcon(const gfx::VectorIcon* vector_icon) override;
   void UpdateWithMuteStatus(bool mute) override {}
diff --git a/components/media_message_center/mock_media_notification_view.h b/components/media_message_center/mock_media_notification_view.h
index b2888672..72b2e41e 100644
--- a/components/media_message_center/mock_media_notification_view.h
+++ b/components/media_message_center/mock_media_notification_view.h
@@ -32,6 +32,8 @@
   MOCK_METHOD1(UpdateWithMediaPosition,
                void(const media_session::MediaPosition&));
   MOCK_METHOD1(UpdateWithMediaArtwork, void(const gfx::ImageSkia&));
+  MOCK_METHOD2(UpdateWithChapterArtwork,
+               void(int index, const gfx::ImageSkia& image));
   MOCK_METHOD1(UpdateWithFavicon, void(const gfx::ImageSkia&));
   MOCK_METHOD1(UpdateWithVectorIcon, void(const gfx::VectorIcon* vector_icon));
   MOCK_METHOD1(UpdateWithMuteStatus, void(bool));
diff --git a/components/omnibox/browser/shortcuts_database.cc b/components/omnibox/browser/shortcuts_database.cc
index 38bcfe6..6106c54 100644
--- a/components/omnibox/browser/shortcuts_database.cc
+++ b/components/omnibox/browser/shortcuts_database.cc
@@ -65,8 +65,7 @@
                            sql::Statement* stmt) {
   // Attempt to recover a corrupt database, if it is eligible to be recovered.
   if (sql::BuiltInRecovery::RecoverIfPossible(
-          db, extended_error, sql::BuiltInRecovery::Strategy::kRecoverOrRaze,
-          &omnibox::kShortcutsDatabaseUseBuiltInRecoveryIfSupported)) {
+          db, extended_error, sql::BuiltInRecovery::Strategy::kRecoverOrRaze)) {
     // Recovery was attempted. The database handle has been poisoned and the
     // error callback has been reset.
 
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index cf34b09..95c1701 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -542,13 +542,6 @@
              "StarterPackExpansion",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// When enabled, prefer to use the new recovery module to recover the
-// `ShortcutsDatabase` database. See https://crbug.com/1385500 for details.
-// This is a kill switch and is not intended to be used in a field trial.
-BASE_FEATURE(kShortcutsDatabaseUseBuiltInRecoveryIfSupported,
-             "ShortcutsDatabaseUseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // If enabled, |SearchProvider| will not function in Zero Suggest.
 BASE_FEATURE(kAblateSearchProviderWarmup,
              "AblateSearchProviderWarmup",
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index 2c2317e9..08e15f7 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -159,9 +159,6 @@
 BASE_DECLARE_FEATURE(kPolicyIndicationForManagedDefaultSearch);
 BASE_DECLARE_FEATURE(kStarterPackExpansion);
 
-// Kill switch for use of the new SQL recovery module in `ShortcutsDatabase`.
-BASE_DECLARE_FEATURE(kShortcutsDatabaseUseBuiltInRecoveryIfSupported);
-
 BASE_DECLARE_FEATURE(kAblateSearchProviderWarmup);
 
 BASE_DECLARE_FEATURE(kOmniboxShortcutsAndroid);
diff --git a/components/optimization_guide/features.gni b/components/optimization_guide/features.gni
index 516ff8f..c95ae20 100644
--- a/components/optimization_guide/features.gni
+++ b/components/optimization_guide/features.gni
@@ -29,8 +29,7 @@
   # Mac: //chrome/installer/mac/signing/parts.py
   # Windows: //chrome/installer/mini_installer/chrome.release and internal archive files
   if (is_fuchsia || is_ios || is_android) {
-    # The library this pulls in depends on open-source LevelDB which is not supported for Fuchsia.
-    # iOS and Android should work but is not included in the set we release for, so we do
+    # Fuchsia, iOS, and Android should work but is not included in the set we release for, so we do
     # not needlessly increase the binary size.
     build_with_internal_optimization_guide = false
   } else if (build_with_tflite_lib) {
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index cf123da..ee190098 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit cf123dadc9bb84e815991922cc9d4d9277b8b4e5
+Subproject commit ee19009832d5daab66094b1325d8d42ebb61e1ad
diff --git a/components/optimization_guide/proto/features/tab_organization.proto b/components/optimization_guide/proto/features/tab_organization.proto
index e5507d6..8661f314 100644
--- a/components/optimization_guide/proto/features/tab_organization.proto
+++ b/components/optimization_guide/proto/features/tab_organization.proto
@@ -19,6 +19,9 @@
 
   // The tabs that belong in this organization.
   repeated Tab tabs = 2;
+
+  // The id of the pre-existing tab group this corresponds to, if any.
+  optional string group_id = 3;
 }
 
 message Tab {
@@ -38,6 +41,13 @@
 
   // The tab that was active at the time the user requested tab organization.
   int64 active_tab_id = 2;
+
+  // All pre-existing tab groups.
+  repeated TabOrganization pre_existing_tab_groups = 3;
+
+  // Whether or not the tab organization response can include reorganizing
+  // existing tab groups.
+  bool allow_reorganizing_existing_groups = 4;
 }
 
 message TabOrganizationResponse {
diff --git a/components/page_image_service/features.cc b/components/page_image_service/features.cc
index b43acdd..4046507 100644
--- a/components/page_image_service/features.cc
+++ b/components/page_image_service/features.cc
@@ -23,6 +23,6 @@
 // images for already synced sync entities can be fetched.
 BASE_FEATURE(kImageServiceObserveSyncDownloadStatus,
              "ImageServiceObserveSyncDownloadStatus",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 }  // namespace page_image_service
diff --git a/components/page_info/android/page_info_controller_android.cc b/components/page_info/android/page_info_controller_android.cc
index 5e55a45..7705db8 100644
--- a/components/page_info/android/page_info_controller_android.cc
+++ b/components/page_info/android/page_info_controller_android.cc
@@ -147,10 +147,7 @@
     permissions_to_display.push_back(
         ContentSettingsType::FEDERATED_IDENTITY_API);
   }
-  if (base::FeatureList::IsEnabled(
-          permissions::features::kPermissionStorageAccessAPI)) {
     permissions_to_display.push_back(ContentSettingsType::STORAGE_ACCESS);
-  }
 
   std::map<ContentSettingsType, ContentSetting>
       user_specified_settings_to_display;
diff --git a/components/page_info/page_info.cc b/components/page_info/page_info.cc
index 4e3807d..cd019a2 100644
--- a/components/page_info/page_info.cc
+++ b/components/page_info/page_info.cc
@@ -1337,12 +1337,6 @@
   }
 
   for (ContentSettingsType type : kTwoPatternPermissions) {
-    if (type == ContentSettingsType::STORAGE_ACCESS &&
-        !base::FeatureList::IsEnabled(
-            permissions::features::kPermissionStorageAccessAPI)) {
-      continue;
-    }
-
     for (auto& requester : GetTwoSitePermissionRequesters(type)) {
       PermissionInfo permission_info;
       permission_info.type = type;
diff --git a/components/page_info/page_info_ui.cc b/components/page_info/page_info_ui.cc
index 27b6c50..720e4e5 100644
--- a/components/page_info/page_info_ui.cc
+++ b/components/page_info/page_info_ui.cc
@@ -888,10 +888,12 @@
       DCHECK_EQ(opposite_to_block_setting, CONTENT_SETTING_ALLOW);
       SetTargetContentSetting(permission, CONTENT_SETTING_BLOCK);
       permission.is_one_time = false;
+      permission.is_in_use = false;
       break;
     case CONTENT_SETTING_BLOCK:
       SetTargetContentSetting(permission, opposite_to_block_setting);
       permission.is_one_time = false;
+      permission.is_in_use = false;
       break;
     case CONTENT_SETTING_DEFAULT: {
       CreateOppositeToDefaultSiteException(permission,
@@ -903,6 +905,7 @@
               permission.type)) {
         permission.is_one_time = true;
       }
+      permission.is_in_use = false;
       break;
     }
     case CONTENT_SETTING_ASK:
diff --git a/components/password_manager/content/browser/content_password_manager_driver.cc b/components/password_manager/content/browser/content_password_manager_driver.cc
index 0a74325..d4ac651 100644
--- a/components/password_manager/content/browser/content_password_manager_driver.cc
+++ b/components/password_manager/content/browser/content_password_manager_driver.cc
@@ -208,6 +208,13 @@
   GetPasswordGenerationAgent()->FocusNextFieldAfterPasswords();
 }
 
+void ContentPasswordManagerDriver::FillField(autofill::FieldRendererId field_id,
+                                             const std::u16string& value) {
+  if (const auto& agent = GetPasswordAutofillAgent()) {
+    agent->FillField(field_id, value);
+  }
+}
+
 void ContentPasswordManagerDriver::FillSuggestion(
     const std::u16string& username,
     const std::u16string& password) {
diff --git a/components/password_manager/content/browser/content_password_manager_driver.h b/components/password_manager/content/browser/content_password_manager_driver.h
index b9695ad..52099a70 100644
--- a/components/password_manager/content/browser/content_password_manager_driver.h
+++ b/components/password_manager/content/browser/content_password_manager_driver.h
@@ -69,6 +69,8 @@
       autofill::FieldRendererId generation_element_id,
       const std::u16string& password) override;
   void FocusNextFieldAfterPasswords() override;
+  void FillField(autofill::FieldRendererId field_id,
+                 const std::u16string& value) override;
   void FillSuggestion(const std::u16string& username,
                       const std::u16string& password) override;
   void FillIntoFocusedField(bool is_password,
diff --git a/components/password_manager/content/browser/content_password_manager_driver_unittest.cc b/components/password_manager/content/browser/content_password_manager_driver_unittest.cc
index dc69daf..0098c9f 100644
--- a/components/password_manager/content/browser/content_password_manager_driver_unittest.cc
+++ b/components/password_manager/content/browser/content_password_manager_driver_unittest.cc
@@ -104,6 +104,10 @@
               PreviewField,
               (autofill::FieldRendererId, const std::u16string&),
               (override));
+  MOCK_METHOD(void,
+              FillField,
+              (autofill::FieldRendererId, const std::u16string&),
+              (override));
 #if BUILDFLAG(IS_ANDROID)
   MOCK_METHOD(void, KeyboardReplacingSurfaceClosed, (bool), (override));
   MOCK_METHOD(void, TriggerFormSubmission, (), (override));
diff --git a/components/password_manager/core/browser/password_manager_driver.h b/components/password_manager/core/browser/password_manager_driver.h
index aecd7491..25d9bbd 100644
--- a/components/password_manager/core/browser/password_manager_driver.h
+++ b/components/password_manager/core/browser/password_manager_driver.h
@@ -85,6 +85,11 @@
   // in account creation).
   virtual void FocusNextFieldAfterPasswords() {}
 
+  // Tells the renderer to fill the given `value` into the field identified by
+  // the `field_id`.
+  virtual void FillField(autofill::FieldRendererId field_id,
+                         const std::u16string& value) {}
+
   // Tells the driver to fill the form with the `username` and `password`.
   virtual void FillSuggestion(const std::u16string& username,
                               const std::u16string& password) = 0;
diff --git a/components/password_manager/core/browser/password_manual_fallback_flow.cc b/components/password_manager/core/browser/password_manual_fallback_flow.cc
index 2da78e6..9cdcfe3 100644
--- a/components/password_manager/core/browser/password_manual_fallback_flow.cc
+++ b/components/password_manager/core/browser/password_manual_fallback_flow.cc
@@ -108,6 +108,8 @@
       // TODO(b/321678448): Fill password form for acceptable suggestions.
       break;
     case autofill::PopupItemId::kPasswordFieldByFieldFilling:
+      password_manager_driver_->FillField(saved_field_id_,
+                                          suggestion.main_text.value);
       // TODO(b/321678448): Fill username.
       break;
     case autofill::PopupItemId::kFillPassword:
@@ -124,6 +126,8 @@
       // Other suggestion types are not supported.
       NOTREACHED_NORETURN();
   }
+  autofill_client_->HideAutofillPopup(
+      autofill::PopupHidingReason::kAcceptSuggestion);
 }
 
 void PasswordManualFallbackFlow::DidPerformButtonActionForSuggestion(
diff --git a/components/password_manager/core/browser/password_manual_fallback_flow_unittest.cc b/components/password_manager/core/browser/password_manual_fallback_flow_unittest.cc
index fa09906..d521e57 100644
--- a/components/password_manager/core/browser/password_manual_fallback_flow_unittest.cc
+++ b/components/password_manager/core/browser/password_manual_fallback_flow_unittest.cc
@@ -10,6 +10,7 @@
 #include "components/affiliations/core/browser/fake_affiliation_service.h"
 #include "components/autofill/core/browser/test_autofill_client.h"
 #include "components/autofill/core/browser/ui/autofill_popup_delegate.h"
+#include "components/autofill/core/common/autofill_test_utils.h"
 #include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
 #include "components/password_manager/core/browser/password_store/test_password_store.h"
@@ -25,6 +26,7 @@
 using autofill::AutofillPopupDelegate;
 using autofill::AutofillSuggestionTriggerSource;
 using autofill::FieldRendererId;
+using autofill::PopupHidingReason;
 using autofill::PopupItemId;
 using autofill::TestAutofillClient;
 using autofill::test::AutofillUnitTestEnvironment;
@@ -44,6 +46,7 @@
               (const AutofillClient::PopupOpenArgs&,
                base::WeakPtr<AutofillPopupDelegate>),
               (override));
+  MOCK_METHOD(void, HideAutofillPopup, (PopupHidingReason), (override));
 };
 
 class MockPasswordManagerDriver : public StubPasswordManagerDriver {
@@ -54,6 +57,10 @@
               PreviewField,
               (FieldRendererId, const std::u16string&),
               (override));
+  MOCK_METHOD(void,
+              FillField,
+              (FieldRendererId, const std::u16string&),
+              (override));
 };
 
 class PasswordManualFallbackFlowTest : public ::testing::Test {
@@ -258,4 +265,23 @@
       PopupItemId::kPasswordFieldByFieldFilling, u"username@example.com"));
 }
 
+// Test that username field-by-field suggestion is filled into the correct field
+// by the manual fallback flow.
+TEST_F(PasswordManualFallbackFlowTest, AcceptUsernameFieldByFieldSuggestion) {
+  ProcessPasswordStoreUpdates();
+
+  const FieldRendererId field_id = MakeFieldRendererId();
+  flow().RunFlow(field_id, gfx::RectF{}, TextDirection::LEFT_TO_RIGHT);
+
+  EXPECT_CALL(driver(),
+              FillField(field_id, std::u16string(u"username@example.com")));
+  EXPECT_CALL(autofill_client(),
+              HideAutofillPopup(PopupHidingReason::kAcceptSuggestion));
+  flow().DidAcceptSuggestion(
+      autofill::test::CreateAutofillSuggestion(
+          PopupItemId::kPasswordFieldByFieldFilling, u"username@example.com"),
+      AutofillPopupDelegate::SuggestionPosition{.row = 0,
+                                                .sub_popup_level = 1});
+}
+
 }  // namespace password_manager
diff --git a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionsAndroidFeatureList.java b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionsAndroidFeatureList.java
index 62c20d8..8663269 100644
--- a/components/permissions/android/java/src/org/chromium/components/permissions/PermissionsAndroidFeatureList.java
+++ b/components/permissions/android/java/src/org/chromium/components/permissions/PermissionsAndroidFeatureList.java
@@ -16,6 +16,4 @@
     public static final String BLOCK_MIDI_BY_DEFAULT = "BlockMidiByDefault";
 
     public static final String ONE_TIME_PERMISSION = "OneTimePermission";
-
-    public static final String PERMISSION_STORAGE_ACCESS = "PermissionStorageAccessAPI";
 }
diff --git a/components/permissions/android/permission_prompt/permission_prompt_android.cc b/components/permissions/android/permission_prompt/permission_prompt_android.cc
index b987e53f..0b5a0cf 100644
--- a/components/permissions/android/permission_prompt/permission_prompt_android.cc
+++ b/components/permissions/android/permission_prompt/permission_prompt_android.cc
@@ -113,9 +113,7 @@
   const std::vector<raw_ptr<PermissionRequest, VectorExperimental>>& requests =
       delegate_->Requests();
   if (requests.size() == 1) {
-    if (requests[0]->request_type() == RequestType::kStorageAccess &&
-        base::FeatureList::IsEnabled(
-            permissions::features::kPermissionStorageAccessAPI)) {
+    if (requests[0]->request_type() == RequestType::kStorageAccess) {
       return IDR_ANDROID_GLOBE;
     }
     return permissions::GetIconId(requests[0]->request_type());
@@ -152,9 +150,7 @@
       delegate_->Requests();
   CHECK_GT(requests.size(), 0U);
 
-  return requests[0]->request_type() == RequestType::kStorageAccess &&
-         base::FeatureList::IsEnabled(
-             permissions::features::kPermissionStorageAccessAPI);
+  return requests[0]->request_type() == RequestType::kStorageAccess;
 }
 
 GURL PermissionPromptAndroid::GetRequestingOrigin() const {
diff --git a/components/permissions/android/permissions_android_feature_map.cc b/components/permissions/android/permissions_android_feature_map.cc
index 3dece43..45b1ea50 100644
--- a/components/permissions/android/permissions_android_feature_map.cc
+++ b/components/permissions/android/permissions_android_feature_map.cc
@@ -22,7 +22,6 @@
     &kAndroidApproximateLocationPermissionSupport,
     &::features::kBlockMidiByDefault,
     &features::kOneTimePermission,
-    &features::kPermissionStorageAccessAPI,
 };
 
 // static
diff --git a/components/permissions/features.cc b/components/permissions/features.cc
index 5600daf..ecb84124 100644
--- a/components/permissions/features.cc
+++ b/components/permissions/features.cc
@@ -112,13 +112,6 @@
 
 #endif  // BUILDFLAG(IS_ANDROID)
 
-// When enabled, permission grants for Storage Access API will be enabled.
-// This includes enabling prompts, a new settings page and page info and
-// omnibox integration.
-BASE_FEATURE(kPermissionStorageAccessAPI,
-             "PermissionStorageAccessAPI",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // When enabled "window-placement" may be used as an alias for
 // "window-management". Additionally, reverse mappings (i.e. enum to string)
 // will default to the legacy strings ("window-placement").
diff --git a/components/permissions/features.h b/components/permissions/features.h
index 82902a4..3d2ac1e 100644
--- a/components/permissions/features.h
+++ b/components/permissions/features.h
@@ -68,9 +68,6 @@
 #endif  // BUILDFLAG(IS_ANDROID)
 
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
-BASE_DECLARE_FEATURE(kPermissionStorageAccessAPI);
-
-COMPONENT_EXPORT(PERMISSIONS_COMMON)
 BASE_DECLARE_FEATURE(kWindowPlacementPermissionAlias);
 
 COMPONENT_EXPORT(PERMISSIONS_COMMON)
diff --git a/components/permissions/permission_request.cc b/components/permissions/permission_request.cc
index 15a8693..58fa158 100644
--- a/components/permissions/permission_request.cc
+++ b/components/permissions/permission_request.cc
@@ -126,8 +126,6 @@
       break;
     case RequestType::kStorageAccess:
       // The SA prompt does not currently bold any part of its message.
-      if (base::FeatureList::IsEnabled(
-              permissions::features::kPermissionStorageAccessAPI)) {
         return AnnotatedMessageText(
             l10n_util::GetStringFUTF16(
                 IDS_CONCAT_TWO_STRINGS_WITH_PERIODS,
@@ -139,12 +137,6 @@
                     requesting_origin_string_formatted,
                     embedding_origin_string_formatted)),
             /*bolded_ranges=*/{});
-      }
-      return AnnotatedMessageText(
-          l10n_util::GetStringFUTF16(IDS_STORAGE_ACCESS_INFOBAR_TEXT,
-                                     requesting_origin_string_formatted,
-                                     embedding_origin_string_formatted),
-          /*bolded_ranges=*/{});
     case RequestType::kTopLevelStorageAccess:
       NOTREACHED();
       break;
@@ -352,9 +344,7 @@
 }
 
 bool PermissionRequest::ShouldUseTwoOriginPrompt() const {
-  return request_type() == RequestType::kStorageAccess &&
-         base::FeatureList::IsEnabled(
-             permissions::features::kPermissionStorageAccessAPI);
+  return request_type() == RequestType::kStorageAccess;
 }
 
 void PermissionRequest::PermissionGranted(bool is_one_time) {
diff --git a/components/permissions/request_type.cc b/components/permissions/request_type.cc
index f66f790d..fad9b73f 100644
--- a/components/permissions/request_type.cc
+++ b/components/permissions/request_type.cc
@@ -64,10 +64,7 @@
       return IDR_ANDROID_INFOBAR_PROTECTED_MEDIA_IDENTIFIER;
     case RequestType::kStorageAccess:
     case RequestType::kTopLevelStorageAccess:
-      return base::FeatureList::IsEnabled(
-                 permissions::features::kPermissionStorageAccessAPI)
-                 ? IDR_ANDROID_STORAGE_ACCESS
-                 : IDR_ANDROID_INFOBAR_PERMISSION_COOKIE;
+      return IDR_ANDROID_STORAGE_ACCESS;
   }
   NOTREACHED();
   return 0;
@@ -139,12 +136,7 @@
 #endif
     case RequestType::kStorageAccess:
     case RequestType::kTopLevelStorageAccess:
-      if (base::FeatureList::IsEnabled(
-              permissions::features::kPermissionStorageAccessAPI)) {
-        return vector_icons::kStorageAccessIcon;
-      }
-      return cr23 ? vector_icons::kCookieChromeRefreshIcon
-                  : vector_icons::kCookieIcon;
+      return vector_icons::kStorageAccessIcon;
     case RequestType::kWindowManagement:
       return cr23 ? vector_icons::kSelectWindowChromeRefreshIcon
                   : vector_icons::kSelectWindowIcon;
diff --git a/components/permissions/test/mock_permission_prompt.cc b/components/permissions/test/mock_permission_prompt.cc
index 8e29d79..6719ac49 100644
--- a/components/permissions/test/mock_permission_prompt.cc
+++ b/components/permissions/test/mock_permission_prompt.cc
@@ -73,9 +73,7 @@
     EXPECT_FALSE(permissions::GetIconId(request_type).is_empty());
 #endif
     EXPECT_EQ(request->ShouldUseTwoOriginPrompt(),
-              request_type == permissions::RequestType::kStorageAccess &&
-                  base::FeatureList::IsEnabled(
-                      permissions::features::kPermissionStorageAccessAPI));
+              request_type == permissions::RequestType::kStorageAccess);
   }
 }
 
diff --git a/components/policy/resources/templates/policies.yaml b/components/policy/resources/templates/policies.yaml
index 655439f..16301703 100644
--- a/components/policy/resources/templates/policies.yaml
+++ b/components/policy/resources/templates/policies.yaml
@@ -1231,6 +1231,7 @@
   1230: DefaultDirectSocketsSetting
   1231: DirectSocketsAllowedForUrls
   1232: DirectSocketsBlockedForUrls
+  1233: ProductSpecificationsEnabled
 atomic_groups:
   1: Homepage
   2: RemoteAccess
diff --git a/components/policy/resources/templates/policy_definitions/Miscellaneous/ProductSpecificationsEnabled.yaml b/components/policy/resources/templates/policy_definitions/Miscellaneous/ProductSpecificationsEnabled.yaml
new file mode 100644
index 0000000..6a07643
--- /dev/null
+++ b/components/policy/resources/templates/policy_definitions/Miscellaneous/ProductSpecificationsEnabled.yaml
@@ -0,0 +1,29 @@
+caption: Allow the product specifications feature to be enabled
+default: true
+desc: |-
+  If this policy is set to Enabled or not set, product specifications will be available to users.
+
+  If this policy is set to Disabled, product specifications will be unavailable.
+example_value: false
+features:
+  dynamic_refresh: true
+  per_profile: true
+future_on:
+- ios
+- android
+items:
+- caption: The product specifications feature will be available to users.
+  value: true
+- caption: The product specifications feature will not be available to users.
+  value: false
+owners:
+- aymana@chromium.org
+- mdjones@chromium.org
+- chrome-shopping@google.com
+schema:
+  type: boolean
+supported_on:
+- chrome.*:124-
+- chrome_os:124-
+tags: []
+type: main
diff --git a/components/policy/test/data/pref_mapping/ProductSpecificationsEnabled.json b/components/policy/test/data/pref_mapping/ProductSpecificationsEnabled.json
new file mode 100644
index 0000000..6082a93
--- /dev/null
+++ b/components/policy/test/data/pref_mapping/ProductSpecificationsEnabled.json
@@ -0,0 +1,45 @@
+[
+  {
+    "os": [
+      "win",
+      "linux",
+      "mac",
+      "chromeos_ash",
+      "chromeos_lacros",
+      "android"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {},
+        "prefs": {
+          "product_specifications_enabled": {
+            "default_value": true,
+            "location": "user_profile"
+          }
+        }
+      },
+      {
+        "policies": {
+          "ProductSpecificationsEnabled": true
+        },
+        "prefs": {
+          "product_specifications_enabled": {
+              "value": true,
+              "location": "user_profile"
+          }
+        }
+      },
+      {
+        "policies": {
+          "ProductSpecificationsEnabled": false
+        },
+        "prefs": {
+          "product_specifications_enabled": {
+              "value": false,
+              "location": "user_profile"
+          }
+        }
+      }
+    ]
+  }
+]
diff --git a/components/printing/test/print_render_frame_helper_browsertest.cc b/components/printing/test/print_render_frame_helper_browsertest.cc
index 643e2a8..3882636 100644
--- a/components/printing/test/print_render_frame_helper_browsertest.cc
+++ b/components/printing/test/print_render_frame_helper_browsertest.cc
@@ -1392,9 +1392,9 @@
     EXPECT_EQ(expected_content_width, page_layout->content_width);
     EXPECT_EQ(expected_content_height, page_layout->content_height);
     EXPECT_EQ(expected_margin_top, page_layout->margin_top);
-    EXPECT_EQ(expected_margin_right, page_layout->margin_right);
-    EXPECT_EQ(expected_margin_left, page_layout->margin_left);
     EXPECT_EQ(expected_margin_bottom, page_layout->margin_bottom);
+    EXPECT_EQ(expected_margin_left, page_layout->margin_left);
+    EXPECT_EQ(expected_margin_right, page_layout->margin_right);
     EXPECT_EQ(expected_all_pages_have_custom_size,
               preview_ui()->all_pages_have_custom_size());
     EXPECT_EQ(expected_all_pages_have_custom_orientation,
@@ -2075,6 +2075,291 @@
   OnClosePrintPreviewDialog();
 }
 
+#if defined(MOCK_PRINTER_SUPPORTS_PAGE_IMAGES)
+
+TEST_F(PrintRenderFrameHelperPreviewTest,
+       MarginsSizeAndInputScaleAndAvoidOverflowScaleToPrinter3) {
+  // The default page size in these tests is US Letter - 8.5 by 11 inches.
+  // Setting the page size to 3 inches and margins to 0.5 inches results in a
+  // page area of 2 by 2 inches. Setting the input scale factor to 200% shrinks
+  // this to 1 inch. There's a 1.5in wide block in the test. To make it fit
+  // without overflowing, Blink will increase the page area size by 3/2 (1.5/1),
+  // i.e. 50% larger, so that the final page area for layout is 1.5 by 1.5
+  // inches. Content that is 5.25 inches tall should therefore require 3 and a
+  // half pages (1.5 * 3.5 = 5.25). The specified page size is smaller than the
+  // paper (8.5x11 inches), and should be centered on paper, meaning that the
+  // margins will be changed so that the sum of the margins and the page size
+  // will be equal to the paper size.
+  LoadHTML(R"HTML(
+    <style>
+      @page { margin:0.5in; size:3in; }
+      body { margin:0; }
+    </style>
+    <div style="width:1.5in; height:5.25in; background:#0000ff;"></div>
+  )HTML");
+
+  print_settings().Set(kSettingPrinterType,
+                       static_cast<int>(mojom::PrinterType::kLocal));
+  print_settings().Set(kSettingScaleFactor, 200);
+  print_settings().Set(kSettingShouldPrintBackgrounds, true);
+
+  printer()->set_should_generate_page_images(true);
+
+  OnPrintPreview();
+
+  EXPECT_EQ(0u, preview_ui()->print_preview_pages_remaining());
+  VerifyDefaultPageLayout(144, 144, 324, 324, 234, 234, true, true);
+  VerifyPreviewPageCount(4);
+
+  // Look for the #0000ff background on all the pages.
+
+  // First page:
+  const MockPrinterPage* page = printer()->GetPrinterPage(0);
+  ASSERT_TRUE(page);
+  const printing::Image* image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(612, 792));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(233, 323), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(234, 324), 0x0000ffU);  // blue inside
+  // Bottom right page content area corner.
+  EXPECT_EQ(image->pixel_at(377, 467), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(378, 468), 0xffffffU);  // white outside
+
+  // Second page:
+  page = printer()->GetPrinterPage(1);
+  ASSERT_TRUE(page);
+  image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(612, 792));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(233, 323), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(234, 324), 0x0000ffU);  // blue inside
+  // Bottom right page content area corner.
+  EXPECT_EQ(image->pixel_at(377, 467), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(378, 468), 0xffffffU);  // white outside
+
+  // Third page:
+  page = printer()->GetPrinterPage(2);
+  ASSERT_TRUE(page);
+  image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(612, 792));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(233, 323), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(234, 324), 0x0000ffU);  // blue inside
+  // Bottom right page content area corner.
+  EXPECT_EQ(image->pixel_at(377, 467), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(378, 468), 0xffffffU);  // white outside
+
+  // Fourth and last page:
+  page = printer()->GetPrinterPage(3);
+  ASSERT_TRUE(page);
+  image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(612, 792));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(233, 323), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(234, 324), 0x0000ffU);  // blue inside
+  // Bottom right corner of the DIV (which occupies half of the last page).
+  EXPECT_EQ(image->pixel_at(377, 395), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(378, 396), 0xffffffU);  // white outside
+
+  OnClosePrintPreviewDialog();
+}
+
+TEST_F(PrintRenderFrameHelperPreviewTest,
+       MarginsSizeAndInputScaleAndAvoidOverflowScaleToPrinter4) {
+  // The default page size in these tests is US Letter - 8.5 by 11 inches.
+  // Setting the page size to 5 by 3 inches and margins to 0.5 inches results in
+  // a page area of 4 by 2 inches. Setting the input scale factor to 200%
+  // shrinks this to 2 by 1 inches. There's a 3in wide block in the test. To
+  // make it fit without overflowing, Blink will increase the page area size by
+  // 3/2, i.e. 50% larger, so that the final page area for layout is 3 by 1.5
+  // inches. Content that is 2.25 inches tall should therefore require one and a
+  // half page (1.5 * 1.5 = 2.25). The specified page size is smaller than the
+  // paper (8.5x11 inches), and should be centered on paper, meaning that the
+  // margins will be changed so that the sum of the margins and the page size
+  // will be equal to the paper size. Additionally, the orientation is
+  // landscape.
+  LoadHTML(R"HTML(
+    <style>
+      @page { margin:0.5in; size:5in 3in; }
+      body { margin:0; }
+    </style>
+    <div style="width:3in; height:2.25in; background:#0000ff;"></div>
+  )HTML");
+
+  print_settings().Set(kSettingPrinterType,
+                       static_cast<int>(mojom::PrinterType::kLocal));
+  print_settings().Set(kSettingScaleFactor, 200);
+  print_settings().Set(kSettingShouldPrintBackgrounds, true);
+
+  printer()->set_should_generate_page_images(true);
+
+  OnPrintPreview();
+
+  EXPECT_EQ(0u, preview_ui()->print_preview_pages_remaining());
+  VerifyDefaultPageLayout(288, 144, 234, 234, 252, 252, true, true);
+  VerifyPreviewPageCount(2);
+
+  // Look for the #0000ff background on all the pages.
+
+  // First page:
+  const MockPrinterPage* page = printer()->GetPrinterPage(0);
+  ASSERT_TRUE(page);
+  const printing::Image* image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(792, 612));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(251, 233), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(252, 234), 0x0000ffU);  // blue inside
+  // Bottom right page content area corner.
+  EXPECT_EQ(image->pixel_at(539, 377), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(540, 378), 0xffffffU);  // white outside
+
+  // Second page:
+  page = printer()->GetPrinterPage(1);
+  ASSERT_TRUE(page);
+  image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(792, 612));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(251, 233), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(252, 234), 0x0000ffU);  // blue inside
+  // Bottom right corner of the DIV (which occupies half of the last page).
+  EXPECT_EQ(image->pixel_at(539, 305), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(540, 306), 0xffffffU);  // white outside
+
+  OnClosePrintPreviewDialog();
+}
+
+TEST_F(PrintRenderFrameHelperPreviewTest,
+       MarginsSizeAndInputScaleAndAvoidOverflowScaleToPrinter5) {
+  // The default page size in these tests is US Letter - 8.5 by 11 inches.
+  // Setting the page size to 34 inches and margins to 1 inch results in a page
+  // area of 32 inches. Setting the input scale factor to 200% shrinks this to
+  // 16 inches. There's a 24in wide block in the test. To make it fit without
+  // overflowing, Blink will increase the page area size by 3/2 (24/16),
+  // i.e. 50% larger, so that the final page area for layout is 24 by 24 inches.
+  // Content that is 36 inches tall should therefore require one and a half page
+  // (1.5 * 1.5 = 2.25). The specified page size is larger than the paper
+  // (8.5x11 inches), and needs to be zoomed down to fit. The zoom factor will
+  // be 0.25. 8.5 / 34 (short edges) = 0.25, which is less than 11 / 34 (long
+  // edges). Margins are also adjusted accordingly, since it's essentially the
+  // entire page box that's scaled down. The result should be centered on paper.
+  LoadHTML(R"HTML(
+    <style>
+      @page { margin:1in; size:34in; }
+      body { margin:0; }
+    </style>
+    <div style="width:24in; height:36in; background:#0000ff;"></div>
+  )HTML");
+
+  print_settings().Set(kSettingPrinterType,
+                       static_cast<int>(mojom::PrinterType::kLocal));
+  print_settings().Set(kSettingScaleFactor, 200);
+  print_settings().Set(kSettingShouldPrintBackgrounds, true);
+
+  printer()->set_should_generate_page_images(true);
+
+  OnPrintPreview();
+
+  EXPECT_EQ(0u, preview_ui()->print_preview_pages_remaining());
+  VerifyDefaultPageLayout(576, 576, 108, 108, 18, 18, true, true);
+  VerifyPreviewPageCount(2);
+
+  // Look for the #0000ff background on the first and last pages.
+
+  // First page:
+  const MockPrinterPage* page = printer()->GetPrinterPage(0);
+  ASSERT_TRUE(page);
+  const printing::Image* image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(612, 792));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(17, 107), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(18, 108), 0x0000ffU);  // blue inside
+  // Bottom right page content area corner.
+  EXPECT_EQ(image->pixel_at(557, 683), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(558, 684), 0xffffffU);  // white outside
+
+  // Second page:
+  page = printer()->GetPrinterPage(1);
+  ASSERT_TRUE(page);
+  image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(612, 792));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(17, 107), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(18, 108), 0x0000ffU);  // blue inside
+  // Bottom right corner of the DIV (which occupies half of the last page).
+  EXPECT_EQ(image->pixel_at(557, 395), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(558, 396), 0xffffffU);  // white outside
+
+  OnClosePrintPreviewDialog();
+}
+
+TEST_F(PrintRenderFrameHelperPreviewTest,
+       MarginsSizeAndInputScaleAndAvoidOverflowScaleToPrinter6) {
+  // The default page size in these tests is US Letter - 8.5 by 11 inches.
+  // Setting the page size to 33 by 18 inches and margins to 1 inch results in a
+  // page area of 31 by 16 inches. Setting the input scale factor to 200%
+  // shrinks this to 15.5 by 8 inches. There's a 23.25in wide block in the
+  // test. To make it fit without overflowing, Blink will increase the page area
+  // size by 3/2 (23.25/15.5), i.e. 50% larger, so that the final page area for
+  // layout is 23.25 by 12 inches. Content that is 18 inches tall should
+  // therefore require one and a half page (12 * 1.5 = 18). The specified page
+  // size is larger than the paper (8.5x11 inches), and needs to be zoomed down
+  // to fit. The zoom factor will be 1/3. 11 / 33 (long edges) = 1/3, which is
+  // less than 8.5 / 18 (short edges). Margins are also adjusted accordingly,
+  // since it's essentially the entire page box that's scaled down.
+  // Additionally, the orientation is landscape. The result should be centered
+  // on paper.
+  LoadHTML(R"HTML(
+    <style>
+      @page { margin:1in; size:33in 18in; }
+      body { margin:0; }
+    </style>
+    <div style="width:23.25in; height:18in; background:#0000ff;"></div>
+  )HTML");
+
+  print_settings().Set(kSettingPrinterType,
+                       static_cast<int>(mojom::PrinterType::kLocal));
+  print_settings().Set(kSettingScaleFactor, 200);
+  print_settings().Set(kSettingShouldPrintBackgrounds, true);
+
+  printer()->set_should_generate_page_images(true);
+
+  OnPrintPreview();
+
+  EXPECT_EQ(0u, preview_ui()->print_preview_pages_remaining());
+  VerifyDefaultPageLayout(744, 384, 114, 114, 24, 24, true, true);
+  VerifyPreviewPageCount(2);
+
+  // Look for the #0000ff background on the first and last pages.
+
+  // First page:
+  const MockPrinterPage* page = printer()->GetPrinterPage(0);
+  ASSERT_TRUE(page);
+  const printing::Image* image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(792, 612));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(23, 113), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(24, 114), 0x0000ffU);  // blue inside
+  // Bottom right page content area corner.
+  EXPECT_EQ(image->pixel_at(767, 497), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(768, 498), 0xffffffU);  // white outside
+
+  // Second page:
+  page = printer()->GetPrinterPage(1);
+  ASSERT_TRUE(page);
+  image = &page->image();
+  ASSERT_EQ(image->size(), gfx::Size(792, 612));
+  // Top left page content area corner.
+  EXPECT_EQ(image->pixel_at(23, 113), 0xffffffU);  // white outside
+  EXPECT_EQ(image->pixel_at(24, 114), 0x0000ffU);  // blue inside
+  // Bottom right corner of the DIV (which occupies half of the last page).
+  EXPECT_EQ(image->pixel_at(767, 305), 0x0000ffU);  // blue inside
+  EXPECT_EQ(image->pixel_at(768, 306), 0xffffffU);  // white outside
+
+  OnClosePrintPreviewDialog();
+}
+
+#endif  // MOCK_PRINTER_SUPPORTS_PAGE_IMAGES
+
 // Test to verify that print preview workflow honor the orientation settings
 // specified in css.
 TEST_F(PrintRenderFrameHelperPreviewTest, PrintPreviewHonorsOrientationCss) {
diff --git a/components/saved_tab_groups/README.md b/components/saved_tab_groups/README.md
index d77e2bcf..24689937 100644
--- a/components/saved_tab_groups/README.md
+++ b/components/saved_tab_groups/README.md
@@ -7,4 +7,16 @@
 
 Saved Tab Groups are built on sync's storage layer. This storage solution works
 across sessions and when sync is enabled, saved tab groups will sync across
-devices updating the tabs and other metadata about the tab group in real-time.
\ No newline at end of file
+devices updating the tabs and other metadata about the tab group in real-time.
+
+## Testing
+
+To run all the relevant C++ unit tests, you can run the `components_unittests`
+target and give the `saved_tab_groups` filter file as an argument:
+
+```
+./out/Default/components_unittests --test-launcher-filter-file=components/saved_tab_groups/components_unittests.filter
+```
+
+To keep the list of tests updated, you must add the test group name to the
+`components_unittests.filter` file whenever writing a new test file.
diff --git a/components/saved_tab_groups/components_unittests.filter b/components/saved_tab_groups/components_unittests.filter
new file mode 100644
index 0000000..373e86d
--- /dev/null
+++ b/components/saved_tab_groups/components_unittests.filter
@@ -0,0 +1,5 @@
+*SavedTabGroupConversionTest.*
+*SavedTabGroupModelObserverTest.*
+*SavedTabGroupModelTest.*
+*SavedTabGroupSyncBridgeTest.*
+*SavedTabGroupTest.*
diff --git a/components/services/storage/public/mojom/service_worker_storage_control.mojom b/components/services/storage/public/mojom/service_worker_storage_control.mojom
index 4b1de345..f1bfdd6 100644
--- a/components/services/storage/public/mojom/service_worker_storage_control.mojom
+++ b/components/services/storage/public/mojom/service_worker_storage_control.mojom
@@ -7,6 +7,7 @@
 import "components/services/storage/public/mojom/service_worker_database.mojom";
 import "components/services/storage/public/mojom/storage_policy_update.mojom";
 import "mojo/public/mojom/base/big_buffer.mojom";
+import "mojo/public/mojom/base/byte_string.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "services/network/public/mojom/url_response_head.mojom";
 import "third_party/blink/public/mojom/service_worker/service_worker_fetch_handler_type.mojom";
@@ -122,13 +123,13 @@
 };
 
 // Represents an entry of user data which is stored with a service worker
-// registration. An entry of user data is an arbitrary key-vaule pair. The
+// registration. An entry of user data is an arbitrary key-value pair. The
 // lifetime of user data is tied up with the registration.
 // It will be deleted when the corresponding registration is deleted.
 struct ServiceWorkerUserData {
   int64 registration_id;
   string key;
-  string value;
+  mojo_base.mojom.ByteString value;
 };
 
 // Controls the state of service worker storage within a partition. This is a
@@ -284,7 +285,8 @@
   // Succeeds only when all keys are found. On success, the size and the order
   // of |values| are the same as |keys|.
   GetUserData(int64 registration_id, array<string> keys) =>
-      (ServiceWorkerDatabaseStatus status, array<string> values);
+      (ServiceWorkerDatabaseStatus status,
+       array<mojo_base.mojom.ByteString> values);
   // Stores `user_data` on persistent storage.
   StoreUserData(int64 registration_id,
                 blink.mojom.StorageKey key,
@@ -297,11 +299,13 @@
   // Gets user data values associated with the given |registration_id|
   // filtered by |key_prefix|.
   GetUserDataByKeyPrefix(int64 registration_id, string key_prefix) =>
-      (ServiceWorkerDatabaseStatus status, array<string> values);
+      (ServiceWorkerDatabaseStatus status,
+       array<mojo_base.mojom.ByteString> values);
   // Gets user data associated with the given |registration_id| filtered by
   // |key_prefix|. Returns user data as key-value pairs.
   GetUserKeysAndDataByKeyPrefix(int64 registration_id, string key_prefix) =>
-      (ServiceWorkerDatabaseStatus status, map<string, string> user_data);
+      (ServiceWorkerDatabaseStatus status,
+       map<string, mojo_base.mojom.ByteString> user_data);
   // Clears user data associated with the given |registration_id| filtered by
   // |key_prefix|.
   ClearUserDataByKeyPrefixes(int64 registratation_id,
diff --git a/components/sync/base/features.cc b/components/sync/base/features.cc
index 4521baf4..b18a4cf 100644
--- a/components/sync/base/features.cc
+++ b/components/sync/base/features.cc
@@ -121,12 +121,7 @@
 
 BASE_FEATURE(kSyncPollImmediatelyOnEveryStartup,
              "SyncPollImmediatelyOnEveryStartup2",
-#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_MAC) || \
-    BUILDFLAG(IS_WIN)
-             base::FEATURE_ENABLED_BY_DEFAULT
-#else
              base::FEATURE_DISABLED_BY_DEFAULT
-#endif
 );
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
diff --git a/content/app/ios/appex/BUILD.gn b/content/app/ios/appex/BUILD.gn
new file mode 100644
index 0000000..615d5fc
--- /dev/null
+++ b/content/app/ios/appex/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/config/ios/rules.gni")
+import("//build/config/ios/swift_source_set.gni")
+import("//ios/build/chrome_build.gni")
+import("//ios/build/config.gni")
+
+source_set("content_main_thunk") {
+  sources = [
+    "content_main_thunk.cc",
+    "content_main_thunk.h",
+  ]
+
+  deps = [
+    "//base",
+    "//content/public/app",
+  ]
+  frameworks = [ "Foundation.framework" ]
+}
+
+swift_source_set("content_process") {
+  sources = [ "content_process.swift" ]
+  bridge_header = "content_main_thunk.h"
+
+  frameworks = [
+    "Foundation.framework",
+    "ExtensionFoundation.framework",
+    "BrowserEngineCore.framework",
+    "BrowserEngineKit.framework",
+  ]
+
+  deps = [ ":content_main_thunk" ]
+}
diff --git a/content/app/ios/appex/content_main_thunk.cc b/content/app/ios/appex/content_main_thunk.cc
new file mode 100644
index 0000000..14b854c
--- /dev/null
+++ b/content/app/ios/appex/content_main_thunk.cc
@@ -0,0 +1,30 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <pthread.h>
+#include <xpc/xpc.h>
+#include "base/mac/mach_port_rendezvous.h"
+
+// Leaked variables for now.
+static size_t g_argc = 0;
+static const char** g_argv = nullptr;
+static pthread_t g_main_thread;
+
+// The embedder must implement this.
+extern "C" int ContentProcessMain(int argc, const char** argv);
+
+extern "C" void ContentProcessInit() {}
+
+void* RunMain(void* data) {
+  ContentProcessMain((int)g_argc, g_argv);
+  return nullptr;
+}
+
+extern "C" void ContentProcessHandleNewConnection(xpc_connection_t connection) {
+  // TODO(dtapuska): For now we create our own main thread, figure out if we can
+  // use the ExtensionMain (thread 0) as the main thread but calling
+  // CFRunLoopRunInMode seems to crash it so we can't enter a nested event loop
+  // with some objects on the stack.
+  pthread_create(&g_main_thread, NULL, RunMain, NULL);
+}
diff --git a/content/app/ios/appex/content_main_thunk.h b/content/app/ios/appex/content_main_thunk.h
new file mode 100644
index 0000000..87d4d075
--- /dev/null
+++ b/content/app/ios/appex/content_main_thunk.h
@@ -0,0 +1,22 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_APP_IOS_APPEX_CONTENT_MAIN_THUNK_H_
+#define CONTENT_APP_IOS_APPEX_CONTENT_MAIN_THUNK_H_
+
+#import <Foundation/Foundation.h>
+#import <xpc/xpc.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void ContentProcessInit();
+void ContentProcessHandleNewConnection(xpc_connection_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  // CONTENT_APP_IOS_APPEX_CONTENT_MAIN_THUNK_H_
diff --git a/content/app/ios/appex/content_process.appex.entitlements b/content/app/ios/appex/content_process.appex.entitlements
new file mode 100644
index 0000000..469c5b2a
--- /dev/null
+++ b/content/app/ios/appex/content_process.appex.entitlements
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.developer.web-browser-engine.webcontent</key>
+	<true/>
+	<key>com.apple.security.get-task-allow</key>
+	<true/>
+	<key>com.apple.developer.cs.allow-jit</key>
+	<true/>
+</dict>
+</plist>
\ No newline at end of file
diff --git a/content/app/ios/appex/content_process.swift b/content/app/ios/appex/content_process.swift
new file mode 100644
index 0000000..936fab0
--- /dev/null
+++ b/content/app/ios/appex/content_process.swift
@@ -0,0 +1,18 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import BrowserEngineKit
+import ExtensionFoundation
+import Foundation
+
+@main
+class ContentProcess: WebContentExtension {
+  required init() {
+    ContentProcessInit()
+  }
+
+  public func handle(xpcConnection: xpc_connection_t) {
+    ContentProcessHandleNewConnection(xpcConnection)
+  }
+}
diff --git a/content/browser/attribution_reporting/attribution_features.cc b/content/browser/attribution_reporting/attribution_features.cc
index 6badaba..d2ff5a4 100644
--- a/content/browser/attribution_reporting/attribution_features.cc
+++ b/content/browser/attribution_reporting/attribution_features.cc
@@ -8,13 +8,6 @@
 
 namespace content {
 
-// When enabled, prefer to use the new recovery module to recover the
-// `AttributionStorageSql` database. See https://crbug.com/1385500 for details.
-// This is a kill switch and is not intended to be used in a field trial.
-BASE_FEATURE(kAttributionStorageUseBuiltInRecoveryIfSupported,
-             "AttributionStorageUseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BASE_FEATURE(kAttributionVerboseDebugReporting,
              "AttributionVerboseDebugReporting",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/content/browser/attribution_reporting/attribution_features.h b/content/browser/attribution_reporting/attribution_features.h
index 0a3ef23..be8e322 100644
--- a/content/browser/attribution_reporting/attribution_features.h
+++ b/content/browser/attribution_reporting/attribution_features.h
@@ -10,9 +10,6 @@
 
 namespace content {
 
-CONTENT_EXPORT BASE_DECLARE_FEATURE(
-    kAttributionStorageUseBuiltInRecoveryIfSupported);
-
 CONTENT_EXPORT BASE_DECLARE_FEATURE(kAttributionVerboseDebugReporting);
 
 CONTENT_EXPORT BASE_DECLARE_FEATURE(
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index 671fcaaa..258f6b1b 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -2649,8 +2649,7 @@
   // Attempt to recover a corrupt database, if it is eligible to be recovered.
   if (sql::BuiltInRecovery::RecoverIfPossible(
           &db_, extended_error,
-          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
-          &kAttributionStorageUseBuiltInRecoveryIfSupported)) {
+          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
     // Recovery was attempted. The database handle has been poisoned and the
     // error callback has been reset.
 
diff --git a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
index a44bc3c..841a4019 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql_unittest.cc
@@ -261,38 +261,9 @@
   return str;
 }
 
-// See https://crbug.com/1385500 for details. These tests can become
-// un-parameterized once the legacy recovery module is no longer used.
-enum class BuiltInRecoveryFeatureFlagState {
-  kDisabled,
-  kEnabled,
-};
-
-class AttributionStorageSqlTest
-    : public testing::TestWithParam<BuiltInRecoveryFeatureFlagState> {
+class AttributionStorageSqlTest : public testing::Test {
  public:
-  AttributionStorageSqlTest() {
-    // Whether or not database recovery uses the new built-in recovery module is
-    // predicated on both the per-database feature flag and the overarching
-    // feature flag being enabled. For the sake of these tests, just assume both
-    // or neither are set.
-    std::vector<base::test::FeatureRef> use_builtin_recovery_features{
-        kAttributionStorageUseBuiltInRecoveryIfSupported,
-        sql::features::kUseBuiltInRecoveryIfSupported};
-
-    switch (GetParam()) {
-      case BuiltInRecoveryFeatureFlagState::kDisabled:
-        scoped_feature_list_.InitWithFeatures(
-            /*enabled_features=*/{},
-            /*disabled_features=*/use_builtin_recovery_features);
-        break;
-      case BuiltInRecoveryFeatureFlagState::kEnabled:
-        scoped_feature_list_.InitWithFeatures(
-            /*enabled_features=*/use_builtin_recovery_features,
-            /*disabled_features=*/{});
-        break;
-    }
-  }
+  AttributionStorageSqlTest() = default;
 
   void SetUp() override { ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); }
 
@@ -338,11 +309,6 @@
 
   ConfigurableStorageDelegate* delegate() { return delegate_; }
 
-  bool UseBuiltInRecovery() const {
-    return GetParam() == BuiltInRecoveryFeatureFlagState::kEnabled &&
-           sql::BuiltInRecovery::IsSupported();
-  }
-
   void ExpectImpressionRows(size_t expected) {
     sql::Database raw_db;
     EXPECT_TRUE(raw_db.Open(db_path()));
@@ -432,7 +398,7 @@
   raw_ptr<ConfigurableStorageDelegate> delegate_ = nullptr;
 };
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        DatabaseInitialized_TablesAndIndexesLazilyInitialized) {
   base::HistogramTester histograms;
 
@@ -466,7 +432,7 @@
   EXPECT_TRUE(base::PathExists(db_path()));
 }
 
-TEST_P(AttributionStorageSqlTest, DatabaseReopened_DataPersisted) {
+TEST_F(AttributionStorageSqlTest, DatabaseReopened_DataPersisted) {
   OpenDatabase();
   AddReportToStorage();
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(1));
@@ -475,7 +441,7 @@
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(1));
 }
 
-TEST_P(AttributionStorageSqlTest, CorruptDatabase_RecoveredOnOpen) {
+TEST_F(AttributionStorageSqlTest, CorruptDatabase_RecoveredOnOpen) {
   OpenDatabase();
   AddReportToStorage();
   EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(1));
@@ -490,19 +456,13 @@
   // Open that database and ensure that it does not fail.
   EXPECT_NO_FATAL_FAILURE(OpenDatabase());
 
-  if (UseBuiltInRecovery()) {
-    // The database should have been recovered.
-    EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(1));
-  } else {
-    // The recovery process does not recover tables without row IDs, causing no
-    // data to be returned here. See https://crbug.com/1418026.
-    EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(0));
-  }
+  // The database should have been recovered.
+  EXPECT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(1));
 
   EXPECT_TRUE(expecter.SawExpectedErrors());
 }
 
-TEST_P(AttributionStorageSqlTest, VersionTooNew_RazesDB) {
+TEST_F(AttributionStorageSqlTest, VersionTooNew_RazesDB) {
   OpenDatabase();
   AddReportToStorage();
   ASSERT_THAT(storage()->GetAttributionReports(base::Time::Now()), SizeIs(1));
@@ -525,7 +485,7 @@
   ASSERT_THAT(storage()->GetAttributionReports(base::Time::Now()), IsEmpty());
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        StoreAndRetrieveReportWithVerification_FeatureEnabled) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(
@@ -569,7 +529,7 @@
   CloseDatabase();
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        StoreAndRetrieveReportWithoutVerification_FeatureEnabled) {
   OpenDatabase();
   base::test::ScopedFeatureList feature_list;
@@ -600,7 +560,7 @@
   CloseDatabase();
 }
 
-TEST_P(AttributionStorageSqlTest, NullReportWithVerification_FeatureEnabled) {
+TEST_F(AttributionStorageSqlTest, NullReportWithVerification_FeatureEnabled) {
   OpenDatabase();
 
   base::test::ScopedFeatureList feature_list;
@@ -649,7 +609,7 @@
   CloseDatabase();
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        BothRealAndNullReports_OnlyOneReportWithVerification) {
   OpenDatabase();
 
@@ -698,7 +658,7 @@
   CloseDatabase();
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        BothRealAndNullReportsReverseShuffle_OnlyOneReportWithVerification) {
   OpenDatabase();
 
@@ -748,7 +708,7 @@
   CloseDatabase();
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        BothRealAndNullReports_MultipleReportsWithVerification) {
   OpenDatabase();
 
@@ -818,7 +778,7 @@
 }
 
 // Create a source with three triggers and craft a query that will target all.
-TEST_P(AttributionStorageSqlTest, ClearDataRangeMultipleReports) {
+TEST_F(AttributionStorageSqlTest, ClearDataRangeMultipleReports) {
   base::HistogramTester histograms;
 
   OpenDatabase();
@@ -880,7 +840,7 @@
 //  target C2 and A2, which will in turn delete the source. We should ensure
 //  that C1 and A1 are properly deleted (reports should not be stored
 //  unattributed).
-TEST_P(AttributionStorageSqlTest, ClearDataWithVestigialConversion) {
+TEST_F(AttributionStorageSqlTest, ClearDataWithVestigialConversion) {
   base::HistogramTester histograms;
 
   OpenDatabase();
@@ -930,7 +890,7 @@
 }
 
 // Same as the above test, but with a null filter.
-TEST_P(AttributionStorageSqlTest, ClearAllDataWithVestigialConversion) {
+TEST_F(AttributionStorageSqlTest, ClearAllDataWithVestigialConversion) {
   base::HistogramTester histograms;
 
   OpenDatabase();
@@ -977,7 +937,7 @@
 }
 
 // The max time range with a null filter should delete everything.
-TEST_P(AttributionStorageSqlTest, DeleteEverything) {
+TEST_F(AttributionStorageSqlTest, DeleteEverything) {
   base::HistogramTester histograms;
 
   OpenDatabase();
@@ -1024,7 +984,7 @@
       "Conversions.ReportsDeletedInDataClearOperation.Aggregatable", 2, 1);
 }
 
-TEST_P(AttributionStorageSqlTest, ClearData_KeepRateLimitData) {
+TEST_F(AttributionStorageSqlTest, ClearData_KeepRateLimitData) {
   OpenDatabase();
   storage()->StoreSource(SourceBuilder().Build());
   EXPECT_EQ(AttributionTrigger::EventLevelResult::kSuccess,
@@ -1062,7 +1022,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest, DeleteAttributionDataByDataKey) {
+TEST_F(AttributionStorageSqlTest, DeleteAttributionDataByDataKey) {
   OpenDatabase();
   storage()->StoreSource(SourceBuilder()
                              .SetReportingOrigin(*SuitableOrigin::Deserialize(
@@ -1101,7 +1061,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest, MaxSourcesPerOrigin) {
+TEST_F(AttributionStorageSqlTest, MaxSourcesPerOrigin) {
   OpenDatabase();
   delegate()->set_max_sources_per_origin(2);
   storage()->StoreSource(SourceBuilder().Build());
@@ -1121,7 +1081,7 @@
   EXPECT_EQ(3u, rate_limit_rows);
 }
 
-TEST_P(AttributionStorageSqlTest, MaxReportsPerDestination) {
+TEST_F(AttributionStorageSqlTest, MaxReportsPerDestination) {
   OpenDatabase();
   delegate()->set_max_reports_per_destination(
       AttributionReport::Type::kEventLevel, 2);
@@ -1145,7 +1105,7 @@
   EXPECT_EQ(3u, rate_limit_rows);
 }
 
-TEST_P(AttributionStorageSqlTest, CantOpenDb_FailsSilentlyInRelease) {
+TEST_F(AttributionStorageSqlTest, CantOpenDb_FailsSilentlyInRelease) {
   base::CreateDirectoryAndGetError(db_path(), nullptr);
 
   auto sql_storage = std::make_unique<AttributionStorageSql>(
@@ -1162,7 +1122,7 @@
                 .event_level_status());
 }
 
-TEST_P(AttributionStorageSqlTest, DatabaseDirDoesExist_CreateDirAndOpenDB) {
+TEST_F(AttributionStorageSqlTest, DatabaseDirDoesExist_CreateDirAndOpenDB) {
   // Give the storage layer a database directory that doesn't exist.
   std::unique_ptr<AttributionStorage> storage =
       std::make_unique<AttributionStorageSql>(
@@ -1177,7 +1137,7 @@
                 .event_level_status());
 }
 
-TEST_P(AttributionStorageSqlTest, DBinitializationSucceeds_HistogramsRecorded) {
+TEST_F(AttributionStorageSqlTest, DBinitializationSucceeds_HistogramsRecorded) {
   {
     base::HistogramTester histograms;
 
@@ -1219,7 +1179,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        DBinitializationSucceeds_SourcesPerSourceOrginHistogramsRecorded) {
   auto create_n_origins = [](size_t n) -> std::vector<SuitableOrigin> {
     std::vector<SuitableOrigin> origins;
@@ -1284,7 +1244,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest, MaxUint64StorageSucceeds) {
+TEST_F(AttributionStorageSqlTest, MaxUint64StorageSucceeds) {
   constexpr uint64_t kMaxUint64 = std::numeric_limits<uint64_t>::max();
 
   OpenDatabase();
@@ -1305,7 +1265,7 @@
               ElementsAre(TriggerDebugKeyIs(kMaxUint64)));
 }
 
-TEST_P(AttributionStorageSqlTest, ImpressionNotExpired_NotDeleted) {
+TEST_F(AttributionStorageSqlTest, ImpressionNotExpired_NotDeleted) {
   OpenDatabase();
 
   storage()->StoreSource(
@@ -1318,7 +1278,7 @@
   ExpectImpressionRows(2u);
 }
 
-TEST_P(AttributionStorageSqlTest, ImpressionExpired_Deleted) {
+TEST_F(AttributionStorageSqlTest, ImpressionExpired_Deleted) {
   OpenDatabase();
 
   storage()->StoreSource(
@@ -1332,7 +1292,7 @@
   ExpectImpressionRows(1u);
 }
 
-TEST_P(AttributionStorageSqlTest, ImpressionExpired_TooFrequent_NotDeleted) {
+TEST_F(AttributionStorageSqlTest, ImpressionExpired_TooFrequent_NotDeleted) {
   OpenDatabase();
 
   delegate()->set_delete_expired_sources_frequency(base::Milliseconds(4));
@@ -1348,7 +1308,7 @@
   ExpectImpressionRows(2u);
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        ExpiredImpressionWithPendingConversion_NotDeleted) {
   OpenDatabase();
 
@@ -1366,7 +1326,7 @@
   ExpectImpressionRows(2u);
 }
 
-TEST_P(AttributionStorageSqlTest, TwoImpressionsOneExpired_OneDeleted) {
+TEST_F(AttributionStorageSqlTest, TwoImpressionsOneExpired_OneDeleted) {
   OpenDatabase();
 
   storage()->StoreSource(
@@ -1383,7 +1343,7 @@
   ExpectImpressionRows(2u);
 }
 
-TEST_P(AttributionStorageSqlTest, ExpiredImpressionWithSentConversion_Deleted) {
+TEST_F(AttributionStorageSqlTest, ExpiredImpressionWithSentConversion_Deleted) {
   OpenDatabase();
 
   const base::TimeDelta kReportDelay = base::Milliseconds(5);
@@ -1410,7 +1370,7 @@
   ExpectImpressionRows(1u);
 }
 
-TEST_P(AttributionStorageSqlTest, DeleteAggregatableAttributionReport) {
+TEST_F(AttributionStorageSqlTest, DeleteAggregatableAttributionReport) {
   OpenDatabase();
 
   storage()->StoreSource(TestAggregatableSourceProvider().GetBuilder().Build());
@@ -1438,7 +1398,7 @@
   CloseDatabase();
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        ExpiredSourceWithPendingAggregatableAttribution_NotDeleted) {
   OpenDatabase();
 
@@ -1474,7 +1434,7 @@
   ExpectImpressionRows(2u);
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        ExpiredSourceWithSentAggregatableAttribution_Deleted) {
   OpenDatabase();
 
@@ -1512,7 +1472,7 @@
   ExpectImpressionRows(1u);
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        InvalidSourceOriginOrSite_FailsDeserialization) {
   const struct {
     const char* sql;
@@ -1558,7 +1518,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest, CreateReport_DeletesUnattributedSources) {
+TEST_F(AttributionStorageSqlTest, CreateReport_DeletesUnattributedSources) {
   OpenDatabase();
   storage()->StoreSource(SourceBuilder().Build());
   storage()->StoreSource(SourceBuilder().Build());
@@ -1573,7 +1533,7 @@
   ExpectImpressionRows(1);
 }
 
-TEST_P(AttributionStorageSqlTest, CreateReport_DeactivatesAttributedSources) {
+TEST_F(AttributionStorageSqlTest, CreateReport_DeactivatesAttributedSources) {
   OpenDatabase();
   storage()->StoreSource(SourceBuilder().SetPriority(1).Build());
   MaybeCreateAndStoreEventLevelReport(DefaultTrigger());
@@ -1585,7 +1545,7 @@
 }
 
 // Tests that invalid filter keys present in the serialized data are removed.
-TEST_P(AttributionStorageSqlTest, DeserializeFilterData_RemovesReservedKeys) {
+TEST_F(AttributionStorageSqlTest, DeserializeFilterData_RemovesReservedKeys) {
   {
     OpenDatabase();
     storage()->StoreSource(SourceBuilder().Build());
@@ -1615,7 +1575,7 @@
               ElementsAre(Pair("x", ElementsAre("y"))));
 }
 
-TEST_P(AttributionStorageSqlTest, ReportTablesStoreDestinationOrigin) {
+TEST_F(AttributionStorageSqlTest, ReportTablesStoreDestinationOrigin) {
   constexpr char kDestinationOriginA[] = "https://a.d.test";
   constexpr char kDestinationOriginB[] = "https://b.d.test";
 
@@ -1654,7 +1614,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest, FakeReportUsesSourceOriginAsContext) {
+TEST_F(AttributionStorageSqlTest, FakeReportUsesSourceOriginAsContext) {
   OpenDatabase();
 
   delegate()->set_randomized_response(std::vector<FakeEventLevelReport>{
@@ -1681,7 +1641,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        InvalidExpiryOrReportTime_FailsDeserialization) {
   static constexpr const char* kUpdateSqls[] = {
       "UPDATE sources SET expiry_time=?",
@@ -1743,7 +1703,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        RandomizedResponseRateNotStored_RecalculatedWhenHandled) {
   {
     OpenDatabase();
@@ -1788,7 +1748,7 @@
               ElementsAre(RandomizedResponseRateIs(0.2)));
 }
 
-TEST_P(AttributionStorageSqlTest, EpsilonNotStored_RecalculatedWhenHandled) {
+TEST_F(AttributionStorageSqlTest, EpsilonNotStored_RecalculatedWhenHandled) {
   {
     OpenDatabase();
     storage()->StoreSource(SourceBuilder().Build());
@@ -1833,7 +1793,7 @@
                            attribution_reporting::EventLevelEpsilon())));
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        TriggerDataNotStored_RecalculatedWhenHandled) {
   {
     OpenDatabase();
@@ -1893,7 +1853,7 @@
 
 // Having the missing field default to the correct value allows us to avoid a
 // DB migration to populate the field.
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        MissingTriggerDataMatchingProtoField_DefaultsToModulus) {
   proto::AttributionReadOnlySourceData msg;
   ASSERT_FALSE(msg.has_trigger_data_matching());
@@ -1901,7 +1861,7 @@
             proto::AttributionReadOnlySourceData::MODULUS);
 }
 
-TEST_P(AttributionStorageSqlTest, InvalidReportingOrigin_FailsDeserialization) {
+TEST_F(AttributionStorageSqlTest, InvalidReportingOrigin_FailsDeserialization) {
   const struct {
     const char* desc;
     const char* reporting_origin;
@@ -1952,7 +1912,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        InvalidEventLevelMetadata_FailsDeserialization) {
   const struct {
     const char* desc;
@@ -2042,7 +2002,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        InvalidAggregatableMetadata_FailsDeserialization) {
   const struct {
     const char* desc;
@@ -2241,7 +2201,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        InvalidNullAggregatableMetadata_FailsDeserialization) {
   const struct {
     const char* desc;
@@ -2339,7 +2299,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        NullAggregatableReport_ValidSourceMatched_FailsDeserialization) {
   OpenDatabase();
   storage()->StoreSource(SourceBuilder().Build());
@@ -2374,7 +2334,7 @@
                                1);
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        NullAggregatableReport_CorruptedSourceMatched_FailsDeserialization) {
   OpenDatabase();
   storage()->StoreSource(SourceBuilder().Build());
@@ -2410,7 +2370,7 @@
                                1);
 }
 
-TEST_P(AttributionStorageSqlTest, InvalidStoredReportFields_MarkedAsCorrupted) {
+TEST_F(AttributionStorageSqlTest, InvalidStoredReportFields_MarkedAsCorrupted) {
   const struct {
     const char* desc;
     bool source_id_mismatch = false;
@@ -2604,7 +2564,7 @@
   }
 }
 
-TEST_P(AttributionStorageSqlTest,
+TEST_F(AttributionStorageSqlTest,
        InvalidReportCorrespondingSourceFields_MarkedAsCorrupted) {
   base::HistogramTester histograms;
   OpenDatabase();
@@ -2714,7 +2674,7 @@
   histograms.ExpectTotalCount("Conversions.CorruptReportsInDatabase5", 27);
 }
 
-TEST_P(AttributionStorageSqlTest, SourceDebugKeyAndDebugCookieSetCombination) {
+TEST_F(AttributionStorageSqlTest, SourceDebugKeyAndDebugCookieSetCombination) {
   const struct {
     const char* desc;
     std::optional<bool> debug_cookie_set;
@@ -2812,11 +2772,5 @@
   }
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    AttributionStorageSqlTest,
-    testing::Values(BuiltInRecoveryFeatureFlagState::kDisabled,
-                    BuiltInRecoveryFeatureFlagState::kEnabled));
-
 }  // namespace
 }  // namespace content
diff --git a/content/browser/attribution_reporting/privacy_math.cc b/content/browser/attribution_reporting/privacy_math.cc
index 3be3f64c4..448de122 100644
--- a/content/browser/attribution_reporting/privacy_math.cc
+++ b/content/browser/attribution_reporting/privacy_math.cc
@@ -30,6 +30,14 @@
 
 namespace {
 
+// Since base/numerics does not support checked math for 128 bit types,
+// implement it ourselves. This copies the relevant check from CheckedMulImpl.
+absl::uint128 CheckMul(absl::uint128 x, absl::uint128 y) {
+  // Note this is safe even with division with a remainder.
+  CHECK(y == 0 || x <= absl::Uint128Max() / y);
+  return x * y;
+}
+
 // The max possible number of state combinations given a valid input.
 // This comes from 20 maximum total reports, 20 reports per type, 5 windows per
 // type, and 32 distinct trigger data values.
@@ -195,7 +203,7 @@
 
   // Optimized fast-path.
   if (specs.SingleSharedSpec()) {
-    int64_t states = internal::GetNumberOfStarsAndBarsSequences(
+    absl::uint128 states = internal::GetNumberOfStarsAndBarsSequences(
         /*num_stars=*/max_reports,
         /*num_bars=*/specs.size() * num_windows);
     DCHECK_EQ(states, GetNumStatesRecursive(it, max_reports, num_windows,
@@ -264,7 +272,7 @@
 
 namespace internal {
 
-int64_t BinomialCoefficient(int n, int k) {
+absl::uint128 BinomialCoefficient(int n, int k) {
   DCHECK_GE(n, 0);
   DCHECK_GE(k, 0);
 
@@ -284,16 +292,17 @@
   }
 
   // (n choose k) = n (n -1) ... (n - (k - 1)) / k!
-  // = mul((n + i - i) / i), i from 1 -> k.
+  // = mul((n + 1 - i) / i), i from 1 -> k.
   //
   // You might be surprised that this algorithm works just fine with integer
   // division (i.e. division occurs cleanly with no remainder). However, this is
   // true for a very simple reason. Imagine a value of `i` causes division with
   // remainder in the below algorithm. This immediately implies that
   // (n choose i) is fractional, which we know is not the case.
-  int64_t result = 1;
+  absl::uint128 result = 1;
   for (int i = 1; i <= k; i++) {
-    result = base::CheckMul(result, n + 1 - i).ValueOrDie();
+    absl::uint128 term = n + 1 - i;
+    result = CheckMul(result, term);
     DCHECK_EQ(0, result % i);
     result = result / i;
   }
@@ -317,7 +326,8 @@
 //
 // We find this set via a simple greedy algorithm.
 // http://math0.wvstateu.edu/~baker/cs405/code/Combinadics.html
-std::vector<int> GetKCombinationAtIndex(int64_t combination_index, int k) {
+std::vector<int> GetKCombinationAtIndex(absl::uint128 combination_index,
+                                        int k) {
   DCHECK_GE(combination_index, 0);
   DCHECK_GE(k, 0);
   // `k` can be no more than max number of event level reports per source (20).
@@ -333,18 +343,20 @@
   // maximum a such that (a choose k) <= `combination_index`. Let a_k = a. Use
   // the previous binomial coefficient to compute the next one. Note: possible
   // to speed this up via something other than incremental search.
-  int64_t target = combination_index;
+  absl::uint128 target = combination_index;
   int candidate = k - 1;
-  int64_t binomial_coefficient = 0;       // BinomialCoefficient(candidate, k)
-  int64_t next_binomial_coefficient = 1;  // BinomialCoefficient(candidate+1, k)
+
+  // BinomialCoefficient(candidate, k)
+  absl::uint128 binomial_coefficient = 0;
+  // BinomialCoefficient(candidate+1, k)
+  absl::uint128 next_binomial_coefficient = 1;
   while (next_binomial_coefficient <= target) {
     candidate++;
     binomial_coefficient = next_binomial_coefficient;
     DCHECK_EQ(binomial_coefficient, BinomialCoefficient(candidate, k));
 
     // (n + 1 choose k) = (n choose k) * (n + 1) / (n + 1 - k)
-    next_binomial_coefficient =
-        base::CheckMul(binomial_coefficient, candidate + 1).ValueOrDie();
+    next_binomial_coefficient = CheckMul(binomial_coefficient, candidate + 1);
     next_binomial_coefficient /= candidate + 1 - k;
   }
   // We know from the k-combination definition, all subsequent values will be
@@ -362,14 +374,15 @@
         return output_k_combination;
       }
       // (n - 1 choose k - 1) = (n choose k) * k / n
-      binomial_coefficient = binomial_coefficient * (current_k) / candidate;
+      binomial_coefficient =
+          CheckMul(binomial_coefficient, current_k) / candidate;
 
       current_k--;
       candidate--;
     } else {
       // (n - 1 choose k) = (n choose k) * (n - k) / n
       binomial_coefficient =
-          binomial_coefficient * (candidate - current_k) / candidate;
+          CheckMul(binomial_coefficient, candidate - current_k) / candidate;
 
       candidate--;
     }
@@ -394,13 +407,13 @@
   return reports;
 }
 
-int64_t GetNumberOfStarsAndBarsSequences(int num_stars, int num_bars) {
+absl::uint128 GetNumberOfStarsAndBarsSequences(int num_stars, int num_bars) {
   return BinomialCoefficient(num_stars + num_bars, num_stars);
 }
 
 std::vector<int> GetStarIndices(int num_stars,
                                 int num_bars,
-                                int64_t sequence_index) {
+                                absl::uint128 sequence_index) {
   DCHECK_LT(sequence_index,
             GetNumberOfStarsAndBarsSequences(num_stars, num_bars));
   return GetKCombinationAtIndex(sequence_index, num_stars);
@@ -449,7 +462,7 @@
 std::vector<FakeEventLevelReport> GetFakeReportsForSequenceIndex(
     const attribution_reporting::TriggerSpecs& specs,
     int max_reports,
-    int64_t random_stars_and_bars_sequence_index) {
+    absl::uint128 random_stars_and_bars_sequence_index) {
   const attribution_reporting::TriggerSpec* single_spec =
       specs.SingleSharedSpec();
   CHECK(single_spec);
@@ -513,9 +526,7 @@
     DCHECK_LT(sequence_index, kMaxNumCombinations);
     fake_reports = specs.SingleSharedSpec()
                        ? internal::GetFakeReportsForSequenceIndex(
-                             specs, max_reports,
-                             base::checked_cast<int64_t>(
-                                 absl::Uint128Low64(sequence_index)))
+                             specs, max_reports, sequence_index)
                        : internal::GetFakeReportsForSequenceIndex(
                              specs, max_reports, sequence_index, map);
   }
diff --git a/content/browser/attribution_reporting/privacy_math.h b/content/browser/attribution_reporting/privacy_math.h
index 1456753..4faa896 100644
--- a/content/browser/attribution_reporting/privacy_math.h
+++ b/content/browser/attribution_reporting/privacy_math.h
@@ -100,7 +100,7 @@
 //
 // Note: large values of `n` and `k` may overflow. This function internally uses
 // checked_math to crash safely if this occurs.
-CONTENT_EXPORT int64_t BinomialCoefficient(int n, int k);
+CONTENT_EXPORT absl::uint128 BinomialCoefficient(int n, int k);
 
 // Returns the k-combination associated with the number `combination_index`. In
 // other words, returns the combination of `k` integers uniquely indexed by
@@ -109,21 +109,21 @@
 //
 // The returned vector is guaranteed to have size `k`.
 CONTENT_EXPORT std::vector<int> GetKCombinationAtIndex(
-    int64_t combination_index,
+    absl::uint128 combination_index,
     int k);
 
 // Returns the number of possible sequences of "stars and bars" sequences
 // https://en.wikipedia.org/wiki/Stars_and_bars_(combinatorics),
 // which is equivalent to (num_stars + num_bars choose num_stars).
-CONTENT_EXPORT int64_t GetNumberOfStarsAndBarsSequences(int num_stars,
-                                                        int num_bars);
+CONTENT_EXPORT absl::uint128 GetNumberOfStarsAndBarsSequences(int num_stars,
+                                                              int num_bars);
 
 // Returns a vector of the indices of every star in the stars and bars sequence
 // indexed by `sequence_index`. The indexing technique uses the k-combination
 // utility documented above.
 CONTENT_EXPORT std::vector<int> GetStarIndices(int num_stars,
                                                int num_bars,
-                                               int64_t sequence_index);
+                                               absl::uint128 sequence_index);
 
 // From a vector with the index of every star in a stars and bars sequence,
 // returns a vector which, for every star, counts the number of bars preceding
@@ -154,7 +154,7 @@
 CONTENT_EXPORT std::vector<FakeEventLevelReport> GetFakeReportsForSequenceIndex(
     const attribution_reporting::TriggerSpecs&,
     int max_event_level_reports,
-    int64_t random_stars_and_bars_sequence_index);
+    absl::uint128 random_stars_and_bars_sequence_index);
 
 // Note: this method for sampling is not 1:1 with the above function for the
 // same sequence index, even for equivalent API configs.
diff --git a/content/browser/attribution_reporting/privacy_math_unittest.cc b/content/browser/attribution_reporting/privacy_math_unittest.cc
index 49634dc..29c9188 100644
--- a/content/browser/attribution_reporting/privacy_math_unittest.cc
+++ b/content/browser/attribution_reporting/privacy_math_unittest.cc
@@ -28,6 +28,46 @@
 using ::attribution_reporting::MaxEventLevelReports;
 using ::attribution_reporting::mojom::SourceType;
 
+attribution_reporting::TriggerSpecs SpecsFromWindowList(
+    std::vector<int> windows_per_type,
+    bool collapse_into_single_spec) {
+  attribution_reporting::TriggerSpecs::TriggerDataIndices indices;
+  std::vector<attribution_reporting::TriggerSpec> raw_specs;
+
+  bool supportable_by_single_spec = base::ranges::all_of(
+      windows_per_type, [&](int w) { return w == windows_per_type[0]; });
+
+  if (collapse_into_single_spec && supportable_by_single_spec) {
+    std::vector<base::TimeDelta> deltas;
+    for (int i = 0; i < windows_per_type[0]; i++) {
+      deltas.emplace_back(base::Days(1) + base::Days(i));
+    }
+    for (int i = 0; i < static_cast<int>(windows_per_type.size()); ++i) {
+      indices[i] = 0;
+    }
+    raw_specs.emplace_back(*attribution_reporting::EventReportWindows::Create(
+        base::Days(0), deltas));
+    auto specs =
+        *attribution_reporting::TriggerSpecs::Create(indices, raw_specs);
+    return specs;
+  }
+
+  int index = 0;
+  for (int windows : windows_per_type) {
+    std::vector<base::TimeDelta> deltas;
+    for (int i = 0; i < windows; i++) {
+      deltas.emplace_back(base::Days(1) + base::Days(i));
+    }
+    raw_specs.emplace_back(*attribution_reporting::EventReportWindows::Create(
+        base::Days(0), deltas));
+    indices[index] = index;
+    index++;
+  }
+
+  auto specs = *attribution_reporting::TriggerSpecs::Create(indices, raw_specs);
+  return specs;
+}
+
 TEST(PrivacyMathTest, BinomialCoefficient) {
   // Test cases generated via a python program using scipy.special.comb.
   struct {
@@ -133,9 +173,9 @@
 // `index` = \sum_{i=1}^k {a_i}\choose{i}
 TEST(PrivacyMathTest, GetKCombination_MatchesDefinition) {
   for (int k = 1; k < 5; k++) {
-    for (int index = 0; index < 3000; index++) {
+    for (absl::uint128 index = 0; index < 3000; index++) {
       std::vector<int> combination = internal::GetKCombinationAtIndex(index, k);
-      int sum = 0;
+      absl::uint128 sum = 0;
       for (int i = 0; i < k; i++) {
         sum += internal::BinomialCoefficient(combination[i], k - i);
       }
@@ -251,7 +291,7 @@
 // https://github.com/WICG/attribution-reporting-api/blob/ab43f8c989cf881ffd7a7f71801b98d649ed164a/flexible-event/privacy.test.ts#L38-L69
 TEST(PrivacyMathTest, ComputeChannelCapacity) {
   const struct {
-    int64_t num_states;
+    absl::uint128 num_states;
     double epsilon;
     double expected;
   } kTestCases[] = {
@@ -407,7 +447,7 @@
   //
   // The probability that t trials are not enough to see all possible results is
   // at most n^{-t/(n*ln(n)) + 1}.
-  EXPECT_EQ(static_cast<int64_t>(output_counts.size()), num_states);
+  EXPECT_EQ(static_cast<absl::uint128>(output_counts.size()), num_states);
 
   // For any of the n possible results, the expected number of times it is seen
   // is equal to 1/n. Moreover, for any possible result, the probability that it
@@ -499,48 +539,50 @@
                            /*tolerance=*/0.1);
 }
 
+const struct {
+  MaxEventLevelReports max_reports;
+  std::vector<int> windows_per_type;
+  absl::uint128 expected_num_states;
+} kNumStateTestCases[] = {
+    {MaxEventLevelReports(3), {3, 3, 3, 3, 3, 3, 3, 3}, 2925},
+    {MaxEventLevelReports(1), {1, 1}, 3},
+
+    {MaxEventLevelReports(1), {1}, 2},
+    {MaxEventLevelReports(5), {1}, 6},
+    {MaxEventLevelReports(2), {1, 1, 2, 2}, 28},
+    {MaxEventLevelReports(3), {1, 1, 2, 2, 3, 3}, 455},
+
+    // Cases for # of states > 10000 will skip the unique check, otherwise the
+    // tests won't ever finish.
+    {MaxEventLevelReports(20), {5, 5, 5, 5, 5, 5, 5, 5}, 4191844505805495},
+
+    // This input would overflow any 64 bit integer.
+    {MaxEventLevelReports(20),
+     {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+      5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5},
+     absl::MakeUint128(/*high=*/9494472u, /*low=*/10758590974061625903u)},
+};
+
+TEST(PrivacyMathTest, GetNumStates) {
+  for (const auto& test_case : kNumStateTestCases) {
+    // Test both single spec and multi-spec variants to ensure both code paths
+    // (optimized and non) get exercised.
+    auto specs = SpecsFromWindowList(test_case.windows_per_type,
+                                     /*collapse_into_single_spec=*/true);
+    EXPECT_EQ(test_case.expected_num_states,
+              GetNumStates(specs, test_case.max_reports));
+
+    specs = SpecsFromWindowList(test_case.windows_per_type,
+                                /*collapse_into_single_spec=*/false);
+    EXPECT_EQ(test_case.expected_num_states,
+              GetNumStates(specs, test_case.max_reports));
+  }
+}
+
 TEST(PrivacyMathTest, NumStatesForTriggerSpecs_UniqueSampling) {
-  const struct {
-    MaxEventLevelReports max_reports;
-    std::vector<int> windows_per_type;
-    absl::uint128 expected_num_states;
-  } kTestCases[] = {
-      {MaxEventLevelReports(3), {3, 3, 3, 3, 3, 3, 3, 3}, 2925},
-      {MaxEventLevelReports(1), {1, 1}, 3},
-
-      {MaxEventLevelReports(1), {1}, 2},
-      {MaxEventLevelReports(5), {1}, 6},
-      {MaxEventLevelReports(2), {1, 1, 2, 2}, 28},
-      {MaxEventLevelReports(3), {1, 1, 2, 2, 3, 3}, 455},
-
-      // Cases for # of states > 10000 will skip the unique check, otherwise the
-      // tests won't ever finish.
-      {MaxEventLevelReports(20), {5, 5, 5, 5, 5, 5, 5, 5}, 4191844505805495},
-
-      // This input would overflow any 64 bit integer.
-      {MaxEventLevelReports(20),
-       {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
-        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5},
-       absl::MakeUint128(/*high=*/9494472u, /*low=*/10758590974061625903u)},
-  };
-
-  for (const auto& test_case : kTestCases) {
-    attribution_reporting::TriggerSpecs::TriggerDataIndices indices;
-    std::vector<attribution_reporting::TriggerSpec> raw_specs;
-    int index = 0;
-    for (int windows : test_case.windows_per_type) {
-      std::vector<base::TimeDelta> deltas;
-      for (int i = 0; i < windows; i++) {
-        deltas.emplace_back(base::Days(1) + base::Days(i));
-      }
-      raw_specs.emplace_back(*attribution_reporting::EventReportWindows::Create(
-          base::Days(0), deltas));
-      indices[index] = index;
-      index++;
-    }
-
-    auto specs =
-        *attribution_reporting::TriggerSpecs::Create(indices, raw_specs);
+  for (const auto& test_case : kNumStateTestCases) {
+    auto specs = SpecsFromWindowList(test_case.windows_per_type,
+                                     /*collapse_into_single_spec=*/false);
     ASSERT_EQ(test_case.expected_num_states,
               GetNumStates(specs, test_case.max_reports));
 
diff --git a/content/browser/bluetooth/advertisement_client.cc b/content/browser/bluetooth/advertisement_client.cc
index 0023549..bf1370b 100644
--- a/content/browser/bluetooth/advertisement_client.cc
+++ b/content/browser/bluetooth/advertisement_client.cc
@@ -5,6 +5,7 @@
 #include "content/browser/bluetooth/advertisement_client.h"
 
 #include <utility>
+#include <vector>
 
 #include "content/browser/bluetooth/bluetooth_blocklist.h"
 #include "content/browser/bluetooth/bluetooth_metrics.h"
@@ -62,7 +63,7 @@
   }
 
   auto filtered_event = event.Clone();
-  base::EraseIf(filtered_event->uuids, [this](const BluetoothUUID& uuid) {
+  std::erase_if(filtered_event->uuids, [this](const BluetoothUUID& uuid) {
     return !service_->IsAllowedToAccessService(device_id_, uuid);
   });
   base::EraseIf(
diff --git a/content/browser/bluetooth/web_bluetooth_service_impl.cc b/content/browser/bluetooth/web_bluetooth_service_impl.cc
index 46818d4..7ff0f0ed 100644
--- a/content/browser/bluetooth/web_bluetooth_service_impl.cc
+++ b/content/browser/bluetooth/web_bluetooth_service_impl.cc
@@ -12,9 +12,9 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/containers/queue.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -540,7 +540,7 @@
 
   connected_devices_->CloseConnectionsToDevicesNotInList(permitted_ids);
 
-  base::EraseIf(watch_advertisements_clients_,
+  std::erase_if(watch_advertisements_clients_,
                 [&](const std::unique_ptr<WatchAdvertisementsClient>& client) {
                   return !base::Contains(permitted_ids, client->device_id());
                 });
@@ -1452,11 +1452,11 @@
 
   // TODO(https://crbug.com/1087007): These two classes can potentially be
   // combined into the same container.
-  base::EraseIf(scanning_clients_,
+  std::erase_if(scanning_clients_,
                 [](const std::unique_ptr<ScanningClient>& client) {
                   return !client->is_connected();
                 });
-  base::EraseIf(watch_advertisements_clients_,
+  std::erase_if(watch_advertisements_clients_,
                 [](const std::unique_ptr<WatchAdvertisementsClient>& client) {
                   return !client->is_connected();
                 });
diff --git a/content/browser/browsing_topics/browsing_topics_site_data_storage.cc b/content/browser/browsing_topics/browsing_topics_site_data_storage.cc
index 82d4bd5..92400bc 100644
--- a/content/browser/browsing_topics/browsing_topics_site_data_storage.cc
+++ b/content/browser/browsing_topics/browsing_topics_site_data_storage.cc
@@ -26,14 +26,6 @@
 
 }  // namespace
 
-// When enabled, prefer to use the new recovery module to recover the
-// `BrowsingTopicsSiteDataStorage` database. See https://crbug.com/1385500 for
-// details. This is a kill switch and is not intended to be used in a field
-// trial.
-BASE_FEATURE(kBrowsingTopicsSiteDataStorageUseBuiltInRecoveryIfSupported,
-             "BrowsingTopicsSiteDataStorageUseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 BrowsingTopicsSiteDataStorage::BrowsingTopicsSiteDataStorage(
     const base::FilePath& path_to_database)
     : path_to_database_(path_to_database) {
@@ -353,8 +345,7 @@
   // Attempt to recover a corrupt database, if it is eligible to be recovered.
   if (sql::BuiltInRecovery::RecoverIfPossible(
           db_.get(), extended_error,
-          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
-          &kBrowsingTopicsSiteDataStorageUseBuiltInRecoveryIfSupported)) {
+          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
     // Recovery was attempted. The database handle has been poisoned and the
     // error callback has been reset.
 
diff --git a/content/browser/browsing_topics/browsing_topics_site_data_storage.h b/content/browser/browsing_topics/browsing_topics_site_data_storage.h
index a3e46ef1..0c8e0ed2 100644
--- a/content/browser/browsing_topics/browsing_topics_site_data_storage.h
+++ b/content/browser/browsing_topics/browsing_topics_site_data_storage.h
@@ -27,9 +27,6 @@
 
 namespace content {
 
-BASE_DECLARE_FEATURE(
-    kBrowsingTopicsSiteDataStorageUseBuiltInRecoveryIfSupported);
-
 class CONTENT_EXPORT BrowsingTopicsSiteDataStorage {
  public:
   explicit BrowsingTopicsSiteDataStorage(
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index 1792a7f..212357a 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -6,10 +6,10 @@
 
 #include <tuple>
 #include <utility>
+#include <vector>
 
 #include "base/command_line.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/debug/crash_logging.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
@@ -2301,7 +2301,7 @@
     base::AutoLock isolated_origins_lock(isolated_origins_lock_);
 
     for (auto& iter : isolated_origins_) {
-      base::EraseIf(iter.second,
+      std::erase_if(iter.second,
                     [&browser_context](const IsolatedOriginEntry& entry) {
                       // Remove if BrowserContext matches.
                       return (entry.browser_context() == &browser_context);
@@ -2702,7 +2702,7 @@
   {
     base::AutoLock isolated_origins_lock(isolated_origins_lock_);
     for (auto& iter : isolated_origins_) {
-      base::EraseIf(iter.second, [&browsing_instance_id](
+      std::erase_if(iter.second, [&browsing_instance_id](
                                      const IsolatedOriginEntry& entry) {
         // Remove entries that are specific to `browsing_instance_id` and
         // do not apply to future BrowsingInstances.
@@ -2828,7 +2828,7 @@
     const url::Origin& origin) {
   GURL key(SiteInfo::GetSiteForOrigin(origin));
   base::AutoLock isolated_origins_lock(isolated_origins_lock_);
-  base::EraseIf(isolated_origins_[key],
+  std::erase_if(isolated_origins_[key],
                 [&origin](const IsolatedOriginEntry& entry) {
                   // Remove if origin matches.
                   return (entry.origin() == origin);
diff --git a/content/browser/content_index/content_index_database.cc b/content/browser/content_index/content_index_database.cc
index 52505750..38950c3 100644
--- a/content/browser/content_index/content_index_database.cc
+++ b/content/browser/content_index/content_index_database.cc
@@ -7,9 +7,9 @@
 #include <optional>
 #include <set>
 #include <string>
+#include <vector>
 
 #include "base/barrier_closure.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
 #include "content/browser/background_fetch/storage/image_helpers.h"
@@ -474,7 +474,7 @@
 
   if (!corrupted_sw_ids.empty()) {
     // Remove soon-to-be-deleted entries.
-    base::EraseIf(entries, [&corrupted_sw_ids](const auto& entry) {
+    std::erase_if(entries, [&corrupted_sw_ids](const auto& entry) {
       return corrupted_sw_ids.count(entry.service_worker_registration_id);
     });
 
diff --git a/content/browser/devtools/devtools_agent_host_impl.cc b/content/browser/devtools/devtools_agent_host_impl.cc
index d9ad8f6..f16aff8 100644
--- a/content/browser/devtools/devtools_agent_host_impl.cc
+++ b/content/browser/devtools/devtools_agent_host_impl.cc
@@ -9,7 +9,6 @@
 
 #include "base/command_line.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/no_destructor.h"
@@ -354,7 +353,7 @@
   DCHECK_EQ(session, session_owned.get());
   // Make sure we dispose session prior to reporting it to the host.
   session->Dispose();
-  base::Erase(sessions_, session);
+  std::erase(sessions_, session);
   session_by_client_.erase(session->GetClient());
   DetachSession(session);
   DevToolsManager* manager = DevToolsManager::GetInstance();
diff --git a/content/browser/first_party_sets/database/first_party_sets_database.cc b/content/browser/first_party_sets/database/first_party_sets_database.cc
index 6d20e2b..8cda672c 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database.cc
+++ b/content/browser/first_party_sets/database/first_party_sets_database.cc
@@ -34,13 +34,6 @@
 
 namespace content {
 
-// When enabled, prefer to use the new recovery module to recover the
-// `FirstPartySetsDatabase` database. See https://crbug.com/1385500 for details.
-// This is a kill switch and is not intended to be used in a field trial.
-BASE_FEATURE(kFirstPartySetsDatabaseUseBuiltInRecoveryIfSupported,
-             "FirstPartySetsDatabaseUseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 namespace {
 
 // Version number of the database.
@@ -743,8 +736,7 @@
   // Attempt to recover a corrupt database, if it is eligible to be recovered.
   if (sql::BuiltInRecovery::RecoverIfPossible(
           db_.get(), extended_error,
-          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
-          &kFirstPartySetsDatabaseUseBuiltInRecoveryIfSupported)) {
+          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
     // Recovery was attempted. The database handle has been poisoned and the
     // error callback has been reset.
 
diff --git a/content/browser/first_party_sets/database/first_party_sets_database.h b/content/browser/first_party_sets/database/first_party_sets_database.h
index 624dccb3..1ebfc57 100644
--- a/content/browser/first_party_sets/database/first_party_sets_database.h
+++ b/content/browser/first_party_sets/database/first_party_sets_database.h
@@ -34,8 +34,6 @@
 
 namespace content {
 
-BASE_DECLARE_FEATURE(kFirstPartySetsDatabaseUseBuiltInRecoveryIfSupported);
-
 // Wraps its own `sql::Database` instance on behalf of the First-Party Sets
 // database implementation. This class must be accessed and destroyed on the
 // same sequence. The sequence must outlive |this|.
diff --git a/content/browser/first_party_sets/first_party_set_parser.cc b/content/browser/first_party_sets/first_party_set_parser.cc
index 5465b78..d42edb0 100644
--- a/content/browser/first_party_sets/first_party_set_parser.cc
+++ b/content/browser/first_party_sets/first_party_set_parser.cc
@@ -348,14 +348,14 @@
 
     // Erase invalid members/primaries, and collect primary sites that might
     // become singletons.
-    base::EraseIf(
+    std::erase_if(
         sets,
         [&](const std::pair<net::SchemefulSite, net::FirstPartySetEntry>& pair)
             -> bool { return IsInvalidEntry(pair, &possible_singletons); });
 
     // Erase invalid aliases, and collect canonical sites that are primaries and
     // might become singletons.
-    base::EraseIf(
+    std::erase_if(
         aliases,
         [&](const std::pair<net::SchemefulSite, net::SchemefulSite>& pair)
             -> bool {
@@ -390,7 +390,7 @@
       return;
     }
 
-    base::EraseIf(
+    std::erase_if(
         sets,
         [&](const std::pair<net::SchemefulSite, net::FirstPartySetEntry>& pair)
             -> bool { return possible_singletons.contains(pair.first); });
@@ -425,8 +425,8 @@
     const auto is_singleton = [](const SingleSet& set) {
       return set.size() <= 1;
     };
-    base::EraseIf(lists.additions, is_singleton);
-    base::EraseIf(lists.replacements, is_singleton);
+    std::erase_if(lists.additions, is_singleton);
+    std::erase_if(lists.replacements, is_singleton);
   }
 
   std::vector<ParseWarning>& warnings() { return warnings_; }
diff --git a/content/browser/indexed_db/BUILD.gn b/content/browser/indexed_db/BUILD.gn
index a9418ef..63d37f8 100644
--- a/content/browser/indexed_db/BUILD.gn
+++ b/content/browser/indexed_db/BUILD.gn
@@ -16,7 +16,6 @@
     "file_path_util.h",
     "file_stream_reader_to_data_pipe.cc",
     "file_stream_reader_to_data_pipe.h",
-    "indexed_db.h",
     "indexed_db_active_blob_registry.cc",
     "indexed_db_active_blob_registry.h",
     "indexed_db_backing_store.cc",
diff --git a/content/browser/indexed_db/database_impl.cc b/content/browser/indexed_db/database_impl.cc
index 5cce4d7..18ca9d4 100644
--- a/content/browser/indexed_db/database_impl.cc
+++ b/content/browser/indexed_db/database_impl.cc
@@ -17,6 +17,7 @@
 #include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h"
 #include "content/browser/indexed_db/indexed_db_callback_helpers.h"
 #include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_cursor.h"
 #include "content/browser/indexed_db/indexed_db_database_callbacks.h"
 #include "content/browser/indexed_db/indexed_db_transaction.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
@@ -195,7 +196,8 @@
   transaction->ScheduleTask(BindWeakOperation(
       &IndexedDBDatabase::GetOperation, connection_->database()->AsWeakPtr(),
       object_store_id, index_id, std::make_unique<IndexedDBKeyRange>(key_range),
-      key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE,
+      key_only ? indexed_db::CursorType::kKeyOnly
+               : indexed_db::CursorType::kKeyAndValue,
       std::move(aborting_callback)));
 }
 
@@ -259,7 +261,8 @@
   transaction->ScheduleTask(BindWeakOperation(
       &IndexedDBDatabase::GetAllOperation, connection_->database()->AsWeakPtr(),
       object_store_id, index_id, std::make_unique<IndexedDBKeyRange>(key_range),
-      key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE,
+      key_only ? indexed_db::CursorType::kKeyOnly
+               : indexed_db::CursorType::kKeyAndValue,
       max_count, std::move(aborting_callback)));
 }
 
@@ -398,8 +401,8 @@
   params->index_id = index_id;
   params->key_range = std::make_unique<IndexedDBKeyRange>(key_range);
   params->direction = direction;
-  params->cursor_type =
-      key_only ? indexed_db::CURSOR_KEY_ONLY : indexed_db::CURSOR_KEY_AND_VALUE;
+  params->cursor_type = key_only ? indexed_db::CursorType::kKeyOnly
+                                 : indexed_db::CursorType::kKeyAndValue;
   params->task_type = task_type;
   params->callback = std::move(aborting_callback);
   transaction->ScheduleTask(
diff --git a/content/browser/indexed_db/indexed_db.h b/content/browser/indexed_db/indexed_db.h
deleted file mode 100644
index f5078d3..0000000
--- a/content/browser/indexed_db/indexed_db.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2013 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_
-#define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_
-
-namespace content {
-
-namespace indexed_db {
-
-enum CursorType {
-  CURSOR_KEY_AND_VALUE = 0,
-  CURSOR_KEY_ONLY
-};
-
-}  // namespace indexed_db
-
-}  // namespace content
-
-#endif  // CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_H_
diff --git a/content/browser/indexed_db/indexed_db_backing_store.h b/content/browser/indexed_db/indexed_db_backing_store.h
index aa72cc5..558a2de 100644
--- a/content/browser/indexed_db/indexed_db_backing_store.h
+++ b/content/browser/indexed_db/indexed_db_backing_store.h
@@ -28,7 +28,6 @@
 #include "components/services/storage/indexed_db/locks/partitioned_lock.h"
 #include "components/services/storage/privileged/mojom/indexed_db_control_test.mojom.h"
 #include "components/services/storage/public/cpp/buckets/bucket_locator.h"
-#include "content/browser/indexed_db/indexed_db.h"
 #include "content/browser/indexed_db/indexed_db_external_object.h"
 #include "content/browser/indexed_db/indexed_db_external_object_storage.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
diff --git a/content/browser/indexed_db/indexed_db_bucket_context.cc b/content/browser/indexed_db/indexed_db_bucket_context.cc
index 8da6aee..3f9a7b4 100644
--- a/content/browser/indexed_db/indexed_db_bucket_context.cc
+++ b/content/browser/indexed_db/indexed_db_bucket_context.cc
@@ -4,63 +4,100 @@
 
 #include "content/browser/indexed_db/indexed_db_bucket_context.h"
 
+#include <inttypes.h>
+#include <stddef.h>
+#include <atomic>
+#include <compare>
 #include <list>
+#include <ostream>
+#include <set>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
-#include "base/command_line.h"
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/compiler_specific.h"
 #include "base/containers/contains.h"
-#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_base.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/no_destructor.h"
+#include "base/numerics/checked_math.h"
+#include "base/numerics/safe_conversions.h"
 #include "base/rand_util.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/synchronization/waitable_event.h"
+#include "base/system/sys_info.h"
+#include "base/task/sequenced_task_runner.h"
+#include "base/task/task_runner.h"
+#include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
 #include "base/timer/elapsed_timer.h"
-#include "base/trace_event/base_tracing.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/trace_event/memory_allocator_dump.h"
 #include "base/trace_event/memory_dump_manager.h"
 #include "base/trace_event/process_memory_dump.h"
 #include "base/uuid.h"
 #include "build/build_config.h"
+#include "components/services/storage/indexed_db/leveldb/leveldb_state.h"
+#include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
 #include "components/services/storage/indexed_db/scopes/leveldb_scopes.h"
 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.h"
 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_factory.h"
 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction.h"
 #include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h"
-#include "components/services/storage/privileged/mojom/indexed_db_control.mojom.h"
+#include "components/services/storage/privileged/mojom/indexed_db_internals_types.mojom-shared.h"
+#include "components/services/storage/privileged/mojom/indexed_db_internals_types.mojom.h"
+#include "components/services/storage/public/mojom/blob_storage_context.mojom-shared.h"
 #include "components/services/storage/public/mojom/blob_storage_context.mojom.h"
 #include "content/browser/indexed_db/features.h"
 #include "content/browser/indexed_db/file_path_util.h"
 #include "content/browser/indexed_db/file_stream_reader_to_data_pipe.h"
 #include "content/browser/indexed_db/indexed_db_active_blob_registry.h"
 #include "content/browser/indexed_db/indexed_db_backing_store.h"
-#include "content/browser/indexed_db/indexed_db_bucket_context.h"
 #include "content/browser/indexed_db/indexed_db_bucket_context_handle.h"
 #include "content/browser/indexed_db/indexed_db_compaction_task.h"
 #include "content/browser/indexed_db/indexed_db_connection.h"
-#include "content/browser/indexed_db/indexed_db_context_impl.h"
 #include "content/browser/indexed_db/indexed_db_data_format_version.h"
+#include "content/browser/indexed_db/indexed_db_data_loss_info.h"
 #include "content/browser/indexed_db/indexed_db_database.h"
 #include "content/browser/indexed_db/indexed_db_database_callbacks.h"
 #include "content/browser/indexed_db/indexed_db_database_error.h"
+#include "content/browser/indexed_db/indexed_db_external_object.h"
 #include "content/browser/indexed_db/indexed_db_factory_client.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_operations.h"
 #include "content/browser/indexed_db/indexed_db_pending_connection.h"
 #include "content/browser/indexed_db/indexed_db_pre_close_task_queue.h"
 #include "content/browser/indexed_db/indexed_db_reporting.h"
-#include "content/browser/indexed_db/indexed_db_task_helper.h"
 #include "content/browser/indexed_db/indexed_db_tombstone_sweeper.h"
 #include "content/browser/indexed_db/indexed_db_transaction.h"
+#include "content/browser/indexed_db/list_set.h"
 #include "content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h"
+#include "env_chromium.h"
+#include "mojo/public/cpp/base/big_buffer.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
+#include "mojo/public/cpp/system/data_pipe.h"
 #include "net/base/net_errors.h"
 #include "storage/browser/file_system/file_stream_reader.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
+#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_transfer_token.mojom.h"
+#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-shared.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
 #include "third_party/leveldatabase/leveldb_chrome.h"
 
diff --git a/content/browser/indexed_db/indexed_db_connection_coordinator.cc b/content/browser/indexed_db/indexed_db_connection_coordinator.cc
index 3ad2bda..dd06e8aa 100644
--- a/content/browser/indexed_db/indexed_db_connection_coordinator.cc
+++ b/content/browser/indexed_db/indexed_db_connection_coordinator.cc
@@ -4,33 +4,54 @@
 
 #include "content/browser/indexed_db/indexed_db_connection_coordinator.h"
 
+#include <atomic>
+#include <map>
 #include <set>
+#include <string>
 #include <tuple>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
 #include "base/auto_reset.h"
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
+#include "base/functional/callback_tags.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/metrics/histogram_base.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/notreached.h"
 #include "base/strings/string_number_conversions.h"
+#include "components/services/storage/indexed_db/locks/partitioned_lock.h"
 #include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
 #include "components/services/storage/indexed_db/scopes/leveldb_scope.h"
 #include "components/services/storage/indexed_db/scopes/leveldb_scopes.h"
 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.h"
 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_factory.h"
 #include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_transaction.h"
+#include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
 #include "content/browser/indexed_db/indexed_db_bucket_context.h"
+#include "content/browser/indexed_db/indexed_db_bucket_context_handle.h"
 #include "content/browser/indexed_db/indexed_db_callback_helpers.h"
+#include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_data_loss_info.h"
 #include "content/browser/indexed_db/indexed_db_database.h"
 #include "content/browser/indexed_db/indexed_db_database_callbacks.h"
 #include "content/browser/indexed_db/indexed_db_database_error.h"
 #include "content/browser/indexed_db/indexed_db_factory_client.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
+#include "content/browser/indexed_db/indexed_db_pending_connection.h"
 #include "content/browser/indexed_db/indexed_db_reporting.h"
+#include "content/browser/indexed_db/indexed_db_task_helper.h"
+#include "content/browser/indexed_db/indexed_db_transaction.h"
+#include "content/browser/indexed_db/list_set.h"
 #include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
-#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
+#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-shared.h"
 #include "third_party/leveldatabase/env_chromium.h"
 
 using base::NumberToString16;
diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc
index a578367..a64e482 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.cc
+++ b/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -4,48 +4,69 @@
 
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
 
+#include <algorithm>
+#include <compare>
+#include <functional>
 #include <memory>
+#include <ostream>
 #include <string>
-#include <string_view>
+#include <type_traits>
 #include <utility>
 #include <vector>
 
 #include "base/barrier_callback.h"
+#include "base/check.h"
 #include "base/check_op.h"
+#include "base/containers/contains.h"
+#include "base/files/file.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
+#include "base/location.h"
+#include "base/numerics/clamped_math.h"
 #include "base/ranges/algorithm.h"
-#include "base/strings/string_util.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/task/bind_post_task.h"
 #include "base/task/sequenced_task_runner.h"
+#include "base/task/task_runner.h"
+#include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
 #include "base/trace_event/base_tracing.h"
-#include "base/values.h"
+#include "base/trace_event/common/trace_event_common.h"
+#include "base/types/expected.h"
+#include "base/types/strong_alias.h"
+#include "build/build_config.h"
+#include "components/services/storage/privileged/mojom/indexed_db_control.mojom-shared.h"
 #include "components/services/storage/privileged/mojom/indexed_db_internals_types.mojom.h"
 #include "components/services/storage/public/cpp/buckets/bucket_info.h"
+#include "components/services/storage/public/cpp/buckets/bucket_init_params.h"
 #include "components/services/storage/public/cpp/buckets/bucket_locator.h"
-#include "components/services/storage/public/cpp/buckets/constants.h"
 #include "components/services/storage/public/cpp/constants.h"
 #include "components/services/storage/public/cpp/quota_error_or.h"
 #include "components/services/storage/public/mojom/quota_client.mojom.h"
+#include "components/services/storage/public/mojom/storage_policy_update.mojom.h"
 #include "content/browser/indexed_db/features.h"
 #include "content/browser/indexed_db/file_path_util.h"
 #include "content/browser/indexed_db/indexed_db_bucket_context.h"
+#include "content/browser/indexed_db/indexed_db_database_error.h"
 #include "content/browser/indexed_db/indexed_db_factory_client.h"
+#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
+#include "net/base/schemeful_site.h"
 #include "storage/browser/quota/quota_client_type.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
 #include "storage/common/database/database_identifier.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
+#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-shared.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
-#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
-#include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 #include "third_party/zlib/google/zip.h"
 #include "url/origin.h"
 
diff --git a/content/browser/indexed_db/indexed_db_control_wrapper.cc b/content/browser/indexed_db/indexed_db_control_wrapper.cc
index 8210250..27e0165 100644
--- a/content/browser/indexed_db/indexed_db_control_wrapper.cc
+++ b/content/browser/indexed_db/indexed_db_control_wrapper.cc
@@ -4,7 +4,16 @@
 
 #include "content/browser/indexed_db/indexed_db_control_wrapper.h"
 
-#include "base/task/sequenced_task_runner.h"
+#include <ostream>
+#include <utility>
+
+#include "base/check.h"
+#include "base/functional/bind.h"
+#include "components/services/storage/public/cpp/buckets/bucket_locator.h"
+#include "components/services/storage/public/mojom/storage_policy_update.mojom.h"
+#include "content/browser/indexed_db/indexed_db_context_impl.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 
 namespace content {
diff --git a/content/browser/indexed_db/indexed_db_cursor.cc b/content/browser/indexed_db/indexed_db_cursor.cc
index ec0e152e..0d3a035f 100644
--- a/content/browser/indexed_db/indexed_db_cursor.cc
+++ b/content/browser/indexed_db/indexed_db_cursor.cc
@@ -304,10 +304,10 @@
     found_primary_keys.push_back(cursor_->primary_key());
 
     switch (cursor_type_) {
-      case indexed_db::CURSOR_KEY_ONLY:
+      case indexed_db::CursorType::kKeyOnly:
         found_values.push_back(IndexedDBValue());
         break;
-      case indexed_db::CURSOR_KEY_AND_VALUE: {
+      case indexed_db::CursorType::kKeyAndValue: {
         IndexedDBValue value;
         value.swap(*cursor_->value());
         size_estimate += value.SizeEstimate();
diff --git a/content/browser/indexed_db/indexed_db_cursor.h b/content/browser/indexed_db/indexed_db_cursor.h
index 3a7c803..0e22fa2 100644
--- a/content/browser/indexed_db/indexed_db_cursor.h
+++ b/content/browser/indexed_db/indexed_db_cursor.h
@@ -24,6 +24,10 @@
 
 namespace content {
 
+namespace indexed_db {
+enum class CursorType { kKeyAndValue = 0, kKeyOnly = 1 };
+}  // namespace indexed_db
+
 class IndexedDBCursor : public blink::mojom::IDBCursor {
  public:
   // Creates a new self-owned instance and binds to `pending_remote`.
@@ -54,8 +58,9 @@
     return cursor_->primary_key();
   }
   IndexedDBValue* Value() const {
-    return (cursor_type_ == indexed_db::CURSOR_KEY_ONLY) ? nullptr
-                                                         : cursor_->value();
+    return (cursor_type_ == indexed_db::CursorType::kKeyOnly)
+               ? nullptr
+               : cursor_->value();
   }
 
   void Close();
diff --git a/content/browser/indexed_db/indexed_db_database.cc b/content/browser/indexed_db/indexed_db_database.cc
index 689dab8..d4828f7 100644
--- a/content/browser/indexed_db/indexed_db_database.cc
+++ b/content/browser/indexed_db/indexed_db_database.cc
@@ -744,7 +744,7 @@
   } else {
     if (index_id == IndexedDBIndexMetadata::kInvalidId) {
       // ObjectStore Retrieval Operation
-      if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+      if (cursor_type == indexed_db::CursorType::kKeyOnly) {
         backing_store_cursor = backing_store()->OpenObjectStoreKeyCursor(
             transaction->BackingStoreTransaction(), id(), object_store_id,
             *key_range, blink::mojom::IDBCursorDirection::Next, &s);
@@ -753,7 +753,7 @@
             transaction->BackingStoreTransaction(), id(), object_store_id,
             *key_range, blink::mojom::IDBCursorDirection::Next, &s);
       }
-    } else if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+    } else if (cursor_type == indexed_db::CursorType::kKeyOnly) {
       // Index Value Retrieval Operation
       backing_store_cursor = backing_store()->OpenIndexKeyCursor(
           transaction->BackingStoreTransaction(), id(), object_store_id,
@@ -802,7 +802,7 @@
       return s;
     }
 
-    if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+    if (cursor_type == indexed_db::CursorType::kKeyOnly) {
       std::move(callback).Run(
           blink::mojom::IDBDatabaseGetResult::NewKey(std::move(*key)));
       return s;
@@ -839,7 +839,7 @@
     std::move(callback).Run(blink::mojom::IDBDatabaseGetResult::NewEmpty(true));
     return s;
   }
-  if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+  if (cursor_type == indexed_db::CursorType::kKeyOnly) {
     // Index Value Retrieval Operation
     std::move(callback).Run(
         blink::mojom::IDBDatabaseGetResult::NewKey(std::move(*primary_key)));
@@ -911,7 +911,7 @@
   Status s = Status::OK();
   std::unique_ptr<IndexedDBBackingStore::Cursor> cursor;
 
-  if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+  if (cursor_type == indexed_db::CursorType::kKeyOnly) {
     // Retrieving keys
     if (index_id == IndexedDBIndexMetadata::kInvalidId) {
       // Object Store: Key Retrieval Operation
@@ -990,7 +990,7 @@
     IndexedDBReturnValue return_value;
     IndexedDBKey return_key;
 
-    if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+    if (cursor_type == indexed_db::CursorType::kKeyOnly) {
       return_key = cursor->primary_key();
     } else {
       // Retrieving values
@@ -1001,13 +1001,14 @@
       }
     }
 
-    if (cursor_type == indexed_db::CURSOR_KEY_ONLY)
+    if (cursor_type == indexed_db::CursorType::kKeyOnly) {
       found_keys.push_back(return_key);
-    else
+    } else {
       found_values.push_back(return_value);
+    }
 
     // Periodically stream values and keys if we have too many.
-    if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+    if (cursor_type == indexed_db::CursorType::kKeyOnly) {
       if (found_keys.size() >= max_values_before_sending) {
         result_sink->ReceiveKeys(std::move(found_keys));
         found_keys.clear();
@@ -1021,7 +1022,7 @@
     }
   }
 
-  if (cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+  if (cursor_type == indexed_db::CursorType::kKeyOnly) {
     if (!found_keys.empty()) {
       result_sink->ReceiveKeys(std::move(found_keys));
     }
@@ -1249,7 +1250,7 @@
   Status s;
   std::unique_ptr<IndexedDBBackingStore::Cursor> backing_store_cursor;
   if (params->index_id == IndexedDBIndexMetadata::kInvalidId) {
-    if (params->cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+    if (params->cursor_type == indexed_db::CursorType::kKeyOnly) {
       DCHECK_EQ(params->task_type, blink::mojom::IDBTaskType::Normal);
       backing_store_cursor = backing_store()->OpenObjectStoreKeyCursor(
           transaction->BackingStoreTransaction(), id(), params->object_store_id,
@@ -1261,7 +1262,7 @@
     }
   } else {
     DCHECK_EQ(params->task_type, blink::mojom::IDBTaskType::Normal);
-    if (params->cursor_type == indexed_db::CURSOR_KEY_ONLY) {
+    if (params->cursor_type == indexed_db::CursorType::kKeyOnly) {
       backing_store_cursor = backing_store()->OpenIndexKeyCursor(
           transaction->BackingStoreTransaction(), id(), params->object_store_id,
           params->index_id, *params->key_range, params->direction, &s);
diff --git a/content/browser/indexed_db/indexed_db_database.h b/content/browser/indexed_db/indexed_db_database.h
index 137fa4b9a..1f08d8a 100644
--- a/content/browser/indexed_db/indexed_db_database.h
+++ b/content/browser/indexed_db/indexed_db_database.h
@@ -24,7 +24,6 @@
 #include "base/memory/weak_ptr.h"
 #include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
 #include "components/services/storage/public/cpp/buckets/bucket_locator.h"
-#include "content/browser/indexed_db/indexed_db.h"
 #include "content/browser/indexed_db/indexed_db_backing_store.h"
 #include "content/browser/indexed_db/indexed_db_connection_coordinator.h"
 #include "content/browser/indexed_db/indexed_db_factory_client.h"
@@ -51,6 +50,10 @@
 class IndexedDBTransaction;
 struct IndexedDBValue;
 
+namespace indexed_db {
+enum class CursorType;
+}
+
 class CONTENT_EXPORT IndexedDBDatabase {
  public:
   // Identifier is pair of (bucket_locator, database name).
diff --git a/content/browser/indexed_db/indexed_db_database_unittest.cc b/content/browser/indexed_db/indexed_db_database_unittest.cc
index 2c743f0..266fc44 100644
--- a/content/browser/indexed_db/indexed_db_database_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_database_unittest.cc
@@ -25,7 +25,6 @@
 #include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
 #include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom.h"
 #include "components/services/storage/public/cpp/buckets/bucket_locator.h"
-#include "content/browser/indexed_db/indexed_db.h"
 #include "content/browser/indexed_db/indexed_db_backing_store.h"
 #include "content/browser/indexed_db/indexed_db_bucket_context.h"
 #include "content/browser/indexed_db/indexed_db_connection.h"
diff --git a/content/browser/indexed_db/indexed_db_factory_client.cc b/content/browser/indexed_db/indexed_db_factory_client.cc
index 73f01563..19821c6 100644
--- a/content/browser/indexed_db/indexed_db_factory_client.cc
+++ b/content/browser/indexed_db/indexed_db_factory_client.cc
@@ -4,26 +4,22 @@
 
 #include "content/browser/indexed_db/indexed_db_factory_client.h"
 
-#include <stddef.h>
-
-#include <algorithm>
+#include <forward_list>
 #include <memory>
+#include <ostream>
 #include <utility>
 
+#include "base/check.h"
+#include "base/check_op.h"
 #include "base/functional/bind.h"
+#include "base/location.h"
 #include "base/task/sequenced_task_runner.h"
-#include "base/time/time.h"
 #include "content/browser/indexed_db/database_impl.h"
 #include "content/browser/indexed_db/indexed_db_connection.h"
-#include "content/browser/indexed_db/indexed_db_context_impl.h"
-#include "content/browser/indexed_db/indexed_db_cursor.h"
+#include "content/browser/indexed_db/indexed_db_data_loss_info.h"
 #include "content/browser/indexed_db/indexed_db_database_error.h"
-#include "content/browser/indexed_db/indexed_db_return_value.h"
-#include "content/browser/indexed_db/indexed_db_transaction.h"
-#include "content/browser/indexed_db/indexed_db_value.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
-#include "storage/browser/quota/quota_manager.h"
-#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
+#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-shared.h"
 
 using blink::IndexedDBDatabaseMetadata;
 using blink::IndexedDBKey;
diff --git a/content/browser/indexed_db/indexed_db_unittest.cc b/content/browser/indexed_db/indexed_db_unittest.cc
index b62441b..e9dce0f 100644
--- a/content/browser/indexed_db/indexed_db_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_unittest.cc
@@ -2,50 +2,89 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <stdint.h>
+#include <inttypes.h>
+#include <map>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <set>
+#include <string>
+#include <tuple>
+#include <type_traits>
 #include <utility>
+#include <vector>
 
+#include "base/auto_reset.h"
 #include "base/barrier_closure.h"
+#include "base/check.h"
+#include "base/containers/contains.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
-#include "base/memory/raw_ptr.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
+#include "base/location.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/numerics/clamped_math.h"
 #include "base/run_loop.h"
 #include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
-#include "base/test/bind.h"
 #include "base/test/gmock_callback_support.h"
 #include "base/test/mock_callback.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/test/test_future.h"
-#include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
-#include "components/services/storage/indexed_db/transactional_leveldb/transactional_leveldb_database.h"
-#include "components/services/storage/privileged/mojom/indexed_db_control.mojom-test-utils.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "base/unguessable_token.h"
+#include "build/build_config.h"
+#include "components/services/storage/indexed_db/locks/partitioned_lock_id.h"
+#include "components/services/storage/privileged/mojom/indexed_db_control.mojom.h"
+#include "components/services/storage/public/cpp/buckets/bucket_id.h"
+#include "components/services/storage/public/cpp/buckets/bucket_info.h"
+#include "components/services/storage/public/cpp/buckets/bucket_init_params.h"
 #include "components/services/storage/public/cpp/buckets/bucket_locator.h"
+#include "components/services/storage/public/cpp/buckets/constants.h"
+#include "components/services/storage/public/cpp/quota_error_or.h"
+#include "components/services/storage/public/mojom/storage_policy_update.mojom.h"
 #include "content/browser/indexed_db/features.h"
+#include "content/browser/indexed_db/indexed_db_backing_store.h"
 #include "content/browser/indexed_db/indexed_db_bucket_context.h"
-#include "content/browser/indexed_db/indexed_db_connection.h"
+#include "content/browser/indexed_db/indexed_db_bucket_context_handle.h"
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
 #include "content/browser/indexed_db/indexed_db_data_format_version.h"
 #include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
 #include "content/browser/indexed_db/indexed_db_pre_close_task_queue.h"
-#include "content/browser/indexed_db/mock_indexed_db_factory_client.h"
 #include "content/browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h"
 #include "content/browser/indexed_db/mock_mojo_indexed_db_factory_client.h"
+#include "env_chromium.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/struct_ptr.h"
 #include "net/base/features.h"
 #include "net/base/schemeful_site.h"
-#include "storage/browser/quota/quota_manager.h"
-#include "storage/browser/quota/special_storage_policy.h"
+#include "storage/browser/test/mock_quota_manager.h"
 #include "storage/browser/test/mock_quota_manager_proxy.h"
 #include "storage/browser/test/mock_special_storage_policy.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/indexeddb/indexeddb_key.h"
+#include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
+#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
-#include "third_party/leveldatabase/src/include/leveldb/env.h"
+#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
+#include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
+#include "third_party/blink/public/mojom/storage_key/ancestor_chain_bit.mojom-shared.h"
+#include "url/gurl.h"
+#include "url/origin.h"
 
 using base::test::RunClosure;
 using blink::IndexedDBDatabaseMetadata;
diff --git a/content/browser/interest_group/auction_runner.cc b/content/browser/interest_group/auction_runner.cc
index 44e3d0c..5b6aa78 100644
--- a/content/browser/interest_group/auction_runner.cc
+++ b/content/browser/interest_group/auction_runner.cc
@@ -650,7 +650,7 @@
                       update_owners.end());
 
   // Filter owners not allowed to update.
-  base::EraseIf(update_owners, [this](const url::Origin& owner) {
+  std::erase_if(update_owners, [this](const url::Origin& owner) {
     return !is_interest_group_api_allowed_callback_.Run(
         ContentBrowserClient::InterestGroupApiOperation::kUpdate, owner);
   });
diff --git a/content/browser/interest_group/bidding_and_auction_serializer.cc b/content/browser/interest_group/bidding_and_auction_serializer.cc
index 73e7668..17bb44c 100644
--- a/content/browser/interest_group/bidding_and_auction_serializer.cc
+++ b/content/browser/interest_group/bidding_and_auction_serializer.cc
@@ -8,7 +8,6 @@
 #include <vector>
 
 #include "base/command_line.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/feature_list.h"
 #include "base/json/json_string_value_serializer.h"
 #include "base/metrics/histogram_functions.h"
@@ -185,7 +184,7 @@
     scoped_refptr<StorageInterestGroups> groups) {
   std::vector<SingleStorageInterestGroup> groups_to_add =
       groups->GetInterestGroups();
-  base::EraseIf(groups_to_add, [](const SingleStorageInterestGroup& group) {
+  std::erase_if(groups_to_add, [](const SingleStorageInterestGroup& group) {
     return (!group->interest_group.ads) ||
            (group->interest_group.ads->size() == 0);
   });
diff --git a/content/browser/interest_group/interest_group_auction.cc b/content/browser/interest_group/interest_group_auction.cc
index 8fd265c..b5c8ae8e 100644
--- a/content/browser/interest_group/interest_group_auction.cc
+++ b/content/browser/interest_group/interest_group_auction.cc
@@ -3891,14 +3891,14 @@
   }
 
   // Ignore interest groups with no bidding script or no ads.
-  base::EraseIf(interest_groups, [](const SingleStorageInterestGroup& bidder) {
+  std::erase_if(interest_groups, [](const SingleStorageInterestGroup& bidder) {
     return !bidder->interest_group.bidding_url || !bidder->interest_group.ads ||
            bidder->interest_group.ads->empty();
   });
 
   // Ignore interest groups that don't provide the requested seller
   // capabilities.
-  base::EraseIf(interest_groups,
+  std::erase_if(interest_groups,
                 [this](const SingleStorageInterestGroup& bidder) {
                   return !GroupSatisfiesAllCapabilities(
                       bidder->interest_group,
diff --git a/content/browser/interest_group/interest_group_auction_reporter.cc b/content/browser/interest_group/interest_group_auction_reporter.cc
index 99b05c5..a340b7c 100644
--- a/content/browser/interest_group/interest_group_auction_reporter.cc
+++ b/content/browser/interest_group/interest_group_auction_reporter.cc
@@ -16,7 +16,6 @@
 #include <utility>
 #include <vector>
 
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/containers/flat_map.h"
 #include "base/feature_list.h"
 #include "base/functional/callback.h"
@@ -1146,7 +1145,7 @@
 
 void InterestGroupAuctionReporter::EnforceAttestationsReportUrls(
     std::vector<GURL>& urls) {
-  base::EraseIf(urls, [this](const GURL& url) { return !CheckReportUrl(url); });
+  std::erase_if(urls, [this](const GURL& url) { return !CheckReportUrl(url); });
 }
 
 }  // namespace content
diff --git a/content/browser/interest_group/interest_group_features.cc b/content/browser/interest_group/interest_group_features.cc
index 4cf49df..4f24fce 100644
--- a/content/browser/interest_group/interest_group_features.cc
+++ b/content/browser/interest_group/interest_group_features.cc
@@ -25,7 +25,7 @@
 // Enable updating userBiddingSignals when updating a user's interests groups.
 BASE_FEATURE(kEnableUpdatingUserBiddingSignals,
              "EnableUpdatingUserBiddingSignals",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enable write ahead logging for interest group storage.
 BASE_FEATURE(kFledgeEnableWALForInterestGroupStorage,
diff --git a/content/browser/preloading/prefetch/contamination_delay_browsertest.cc b/content/browser/preloading/prefetch/contamination_delay_browsertest.cc
index e12e349..82cb64f 100644
--- a/content/browser/preloading/prefetch/contamination_delay_browsertest.cc
+++ b/content/browser/preloading/prefetch/contamination_delay_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/strings/string_number_conversions.h"
 #include "base/test/run_until.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
@@ -31,9 +32,20 @@
 class ContaminationDelayBrowserTest : public ContentBrowserTest {
  protected:
   ContaminationDelayBrowserTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {features::kPrefetchStateContaminationMitigation,
-         features::kPrefetchRedirects},
+    scoped_feature_list_.InitWithFeaturesAndParameters(
+        {{features::kPrefetchStateContaminationMitigation, {}},
+         {features::kPrefetchRedirects, {}},
+         // This is needed specifically for CrOS MSAN, where we apply a 10x
+         // multiplier to all test timeouts, which happens to be enough to push
+         // the response delay in this test (which is scaled in that way to
+         // match the slowdown of everything else) over the default prefetch
+         // timeout. To be resilient also to changes in that value, it is
+         // expressly overridden here to be a timeout that is much longer and
+         // scales with the timeout multiplier.
+         {features::kPrefetchUseContentRefactor,
+          {{"prefetch_timeout_ms",
+            base::NumberToString(
+                TestTimeouts::action_max_timeout().InMilliseconds())}}}},
         {});
   }
 
@@ -70,7 +82,10 @@
     ASSERT_TRUE(base::test::RunUntil([&] {
       return prefetch_document_manager->GetReferringPageMetrics()
                  .prefetch_successful_count >= 1;
-    })) << "timed out waiting for prefetch to complete";
+    })) << "timed out waiting for prefetch to complete ("
+        << prefetch_document_manager->GetReferringPageMetrics()
+               .prefetch_attempted_count
+        << " attempted)";
   }
 
  private:
@@ -113,13 +128,7 @@
   EXPECT_GE(timer.Elapsed(), response_delay());
 }
 
-// TODO(crbug.com/325359478): Fix and re-enable for MSAN.
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_IgnoresSameOrigin DISABLED_IgnoresSameOrigin
-#else
-#define MAYBE_IgnoresSameOrigin IgnoresSameOrigin
-#endif
-IN_PROC_BROWSER_TEST_F(ContaminationDelayBrowserTest, MAYBE_IgnoresSameOrigin) {
+IN_PROC_BROWSER_TEST_F(ContaminationDelayBrowserTest, IgnoresSameOrigin) {
   GURL referrer_url =
       embedded_test_server()->GetURL("referrer.localhost", "/title1.html");
   GURL prefetch_url =
@@ -132,13 +141,7 @@
   EXPECT_LT(timer.Elapsed(), response_delay());
 }
 
-// TODO(crbug.com/325359478): Fix and re-enable for MSAN.
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_IgnoresSameSite DISABLED_IgnoresSameSite
-#else
-#define MAYBE_IgnoresSameSite IgnoresSameSite
-#endif
-IN_PROC_BROWSER_TEST_F(ContaminationDelayBrowserTest, MAYBE_IgnoresSameSite) {
+IN_PROC_BROWSER_TEST_F(ContaminationDelayBrowserTest, IgnoresSameSite) {
   GURL referrer_url =
       embedded_test_server()->GetURL("referrer.localhost", "/title1.html");
   GURL prefetch_url =
@@ -151,13 +154,7 @@
   EXPECT_LT(timer.Elapsed(), response_delay());
 }
 
-// TODO(crbug.com/325359478): Fix and re-enable for MSAN.
-#if defined(MEMORY_SANITIZER)
-#define MAYBE_IgnoresIfExempt DISABLED_IgnoresIfExempt
-#else
-#define MAYBE_IgnoresIfExempt IgnoresIfExempt
-#endif
-IN_PROC_BROWSER_TEST_F(ContaminationDelayBrowserTest, MAYBE_IgnoresIfExempt) {
+IN_PROC_BROWSER_TEST_F(ContaminationDelayBrowserTest, IgnoresIfExempt) {
   GURL referrer_url =
       embedded_test_server()->GetURL("referrer.localhost", "/title1.html");
   GURL prefetch_url =
diff --git a/content/browser/preloading/prefetch/prefetch_container.cc b/content/browser/preloading/prefetch/prefetch_container.cc
index bdef936..43872b3d 100644
--- a/content/browser/preloading/prefetch/prefetch_container.cc
+++ b/content/browser/preloading/prefetch/prefetch_container.cc
@@ -703,7 +703,7 @@
 
   // To avoid spurious reordering, don't remove headers that will be updated
   // anyway.
-  base::EraseIf(headers_to_remove, [&](const std::string& header) {
+  std::erase_if(headers_to_remove, [&](const std::string& header) {
     return updated_headers.HasHeader(header);
   });
 
diff --git a/content/browser/preloading/prefetch/prefetch_document_manager.cc b/content/browser/preloading/prefetch/prefetch_document_manager.cc
index f6a19925..f485e64 100644
--- a/content/browser/preloading/prefetch/prefetch_document_manager.cc
+++ b/content/browser/preloading/prefetch/prefetch_document_manager.cc
@@ -10,7 +10,6 @@
 #include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "content/browser/browser_context_impl.h"
 #include "content/browser/preloading/prefetch/prefetch_container.h"
 #include "content/browser/preloading/prefetch/prefetch_params.h"
@@ -244,7 +243,7 @@
         return true;
       };
 
-  base::EraseIf(candidates, should_process_entry);
+  std::erase_if(candidates, should_process_entry);
 
   for (auto& [prefetch_url, prefetch_type, referrer, no_vary_search_expected] :
        prefetches) {
diff --git a/content/browser/preloading/prefetch/prefetch_service.cc b/content/browser/preloading/prefetch/prefetch_service.cc
index bfd0f277..ef62778 100644
--- a/content/browser/preloading/prefetch/prefetch_service.cc
+++ b/content/browser/preloading/prefetch/prefetch_service.cc
@@ -7,10 +7,10 @@
 #include <memory>
 #include <optional>
 #include <utility>
+#include <vector>
 
 #include "base/auto_reset.h"
 #include "base/barrier_closure.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/containers/fixed_flat_set.h"
 #include "base/feature_list.h"
 #include "base/location.h"
@@ -1426,7 +1426,7 @@
     prefetch_container->UpdateServingPageMetrics();
   }
 
-  base::EraseIf(matches, [](const auto* prefetch_container) {
+  std::erase_if(matches, [](const auto* prefetch_container) {
     if (prefetch_container->HasPrefetchBeenConsideredToServe()) {
       DVLOG(1) << "PrefetchService::FindPrefetchContainerToServe: skipped "
                << "because already considered to serve: "
diff --git a/content/browser/preloading/preloading_decider.cc b/content/browser/preloading/preloading_decider.cc
index 599a272..f27c9bf 100644
--- a/content/browser/preloading/preloading_decider.cc
+++ b/content/browser/preloading/preloading_decider.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/preloading/preloading_decider.h"
 
+#include <vector>
+
 #include "base/check_op.h"
 #include "base/containers/enum_set.h"
 #include "base/feature_list.h"
@@ -370,7 +372,7 @@
   // The candidates remaining after this call will be all eager candidates,
   // and all non-eager candidates whose (url, action) pair has already been
   // processed.
-  base::EraseIf(candidates, should_mark_as_on_standby);
+  std::erase_if(candidates, should_mark_as_on_standby);
 
   prefetcher_.ProcessCandidatesForPrefetch(candidates);
 
diff --git a/content/browser/preloading/prerenderer_impl.cc b/content/browser/preloading/prerenderer_impl.cc
index ad43cb2..692ac9b8 100644
--- a/content/browser/preloading/prerenderer_impl.cc
+++ b/content/browser/preloading/prerenderer_impl.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/preloading/prerenderer_impl.h"
 
+#include <vector>
+
 #include "content/browser/preloading/preloading.h"
 #include "content/browser/preloading/preloading_attempt_impl.h"
 #include "content/browser/preloading/preloading_trigger_type_impl.h"
@@ -175,7 +177,7 @@
     // requests rejected by PrerenderHostRegistry can be filtered out. But
     // ideally PrerenderHostRegistry should implement the history management
     // mechanism by itself.
-    base::EraseIf(started_prerenders_, [&](const PrerenderInfo& x) {
+    std::erase_if(started_prerenders_, [&](const PrerenderInfo& x) {
       return base::Contains(removed_prerender_rules_set, x.prerender_host_id);
     });
   }
diff --git a/content/browser/renderer_host/media/media_devices_manager.cc b/content/browser/renderer_host/media/media_devices_manager.cc
index 025f457..0451462 100644
--- a/content/browser/renderer_host/media/media_devices_manager.cc
+++ b/content/browser/renderer_host/media/media_devices_manager.cc
@@ -10,10 +10,10 @@
 #include <functional>
 #include <map>
 #include <string>
+#include <vector>
 
 #include "base/command_line.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
 #include "base/metrics/histogram_functions.h"
@@ -715,7 +715,7 @@
   video_capture_manager_->GetDeviceSupportedFormats(device_id, &formats);
   ReplaceInvalidFrameRatesWithFallback(&formats);
   // Remove formats that have zero resolution.
-  base::EraseIf(formats, [](const media::VideoCaptureFormat& format) {
+  std::erase_if(formats, [](const media::VideoCaptureFormat& format) {
     return format.frame_size.GetArea() <= 0;
   });
 
@@ -1177,7 +1177,7 @@
                    false /* ignore_group_id */);
   }
 
-  base::EraseIf(requests_, [this](EnumerationRequest& request) {
+  std::erase_if(requests_, [this](EnumerationRequest& request) {
     if (IsEnumerationRequestReady(request)) {
       std::move(request.callback).Run(current_snapshot_);
       return true;
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 68dee94..d77da7d 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -12,11 +12,11 @@
 #include <tuple>
 #include <unordered_map>
 #include <utility>
+#include <vector>
 
 #include "base/check.h"
 #include "base/command_line.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/debug/crash_logging.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
@@ -6674,7 +6674,7 @@
 void RenderFrameHostImpl::RemoveDocumentService(
     internal::DocumentServiceBase* document_service,
     base::PassKey<internal::DocumentServiceBase>) {
-  base::Erase(document_associated_data_->services(), document_service);
+  std::erase(document_associated_data_->services(), document_service);
 }
 
 FrameTreeNode* RenderFrameHostImpl::FindAndVerifyChild(
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 1f9bdd1..fb8f39a 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -18,7 +18,6 @@
 #include "base/check.h"
 #include "base/command_line.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/debug/alias.h"
 #include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
@@ -1872,7 +1871,7 @@
 
 void RenderWidgetHostImpl::RemoveKeyPressEventCallback(
     const KeyPressEventCallback& callback) {
-  base::Erase(key_press_event_callbacks_, callback);
+  std::erase(key_press_event_callbacks_, callback);
 }
 
 void RenderWidgetHostImpl::AddMouseEventCallback(
@@ -1883,7 +1882,7 @@
 
 void RenderWidgetHostImpl::RemoveMouseEventCallback(
     const MouseEventCallback& callback) {
-  base::Erase(mouse_event_callbacks_, callback);
+  std::erase(mouse_event_callbacks_, callback);
 }
 
 void RenderWidgetHostImpl::AddSuppressShowingImeCallback(
@@ -1894,7 +1893,7 @@
 
 void RenderWidgetHostImpl::RemoveSuppressShowingImeCallback(
     const SuppressShowingImeCallback& callback) {
-  base::Erase(suppress_showing_ime_callbacks_, callback);
+  std::erase(suppress_showing_ime_callbacks_, callback);
 }
 
 void RenderWidgetHostImpl::AddInputEventObserver(
diff --git a/content/browser/usb/web_usb_service_impl.cc b/content/browser/usb/web_usb_service_impl.cc
index 7d11c1ea..e47579b 100644
--- a/content/browser/usb/web_usb_service_impl.cc
+++ b/content/browser/usb/web_usb_service_impl.cc
@@ -6,9 +6,9 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
@@ -400,7 +400,7 @@
   // permission.
   auto* delegate = GetContentClient()->browser()->GetUsbDelegate();
   auto* browser_context = GetBrowserContext();
-  base::EraseIf(device_clients_, [=](const auto& client) {
+  std::erase_if(device_clients_, [=](const auto& client) {
     auto* device_info =
         delegate->GetDeviceInfo(browser_context, client->device_guid());
     if (!device_info)
@@ -423,7 +423,7 @@
 
 void WebUsbServiceImpl::OnDeviceRemoved(
     const device::mojom::UsbDeviceInfo& device_info) {
-  base::EraseIf(device_clients_, [&device_info](const auto& client) {
+  std::erase_if(device_clients_, [&device_info](const auto& client) {
     return device_info.guid == client->device_guid();
   });
 
@@ -488,7 +488,7 @@
 }
 
 void WebUsbServiceImpl::RemoveDeviceClient(const UsbDeviceClient* client) {
-  base::EraseIf(device_clients_, [client](const auto& this_client) {
+  std::erase_if(device_clients_, [client](const auto& this_client) {
     return client == this_client.get();
   });
 }
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 806cb60..944c8247a 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -5,10 +5,10 @@
 #include "content/browser/webid/federated_auth_request_impl.h"
 
 #include <random>
+#include <vector>
 
 #include "base/command_line.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/callback.h"
 #include "base/json/json_writer.h"
 #include "base/metrics/histogram_macros.h"
@@ -329,16 +329,6 @@
                                     /*for_display=*/true);
 }
 
-bool ShouldSuppressIdpSigninFailureDialog(
-    std::optional<TokenStatus> token_status) {
-  if (!token_status) {
-    return false;
-  }
-
-  return token_status == TokenStatus::kAborted ||
-         token_status == TokenStatus::kUnhandledRequest;
-}
-
 FederatedAuthRequestPageData* GetPageData(RenderFrameHost* render_frame_host) {
   return FederatedAuthRequestPageData::GetOrCreateForPage(
       render_frame_host->GetPage());
@@ -369,7 +359,7 @@
   auto filter = [&login_hint](const IdentityRequestAccount& account) {
     return !base::Contains(account.login_hints, login_hint);
   };
-  base::EraseIf(accounts, filter);
+  std::erase_if(accounts, filter);
   FedCmMetrics::NumAccounts num_matching = ComputeNumMatchingAccounts(accounts);
   base::UmaHistogramEnumeration("Blink.FedCm.LoginHint.NumMatchingAccounts",
                                 num_matching);
@@ -386,12 +376,12 @@
     auto filter = [](const IdentityRequestAccount& account) {
       return account.domain_hints.empty();
     };
-    base::EraseIf(accounts, filter);
+    std::erase_if(accounts, filter);
   } else {
     auto filter = [&domain_hint](const IdentityRequestAccount& account) {
       return !base::Contains(account.domain_hints, domain_hint);
     };
-    base::EraseIf(accounts, filter);
+    std::erase_if(accounts, filter);
   }
   FedCmMetrics::NumAccounts num_matching = ComputeNumMatchingAccounts(accounts);
   base::UmaHistogramEnumeration("Blink.FedCm.DomainHint.NumMatchingAccounts",
@@ -752,18 +742,41 @@
   }
 
   if (HasPendingRequest()) {
-    fedcm_metrics_->RecordRequestTokenStatus(TokenStatus::kTooManyRequests,
-                                             requirement);
+    RpMode pending_request_rp_mode = GetPageData(&render_frame_host())
+                                         ->PendingWebIdentityRequest()
+                                         ->GetRpMode();
+    bool can_replace_pending_request =
+        IsFedCmButtonModeEnabled() &&
+        idp_get_params_ptrs[0]->mode == RpMode::kButton &&
+        render_frame_host().HasTransientUserActivation() &&
+        pending_request_rp_mode != RpMode::kButton;
 
-    AddDevToolsIssue(
-        blink::mojom::FederatedAuthRequestResult::kErrorTooManyRequests);
-    AddConsoleErrorMessage(
-        blink::mojom::FederatedAuthRequestResult::kErrorTooManyRequests);
+    // TODO(crbug.com/326587232): We should add metrics to capture
+    // 1. how often a button flow is triggered while a widget flow is pending
+    // 2. how often a button flow is triggered while a button flow is pending
+    // 3. how often a widget flow is triggered while a button flow is pending
+    if (!can_replace_pending_request) {
+      fedcm_metrics_->RecordRequestTokenStatus(TokenStatus::kTooManyRequests,
+                                               requirement);
 
-    std::move(callback).Run(RequestTokenStatus::kErrorTooManyRequests,
-                            std::nullopt, "", /*error=*/nullptr,
-                            /*is_auto_selected=*/false);
-    return;
+      AddDevToolsIssue(
+          blink::mojom::FederatedAuthRequestResult::kErrorTooManyRequests);
+      AddConsoleErrorMessage(
+          blink::mojom::FederatedAuthRequestResult::kErrorTooManyRequests);
+
+      std::move(callback).Run(RequestTokenStatus::kErrorTooManyRequests,
+                              std::nullopt, "", /*error=*/nullptr,
+                              /*is_auto_selected=*/false);
+      return;
+    }
+    // Cancel the pending request before starting the new button flow request.
+    // TODO(crbug.com/326587232): Use specific error type.
+    GetPageData(&render_frame_host())
+        ->PendingWebIdentityRequest()
+        ->CompleteRequestWithError(FederatedAuthRequestResult::kError,
+                                   /*token_status=*/std::nullopt,
+                                   /*token_error=*/std::nullopt,
+                                   /*should_delay_callback=*/false);
   }
 
   bool intercept = false;
@@ -792,6 +805,8 @@
                                /*should_delay_callback=*/false);
       return;
     }
+  } else {
+    rp_mode_ = RpMode::kWidget;
   }
 
   FederatedApiPermissionStatus permission_status = GetApiPermissionStatus();
@@ -2358,19 +2373,6 @@
     return;
   }
 
-  if (result != FederatedAuthRequestResult::kSuccess &&
-      fetch_data_.for_idp_signin &&
-      !ShouldSuppressIdpSigninFailureDialog(token_status)) {
-    fetch_data_ = FetchData();
-
-    request_dialog_controller_->ShowIdpSigninFailureDialog(base::BindOnce(
-        &FederatedAuthRequestImpl::CompleteRequest,
-        weak_ptr_factory_.GetWeakPtr(), result, std::move(token_status),
-        std::move(token_error), selected_idp_config_url, id_token,
-        should_delay_callback));
-    return;
-  }
-
   if (token_status) {
     fedcm_metrics_->RecordRequestTokenStatus(*token_status,
                                              mediation_requirement_);
@@ -2469,6 +2471,7 @@
   token_error_ = std::nullopt;
   dialog_type_ = kNone;
   identity_selection_type_ = kExplicit;
+  rp_mode_ = RpMode::kWidget;
 }
 
 void FederatedAuthRequestImpl::AddDevToolsIssue(
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 6c6ff88..bddca4e 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -388,6 +388,8 @@
       std::optional<IdpNetworkRequestManager::FedCmErrorUrlType>
           error_url_type);
 
+  RpMode GetRpMode() const { return rp_mode_; }
+
   std::unique_ptr<IdpNetworkRequestManager> network_manager_;
   std::unique_ptr<IdentityRequestDialogController> request_dialog_controller_;
 
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index ec97efb..1d6d571 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -646,8 +646,6 @@
     std::optional<SkColor> brand_text_color;
     // State related to ShowFailureDialog().
     size_t num_show_idp_signin_status_mismatch_dialog_requests{0u};
-    // State related to ShowIdpSigninFailureDialog().
-    bool did_show_idp_signin_failure_dialog{false};
     // State related to ShowErrorDialog().
     bool did_show_error_dialog{false};
     std::optional<TokenError> token_error;
@@ -807,16 +805,6 @@
     }
   }
 
-  void ShowIdpSigninFailureDialog(base::OnceClosure dismiss_callback) override {
-    if (!state_) {
-      return;
-    }
-
-    state_->did_show_idp_signin_failure_dialog = true;
-    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-        FROM_HERE, std::move(dismiss_callback));
-  }
-
   base::WeakPtr<TestDialogController> AsWeakPtr() {
     return weak_ptr_factory_.GetWeakPtr();
   }
@@ -3880,7 +3868,6 @@
 
   EXPECT_EQ(2u, dialog_controller_state_
                     .num_show_idp_signin_status_mismatch_dialog_requests);
-  EXPECT_FALSE(dialog_controller_state_.did_show_idp_signin_failure_dialog);
 
   // After the IdP sign-in status was updated, the endpoints should have been
   // fetched a 2nd time.
@@ -3950,7 +3937,6 @@
   EXPECT_FALSE(did_show_accounts_dialog());
   EXPECT_EQ(1u, dialog_controller_state_
                     .num_show_idp_signin_status_mismatch_dialog_requests);
-  EXPECT_TRUE(dialog_controller_state_.did_show_idp_signin_failure_dialog);
 
   // After the IdP sign-in status was updated, the endpoints should have been
   // fetched a 2nd time.
@@ -3969,118 +3955,6 @@
   CheckAllFedCmSessionIDs();
 }
 
-// Test that the IdP-sign-in-failure-dialog is not shown if there is an error
-// after the user has selected an account.
-TEST_F(FederatedAuthRequestImplTest,
-       FailAfterAccountSelectionHideDialogDoesNotShowIdpSigninFailureDialog) {
-  base::test::ScopedFeatureList list;
-  list.InitAndEnableFeature(features::kFedCmIdpSigninStatusEnabled);
-
-  // Setup dialog controller to fail FedCM request after the user has selected
-  // an account.
-  url::Origin rp_origin_to_disable = main_test_rfh()->GetLastCommittedOrigin();
-  SetDialogController(
-      std::make_unique<DisableApiWhenDialogShownDialogController>(
-          kConfigurationValid, test_api_permission_delegate_.get(),
-          rp_origin_to_disable));
-
-  SetNetworkRequestManager(
-      std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
-  auto* network_manager =
-      static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
-          test_network_request_manager_.get());
-
-  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
-
-  // Setup IdP sign-in status mismatch.
-  network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
-  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
-
-  RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
-  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
-  EXPECT_FALSE(did_show_accounts_dialog());
-
-  // Simulate user signing into IdP by updating the IdP signin status and
-  // calling the observer.
-  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
-  network_manager->accounts_parse_status_ = ParseStatus::kSuccess;
-  federated_auth_request_impl_->OnIdpSigninStatusReceived(
-      kIdpOrigin, /*idp_signin_status=*/true);
-  WaitForCurrentAuthRequest();
-
-  // Check that the FedCM request failed after the account picker was shown.
-  RequestExpectations expectations = {
-      RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorDisabledInSettings,
-      /*standalone_console_message=*/std::nullopt,
-      /*selected_idp_config_url=*/std::nullopt};
-  CheckAuthExpectations(kConfigurationValid, expectations);
-  EXPECT_TRUE(did_show_accounts_dialog());
-
-  // Check that the IdP-sign-in-failure dialog is not shown.
-  EXPECT_FALSE(dialog_controller_state_.did_show_idp_signin_failure_dialog);
-  histogram_tester_.ExpectTotalCount(
-      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 1);
-  histogram_tester_.ExpectTotalCount(
-      "Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
-
-  ExpectUKMPresence("AccountsDialogShown");
-  ExpectUKMPresence("MismatchDialogShown");
-  ExpectUKMPresence("Timing.AccountsDialogShownDuration");
-  ExpectUKMPresence("Timing.MismatchDialogShownDuration");
-  CheckAllFedCmSessionIDs();
-}
-
-// Test that the IdP-sign-in-failure dialog is not shown in the
-// following sequence of events:
-// 1) Failure dialog is shown due to IdP sign-in status mismatch
-// 2) FedCM call is aborted.
-TEST_F(FederatedAuthRequestImplTest,
-       FailureUiAbortDoesNotShowIdpSigninFailureDialog) {
-  base::test::ScopedFeatureList list;
-  list.InitAndEnableFeature(features::kFedCmIdpSigninStatusEnabled);
-
-  SetNetworkRequestManager(
-      std::make_unique<ParseStatusOverrideIdpNetworkRequestManager>());
-  auto* network_manager =
-      static_cast<ParseStatusOverrideIdpNetworkRequestManager*>(
-          test_network_request_manager_.get());
-
-  url::Origin kIdpOrigin = OriginFromString(kProviderUrlFull);
-
-  // Setup IdP sign-in status mismatch.
-  network_manager->accounts_parse_status_ = ParseStatus::kInvalidResponseError;
-  test_permission_delegate_->idp_signin_statuses_[kIdpOrigin] = true;
-
-  RunAuthDontWaitForCallback(kDefaultRequestParameters, kConfigurationValid);
-  EXPECT_TRUE(did_show_idp_signin_status_mismatch_dialog());
-  EXPECT_FALSE(did_show_accounts_dialog());
-
-  // Abort the request before DelayTimer kicks in.
-  federated_auth_request_impl_->CancelTokenRequest();
-
-  RequestExpectations expectations{RequestTokenStatus::kErrorCanceled,
-                                   FederatedAuthRequestResult::kErrorCanceled,
-                                   /*standalone_console_message=*/std::nullopt,
-                                   /*selected_idp_config_url=*/std::nullopt};
-  WaitForCurrentAuthRequest();
-  CheckAuthExpectations(kConfigurationValid, expectations);
-
-  // Abort should not trigger IdP-sign-in-failure dialog.
-  EXPECT_FALSE(dialog_controller_state_.did_show_idp_signin_failure_dialog);
-
-  histogram_tester_.ExpectTotalCount(
-      "Blink.FedCm.Timing.AccountsDialogShownDuration2", 0);
-  histogram_tester_.ExpectTotalCount(
-      "Blink.FedCm.Timing.MismatchDialogShownDuration", 1);
-
-  ExpectNoUKMPresence("AccountsDialogShown");
-  ExpectUKMPresence("MismatchDialogShown");
-  ExpectNoUKMPresence("Timing.AccountsDialogShownDuration");
-  ExpectUKMPresence("Timing.MismatchDialogShownDuration");
-  CheckAllFedCmSessionIDs();
-}
-
 // Test that when IdpSigninStatus API is in the metrics-only mode, that an IDP
 // signed-out status stays signed-out regardless of what is returned by the
 // accounts endpoint.
diff --git a/content/browser/xr/service/vr_service_impl.cc b/content/browser/xr/service/vr_service_impl.cc
index 94c2201..3427141 100644
--- a/content/browser/xr/service/vr_service_impl.cc
+++ b/content/browser/xr/service/vr_service_impl.cc
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/dcheck_is_on.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
@@ -540,7 +539,7 @@
   // features, but we don't need to block creation if an optional feature is
   // not supported. Remove all unsupported optional features from the
   // optional_features collection before handing it off.
-  base::EraseIf(options->optional_features, [runtime](auto& feature) {
+  std::erase_if(options->optional_features, [runtime](auto& feature) {
     return !runtime->SupportsFeature(feature);
   });
 
diff --git a/content/public/browser/identity_request_dialog_controller.cc b/content/public/browser/identity_request_dialog_controller.cc
index b1215841..7ae3923 100644
--- a/content/public/browser/identity_request_dialog_controller.cc
+++ b/content/public/browser/identity_request_dialog_controller.cc
@@ -109,13 +109,6 @@
   return std::nullopt;
 }
 
-void IdentityRequestDialogController::ShowIdpSigninFailureDialog(
-    base::OnceClosure dismiss_callback) {
-  if (!is_interception_enabled_) {
-    std::move(dismiss_callback).Run();
-  }
-}
-
 void IdentityRequestDialogController::ShowUrl(LinkType type, const GURL& url) {}
 
 WebContents* IdentityRequestDialogController::ShowModalDialog(
diff --git a/content/public/browser/identity_request_dialog_controller.h b/content/public/browser/identity_request_dialog_controller.h
index 408c2d9..c1d84095 100644
--- a/content/public/browser/identity_request_dialog_controller.h
+++ b/content/public/browser/identity_request_dialog_controller.h
@@ -179,9 +179,6 @@
   virtual std::string GetTitle() const;
   virtual std::optional<std::string> GetSubtitle() const;
 
-  // Show dialog notifying user that IdP sign-in failed.
-  virtual void ShowIdpSigninFailureDialog(base::OnceClosure dismiss_callback);
-
   // Open a popup or similar that shows the specified URL.
   virtual void ShowUrl(LinkType type, const GURL& url);
 
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 1c2330e..66bf15b 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -619,7 +619,7 @@
 // If the network service is enabled, runs it in process.
 BASE_FEATURE(kNetworkServiceInProcess,
              "NetworkServiceInProcess2",
-#if BUILDFLAG(IS_ANDROID)
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
              base::FEATURE_ENABLED_BY_DEFAULT
 #else
              base::FEATURE_DISABLED_BY_DEFAULT
diff --git a/content/renderer/pepper/pepper_media_device_manager.cc b/content/renderer/pepper/pepper_media_device_manager.cc
index e528bc9..da39a08 100644
--- a/content/renderer/pepper/pepper_media_device_manager.cc
+++ b/content/renderer/pepper/pepper_media_device_manager.cc
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 #include "content/renderer/pepper/pepper_media_device_manager.h"
+#include <vector>
 
 #include "base/check.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
@@ -144,7 +144,7 @@
   SubscriptionList& subscriptions =
       device_change_subscriptions_[static_cast<size_t>(
           ToMediaDeviceType(type))];
-  base::EraseIf(subscriptions,
+  std::erase_if(subscriptions,
                 [subscription_id](const Subscription& subscription) {
                   return subscription.first == subscription_id;
                 });
diff --git a/content/renderer/service_worker/service_worker_provider_context.cc b/content/renderer/service_worker/service_worker_provider_context.cc
index e4d7322..d70da714 100644
--- a/content/renderer/service_worker/service_worker_provider_context.cc
+++ b/content/renderer/service_worker/service_worker_provider_context.cc
@@ -8,7 +8,6 @@
 #include <utility>
 #include <vector>
 
-#include "base/containers/cxx20_erase.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/memory/ref_counted.h"
@@ -361,7 +360,7 @@
 void ServiceWorkerProviderContext::UnregisterWorkerFetchContext(
     blink::mojom::ServiceWorkerWorkerClient* client) {
   CHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
-  base::EraseIf(
+  std::erase_if(
       worker_clients_,
       [client](const mojo::Remote<blink::mojom::ServiceWorkerWorkerClient>&
                    remote_client) { return remote_client.get() == client; });
diff --git a/content/services/auction_worklet/auction_v8_helper_unittest.cc b/content/services/auction_worklet/auction_v8_helper_unittest.cc
index 288c817..3a3622f 100644
--- a/content/services/auction_worklet/auction_v8_helper_unittest.cc
+++ b/content/services/auction_worklet/auction_v8_helper_unittest.cc
@@ -1561,9 +1561,10 @@
 }
 
 TEST_F(AuctionV8HelperTest, ExtractJsonTimeout) {
-  // Use a shorter timeout so test runs faster.
-  const base::TimeDelta kTimeout = base::Milliseconds(20);
-  auto time_limit = helper_->CreateTimeLimit(kTimeout);
+  // While it's tempting to use a shorter timeout since this is a
+  // non-termination test, that flakes occasionally, and even more so under
+  // *SAN, for which the default is auto-adjusted.
+  auto time_limit = helper_->CreateTimeLimit(/*script_timeout=*/std::nullopt);
   auto time_limit_scope =
       std::make_unique<AuctionV8Helper::TimeLimitScope>(time_limit.get());
 
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc
index 0998214..05d31c3 100644
--- a/content/services/auction_worklet/bidder_worklet.cc
+++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -995,7 +995,7 @@
       if (kanon_mode == mojom::KAnonymityBidMode::kEnforce) {
         PrivateAggregationRequests non_kanon_pa_requests =
             std::move(result->pa_requests);
-        base::EraseIf(
+        std::erase_if(
             non_kanon_pa_requests,
             [](const auction_worklet::mojom::PrivateAggregationRequestPtr&
                    request) { return !HasKAnonFailureComponent(request); });
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 7579ffd..22a47b5 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -33,6 +33,8 @@
   import("//third_party/fuchsia-gn-sdk/src/package.gni")
 } else if (is_ios) {
   import("//build/config/ios/config.gni")
+  import("//build/config/ios/swift_source_set.gni")
+  import("//build/ios/extension_bundle_data.gni")
 }
 
 # TODO(crbug.com/1336055, spang): Investigate using shell_views with cast builds as
@@ -45,6 +47,24 @@
   content_shell_major_version = "999"
 }
 
+template("extension2_bundle_data") {
+  assert(defined(invoker.extension_target),
+         "extension_target must be defined for $target_name")
+
+  _extension_name = get_label_info(invoker.extension_target, "name") + ".appex"
+  if (defined(invoker.extension_name)) {
+    _extension_name = invoker.extension_name
+  }
+
+  bundle_data(target_name) {
+    testonly = true
+    public_deps = [ invoker.extension_target ]
+    outputs = [ "{{bundle_contents_dir}}/Extensions/{{source_file_part}}" ]
+    sources = [ get_label_info(invoker.extension_target, "root_out_dir") +
+                "/$_extension_name" ]
+  }
+}
+
 config("content_shell_lib_warnings") {
   if (is_clang) {
     # TODO(thakis): Remove this once http://crbug.com/383820 is figured out
@@ -616,11 +636,11 @@
     # Path to an entitlements file used in ios_content_shell. Can be overridden
     # to provide an alternative.
     ios_content_shell_entitlements_path =
-        "//build/config/ios/entitlements.plist"
+        "//content/shell/app/ios/content_shell.entitlements"
   }
 
   ios_app_bundle("content_shell") {
-    info_plist = "app/ios-Info.plist"
+    info_plist = "app/ios/ios-app.plist"
     testonly = true
 
     sources = [ "app/shell_main.cc" ]
@@ -629,15 +649,29 @@
       ":content_shell_app",
       ":content_shell_lib",
       "//base",
+      "//build:ios_buildflags",
       "//content/public/app",
     ]
     bundle_deps = [
       ":content_shell_framework_resources",
       "app/ios/resources",
     ]
+    if (ios_deployment_target == "17.4") {
+      deps += [ ":content_process_bundle" ]
+    }
     entitlements_path = ios_content_shell_entitlements_path
     bundle_identifier = ios_content_shell_bundle_identifier
   }
+
+  if (ios_deployment_target == "17.4" &&
+      current_toolchain == default_toolchain) {
+    _extension_toolchain = "${current_toolchain}_app_ext"
+    extension2_bundle_data("content_process_bundle") {
+      extension_name = "content_process.appex"
+      extension_target =
+          "//content/shell/app/ios:content_process(${_extension_toolchain})"
+    }
+  }
 } else {
   executable("content_shell") {
     testonly = true
diff --git a/content/shell/app/ios/BUILD.gn b/content/shell/app/ios/BUILD.gn
new file mode 100644
index 0000000..7684e84
--- /dev/null
+++ b/content/shell/app/ios/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//build/apple/compile_entitlements.gni")
+import("//build/apple/tweak_info_plist.gni")
+import("//build/config/ios/rules.gni")
+import("//ios/build/chrome_build.gni")
+import("//ios/build/config.gni")
+
+tweak_info_plist("tweak_info_plist") {
+  info_plist = "ios-content.plist"
+}
+
+compile_entitlements("entitlements") {
+  substitutions = [ "IOS_BUNDLE_ID_PREFIX=$ios_app_bundle_id_prefix" ]
+  output_name = "$target_gen_dir/content_process.appex.entitlements"
+  entitlements_templates =
+      [ "../../../app/ios/appex/content_process.appex.entitlements" ]
+}
+
+ios_appex_bundle("content_process") {
+  testonly = true
+  output_name = "content_process"
+
+  sources = [ "../shell_main.cc" ]
+
+  deps = [
+    "//content/app/ios/appex:content_process",
+    "//content/public/app",
+    "//content/shell:content_shell_app",
+    "//content/shell:content_shell_lib",
+  ]
+  bundle_deps = [
+    "//content/shell:content_shell_framework_resources",
+    "//content/shell/app/ios/resources",
+  ]
+
+  entitlements_target = ":entitlements"
+  info_plist_target = ":tweak_info_plist"
+  bundle_identifier = "$shared_bundle_id_for_test_apps.ContentProcess"
+}
diff --git a/content/shell/app/ios/content_shell.entitlements b/content/shell/app/ios/content_shell.entitlements
new file mode 100644
index 0000000..2848a71
--- /dev/null
+++ b/content/shell/app/ios/content_shell.entitlements
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+        <key>com.apple.developer.web-browser</key>
+        <true/>
+        <key>com.apple.developer.web-browser-engine.host</key>
+        <true/>
+</dict>
+</plist>
\ No newline at end of file
diff --git a/content/shell/app/ios-Info.plist b/content/shell/app/ios/ios-app.plist
similarity index 96%
rename from content/shell/app/ios-Info.plist
rename to content/shell/app/ios/ios-app.plist
index ac61d73..b0557a2 100644
--- a/content/shell/app/ios-Info.plist
+++ b/content/shell/app/ios/ios-app.plist
@@ -56,5 +56,7 @@
 	<string>Allow content_shell access to Bluetooth</string>
 	<key>NSLocationWhenInUseUsageDescription</key>
 	<string>Allow content_shell access to location</string>
+	<key>com.apple.developer.web-browser-engine.host</key>
+	<true/>
 </dict>
 </plist>
diff --git a/content/shell/app/ios/ios-content.plist b/content/shell/app/ios/ios-content.plist
new file mode 100644
index 0000000..c364e13
--- /dev/null
+++ b/content/shell/app/ios/ios-content.plist
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>EXAppExtensionAttributes</key>
+	<dict>
+		<key>EXExtensionPointIdentifier</key>
+		<string>com.apple.web-browser-engine.content</string>
+	</dict>
+	<key>CFBundleIdentifier</key>
+	<string>${BUNDLE_IDENTIFIER}</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleVersion</key>
+	<string>1.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+</dict>
+</plist>
\ No newline at end of file
diff --git a/content/shell/app/shell_main.cc b/content/shell/app/shell_main.cc
index 80d6fe7..9810903d 100644
--- a/content/shell/app/shell_main.cc
+++ b/content/shell/app/shell_main.cc
@@ -16,6 +16,7 @@
 #if BUILDFLAG(IS_IOS)
 #include "base/at_exit.h"                                 // nogncheck
 #include "base/command_line.h"                            // nogncheck
+#include "build/ios_buildflags.h"                         // nogncheck
 #include "content/public/common/content_switches.h"       // nogncheck
 #include "content/shell/app/ios/shell_application_ios.h"
 #include "content/shell/app/ios/web_tests_support_ios.h"
@@ -48,6 +49,19 @@
 
 #elif BUILDFLAG(IS_IOS)
 
+#if BUILDFLAG(IS_IOS_APP_EXTENSION)
+extern "C" int ContentProcessMain(int argc, const char** argv) {
+  // Create this here since it's needed to start the crash handler.
+  base::AtExitManager at_exit;
+  base::CommandLine::Init(argc, argv);
+  content::ShellMainDelegate delegate;
+  content::ContentMainParams params(&delegate);
+  params.argc = argc;
+  params.argv = argv;
+  return content::ContentMain(std::move(params));
+}
+#else
+
 int main(int argc, const char** argv) {
   // Create this here since it's needed to start the crash handler.
   base::AtExitManager at_exit;
@@ -75,6 +89,7 @@
     return content::ContentMain(std::move(params));
   }
 }
+#endif
 
 #else
 
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc
index d53d234..c6cae71 100644
--- a/content/shell/browser/shell_content_browser_client.cc
+++ b/content/shell/browser/shell_content_browser_client.cc
@@ -14,7 +14,6 @@
 
 #include "base/base_switches.h"
 #include "base/command_line.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/containers/flat_set.h"
 #include "base/feature_list.h"
 #include "base/files/file.h"
@@ -346,7 +345,7 @@
 }
 
 ShellContentBrowserClient::~ShellContentBrowserClient() {
-  base::Erase(GetShellContentBrowserClientInstancesImpl(), this);
+  std::erase(GetShellContentBrowserClientInstancesImpl(), this);
 }
 
 std::unique_ptr<BrowserMainParts>
diff --git a/content/shell/browser/shell_devtools_bindings.cc b/content/shell/browser/shell_devtools_bindings.cc
index 63c9a6fd..a528b235 100644
--- a/content/shell/browser/shell_devtools_bindings.cc
+++ b/content/shell/browser/shell_devtools_bindings.cc
@@ -7,10 +7,10 @@
 #include <stddef.h>
 
 #include <utility>
+#include <vector>
 
 #include "base/base64.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/json/json_reader.h"
@@ -186,7 +186,7 @@
 
   auto* bindings = GetShellDevtoolsBindingsInstances();
   DCHECK(base::Contains(*bindings, this));
-  base::Erase(*bindings, this);
+  std::erase(*bindings, this);
 }
 
 // static
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index f86b280e..6acf56e 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -540,6 +540,20 @@
 
 crbug.com/326283382 [ mac angle-metal graphite-enabled ] conformance/context/context-creation-and-destruction.html [ RetryOnFailure ]
 
+## SkiaGraphite on on SwANGLE ##
+
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/context/context-attributes-alpha-depth-stencil-antialias.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/context/context-hidden-alpha.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/misc/copy-tex-image-2d-formats.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/misc/tex-input-validation.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/webgl_canvas/tex-2d-alpha-alpha-unsigned_byte.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/webgl_canvas/tex-2d-luminance-luminance-unsigned_byte.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/webgl_canvas/tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/webgl_canvas/tex-2d-rgb-rgb-unsigned_byte.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/webgl_canvas/tex-2d-rgb-rgb-unsigned_short_5_6_5.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/webgl_canvas/tex-2d-rgba-rgba-unsigned_byte.html [ Failure ]
+crbug.com/41495039 [ mac angle-swiftshader graphite-enabled ] conformance/textures/webgl_canvas/tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html [ Failure ]
+
 ## Mac AMD failures ##
 
 crbug.com/642822 [ mac amd ] conformance/rendering/clipping-wide-points.html [ Failure ]
diff --git a/content/test/web_contents_observer_consistency_checker.cc b/content/test/web_contents_observer_consistency_checker.cc
index dcd5f36..913d0d4 100644
--- a/content/test/web_contents_observer_consistency_checker.cc
+++ b/content/test/web_contents_observer_consistency_checker.cc
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 #include "content/test/web_contents_observer_consistency_checker.h"
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/memory/ptr_util.h"
 #include "base/pending_task.h"
 #include "base/strings/stringprintf.h"
@@ -354,7 +354,7 @@
     WebContentsObserver::MediaStoppedReason reason) {
   CHECK(!web_contents_destroyed_);
   CHECK(base::Contains(active_media_players_, id));
-  base::Erase(active_media_players_, id);
+  std::erase(active_media_players_, id);
 }
 
 bool WebContentsObserverConsistencyChecker::OnMessageReceived(
diff --git a/crypto/apple_keychain_v2.h b/crypto/apple_keychain_v2.h
index 75cbd02..3d14b5eb5 100644
--- a/crypto/apple_keychain_v2.h
+++ b/crypto/apple_keychain_v2.h
@@ -52,9 +52,8 @@
   // ItemDelete wraps the |SecItemDelete| function.
   virtual OSStatus ItemDelete(CFDictionaryRef query);
   // ItemDelete wraps the |SecItemUpdate| function.
-  virtual OSStatus ItemUpdate(
-      CFDictionaryRef query,
-      base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> keychain_data);
+  virtual OSStatus ItemUpdate(CFDictionaryRef query,
+                              CFDictionaryRef keychain_data);
 
  protected:
   AppleKeychainV2();
diff --git a/crypto/apple_keychain_v2.mm b/crypto/apple_keychain_v2.mm
index e018683..2e91e72 100644
--- a/crypto/apple_keychain_v2.mm
+++ b/crypto/apple_keychain_v2.mm
@@ -80,10 +80,9 @@
   return SecItemDelete(query);
 }
 
-OSStatus AppleKeychainV2::ItemUpdate(
-    CFDictionaryRef query,
-    base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> keychain_data) {
-  return SecItemUpdate(query, keychain_data.get());
+OSStatus AppleKeychainV2::ItemUpdate(CFDictionaryRef query,
+                                     CFDictionaryRef keychain_data) {
+  return SecItemUpdate(query, keychain_data);
 }
 
 }  // namespace crypto
diff --git a/crypto/fake_apple_keychain_v2.h b/crypto/fake_apple_keychain_v2.h
index 81803153..bb61d64c 100644
--- a/crypto/fake_apple_keychain_v2.h
+++ b/crypto/fake_apple_keychain_v2.h
@@ -40,8 +40,7 @@
   OSStatus ItemCopyMatching(CFDictionaryRef query, CFTypeRef* result) override;
   OSStatus ItemDelete(CFDictionaryRef query) override;
   OSStatus ItemUpdate(CFDictionaryRef query,
-                      base::apple::ScopedCFTypeRef<CFMutableDictionaryRef>
-                          keychain_data) override;
+                      CFDictionaryRef keychain_data) override;
 
  private:
   // items_ contains the keychain items created by `KeyCreateRandomKey`.
diff --git a/crypto/fake_apple_keychain_v2.mm b/crypto/fake_apple_keychain_v2.mm
index 0d41ee1e..12254b2 100644
--- a/crypto/fake_apple_keychain_v2.mm
+++ b/crypto/fake_apple_keychain_v2.mm
@@ -14,6 +14,7 @@
 #import <Foundation/Foundation.h>
 #import <Security/Security.h>
 
+#include "base/apple/bridging.h"
 #include "base/apple/foundation_util.h"
 #include "base/apple/scoped_cftyperef.h"
 #include "base/check_op.h"
@@ -225,9 +226,8 @@
   return errSecItemNotFound;
 }
 
-OSStatus FakeAppleKeychainV2::ItemUpdate(
-    CFDictionaryRef query,
-    base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> attributes_to_update) {
+OSStatus FakeAppleKeychainV2::ItemUpdate(CFDictionaryRef query,
+                                         CFDictionaryRef attributes_to_update) {
   DCHECK_EQ(base::apple::GetValueFromDictionary<CFStringRef>(query, kSecClass),
             kSecClassKey);
   DCHECK(CFEqual(base::apple::GetValueFromDictionary<CFStringRef>(
@@ -237,8 +237,7 @@
       base::apple::GetValueFromDictionary<CFDataRef>(query,
                                                      kSecAttrApplicationLabel);
   DCHECK(query_credential_id);
-  for (auto it = items_.begin(); it != items_.end(); ++it) {
-    const base::apple::ScopedCFTypeRef<CFDictionaryRef>& item = *it;
+  for (base::apple::ScopedCFTypeRef<CFDictionaryRef>& item : items_) {
     CFDataRef item_credential_id =
         base::apple::GetValueFromDictionary<CFDataRef>(
             item.get(), kSecAttrApplicationLabel);
@@ -249,16 +248,10 @@
     base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> item_copy(
         CFDictionaryCreateMutableCopy(kCFAllocatorDefault, /*capacity=*/0,
                                       item.get()));
-    size_t size = CFDictionaryGetCount(attributes_to_update.get());
-    std::vector<CFStringRef> keys(size, nullptr);
-    std::vector<CFDictionaryRef> values(size, nullptr);
-    CFDictionaryGetKeysAndValues(attributes_to_update.get(),
-                                 reinterpret_cast<const void**>(keys.data()),
-                                 reinterpret_cast<const void**>(values.data()));
-    for (size_t i = 0; i < size; ++i) {
-      CFDictionarySetValue(item_copy.get(), keys[i], values[i]);
-    }
-    *it = base::apple::ScopedCFTypeRef<CFDictionaryRef>(item_copy.release());
+    [base::apple::CFToNSPtrCast(item_copy.get())
+        addEntriesFromDictionary:base::apple::CFToNSPtrCast(
+                                     attributes_to_update)];
+    item = item_copy;
     return errSecSuccess;
   }
   return errSecItemNotFound;
diff --git a/crypto/unexportable_key_unittest.cc b/crypto/unexportable_key_unittest.cc
index 634d959..3d6e54f 100644
--- a/crypto/unexportable_key_unittest.cc
+++ b/crypto/unexportable_key_unittest.cc
@@ -84,6 +84,13 @@
   const base::TimeTicks generate_start = base::TimeTicks::Now();
   std::unique_ptr<crypto::UnexportableSigningKey> key =
       provider->GenerateSigningKeySlowly(algorithms);
+  if (algo == crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256) {
+    if (!key) {
+      GTEST_SKIP()
+          << "Workaround for https://issues.chromium.org/issues/41494935";
+    }
+  }
+
   ASSERT_TRUE(key);
   LOG(INFO) << "Generation took " << (base::TimeTicks::Now() - generate_start);
 
diff --git a/device/bluetooth/bluetooth_adapter_winrt.cc b/device/bluetooth/bluetooth_adapter_winrt.cc
index 9be10888..a7bdfc1 100644
--- a/device/bluetooth/bluetooth_adapter_winrt.cc
+++ b/device/bluetooth/bluetooth_adapter_winrt.cc
@@ -14,7 +14,6 @@
 #include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/containers/span.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
@@ -1369,7 +1368,7 @@
     CreateAdvertisementCallback callback) {
   DCHECK(base::Contains(pending_advertisements_, advertisement));
   auto wrapped_advertisement = base::WrapRefCounted(advertisement);
-  base::Erase(pending_advertisements_, advertisement);
+  std::erase(pending_advertisements_, advertisement);
   std::move(callback).Run(std::move(wrapped_advertisement));
 }
 
@@ -1379,7 +1378,7 @@
     BluetoothAdvertisement::ErrorCode error_code) {
   // Note: We are not DCHECKing that |pending_advertisements_| contains
   // |advertisement|, as this method might be invoked during destruction.
-  base::Erase(pending_advertisements_, advertisement);
+  std::erase(pending_advertisements_, advertisement);
   std::move(error_callback).Run(error_code);
 }
 
diff --git a/device/fido/fido_device_authenticator.cc b/device/fido/fido_device_authenticator.cc
index 34a2376..4377b2a5 100644
--- a/device/fido/fido_device_authenticator.cc
+++ b/device/fido/fido_device_authenticator.cc
@@ -7,9 +7,9 @@
 #include <algorithm>
 #include <numeric>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_functions.h"
@@ -1318,7 +1318,7 @@
       }
     }
   }
-  bool did_erase = base::EraseIf(
+  bool did_erase = std::erase_if(
       *large_blob_array, [&large_blob_keys](const cbor::Value& blob_cbor) {
         std::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
         return blob &&
diff --git a/device/fido/mac/browsing_data_deletion_unittest.mm b/device/fido/mac/browsing_data_deletion_unittest.mm
index 29c6b66..7edc4ac 100644
--- a/device/fido/mac/browsing_data_deletion_unittest.mm
+++ b/device/fido/mac/browsing_data_deletion_unittest.mm
@@ -8,6 +8,7 @@
 #include <Foundation/Foundation.h>
 #include <Security/Security.h>
 
+#include "base/apple/bridging.h"
 #include "base/apple/foundation_util.h"
 #include "base/apple/osstatus_logging.h"
 #include "base/apple/scoped_cftyperef.h"
@@ -35,6 +36,9 @@
 extern const CFStringRef kSecAttrNoLegacy;
 }
 
+using base::apple::CFToNSPtrCast;
+using base::apple::NSToCFPtrCast;
+
 namespace device {
 
 using test::TestCallbackReceiver;
@@ -53,20 +57,15 @@
 // Returns a query to use with Keychain instance methods that returns all
 // credentials in the non-legacy keychain that are tagged with the keychain
 // access group used in this test.
-base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> BaseQuery() {
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> query(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(query.get(), kSecClass, kSecClassKey);
-  base::apple::ScopedCFTypeRef<CFStringRef> access_group_ref(
-      base::SysUTF8ToCFStringRef(kKeychainAccessGroup));
-  CFDictionarySetValue(query.get(), kSecAttrAccessGroup,
-                       access_group_ref.get());
-  CFDictionarySetValue(query.get(), kSecAttrNoLegacy, kCFBooleanTrue);
-  CFDictionarySetValue(query.get(), kSecReturnAttributes, kCFBooleanTrue);
-  CFDictionarySetValue(query.get(), kSecMatchLimit, kSecMatchLimitAll);
-  return query;
+NSDictionary* BaseQuery() {
+  return @{
+    CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey),
+    CFToNSPtrCast(kSecAttrAccessGroup) :
+        base::SysUTF8ToNSString(kKeychainAccessGroup),
+    CFToNSPtrCast(kSecAttrNoLegacy) : @YES,
+    CFToNSPtrCast(kSecReturnAttributes) : @YES,
+    CFToNSPtrCast(kSecMatchLimit) : CFToNSPtrCast(kSecMatchLimitAll),
+  };
 }
 
 // Returns all WebAuthn credentials stored in the keychain, regardless of which
@@ -75,7 +74,8 @@
 base::apple::ScopedCFTypeRef<CFArrayRef> QueryAllCredentials() {
   base::apple::ScopedCFTypeRef<CFArrayRef> items;
   OSStatus status = crypto::AppleKeychainV2::GetInstance().ItemCopyMatching(
-      BaseQuery().get(), reinterpret_cast<CFTypeRef*>(items.InitializeInto()));
+      NSToCFPtrCast(BaseQuery()),
+      reinterpret_cast<CFTypeRef*>(items.InitializeInto()));
   if (status == errSecItemNotFound) {
     // The API returns null, but we should return an empty array instead to
     // distinguish from real errors.
@@ -95,8 +95,8 @@
 }
 
 bool ResetKeychain() {
-  OSStatus status =
-      crypto::AppleKeychainV2::GetInstance().ItemDelete(BaseQuery().get());
+  OSStatus status = crypto::AppleKeychainV2::GetInstance().ItemDelete(
+      NSToCFPtrCast(BaseQuery()));
   if (status != errSecSuccess && status != errSecItemNotFound) {
     OSSTATUS_DLOG(ERROR, status);
     return false;
diff --git a/device/fido/mac/credential_store.mm b/device/fido/mac/credential_store.mm
index 3c6b7ac..39d6b573 100644
--- a/device/fido/mac/credential_store.mm
+++ b/device/fido/mac/credential_store.mm
@@ -29,6 +29,9 @@
 #include "device/fido/mac/credential_metadata.h"
 #include "device/fido/mac/touch_id_context.h"
 
+using base::apple::CFToNSPtrCast;
+using base::apple::NSToCFPtrCast;
+
 namespace device::fido::mac {
 
 namespace {
@@ -37,22 +40,15 @@
 // the keychain item class, keychain access group and RP ID (unless `rp_id` is
 // `nullopt`) filled out. More fields can be set on the return value to refine
 // the query.
-base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> DefaultKeychainQuery(
-    const AuthenticatorConfig& config,
-    std::optional<std::string> rp_id) {
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> query(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(query.get(), kSecClass, kSecClassKey);
-  CFDictionarySetValue(
-      query.get(), kSecAttrAccessGroup,
-      base::SysUTF8ToCFStringRef(config.keychain_access_group).get());
+NSMutableDictionary* DefaultKeychainQuery(const AuthenticatorConfig& config,
+                                          std::optional<std::string> rp_id) {
+  NSMutableDictionary* query = [NSMutableDictionary dictionary];
+  query[CFToNSPtrCast(kSecClass)] = CFToNSPtrCast(kSecClassKey);
+  query[CFToNSPtrCast(kSecAttrAccessGroup)] =
+      base::SysUTF8ToNSString(config.keychain_access_group);
   if (rp_id) {
-    CFDictionarySetValue(
-        query.get(), kSecAttrLabel,
-        base::SysUTF8ToCFStringRef(EncodeRpId(config.metadata_secret, *rp_id))
-            .get());
+    query[CFToNSPtrCast(kSecAttrLabel)] =
+        base::SysUTF8ToNSString(EncodeRpId(config.metadata_secret, *rp_id));
   }
   return query;
 }
@@ -94,22 +90,20 @@
   // keychain access group.
   std::vector<base::apple::ScopedCFTypeRef<CFDictionaryRef>> result;
 
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> query(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(query.get(), kSecClass, kSecClassKey);
-  CFDictionarySetValue(query.get(), kSecAttrAccessGroup,
-                       base::SysUTF8ToCFStringRef(keychain_access_group).get());
-  CFDictionarySetValue(query.get(), kSecMatchLimit, kSecMatchLimitAll);
-  // Return the key reference and its attributes.
-  CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue);
-  CFDictionarySetValue(query.get(), kSecReturnAttributes, kCFBooleanTrue);
+  NSDictionary* query = @{
+    CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey),
+    CFToNSPtrCast(kSecAttrAccessGroup) :
+        base::SysUTF8ToNSString(keychain_access_group),
+    CFToNSPtrCast(kSecMatchLimit) : CFToNSPtrCast(kSecMatchLimitAll),
+    // Return the key reference and its attributes.
+    CFToNSPtrCast(kSecReturnRef) : @YES,
+    CFToNSPtrCast(kSecReturnAttributes) : @YES,
+  };
 
   base::apple::ScopedCFTypeRef<CFArrayRef> keychain_items;
   {
     OSStatus status = crypto::AppleKeychainV2::GetInstance().ItemCopyMatching(
-        query.get(),
+        NSToCFPtrCast(query),
         reinterpret_cast<CFTypeRef*>(keychain_items.InitializeInto()));
     if (status == errSecItemNotFound) {
       DVLOG(1) << "no credentials found";
@@ -221,47 +215,9 @@
     const std::string& rp_id,
     const PublicKeyCredentialUserEntity& user,
     Discoverable discoverable) const {
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> params(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(
-      params.get(), kSecAttrAccessGroup,
-      base::SysUTF8ToCFStringRef(config_.keychain_access_group).get());
-  CFDictionarySetValue(params.get(), kSecAttrKeyType,
-                       kSecAttrKeyTypeECSECPrimeRandom);
-  CFDictionarySetValue(params.get(), kSecAttrKeySizeInBits,
-                       base::apple::NSToCFPtrCast(@256));
-  CFDictionarySetValue(params.get(), kSecAttrSynchronizable, kCFBooleanFalse);
-  CFDictionarySetValue(params.get(), kSecAttrTokenID,
-                       kSecAttrTokenIDSecureEnclave);
+  NSMutableDictionary* private_key_params = [NSMutableDictionary dictionary];
+  private_key_params[CFToNSPtrCast(kSecAttrIsPermanent)] = @YES;
 
-  CFDictionarySetValue(
-      params.get(), kSecAttrLabel,
-      base::SysUTF8ToCFStringRef(EncodeRpId(config_.metadata_secret, rp_id))
-          .get());
-  auto credential_metadata =
-      CredentialMetadata::FromPublicKeyCredentialUserEntity(
-          user, discoverable == kDiscoverable);
-  const std::vector<uint8_t> sealed_metadata = SealCredentialMetadata(
-      config_.metadata_secret, rp_id, credential_metadata);
-  CFDictionarySetValue(params.get(), kSecAttrApplicationTag,
-                       base::apple::NSToCFPtrCast([NSData
-                           dataWithBytes:sealed_metadata.data()
-                                  length:sealed_metadata.size()]));
-  const std::vector<uint8_t> credential_id = GenerateRandomCredentialId();
-  CFDictionarySetValue(
-      params.get(), kSecAttrApplicationLabel,
-      base::apple::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
-                                                length:credential_id.size()]));
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(params.get(), kSecPrivateKeyAttrs,
-                       private_key_params.get());
-  CFDictionarySetValue(private_key_params.get(), kSecAttrIsPermanent,
-                       kCFBooleanTrue);
   // The credential can only be used for signing, and the device needs to be in
   // an unlocked state.
   auto flags = kSecAccessControlPrivateKeyUsage;
@@ -269,17 +225,44 @@
       SecAccessControlCreateWithFlags(
           kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
           flags, /*error=*/nullptr));
-  CFDictionarySetValue(private_key_params.get(), kSecAttrAccessControl,
-                       access_control.get());
+  private_key_params[CFToNSPtrCast(kSecAttrAccessControl)] =
+      (__bridge id)access_control.get();
   if (objc_storage_->authentication_context) {
-    CFDictionarySetValue(
-        private_key_params.get(), kSecUseAuthenticationContext,
-        (__bridge CFTypeRef)objc_storage_->authentication_context);
+    private_key_params[CFToNSPtrCast(kSecUseAuthenticationContext)] =
+        objc_storage_->authentication_context;
   }
+
+  auto credential_metadata =
+      CredentialMetadata::FromPublicKeyCredentialUserEntity(
+          user, discoverable == kDiscoverable);
+  const std::vector<uint8_t> sealed_metadata = SealCredentialMetadata(
+      config_.metadata_secret, rp_id, credential_metadata);
+
+  const std::vector<uint8_t> credential_id = GenerateRandomCredentialId();
+
+  NSDictionary* params = @{
+    CFToNSPtrCast(kSecAttrAccessGroup) :
+        base::SysUTF8ToNSString(config_.keychain_access_group),
+    CFToNSPtrCast(kSecAttrKeyType) :
+        CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom),
+    CFToNSPtrCast(kSecAttrKeySizeInBits) : @256,
+    CFToNSPtrCast(kSecAttrSynchronizable) : @NO,
+    CFToNSPtrCast(kSecAttrTokenID) :
+        CFToNSPtrCast(kSecAttrTokenIDSecureEnclave),
+    CFToNSPtrCast(kSecAttrLabel) :
+        base::SysUTF8ToNSString(EncodeRpId(config_.metadata_secret, rp_id)),
+    CFToNSPtrCast(kSecAttrApplicationTag) :
+        [NSData dataWithBytes:sealed_metadata.data()
+                       length:sealed_metadata.size()],
+    CFToNSPtrCast(kSecAttrApplicationLabel) :
+        [NSData dataWithBytes:credential_id.data() length:credential_id.size()],
+    CFToNSPtrCast(kSecPrivateKeyAttrs) : private_key_params,
+  };
+
   base::apple::ScopedCFTypeRef<CFErrorRef> cferr;
   base::apple::ScopedCFTypeRef<SecKeyRef> private_key =
       crypto::AppleKeychainV2::GetInstance().KeyCreateRandomKey(
-          params.get(), cferr.InitializeInto());
+          NSToCFPtrCast(params), cferr.InitializeInto());
   if (!private_key) {
     FIDO_LOG(ERROR) << "SecKeyCreateRandomKey failed: " << cferr.get();
     return std::nullopt;
@@ -316,42 +299,9 @@
                                            credential_id);
   DCHECK(metadata);
 
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> params(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(
-      params.get(), kSecAttrAccessGroup,
-      base::SysUTF8ToCFStringRef(config_.keychain_access_group).get());
-  CFDictionarySetValue(params.get(), kSecAttrKeyType,
-                       kSecAttrKeyTypeECSECPrimeRandom);
-  CFDictionarySetValue(params.get(), kSecAttrKeySizeInBits,
-                       base::apple::NSToCFPtrCast(@256));
-  CFDictionarySetValue(params.get(), kSecAttrSynchronizable, kCFBooleanFalse);
-  CFDictionarySetValue(params.get(), kSecAttrTokenID,
-                       kSecAttrTokenIDSecureEnclave);
+  NSMutableDictionary* private_key_params = [NSMutableDictionary dictionary];
+  private_key_params[CFToNSPtrCast(kSecAttrIsPermanent)] = @YES;
 
-  CFDictionarySetValue(
-      params.get(), kSecAttrLabel,
-      base::SysUTF8ToCFStringRef(EncodeRpId(config_.metadata_secret, rp_id))
-          .get());
-  CFDictionarySetValue(
-      params.get(), kSecAttrApplicationTag,
-      base::SysUTF8ToCFStringRef(EncodeRpIdAndUserIdDeprecated(
-                                     config_.metadata_secret, rp_id, user.id))
-          .get());
-  CFDictionarySetValue(
-      params.get(), kSecAttrApplicationLabel,
-      base::apple::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
-                                                length:credential_id.size()]));
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> private_key_params(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(params.get(), kSecPrivateKeyAttrs,
-                       private_key_params.get());
-  CFDictionarySetValue(private_key_params.get(), kSecAttrIsPermanent,
-                       kCFBooleanTrue);
   // Credential can only be used when the device is unlocked. Private key is
   // available for signing after user authorization with biometrics or
   // password.
@@ -360,17 +310,35 @@
           kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
           kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence,
           /*error=*/nullptr));
-  CFDictionarySetValue(private_key_params.get(), kSecAttrAccessControl,
-                       access_control.get());
+  private_key_params[CFToNSPtrCast(kSecAttrAccessControl)] =
+      (__bridge id)access_control.get();
   if (objc_storage_->authentication_context) {
-    CFDictionarySetValue(
-        private_key_params.get(), kSecUseAuthenticationContext,
-        (__bridge CFTypeRef)objc_storage_->authentication_context);
+    private_key_params[CFToNSPtrCast(kSecUseAuthenticationContext)] =
+        objc_storage_->authentication_context;
   }
+
+  NSDictionary* params = @{
+    CFToNSPtrCast(kSecAttrAccessGroup) :
+        base::SysUTF8ToNSString(config_.keychain_access_group),
+    CFToNSPtrCast(kSecAttrKeyType) :
+        CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom),
+    CFToNSPtrCast(kSecAttrKeySizeInBits) : @256,
+    CFToNSPtrCast(kSecAttrSynchronizable) : @NO,
+    CFToNSPtrCast(kSecAttrTokenID) :
+        CFToNSPtrCast(kSecAttrTokenIDSecureEnclave),
+    CFToNSPtrCast(kSecAttrLabel) :
+        base::SysUTF8ToNSString(EncodeRpId(config_.metadata_secret, rp_id)),
+    CFToNSPtrCast(kSecAttrApplicationTag) : base::SysUTF8ToNSString(
+        EncodeRpIdAndUserIdDeprecated(config_.metadata_secret, rp_id, user.id)),
+    CFToNSPtrCast(kSecAttrApplicationLabel) :
+        [NSData dataWithBytes:credential_id.data() length:credential_id.size()],
+    CFToNSPtrCast(kSecPrivateKeyAttrs) : private_key_params,
+  };
+
   base::apple::ScopedCFTypeRef<CFErrorRef> cferr;
   base::apple::ScopedCFTypeRef<SecKeyRef> private_key =
       crypto::AppleKeychainV2::GetInstance().KeyCreateRandomKey(
-          params.get(), cferr.InitializeInto());
+          NSToCFPtrCast(params), cferr.InitializeInto());
   if (!private_key) {
     FIDO_LOG(ERROR) << "SecKeyCreateRandomKey failed: " << cferr.get();
     return std::nullopt;
@@ -526,20 +494,18 @@
   // Query all credentials for the RP. Filtering for `rp_id` here ensures we
   // don't retrieve credentials for other profiles, because their
   // `kSecAttrLabel` attribute wouldn't match the encoded RP ID.
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> query =
-      DefaultKeychainQuery(config_, rp_id);
+  NSMutableDictionary* query = DefaultKeychainQuery(config_, rp_id);
   if (objc_storage_->authentication_context) {
-    CFDictionarySetValue(
-        query.get(), kSecUseAuthenticationContext,
-        (__bridge CFTypeRef)objc_storage_->authentication_context);
+    query[CFToNSPtrCast(kSecUseAuthenticationContext)] =
+        objc_storage_->authentication_context;
   }
-  CFDictionarySetValue(query.get(), kSecReturnRef, kCFBooleanTrue);
-  CFDictionarySetValue(query.get(), kSecReturnAttributes, kCFBooleanTrue);
-  CFDictionarySetValue(query.get(), kSecMatchLimit, kSecMatchLimitAll);
+  query[CFToNSPtrCast(kSecReturnRef)] = @YES;
+  query[CFToNSPtrCast(kSecReturnAttributes)] = @YES;
+  query[CFToNSPtrCast(kSecMatchLimit)] = CFToNSPtrCast(kSecMatchLimitAll);
 
   base::apple::ScopedCFTypeRef<CFArrayRef> keychain_items;
   OSStatus status = crypto::AppleKeychainV2::GetInstance().ItemCopyMatching(
-      query.get(),
+      NSToCFPtrCast(query),
       reinterpret_cast<CFTypeRef*>(keychain_items.InitializeInto()));
   if (status == errSecItemNotFound) {
     return std::list<Credential>();
@@ -643,14 +609,13 @@
 
 bool TouchIdCredentialStore::DeleteCredentialById(
     base::span<const uint8_t> credential_id) const {
-  // The sane way to delete a credential would be by SecKeyRef, like so:
+  // The reasonable way to delete a credential would be by SecKeyRef, like so:
   //
-  //   base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> query(
-  //       CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-  //                                 &kCFTypeDictionaryKeyCallBacks,
-  //                                 &kCFTypeDictionaryValueCallBacks));
-  //   CFDictionarySetValue(query, kSecValueRef, sec_key_ref);
-  //   OSStatus status = AppleKeychainV2::GetInstance().ItemDelete(query);
+  //   NSDictionary* query = @{
+  //     CFToNSPtrCast(kSecValueRef) : (__bridge id)sec_key_ref,
+  //   };
+  //   OSStatus status =
+  //       AppleKeychainV2::GetInstance().ItemDelete(NSToCFPtrCast(query));
   //
   // But on macOS that looks for `sec_key_ref` in the legacy keychain instead of
   // the "iOS" keychain that secure enclave credentials live in, and so the call
@@ -658,22 +623,18 @@
   // `kSecUseDataProtectionKeychain` to force a query to the right keychain, but
   // we need to support older versions of macOS for now. Hence, we must delete
   // keychain items by credential ID (stored in `kSecAttrApplicationLabel`).
-  // TODO(https://crbug.com/1463798): Update to this better approach that
+  // TODO(https://crbug.com/40275358): Update to this better approach that
   // requires 10.15 now that Chromium requires 10.15.
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> query(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(
-      query.get(), kSecAttrAccessGroup,
-      base::SysUTF8ToCFStringRef(config_.keychain_access_group).get());
-  CFDictionarySetValue(query.get(), kSecClass, kSecClassKey);
-  CFDictionarySetValue(
-      query.get(), kSecAttrApplicationLabel,
-      base::apple::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
-                                                length:credential_id.size()]));
+  NSDictionary* query = @{
+    CFToNSPtrCast(kSecAttrAccessGroup) :
+        base::SysUTF8ToNSString(config_.keychain_access_group),
+    CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey),
+    CFToNSPtrCast(kSecAttrApplicationLabel) :
+        [NSData dataWithBytes:credential_id.data() length:credential_id.size()],
+  };
+
   OSStatus status =
-      crypto::AppleKeychainV2::GetInstance().ItemDelete(query.get());
+      crypto::AppleKeychainV2::GetInstance().ItemDelete(NSToCFPtrCast(query));
   if (status != errSecSuccess) {
     OSSTATUS_DLOG(ERROR, status) << "SecItemDelete failed";
     return false;
@@ -693,42 +654,34 @@
     FIDO_LOG(ERROR) << "no credentials found";
     return false;
   }
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> params(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  bool found_credential = false;
+
+  NSData* sealed_metadata_data = nil;
   for (Credential& credential : *credentials) {
     if (credential.credential_id == credential_id) {
       credential.metadata.user_name = username;
       std::vector<uint8_t> sealed_metadata = SealCredentialMetadata(
           config_.metadata_secret, credential.rp_id, credential.metadata);
-      CFDictionarySetValue(params.get(), kSecAttrApplicationTag,
-                           base::apple::NSToCFPtrCast([NSData
-                               dataWithBytes:sealed_metadata.data()
-                                      length:sealed_metadata.size()]));
-      found_credential = true;
+      sealed_metadata_data = [NSData dataWithBytes:sealed_metadata.data()
+                                            length:sealed_metadata.size()];
       break;
     }
   }
-  if (!found_credential) {
+  if (!sealed_metadata_data) {
     FIDO_LOG(ERROR) << "no credential with matching credential_id";
     return false;
   }
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> query(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(
-      query.get(), kSecAttrAccessGroup,
-      base::SysUTF8ToCFStringRef(config_.keychain_access_group).get());
-  CFDictionarySetValue(query.get(), kSecClass, kSecClassKey);
-  CFDictionarySetValue(
-      query.get(), kSecAttrApplicationLabel,
-      base::apple::NSToCFPtrCast([NSData dataWithBytes:credential_id.data()
-                                                length:credential_id.size()]));
-  OSStatus status =
-      crypto::AppleKeychainV2::GetInstance().ItemUpdate(query.get(), params);
+
+  NSDictionary* query = @{
+    CFToNSPtrCast(kSecAttrAccessGroup) :
+        base::SysUTF8ToNSString(config_.keychain_access_group),
+    CFToNSPtrCast(kSecClass) : CFToNSPtrCast(kSecClassKey),
+    CFToNSPtrCast(kSecAttrApplicationLabel) :
+        [NSData dataWithBytes:credential_id.data() length:credential_id.size()],
+  };
+  NSDictionary* params =
+      @{CFToNSPtrCast(kSecAttrApplicationTag) : sealed_metadata_data};
+  OSStatus status = crypto::AppleKeychainV2::GetInstance().ItemUpdate(
+      NSToCFPtrCast(query), NSToCFPtrCast(params));
   if (status != errSecSuccess) {
     OSSTATUS_DLOG(ERROR, status) << "SecItemUpdate failed";
     return false;
diff --git a/device/fido/mac/touch_id_context.mm b/device/fido/mac/touch_id_context.mm
index 6556fa5..1396fa2 100644
--- a/device/fido/mac/touch_id_context.mm
+++ b/device/fido/mac/touch_id_context.mm
@@ -24,6 +24,9 @@
 #include "crypto/apple_keychain_v2.h"
 #include "device/fido/mac/authenticator_config.h"
 
+using base::apple::CFToNSPtrCast;
+using base::apple::NSToCFPtrCast;
+
 namespace device::fido::mac {
 
 namespace {
@@ -62,22 +65,19 @@
   base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                 base::BlockingType::MAY_BLOCK);
 
-  base::apple::ScopedCFTypeRef<CFMutableDictionaryRef> params(
-      CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
-                                &kCFTypeDictionaryKeyCallBacks,
-                                &kCFTypeDictionaryValueCallBacks));
-  CFDictionarySetValue(params.get(), kSecAttrKeyType,
-                       kSecAttrKeyTypeECSECPrimeRandom);
-  CFDictionarySetValue(params.get(), kSecAttrKeySizeInBits,
-                       base::apple::NSToCFPtrCast(@256));
-  CFDictionarySetValue(params.get(), kSecAttrTokenID,
-                       kSecAttrTokenIDSecureEnclave);
-  CFDictionarySetValue(params.get(), kSecAttrIsPermanent, kCFBooleanFalse);
+  NSDictionary* params = @{
+    CFToNSPtrCast(kSecAttrKeyType) :
+        CFToNSPtrCast(kSecAttrKeyTypeECSECPrimeRandom),
+    CFToNSPtrCast(kSecAttrKeySizeInBits) : @256,
+    CFToNSPtrCast(kSecAttrTokenID) :
+        CFToNSPtrCast(kSecAttrTokenIDSecureEnclave),
+    CFToNSPtrCast(kSecAttrIsPermanent) : @NO,
+  };
 
   base::apple::ScopedCFTypeRef<CFErrorRef> cferr;
   base::apple::ScopedCFTypeRef<SecKeyRef> private_key(
       crypto::AppleKeychainV2::GetInstance().KeyCreateRandomKey(
-          params.get(), cferr.InitializeInto()));
+          NSToCFPtrCast(params), cferr.InitializeInto()));
   return !!private_key;
 }
 
diff --git a/device/fido/virtual_fido_device.cc b/device/fido/virtual_fido_device.cc
index 9eadb3b9..71fa246 100644
--- a/device/fido/virtual_fido_device.cc
+++ b/device/fido/virtual_fido_device.cc
@@ -6,8 +6,8 @@
 
 #include <tuple>
 #include <utility>
+#include <vector>
 
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/logging.h"
 #include "base/rand_util.h"
 #include "base/ranges/algorithm.h"
@@ -501,7 +501,7 @@
       reader.Materialize().value_or(cbor::Value::ArrayValue());
 
   if (credential->large_blob_key) {
-    base::EraseIf(
+    std::erase_if(
         large_blob_array, [&credential](const cbor::Value& blob_cbor) {
           std::optional<LargeBlobData> blob = LargeBlobData::Parse(blob_cbor);
           return blob && blob->Decrypt(*credential->large_blob_key).has_value();
diff --git a/device/vr/android/web_xr_presentation_state.cc b/device/vr/android/web_xr_presentation_state.cc
index d11fd07..94b4254 100644
--- a/device/vr/android/web_xr_presentation_state.cc
+++ b/device/vr/android/web_xr_presentation_state.cc
@@ -6,8 +6,8 @@
 
 #include <iomanip>
 #include <sstream>
+#include <vector>
 
-#include "base/containers/cxx20_erase.h"
 #include "base/logging.h"
 #include "base/trace_event/trace_event.h"
 #include "components/viz/common/frame_sinks/begin_frame_args.h"
@@ -200,7 +200,7 @@
   // Remove it from the list, and then recycle the frame.
   DVLOG(3) << DebugState() << __func__;
   DCHECK_EQ(state_machine_type_, StateMachineType::kVizComposited);
-  auto erased = base::EraseIf(rendering_frames_,
+  auto erased = std::erase_if(rendering_frames_,
                               [frame](const WebXrFrame* rendering_frame) {
                                 return frame == rendering_frame;
                               });
diff --git a/device/vr/orientation/orientation_device.cc b/device/vr/orientation/orientation_device.cc
index aae624e..ed59c46 100644
--- a/device/vr/orientation/orientation_device.cc
+++ b/device/vr/orientation/orientation_device.cc
@@ -5,8 +5,8 @@
 #include <math.h>
 
 #include <numbers>
+#include <vector>
 
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/no_destructor.h"
@@ -194,7 +194,7 @@
 
 void VROrientationDevice::EndMagicWindowSession(VROrientationSession* session) {
   DVLOG(2) << __func__;
-  base::EraseIf(magic_window_sessions_,
+  std::erase_if(magic_window_sessions_,
                 [session](const std::unique_ptr<VROrientationSession>& item) {
                   return item.get() == session;
                 });
diff --git a/docs/website b/docs/website
index ecc6dae..05de69c 160000
--- a/docs/website
+++ b/docs/website
@@ -1 +1 @@
-Subproject commit ecc6daec0bc6bc14edaad139f726d7a0154d4572
+Subproject commit 05de69cbec5c294245bfb96d9be826065c05e6eb
diff --git a/extensions/browser/api/declarative_net_request/composite_matcher.cc b/extensions/browser/api/declarative_net_request/composite_matcher.cc
index b26bb5f..3cbcd03 100644
--- a/extensions/browser/api/declarative_net_request/composite_matcher.cc
+++ b/extensions/browser/api/declarative_net_request/composite_matcher.cc
@@ -8,9 +8,9 @@
 #include <iterator>
 #include <set>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/ranges/algorithm.h"
 #include "base/time/time.h"
@@ -99,7 +99,7 @@
 }
 
 void CompositeMatcher::RemoveRulesetsWithIDs(const std::set<RulesetID>& ids) {
-  size_t erased_count = base::EraseIf(
+  size_t erased_count = std::erase_if(
       matchers_, [&ids](const std::unique_ptr<RulesetMatcher>& matcher) {
         return base::Contains(ids, matcher->id());
       });
diff --git a/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc b/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
index 2353639..114814d8 100644
--- a/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
+++ b/extensions/browser/api/declarative_net_request/declarative_net_request_api.cc
@@ -9,8 +9,8 @@
 #include <set>
 #include <utility>
 #include <vector>
+
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/time/time.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -65,7 +65,7 @@
   // Filter the rules by the rule IDs, if provided.
   if (rule_filter.rule_ids) {
     const base::flat_set<int>& rule_ids = *rule_filter.rule_ids;
-    base::EraseIf(rules, [rule_ids](const auto& rule) {
+    std::erase_if(rules, [rule_ids](const auto& rule) {
       return !rule_ids.contains(rule.id);
     });
   }
@@ -356,7 +356,7 @@
 
     // Exclude any reserved ruleset IDs since they would correspond to
     // non-static rulesets.
-    base::EraseIf(public_ids, [](const std::string& id) {
+    std::erase_if(public_ids, [](const std::string& id) {
       DCHECK(!id.empty());
       return id[0] == declarative_net_request::kReservedRulesetIDPrefix;
     });
diff --git a/extensions/browser/api/declarative_net_request/extension_url_pattern_index_matcher.cc b/extensions/browser/api/declarative_net_request/extension_url_pattern_index_matcher.cc
index e472f6d..0d8fb50d 100644
--- a/extensions/browser/api/declarative_net_request/extension_url_pattern_index_matcher.cc
+++ b/extensions/browser/api/declarative_net_request/extension_url_pattern_index_matcher.cc
@@ -9,9 +9,9 @@
 #include <list>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "base/check_op.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/notreached.h"
 #include "extensions/browser/api/declarative_net_request/request_action.h"
 #include "extensions/browser/api/declarative_net_request/request_params.h"
@@ -120,7 +120,7 @@
       params, before_request_matchers_, flat::IndexType_modify_headers);
 
   if (min_priority) {
-    base::EraseIf(rules, [&min_priority](const flat_rule::UrlRule* rule) {
+    std::erase_if(rules, [&min_priority](const flat_rule::UrlRule* rule) {
       return rule->priority() <= *min_priority;
     });
   }
diff --git a/extensions/browser/api/declarative_net_request/file_sequence_helper.cc b/extensions/browser/api/declarative_net_request/file_sequence_helper.cc
index 6cf3aff..1bcb7c01 100644
--- a/extensions/browser/api/declarative_net_request/file_sequence_helper.cc
+++ b/extensions/browser/api/declarative_net_request/file_sequence_helper.cc
@@ -7,11 +7,11 @@
 #include <cstdint>
 #include <set>
 #include <utility>
+#include <vector>
 
 #include "base/barrier_closure.h"
 #include "base/check_op.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/files/file_util.h"
 #include "base/files/important_file_writer.h"
 #include "base/functional/bind.h"
@@ -201,7 +201,7 @@
 
   // Remove old rules
   std::set<int> ids_to_remove(rule_ids_to_remove.begin(), rule_ids_to_remove.end());
-  base::EraseIf(*new_rules, [&ids_to_remove](const dnr_api::Rule& rule) {
+  std::erase_if(*new_rules, [&ids_to_remove](const dnr_api::Rule& rule) {
     return base::Contains(ids_to_remove, rule.id);
   });
 
diff --git a/extensions/browser/api/declarative_net_request/rules_monitor_service.cc b/extensions/browser/api/declarative_net_request/rules_monitor_service.cc
index b12c6440..c6b35b7 100644
--- a/extensions/browser/api/declarative_net_request/rules_monitor_service.cc
+++ b/extensions/browser/api/declarative_net_request/rules_monitor_service.cc
@@ -5,10 +5,10 @@
 #include "extensions/browser/api/declarative_net_request/rules_monitor_service.h"
 
 #include <utility>
+#include <vector>
 
 #include "base/check_op.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/containers/queue.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
@@ -673,7 +673,7 @@
 
   std::set<int> ids_to_remove(rule_ids_to_remove.begin(),
                               rule_ids_to_remove.end());
-  base::EraseIf(new_rules, [&ids_to_remove](const dnr_api::Rule& rule) {
+  std::erase_if(new_rules, [&ids_to_remove](const dnr_api::Rule& rule) {
     return base::Contains(ids_to_remove, rule.id);
   });
 
diff --git a/extensions/browser/api/i18n/i18n_api.cc b/extensions/browser/api/i18n/i18n_api.cc
index 0bfdeb6..7797d62 100644
--- a/extensions/browser/api/i18n/i18n_api.cc
+++ b/extensions/browser/api/i18n/i18n_api.cc
@@ -8,7 +8,6 @@
 #include <string>
 #include <vector>
 
-#include "base/containers/cxx20_erase.h"
 #include "base/strings/string_split.h"
 #include "components/language/core/browser/pref_names.h"
 #include "components/prefs/pref_service.h"
@@ -47,7 +46,7 @@
 
   std::vector<std::string> languages = base::SplitString(
       accept_languages, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
-  base::Erase(languages, "");
+  std::erase(languages, "");
 
   if (languages.empty())
     return RespondNow(Error(kEmptyAcceptLanguagesError));
diff --git a/extensions/browser/api/runtime/runtime_api.cc b/extensions/browser/api/runtime/runtime_api.cc
index bac494f6..6d3d98c7 100644
--- a/extensions/browser/api/runtime/runtime_api.cc
+++ b/extensions/browser/api/runtime/runtime_api.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <utility>
+#include <vector>
 
 #include "base/check.h"
 #include "base/functional/bind.h"
@@ -829,7 +830,7 @@
                 std::make_move_iterator(frame_contexts.end()));
 
   // Erase any contexts that don't match the specified filter.
-  base::EraseIf(result,
+  std::erase_if(result,
                 [&filter](const api::runtime::ExtensionContext& context) {
                   return !ExtensionContextMatchesFilter(context, filter);
                 });
diff --git a/extensions/browser/api/socket/udp_socket.cc b/extensions/browser/api/socket/udp_socket.cc
index 01f30e656..953356f 100644
--- a/extensions/browser/api/socket/udp_socket.cc
+++ b/extensions/browser/api/socket/udp_socket.cc
@@ -5,9 +5,9 @@
 #include "extensions/browser/api/socket/udp_socket.h"
 
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/lazy_instance.h"
 #include "base/ranges/algorithm.h"
@@ -308,7 +308,7 @@
                                       const std::string& normalized_address,
                                       int result) {
   if (result == net::OK) {
-    base::Erase(multicast_groups_, normalized_address);
+    std::erase(multicast_groups_, normalized_address);
   }
 
   std::move(callback).Run(result);
diff --git a/extensions/browser/api/web_request/extension_web_request_event_router.cc b/extensions/browser/api/web_request/extension_web_request_event_router.cc
index 8092008..0963fac 100644
--- a/extensions/browser/api/web_request/extension_web_request_event_router.cc
+++ b/extensions/browser/api/web_request/extension_web_request_event_router.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 #include <string_view>
+#include <vector>
 
 #include "base/containers/fixed_flat_map.h"
 #include "base/feature_list.h"
@@ -1660,7 +1661,7 @@
     // the *same order* in the extension. In practice, this should pretty much
     // always be the case, because we require listeners to be set up
     // synchronously.
-    size_t erased = base::EraseIf(
+    size_t erased = std::erase_if(
         listeners, [browser_context, extension_id, sub_event_name](
                        const std::unique_ptr<EventListener>& listener) {
           return listener->id.browser_context == browser_context &&
diff --git a/extensions/browser/extension_prefs.cc b/extensions/browser/extension_prefs.cc
index f310a5e..ee971fa 100644
--- a/extensions/browser/extension_prefs.cc
+++ b/extensions/browser/extension_prefs.cc
@@ -10,10 +10,10 @@
 #include <iterator>
 #include <string_view>
 #include <utility>
+#include <vector>
 
 #include "base/check_op.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/json/values_util.h"
 #include "base/observer_list.h"
 #include "base/ranges/algorithm.h"
@@ -1955,7 +1955,7 @@
       return !Manifest::ShouldAlwaysLoadExtension(info.extension_location,
                                                   is_theme);
     };
-    base::EraseIf(extensions_info, predicate);
+    std::erase_if(extensions_info, predicate);
   }
 
   InitExtensionControlledPrefs(extensions_info);
diff --git a/extensions/browser/script_executor.cc b/extensions/browser/script_executor.cc
index 1fe948a..c50423d 100644
--- a/extensions/browser/script_executor.cc
+++ b/extensions/browser/script_executor.cc
@@ -7,10 +7,10 @@
 #include <map>
 #include <set>
 #include <string>
+#include <vector>
 
 #include "base/check_op.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/dcheck_is_on.h"
 #include "base/functional/bind.h"
 #include "base/hash/hash.h"
@@ -143,7 +143,7 @@
 
   void RenderFrameDeleted(
       content::RenderFrameHost* render_frame_host) override {
-    int erased_count = base::Erase(pending_render_frames_, render_frame_host);
+    int erased_count = std::erase(pending_render_frames_, render_frame_host);
     DCHECK_LE(erased_count, 1);
     if (erased_count == 0)
       return;
@@ -268,7 +268,7 @@
       return;
 
     DCHECK(!pending_render_frames_.empty());
-    size_t erased = base::Erase(pending_render_frames_, render_frame_host);
+    size_t erased = std::erase(pending_render_frames_, render_frame_host);
     DCHECK_EQ(1u, erased);
 
     // TODO(devlin): Do we need to trust the renderer for the URL here? Is there
diff --git a/extensions/browser/service_worker/service_worker_host.cc b/extensions/browser/service_worker/service_worker_host.cc
index a4ce886e..e479982 100644
--- a/extensions/browser/service_worker/service_worker_host.cc
+++ b/extensions/browser/service_worker/service_worker_host.cc
@@ -4,6 +4,8 @@
 
 #include "extensions/browser/service_worker/service_worker_host.h"
 
+#include <vector>
+
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/trace_event/typed_macros.h"
 #include "content/public/browser/browser_context.h"
@@ -356,8 +358,8 @@
   auto* service_worker_host_list = ServiceWorkerHostList::Get(
       render_process_host_, /*create_if_not_exists=*/false);
   CHECK(service_worker_host_list);
-  // base::EraseIf will lead to a call to the destructor for this object.
-  base::EraseIf(service_worker_host_list->list, base::MatchesUniquePtr(this));
+  // std::erase_if will lead to a call to the destructor for this object.
+  std::erase_if(service_worker_host_list->list, base::MatchesUniquePtr(this));
 }
 
 void ServiceWorkerHost::RenderProcessExited(
diff --git a/extensions/browser/user_script_loader.cc b/extensions/browser/user_script_loader.cc
index dea428a..305e03a 100644
--- a/extensions/browser/user_script_loader.cc
+++ b/extensions/browser/user_script_loader.cc
@@ -10,8 +10,8 @@
 #include <string>
 #include <string_view>
 #include <utility>
+#include <vector>
 
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/memory/writable_shared_memory_region.h"
 #include "base/observer_list.h"
@@ -305,7 +305,7 @@
   loaded_scripts_.reset();
 
   // Filter out any scripts that are queued for removal.
-  base::EraseIf(scripts_to_load,
+  std::erase_if(scripts_to_load,
                 [this](const std::unique_ptr<UserScript>& script) {
                   return removed_script_ids_.count(script->id()) > 0u;
                 });
@@ -313,7 +313,7 @@
   // Since all scripts managed by an instance of this class should have unique
   // IDs, remove any already loaded scripts from `scripts_to_load` that will be
   // updated from `added_scripts_map_`.
-  base::EraseIf(scripts_to_load,
+  std::erase_if(scripts_to_load,
                 [this](const std::unique_ptr<UserScript>& script) {
                   return added_scripts_map_.count(script->id()) > 0u;
                 });
diff --git a/extensions/renderer/api/messaging/one_time_message_handler.cc b/extensions/renderer/api/messaging/one_time_message_handler.cc
index be6a480..ef300f94 100644
--- a/extensions/renderer/api/messaging/one_time_message_handler.cc
+++ b/extensions/renderer/api/messaging/one_time_message_handler.cc
@@ -5,9 +5,9 @@
 #include "extensions/renderer/api/messaging/one_time_message_handler.h"
 
 #include <map>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/ranges/algorithm.h"
@@ -562,7 +562,7 @@
 
   // Since there is no way to call the callback anymore, we can remove it from
   // the pending callbacks.
-  base::EraseIf(
+  std::erase_if(
       data->pending_callbacks,
       [raw_callback](const std::unique_ptr<OneTimeMessageCallback>& callback) {
         return reinterpret_cast<CallbackID>(callback.get()) == raw_callback;
diff --git a/extensions/renderer/script_injection_manager.cc b/extensions/renderer/script_injection_manager.cc
index bcd888e..99a08dae 100644
--- a/extensions/renderer/script_injection_manager.cc
+++ b/extensions/renderer/script_injection_manager.cc
@@ -7,8 +7,9 @@
 #include <memory>
 #include <optional>
 #include <utility>
+#include <vector>
+
 #include "base/auto_reset.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
@@ -277,7 +278,7 @@
 }
 
 void ScriptInjectionManager::OnInjectionFinished(ScriptInjection* injection) {
-  base::EraseIf(running_injections_,
+  std::erase_if(running_injections_,
                 [&injection](const std::unique_ptr<ScriptInjection>& mode) {
                   return injection == mode.get();
                 });
@@ -285,7 +286,7 @@
 
 void ScriptInjectionManager::OnUserScriptsUpdated(
     const mojom::HostID& changed_host) {
-  base::EraseIf(
+  std::erase_if(
       pending_injections_,
       [&changed_host](const std::unique_ptr<ScriptInjection>& injection) {
         return changed_host == injection->host_id();
@@ -306,7 +307,7 @@
   // note it.
   active_injection_frames_.erase(frame);
 
-  base::EraseIf(pending_injections_,
+  std::erase_if(pending_injections_,
                 [&frame](const std::unique_ptr<ScriptInjection>& injection) {
                   return injection->render_frame() == frame;
                 });
diff --git a/google_apis/calendar/calendar_api_requests.cc b/google_apis/calendar/calendar_api_requests.cc
index b4a6f18..8f4eb47 100644
--- a/google_apis/calendar/calendar_api_requests.cc
+++ b/google_apis/calendar/calendar_api_requests.cc
@@ -11,6 +11,7 @@
 #include <utility>
 
 #include "base/memory/weak_ptr.h"
+#include "base/strings/strcat.h"
 #include "base/values.h"
 #include "google_apis/calendar/calendar_api_response_types.h"
 #include "google_apis/common/api_error_codes.h"
@@ -56,18 +57,21 @@
 // response.
 constexpr int kMaxResults = 2500;
 
-// Requested fields to be returned in the Event list result.
-constexpr char kCalendarEventListFields[] =
-    "timeZone,etag,kind,items(id,kind,summary,colorId,"
-    "status,start(date),end(date),start(dateTime),end(dateTime),htmlLink,"
-    "attendees(responseStatus,self),attendeesOmitted,"
-    "conferenceData(conferenceId,entryPoints(entryPointType,uri)),"
-    "creator(self))";
-
 // Requested fields to be returned in the CalendarList result.
 constexpr char kCalendarListFields[] =
     "etag,kind,items(id,colorId,selected,primary)";
 
+// Requested fields to be returned in the Event list result.
+std::string GetCalendarEventListFields(bool include_attachments) {
+  return base::StrCat(
+      {"timeZone,etag,kind,items(id,kind,summary,colorId,"
+       "status,start(date),end(date),start(dateTime),end(dateTime),htmlLink,"
+       "attendees(responseStatus,self),attendeesOmitted,"
+       "conferenceData(conferenceId,entryPoints(entryPointType,uri)),"
+       "creator(self)",
+       include_attachments ? ",attachments(title,fileUrl,iconLink)" : "", ")"});
+}
+
 }  // namespace
 
 CalendarApiGetRequest::CalendarApiGetRequest(RequestSender* sender,
@@ -172,7 +176,9 @@
     const base::Time& end_time,
     const std::string& calendar_id,
     const std::string& calendar_color_id)
-    : CalendarApiGetRequest(sender, kCalendarEventListFields),
+    : CalendarApiGetRequest(
+          sender,
+          GetCalendarEventListFields(/*include_attachments=*/false)),
       callback_(std::move(callback)),
       url_generator_(url_generator),
       start_time_(start_time),
@@ -187,8 +193,10 @@
     const CalendarApiUrlGenerator& url_generator,
     CalendarEventListCallback callback,
     const base::Time& start_time,
-    const base::Time& end_time)
-    : CalendarApiGetRequest(sender, kCalendarEventListFields),
+    const base::Time& end_time,
+    bool include_attachments)
+    : CalendarApiGetRequest(sender,
+                            GetCalendarEventListFields(include_attachments)),
       callback_(std::move(callback)),
       url_generator_(url_generator),
       start_time_(start_time),
diff --git a/google_apis/calendar/calendar_api_requests.h b/google_apis/calendar/calendar_api_requests.h
index 48c2f5fc..fa272b4 100644
--- a/google_apis/calendar/calendar_api_requests.h
+++ b/google_apis/calendar/calendar_api_requests.h
@@ -118,7 +118,8 @@
                            const CalendarApiUrlGenerator& url_generator,
                            CalendarEventListCallback callback,
                            const base::Time& start_time,
-                           const base::Time& end_time);
+                           const base::Time& end_time,
+                           bool include_attachments = false);
   CalendarApiEventsRequest(const CalendarApiEventsRequest&) = delete;
   CalendarApiEventsRequest& operator=(const CalendarApiEventsRequest&) = delete;
   ~CalendarApiEventsRequest() override;
diff --git a/google_apis/calendar/calendar_api_requests_unittest.cc b/google_apis/calendar/calendar_api_requests_unittest.cc
index 8f3e6fb..c8038ce 100644
--- a/google_apis/calendar/calendar_api_requests_unittest.cc
+++ b/google_apis/calendar/calendar_api_requests_unittest.cc
@@ -229,5 +229,48 @@
   EXPECT_EQ(events->items()[2]->color_id(), "");
 }
 
+// Tests that CalendarApiEventsRequest can generate the correct url when
+// attachments are requested.
+TEST_F(CalendarApiRequestsTest, GetEventListRequestWithAttachments) {
+  ApiErrorCode error = OTHER_ERROR;
+  std::unique_ptr<EventList> events;
+  base::Time start;
+  base::Time end;
+
+  EXPECT_TRUE(base::Time::FromString("13 Jun 2021 10:00 GMT", &start));
+  EXPECT_TRUE(base::Time::FromString("16 Jun 2021 10:00 GMT", &end));
+
+  {
+    base::RunLoop run_loop;
+    auto request = std::make_unique<CalendarApiEventsRequest>(
+        request_sender_.get(), *url_generator_,
+        test_util::CreateQuitCallback(
+            &run_loop, test_util::CreateCopyResultCallback(&error, &events)),
+        start, end, /*include_attachments=*/true);
+
+    request_sender_->StartRequestWithAuthRetry(std::move(request));
+    run_loop.Run();
+  }
+
+  EXPECT_EQ(HTTP_SUCCESS, error);
+  EXPECT_EQ(net::test_server::METHOD_GET, http_request_.method);
+  EXPECT_EQ(
+      "/calendar/v3/calendars/primary/events"
+      "?timeMin=2021-06-13T10%3A00%3A00.000Z"
+      "&timeMax=2021-06-16T10%3A00%3A00.000Z"
+      "&singleEvents=true"
+      "&maxAttendees=1"
+      "&maxResults=2500"
+      "&fields=timeZone%2Cetag%2Ckind%2Citems(id%2Ckind"
+      "%2Csummary%2CcolorId%2Cstatus"
+      "%2Cstart(date)%2Cend(date)"
+      "%2Cstart(dateTime)%2Cend(dateTime)"
+      "%2ChtmlLink%2Cattendees(responseStatus%2Cself)%2CattendeesOmitted"
+      "%2CconferenceData(conferenceId%2CentryPoints(entryPointType%2Curi))"
+      "%2Ccreator(self)"
+      "%2Cattachments(title%2CfileUrl%2CiconLink))",
+      http_request_.relative_url);
+}
+
 }  // namespace calendar
 }  // namespace google_apis
diff --git a/google_apis/gaia/fake_gaia.cc b/google_apis/gaia/fake_gaia.cc
index 226cda0..4cd0210 100644
--- a/google_apis/gaia/fake_gaia.cc
+++ b/google_apis/gaia/fake_gaia.cc
@@ -11,7 +11,6 @@
 #include "base/base64.h"
 #include "base/base_paths.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
@@ -922,7 +921,7 @@
   std::string gaia_id;
   GetQueryParameter(request.GetURL().query(), "gaia_id", &gaia_id);
 
-  if (!base::Erase(configuration_.signed_out_gaia_ids, gaia_id)) {
+  if (!std::erase(configuration_.signed_out_gaia_ids, gaia_id)) {
     http_response->set_code(net::HTTP_BAD_REQUEST);
     return;
   }
diff --git a/gpu/command_buffer/client/query_tracker.cc b/gpu/command_buffer/client/query_tracker.cc
index 2792af0..807331d 100644
--- a/gpu/command_buffer/client/query_tracker.cc
+++ b/gpu/command_buffer/client/query_tracker.cc
@@ -11,10 +11,10 @@
 #include <limits.h>
 #include <stddef.h>
 #include <stdint.h>
+#include <vector>
 
 #include "base/atomicops.h"
 #include "base/containers/circular_deque.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/numerics/safe_conversions.h"
 #include "gpu/command_buffer/client/gles2_cmd_helper.h"
 #include "gpu/command_buffer/client/gles2_implementation.h"
@@ -32,7 +32,7 @@
 QuerySyncManager::Bucket::~Bucket() = default;
 
 void QuerySyncManager::Bucket::FreePendingSyncs() {
-  base::EraseIf(pending_syncs, [this](const PendingSync& pending) {
+  std::erase_if(pending_syncs, [this](const PendingSync& pending) {
     QuerySync* sync = this->syncs + pending.index;
     if (base::subtle::Acquire_Load(&sync->process_count) ==
         pending.submit_count) {
diff --git a/gpu/command_buffer/service/context_group.cc b/gpu/command_buffer/service/context_group.cc
index 4f6ef80..37e2a54 100644
--- a/gpu/command_buffer/service/context_group.cc
+++ b/gpu/command_buffer/service/context_group.cc
@@ -10,9 +10,9 @@
 #include <algorithm>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "base/command_line.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/memory/raw_ptr.h"
 #include "build/build_config.h"
 #include "gpu/command_buffer/service/buffer_manager.h"
@@ -569,7 +569,7 @@
 }  // namespace anonymous
 
 bool ContextGroup::HaveContexts() {
-  base::EraseIf(decoders_, IsNull);
+  std::erase_if(decoders_, IsNull);
   return !decoders_.empty();
 }
 
@@ -579,7 +579,7 @@
 }
 
 void ContextGroup::Destroy(DecoderContext* decoder, bool have_context) {
-  base::EraseIf(decoders_, WeakPtrEquals<DecoderContext>(decoder));
+  std::erase_if(decoders_, WeakPtrEquals<DecoderContext>(decoder));
 
   // If we still have contexts do nothing.
   if (HaveContexts()) {
diff --git a/gpu/command_buffer/tests/gl_angle_shader_pixel_local_storage_unittest.cc b/gpu/command_buffer/tests/gl_angle_shader_pixel_local_storage_unittest.cc
index d6bdc2c9f..f785d82 100644
--- a/gpu/command_buffer/tests/gl_angle_shader_pixel_local_storage_unittest.cc
+++ b/gpu/command_buffer/tests/gl_angle_shader_pixel_local_storage_unittest.cc
@@ -13,6 +13,7 @@
 #include "gpu/command_buffer/tests/gl_manager.h"
 #include "gpu/command_buffer/tests/gl_test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gl/gl_implementation.h"
 
 namespace gpu {
 class ANGLEShaderPixelLocalStorageTest : public testing::Test {
@@ -171,6 +172,13 @@
     return;
   }
 
+// Test skipped on Intel-based Macs when running Metal. crbug.com/326278125
+#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64)
+  if (gl::GetANGLEImplementation() == gl::ANGLEImplementation::kMetal) {
+    return;
+  }
+#endif
+
   GLuint texs[4];
   glGenTextures(4, texs);
   for (GLuint tex : texs) {
@@ -213,6 +221,13 @@
     return;
   }
 
+// Test skipped on Intel-based Macs when running Metal. crbug.com/326278125
+#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_X86_64)
+  if (gl::GetANGLEImplementation() == gl::ANGLEImplementation::kMetal) {
+    return;
+  }
+#endif
+
   GLuint tex;
   glGenTextures(1, &tex);
   glBindTexture(GL_TEXTURE_2D, tex);
diff --git a/gpu/vulkan/vulkan_image_linux.cc b/gpu/vulkan/vulkan_image_linux.cc
index 1d8005d..dec506c 100644
--- a/gpu/vulkan/vulkan_image_linux.cc
+++ b/gpu/vulkan/vulkan_image_linux.cc
@@ -5,8 +5,8 @@
 #include "gpu/vulkan/vulkan_image.h"
 
 #include <tuple>
+#include <vector>
 
-#include "base/containers/cxx20_erase.h"
 #include "base/logging.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
@@ -152,7 +152,7 @@
 
   // Call GetImageFormatProperties with every modifier and filter the list
   // down to those that we know work.
-  base::EraseIf(props_vector, [&](const VkDrmFormatModifierPropertiesEXT& p) {
+  std::erase_if(props_vector, [&](const VkDrmFormatModifierPropertiesEXT& p) {
     VkPhysicalDeviceImageDrmFormatModifierInfoEXT mod_info = {
         .sType =
             VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT,
@@ -179,7 +179,7 @@
     return false;
 
   // Find compatible modifiers.
-  base::EraseIf(modifiers, [&props_vector](uint64_t modifier) {
+  std::erase_if(modifiers, [&props_vector](uint64_t modifier) {
     for (const auto& modifier_props : props_vector) {
       if (modifier == modifier_props.drmFormatModifier)
         return false;
diff --git a/infra/config/.lucicfgfmtrc b/infra/config/.lucicfgfmtrc
index 0b664a9..7e846306 100644
--- a/infra/config/.lucicfgfmtrc
+++ b/infra/config/.lucicfgfmtrc
@@ -38,7 +38,7 @@
         arg: "builder_group"
         arg: "builder_spec"
         arg: "mirrors"
-        arg: "try_settings"
+        arg: "builder_config_settings"
         arg: "gn_args"
         arg: "targets"
         arg: "pool"
diff --git a/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/gn-args.json b/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/gn-args.json
new file mode 100644
index 0000000..143093c
--- /dev/null
+++ b/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/gn-args.json
@@ -0,0 +1,12 @@
+{
+  "gn_args": {
+    "ffmpeg_branding": "ChromeOS",
+    "is_component_build": true,
+    "is_debug": true,
+    "proprietary_codecs": true,
+    "symbol_level": 1,
+    "target_os": "chromeos",
+    "use_cups": true,
+    "use_remoteexec": true
+  }
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/properties.json b/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/properties.json
new file mode 100644
index 0000000..0c641702
--- /dev/null
+++ b/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/properties.json
@@ -0,0 +1,63 @@
+{
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "additional_exclusions": [
+        "infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/gn-args.json"
+      ],
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "ci",
+              "builder": "linux-chromeos-dbg-oslogin",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-chromiumos-archive",
+              "builder_group": "chromium.fyi",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Debug",
+                "config": "chromium",
+                "target_arch": "intel",
+                "target_bits": 64,
+                "target_platform": "chromeos"
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "chromeos"
+                ],
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "ci",
+          "builder": "linux-chromeos-dbg-oslogin",
+          "project": "chromium"
+        }
+      ]
+    }
+  },
+  "$build/reclient": {
+    "instance": "rbe-chromium-trusted",
+    "jobs": 250,
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
+  },
+  "$recipe_engine/resultdb/test_presentation": {
+    "column_keys": [],
+    "grouping_keys": [
+      "status",
+      "v.test_suite"
+    ]
+  },
+  "builder_group": "chromium.fyi",
+  "recipe": "chromium"
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/shadow-properties.json b/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/shadow-properties.json
new file mode 100644
index 0000000..999510c
--- /dev/null
+++ b/infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/shadow-properties.json
@@ -0,0 +1,8 @@
+{
+  "$build/reclient": {
+    "instance": "rbe-chromium-untrusted",
+    "jobs": 250,
+    "metrics_project": "chromium-reclient-metrics",
+    "scandeps_server": true
+  }
+}
\ No newline at end of file
diff --git a/infra/config/generated/builders/gn_args_locations.json b/infra/config/generated/builders/gn_args_locations.json
index a5607269..bb0b281 100644
--- a/infra/config/generated/builders/gn_args_locations.json
+++ b/infra/config/generated/builders/gn_args_locations.json
@@ -294,6 +294,7 @@
     "linux-blink-heap-verification": "ci/linux-blink-heap-verification/gn-args.json",
     "linux-blink-wpt-reset-rel": "ci/linux-blink-wpt-reset-rel/gn-args.json",
     "linux-chromeos-annotator-rel": "ci/linux-chromeos-annotator-rel/gn-args.json",
+    "linux-chromeos-dbg-oslogin": "ci/linux-chromeos-dbg-oslogin/gn-args.json",
     "linux-fieldtrial-rel": "ci/linux-fieldtrial-rel/gn-args.json",
     "linux-headless-shell-rel": "ci/linux-headless-shell-rel/gn-args.json",
     "linux-lacros-builder-fyi-rel": "ci/linux-lacros-builder-fyi-rel/gn-args.json",
diff --git a/infra/config/generated/health-specs/health-specs.json b/infra/config/generated/health-specs/health-specs.json
index 3068015..5356bf1 100644
--- a/infra/config/generated/health-specs/health-specs.json
+++ b/infra/config/generated/health-specs/health-specs.json
@@ -9501,6 +9501,27 @@
           }
         ]
       },
+      "linux-chromeos-dbg-oslogin": {
+        "contact_team_email": "chrome-browser-infra@google.com",
+        "problem_specs": [
+          {
+            "name": "Unhealthy",
+            "period_days": 7,
+            "score": 5,
+            "thresholds": {
+              "_default": "_default"
+            }
+          },
+          {
+            "name": "Low Value",
+            "period_days": 90,
+            "score": 1,
+            "thresholds": {
+              "_default": "_default"
+            }
+          }
+        ]
+      },
       "linux-chromeos-rel": {
         "contact_team_email": "chromeos-sw-engprod@google.com",
         "problem_specs": [
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index c0ce45b..3c6b02a 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -44284,6 +44284,98 @@
       contact_team_email: "chromeos-sw-engprod@google.com"
     }
     builders {
+      name: "linux-chromeos-dbg-oslogin"
+      swarming_host: "chromium-swarm.appspot.com"
+      dimensions: "builder:linux-chromeos-dbg-oslogin"
+      dimensions: "cores:8"
+      dimensions: "cpu:x86-64"
+      dimensions: "pool:luci.chromium.ci"
+      exe {
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+      }
+      properties:
+        '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/properties.json",'
+        '    "shadow_properties_file": "infra/config/generated/builders/ci/linux-chromeos-dbg-oslogin/shadow-properties.json",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
+        '  },'
+        '  "builder_group": "chromium.fyi",'
+        '  "led_builder_is_bootstrapped": true,'
+        '  "recipe": "chromium"'
+        '}'
+      priority: 35
+      execution_timeout_secs: 36000
+      build_numbers: YES
+      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
+      experiments {
+        key: "chromium_swarming.expose_merge_script_failures"
+        value: 100
+      }
+      experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      resultdb {
+        enable: true
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "ci_test_results"
+          test_results {}
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "gpu_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
+            }
+          }
+        }
+        bq_exports {
+          project: "chrome-luci-data"
+          dataset: "chromium"
+          table: "blink_web_tests_ci_test_results"
+          test_results {
+            predicate {
+              test_id_regexp: "(ninja://[^/]*blink_web_tests/.+)|(ninja://[^/]*_wpt_tests/.+)"
+            }
+          }
+        }
+        history_options {
+          use_invocation_timestamp: true
+        }
+      }
+      description_html: "This builder is used to debug spefically oslogin issues relatedto linux-chromeos-dbg-oslogin"
+      shadow_builder_adjustments {
+        service_account: "chromium-try-builder@chops-service-accounts.iam.gserviceaccount.com"
+        pool: "luci.chromium.try"
+        dimensions: "builder:"
+        dimensions: "builderless:1"
+        dimensions: "pool:luci.chromium.try"
+      }
+      contact_team_email: "chrome-browser-infra@google.com"
+    }
+    builders {
       name: "linux-chromeos-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:linux-chromeos-rel"
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 1229ba4..a443894 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -9513,6 +9513,11 @@
     short_name: "lk"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/linux-chromeos-dbg-oslogin"
+    category: "linux"
+    short_name: "lnx"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/Linux Builder (reclient compare)"
     category: "linux"
     short_name: "re"
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 60171a3..670c8880 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -4843,6 +4843,15 @@
   }
 }
 job {
+  id: "linux-chromeos-dbg-oslogin"
+  realm: "ci"
+  buildbucket {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "ci"
+    builder: "linux-chromeos-dbg-oslogin"
+  }
+}
+job {
   id: "linux-chromeos-rel"
   realm: "ci"
   buildbucket {
@@ -6593,6 +6602,7 @@
   triggers: "linux-chromeos-annotator-rel"
   triggers: "linux-chromeos-archive-rel"
   triggers: "linux-chromeos-dbg"
+  triggers: "linux-chromeos-dbg-oslogin"
   triggers: "linux-chromeos-rel"
   triggers: "linux-codeql-generator"
   triggers: "linux-extended-tracing-rel"
diff --git a/infra/config/generated/testing/mixins.pyl b/infra/config/generated/testing/mixins.pyl
index 1f5c782..7f8052e4 100644
--- a/infra/config/generated/testing/mixins.pyl
+++ b/infra/config/generated/testing/mixins.pyl
@@ -399,6 +399,13 @@
       'service_account': 'chromium-tester@chops-service-accounts.iam.gserviceaccount.com',
     },
   },
+  'chromium-tests-oslogin': {
+    'swarming': {
+      'dimensions': {
+        'pool': 'chromium.tests.oslogin',
+      },
+    },
+  },
   'ci_only': {
     'ci_only': True,
   },
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 7ac9198..99bda77 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -307,16 +307,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 124.0.6325.0',
+    'description': 'Run with ash-chrome version 124.0.6326.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v124.0.6325.0',
-          'revision': 'version:124.0.6325.0',
+          'location': 'lacros_version_skew_tests_v124.0.6326.0',
+          'revision': 'version:124.0.6326.0',
         },
       ],
     },
diff --git a/infra/config/lib/builder_config.star b/infra/config/lib/builder_config.star
index be7f105..5ada922 100644
--- a/infra/config/lib/builder_config.star
+++ b/infra/config/lib/builder_config.star
@@ -315,6 +315,23 @@
         bisect_archive = bisect_archive,
     )
 
+def _ci_settings(
+        *,
+        retry_failed_shards = None):
+    """Settings specific to CI builders.
+
+    Args:
+        retry_failed_shards: (bool) Whether or not failing shards of a test will
+            be retried. If retries for all failed shards of a test succeed, the
+            test will be considered to have passed.
+
+    Returns:
+        A struct that can be passed to the `ci_settings` argument of the builder.
+    """
+    return struct(
+        retry_failed_shards = retry_failed_shards,
+    )
+
 def _try_settings(
         *,
         include_all_triggered_testers = False,
@@ -403,6 +420,7 @@
     android_config = _android_config,
 
     # Function for defining try-specific settings
+    ci_settings = _ci_settings,
     try_settings = _try_settings,
 )
 
@@ -436,7 +454,7 @@
         builder_group,
         builder_spec,
         mirrors,
-        try_settings,
+        settings,
         targets,
         additional_exclusions):
     """Registers the builder config so the properties can be computed.
@@ -450,15 +468,16 @@
         builder_group: The name of the group the builder belongs to.
         builder_spec: The spec describing the configuration for the builder.
         mirrors: References to the builders that the builder should mirror.
-        try_settings: The object determining the try-specific settings.
+        settings: The object determining the additional settings applied to
+            builder_config.
         targets: The targets to be built/run by the builder.
         additional_exclusions: A list of paths that are excluded when analyzing
             the change to determine affected targets. The paths should be
             relative to the per-builder output root dir.
     """
     if not builder_spec and not mirrors:
-        if try_settings:
-            fail("try_settings specified without builder_spec or mirrors")
+        if settings:
+            fail("settings specified without builder_spec or mirrors")
 
         # TODO(gbeaty) Eventually make this a failure for the chromium
         # family of recipes
@@ -471,13 +490,23 @@
     if targets and not builder_spec:
         fail("builder_spec must be set to set targets")
 
-    if not try_settings:
-        try_settings = _try_settings(include_all_triggered_testers = not mirrors)
+    include_all_triggered_testers = None
+    settings_fields = {}
+    if settings:
+        settings_fields = structs.to_proto_properties(settings)
+        include_all_triggered_testers = settings_fields.pop(
+            "include_all_triggered_testers",
+            None,
+        )
+    if include_all_triggered_testers == None:
+        include_all_triggered_testers = not mirrors
+
     builder_config_key = _BUILDER_CONFIG.add(bucket, name, props = dict(
         builder_group = builder_group,
         builder_spec = builder_spec,
         mirrors = mirrors,
-        try_settings = try_settings,
+        include_all_triggered_testers = include_all_triggered_testers,
+        settings_fields = settings_fields,
         additional_exclusions = additional_exclusions,
     ))
 
@@ -531,7 +560,7 @@
     parent = bc_state.parent(node)
     if parent:
         for m in _get_mirroring_nodes(bc_state, parent):
-            if m.props.try_settings.include_all_triggered_testers:
+            if m.props.include_all_triggered_testers:
                 nodes.append(m)
 
     return nodes
@@ -726,7 +755,7 @@
                     if parent:
                         add(parent)
                     add(m, parent)
-                    if node.props.try_settings.include_all_triggered_testers:
+                    if node.props.include_all_triggered_testers:
                         for child in bc_state.children(m):
                             add(child, m)
 
@@ -747,7 +776,7 @@
                     entries = sorted(entries, key = lambda e: _builder_id_sort_key(e["builder_id"])),
                 ),
                 builder_ids = sorted(builder_ids, key = _builder_id_sort_key),
-                **structs.to_proto_properties(node.props.try_settings)
+                **node.props.settings_fields
             )
             if node.props.additional_exclusions:
                 builder_config["additional_exclusions"] = [
@@ -757,7 +786,6 @@
                     )
                     for exclusion in node.props.additional_exclusions
                 ]
-            builder_config.pop("include_all_triggered_testers", None)
 
             if builder_ids_in_scope_for_testing:
                 builder_config["builder_ids_in_scope_for_testing"] = (
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index 70accd1..159ee4ff 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -451,7 +451,7 @@
         builder_group = args.DEFAULT,
         builder_spec = None,
         mirrors = None,
-        try_settings = None,
+        builder_config_settings = None,
         pool = args.DEFAULT,
         ssd = args.DEFAULT,
         sheriff_rotations = None,
@@ -589,8 +589,9 @@
             Cannot be set if `mirrors` is set.
         mirrors: References to the builders that the builder should mirror.
             Cannot be set if `builder_spec` is set.
-        try_settings: Try-builder-specific settings, can only be set if
-            `mirrors` is set.
+        builder_config_settings: Additional builder configuration that used by
+            the recipes. Could be an instance of ci_settings or try_settings.
+            It can only be set if one of builder_spec or mirrors is set.
         pool: a string indicating the pool of the machines that run the builder.
             Emits a dimension of the form 'pool:<pool>'. By default, considered
             None. When running a builder that has no explicit pool dimension,
@@ -761,8 +762,9 @@
 
     if builder_spec and mirrors:
         fail("Only one of builder_spec or mirrors can be set")
-    if try_settings and not (builder_spec or mirrors):
-        fail("try_settings can only be set if builder_spec or mirrors is set")
+    if builder_config_settings and not (builder_spec or mirrors):
+        fail("builder_config_settings can only be set if builder_spec or " +
+             "mirrors is set")
 
     dimensions = {}
 
@@ -1040,7 +1042,7 @@
         builder_group,
         builder_spec,
         mirrors,
-        try_settings,
+        builder_config_settings,
         targets,
         additional_exclusions,
     )
diff --git a/infra/config/subprojects/chromium/angle.try.star b/infra/config/subprojects/chromium/angle.try.star
index 513edb1..f3a0b2f 100644
--- a/infra/config/subprojects/chromium/angle.try.star
+++ b/infra/config/subprojects/chromium/angle.try.star
@@ -46,7 +46,7 @@
         "ci/ios-angle-builder",
         "ci/ios-angle-intel",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 4eb5a3e..74025407 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -291,6 +291,45 @@
 )
 
 ci.builder(
+    name = "linux-chromeos-dbg-oslogin",
+    description_html = "This builder is used to debug spefically oslogin issues related" +
+                       "to linux-chromeos-dbg-oslogin",
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+            apply_configs = [
+                "chromeos",
+            ],
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = [
+                "mb",
+            ],
+            build_config = builder_config.build_config.DEBUG,
+            target_arch = builder_config.target_arch.INTEL,
+            target_bits = 64,
+            target_platform = builder_config.target_platform.CHROMEOS,
+        ),
+        build_gs_bucket = "chromium-chromiumos-archive",
+    ),
+    gn_args = gn_args.config(
+        configs = [
+            "chromeos_with_codecs",
+            "debug_builder",
+            "reclient",
+            "use_cups",
+        ],
+    ),
+    sheriff_rotations = None,
+    console_view_entry = consoles.console_view_entry(
+        category = "linux",
+        short_name = "lnx",
+    ),
+    contact_team_email = "chrome-browser-infra@google.com",
+)
+
+ci.builder(
     name = "linux-chromeos-annotator-rel",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(
diff --git a/infra/config/subprojects/chromium/swangle.try.star b/infra/config/subprojects/chromium/swangle.try.star
index c1a49a2e..130e2f9 100644
--- a/infra/config/subprojects/chromium/swangle.try.star
+++ b/infra/config/subprojects/chromium/swangle.try.star
@@ -53,7 +53,7 @@
     mirrors = [
         "ci/linux-swangle-chromium-x64",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -72,7 +72,7 @@
     mirrors = [
         "ci/linux-swangle-chromium-x64-exp",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -90,7 +90,7 @@
     mirrors = [
         "ci/linux-swangle-tot-swiftshader-x64",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = "ci/linux-swangle-tot-swiftshader-x64",
@@ -103,7 +103,7 @@
     mirrors = [
         "ci/linux-swangle-x64",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = "ci/linux-swangle-x64",
@@ -116,7 +116,7 @@
     mirrors = [
         "ci/linux-swangle-x64-exp",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = "ci/linux-swangle-x64-exp",
@@ -129,7 +129,7 @@
     mirrors = [
         "ci/mac-swangle-chromium-x64",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -148,7 +148,7 @@
     mirrors = [
         "ci/win-swangle-chromium-x86",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -166,7 +166,7 @@
     mirrors = [
         "ci/win-swangle-tot-swiftshader-x64",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = "ci/win-swangle-tot-swiftshader-x64",
@@ -178,7 +178,7 @@
     mirrors = [
         "ci/win-swangle-tot-swiftshader-x86",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -196,7 +196,7 @@
     mirrors = [
         "ci/win-swangle-x64",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = "ci/win-swangle-x64",
@@ -209,7 +209,7 @@
     mirrors = [
         "ci/win-swangle-x86",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
diff --git a/infra/config/subprojects/chromium/try/tryserver.blink.star b/infra/config/subprojects/chromium/try/tryserver.blink.star
index 073bb23..631041b 100644
--- a/infra/config/subprojects/chromium/try/tryserver.blink.star
+++ b/infra/config/subprojects/chromium/try/tryserver.blink.star
@@ -54,7 +54,7 @@
             target_platform = builder_config.target_platform.LINUX,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -81,7 +81,7 @@
 Chrome.\
 """,
     mirrors = ["ci/linux-wpt-chromium-rel"],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = "ci/linux-wpt-chromium-rel",
@@ -108,7 +108,7 @@
         ),
         build_gs_bucket = "chromium-fyi-archive",
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -141,7 +141,7 @@
             target_platform = builder_config.target_platform.WIN,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = gn_args.config(
@@ -174,7 +174,7 @@
             target_platform = builder_config.target_platform.WIN,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = gn_args.config(
@@ -206,7 +206,7 @@
             target_platform = builder_config.target_platform.MAC,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = gn_args.config(
@@ -235,7 +235,7 @@
             target_platform = builder_config.target_platform.MAC,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = gn_args.config(
@@ -266,7 +266,7 @@
         ),
         build_gs_bucket = "chromium-fyi-archive",
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = gn_args.config(
@@ -296,7 +296,7 @@
             target_platform = builder_config.target_platform.MAC,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -325,7 +325,7 @@
             target_platform = builder_config.target_platform.MAC,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = gn_args.config(
@@ -355,7 +355,7 @@
             target_platform = builder_config.target_platform.MAC,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -384,7 +384,7 @@
             target_platform = builder_config.target_platform.MAC,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = gn_args.config(
@@ -414,7 +414,7 @@
             target_platform = builder_config.target_platform.MAC,
         ),
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = True,
     ),
     gn_args = gn_args.config(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
index 242017b..8c7fba1 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.android.star
@@ -986,7 +986,7 @@
     mirrors = [
         "ci/Android arm64 Builder (dbg)",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -1079,7 +1079,7 @@
     mirrors = [
         "ci/Android arm64 Builder All Targets (dbg)",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -1113,7 +1113,7 @@
     mirrors = [
         "ci/Android x64 Builder All Targets (dbg)",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -1162,7 +1162,7 @@
     mirrors = [
         "ci/Android x86 Builder (dbg)",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -1198,7 +1198,7 @@
     mirrors = [
         "ci/android-cronet-arm-rel",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         is_compile_only = True,
     ),
     gn_args = gn_args.config(
@@ -1239,7 +1239,7 @@
         ),
         build_gs_bucket = "chromium-gpu-fyi-archive",
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -1296,7 +1296,7 @@
         "ci/GPU FYI Android arm64 Builder",
         "ci/Android FYI Release (Pixel 6)",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = "ci/GPU FYI Android arm64 Builder",
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.angle.star b/infra/config/subprojects/chromium/try/tryserver.chromium.angle.star
index 0aae4f4..cc713e8 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.angle.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.angle.star
@@ -33,7 +33,7 @@
         "ci/android-angle-chromium-arm64-builder",
         "ci/android-angle-chromium-arm64-nexus5x",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -50,7 +50,7 @@
     mirrors = [
         "ci/fuchsia-angle-builder",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -70,7 +70,7 @@
         "ci/linux-angle-chromium-intel",
         "ci/linux-angle-chromium-nvidia",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -89,7 +89,7 @@
         "ci/mac-angle-chromium-builder",
         "ci/mac-angle-chromium-intel",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -111,7 +111,7 @@
         "ci/win10-angle-chromium-x64-intel",
         "ci/win10-angle-chromium-x64-nvidia",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
@@ -129,7 +129,7 @@
     mirrors = [
         "ci/win-angle-chromium-x86-builder",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
         retry_failed_shards = False,
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star b/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
index 238a09c..2c3975b 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.chromiumos.star
@@ -418,7 +418,7 @@
     mirrors = [
         "ci/linux-chromeos-dbg",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
index 811d40d..0074089b8 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.fuchsia.star
@@ -126,7 +126,7 @@
     mirrors = [
         "ci/fuchsia-x64-dbg",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
index 44fb46a..af44544 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.linux.star
@@ -158,7 +158,7 @@
     mirrors = [
         "ci/linux-archive-rel",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -595,7 +595,7 @@
 This builder should be removed after migrating linux_chromium_asan_rel_ng from Ninja to Siso. b/277863839
 """,
     mirrors = builder_config.copy_from("try/linux_chromium_asan_rel_ng"),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         is_compile_only = True,
     ),
     gn_args = "try/linux_chromium_asan_rel_ng",
@@ -706,7 +706,7 @@
     name = "linux_chromium_compile_dbg_ng",
     branch_selector = branches.selector.LINUX_BRANCHES,
     mirrors = ["ci/Linux Builder (dbg)"],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -741,7 +741,7 @@
 This builder should be removed after migrating linux_chromium_compile_dbg_ng from Ninja to Siso. b/277863839
 """,
     mirrors = builder_config.copy_from("try/linux_chromium_compile_dbg_ng"),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -767,7 +767,7 @@
     mirrors = [
         "ci/Linux Builder",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -870,7 +870,7 @@
 This builder should be removed after migrating linux_chromium_tsan_rel_ng from Ninja to Siso. b/277863839
 """,
     mirrors = builder_config.copy_from("try/linux_chromium_tsan_rel_ng"),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         is_compile_only = True,
     ),
     gn_args = "try/linux_chromium_tsan_rel_ng",
@@ -1010,7 +1010,7 @@
         ),
         build_gs_bucket = "chromium-gpu-fyi-archive",
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
index 6b3fe01..1102168 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.mac.star
@@ -453,7 +453,7 @@
     mirrors = [
         "ci/Mac Builder (dbg)",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -476,7 +476,7 @@
     mirrors = [
         "ci/Mac Builder",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.win.star b/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
index aecdfa4..9608946 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
@@ -187,7 +187,7 @@
     mirrors = [
         "ci/Win Builder (dbg)",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -214,7 +214,7 @@
     mirrors = [
         "ci/Win Builder",
     ],
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         include_all_triggered_testers = True,
         is_compile_only = True,
     ),
@@ -449,7 +449,7 @@
         ),
         build_gs_bucket = "chromium-gpu-fyi-archive",
     ),
-    try_settings = builder_config.try_settings(
+    builder_config_settings = builder_config.try_settings(
         retry_failed_shards = False,
     ),
     gn_args = gn_args.config(
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index 081d747..feb8db79 100644
--- a/infra/config/targets/lacros-version-skew-variants.json
+++ b/infra/config/targets/lacros-version-skew-variants.json
@@ -1,16 +1,16 @@
 {
   "LACROS_VERSION_SKEW_CANARY": {
     "args": [
-      "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 124.0.6325.0",
+    "description": "Run with ash-chrome version 124.0.6326.0",
     "identifier": "Lacros version skew testing ash canary",
     "swarming": {
       "cipd_packages": [
         {
           "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-          "location": "lacros_version_skew_tests_v124.0.6325.0",
-          "revision": "version:124.0.6325.0"
+          "location": "lacros_version_skew_tests_v124.0.6326.0",
+          "revision": "version:124.0.6326.0"
         }
       ]
     }
diff --git a/infra/config/targets/mixins.star b/infra/config/targets/mixins.star
index 231d583..6865e11 100644
--- a/infra/config/targets/mixins.star
+++ b/infra/config/targets/mixins.star
@@ -500,6 +500,15 @@
 )
 
 targets.mixin(
+    name = "chromium-tests-oslogin",
+    swarming = targets.swarming(
+        dimensions = {
+            "pool": "chromium.tests.oslogin",
+        },
+    ),
+)
+
+targets.mixin(
     name = "dawn_end2end_gpu_test",
     args = [
         "--use-gpu-in-tests",
diff --git a/internal b/internal
index f504815..e992b1f 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit f5048154f82911c84ab994c1546290d567a75ec4
+Subproject commit e992b1f6ab810377d0c9cb55aa6a82752df9e8d9
diff --git a/ios/chrome/browser/autofill/model/BUILD.gn b/ios/chrome/browser/autofill/model/BUILD.gn
index 524f8641..e930ecb01 100644
--- a/ios/chrome/browser/autofill/model/BUILD.gn
+++ b/ios/chrome/browser/autofill/model/BUILD.gn
@@ -116,6 +116,7 @@
     "//ios/chrome/browser/infobars/model",
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/browser_state",
+    "//ios/chrome/browser/shared/public/commands:commands",
     "//ios/chrome/browser/signin/model",
     "//ios/chrome/browser/ui/autofill",
     "//third_party/leveldatabase",
diff --git a/ios/chrome/browser/autofill/model/autofill_tab_helper.h b/ios/chrome/browser/autofill/model/autofill_tab_helper.h
index 5d24dba8..f003c10 100644
--- a/ios/chrome/browser/autofill/model/autofill_tab_helper.h
+++ b/ios/chrome/browser/autofill/model/autofill_tab_helper.h
@@ -12,6 +12,7 @@
 #import "ios/web/public/web_state_user_data.h"
 
 @class AutofillAgent;
+@protocol AutofillCommands;
 class ChromeBrowserState;
 @protocol FormSuggestionProvider;
 @class UIViewController;
@@ -32,6 +33,8 @@
   // Sets a weak reference to the view controller used to present UI.
   void SetBaseViewController(UIViewController* base_view_controller);
 
+  void SetCommandsHandler(id<AutofillCommands> commands_handler);
+
   // Returns an object that can provide Autofill suggestions.
   id<FormSuggestionProvider> GetSuggestionProvider();
 
diff --git a/ios/chrome/browser/autofill/model/autofill_tab_helper.mm b/ios/chrome/browser/autofill/model/autofill_tab_helper.mm
index 3b66c52..a33b6e21 100644
--- a/ios/chrome/browser/autofill/model/autofill_tab_helper.mm
+++ b/ios/chrome/browser/autofill/model/autofill_tab_helper.mm
@@ -12,6 +12,7 @@
 #import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
+#import "ios/chrome/browser/shared/public/commands/autofill_commands.h"
 #import "ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h"
 
 AutofillTabHelper::~AutofillTabHelper() = default;
@@ -21,6 +22,11 @@
   autofill_client_->SetBaseViewController(base_view_controller);
 }
 
+void AutofillTabHelper::SetCommandsHandler(
+    id<AutofillCommands> commands_handler) {
+  autofill_client_->set_commands_handler(commands_handler);
+}
+
 id<FormSuggestionProvider> AutofillTabHelper::GetSuggestionProvider() {
   return autofill_agent_;
 }
diff --git a/ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.h b/ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.h
index f00ce112..3287064 100644
--- a/ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.h
+++ b/ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.h
@@ -27,7 +27,7 @@
 class ScriptMessage;
 }  // namespace web
 
-@protocol AutofillBottomSheetCommands;
+@protocol AutofillCommands;
 @class CommandDispatcher;
 @protocol PasswordsAccountStorageNoticeHandler;
 
@@ -76,8 +76,7 @@
   void OnFormMessageReceived(const web::ScriptMessage& message);
 
   // Sets the bottom sheet CommandDispatcher.
-  void SetAutofillBottomSheetHandler(
-      id<AutofillBottomSheetCommands> commands_handler);
+  void SetAutofillBottomSheetHandler(id<AutofillCommands> commands_handler);
 
   // Prepare bottom sheet using data from the password form prediction.
   void AttachPasswordListeners(
@@ -154,7 +153,7 @@
   void ShowPaymentsBottomSheet(const autofill::FormActivityParams params);
 
   // Handler used to request showing the password bottom sheet.
-  __weak id<AutofillBottomSheetCommands> commands_handler_;
+  __weak id<AutofillCommands> commands_handler_;
 
   // Handler used for the passwords account storage notice.
   // TODO(crbug.com/1434606): Remove this when the move to account storage
diff --git a/ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.mm b/ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.mm
index 197a953..1274724 100644
--- a/ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.mm
+++ b/ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_tab_helper.mm
@@ -21,7 +21,7 @@
 #import "ios/chrome/browser/autofill/model/bottom_sheet/autofill_bottom_sheet_observer.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
-#import "ios/chrome/browser/shared/public/commands/autofill_bottom_sheet_commands.h"
+#import "ios/chrome/browser/shared/public/commands/autofill_commands.h"
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/web/public/js_messaging/script_message.h"
@@ -82,7 +82,7 @@
 }
 
 void AutofillBottomSheetTabHelper::SetAutofillBottomSheetHandler(
-    id<AutofillBottomSheetCommands> commands_handler) {
+    id<AutofillCommands> commands_handler) {
   commands_handler_ = commands_handler;
 }
 
@@ -126,8 +126,7 @@
     return;
   }
 
-  __weak id<AutofillBottomSheetCommands> weak_commands_handler =
-      commands_handler_;
+  __weak id<AutofillCommands> weak_commands_handler = commands_handler_;
   [password_account_storage_notice_handler_ showAccountStorageNotice:^{
     [weak_commands_handler showPasswordBottomSheet:params];
   }];
diff --git a/ios/chrome/browser/default_browser/model/utils.mm b/ios/chrome/browser/default_browser/model/utils.mm
index 698d5f5..4207c62 100644
--- a/ios/chrome/browser/default_browser/model/utils.mm
+++ b/ios/chrome/browser/default_browser/model/utils.mm
@@ -616,11 +616,16 @@
 bool HasUserInteractedWithFullscreenPromoBefore() {
   if (base::FeatureList::IsEnabled(
           feature_engagement::kDefaultBrowserEligibilitySlidingWindow)) {
-    return HasRecordedEventForKeyLessThanDelay(
-        kLastTimeUserInteractedWithFullscreenPromo,
-        base::Days(
-            feature_engagement::kDefaultBrowserEligibilitySlidingWindowParam
-                .Get()));
+    // When the total promo count is 1 it means that user has seen only the FRE
+    // promo. The cooldown from FRE will be taken care of in
+    // ```ComputeCooldown```. Here we only need to check the timestamp of the
+    // last promo if users seen more than FRE.
+    return DisplayedFullscreenPromoCount() > 1 &&
+           HasRecordedEventForKeyLessThanDelay(
+               kLastTimeUserInteractedWithFullscreenPromo,
+               base::Days(
+                   feature_engagement::
+                       kDefaultBrowserEligibilitySlidingWindowParam.Get()));
   }
 
   NSNumber* number = GetObjectFromStorageForKey<NSNumber>(
diff --git a/ios/chrome/browser/default_browser/model/utils_unittest.mm b/ios/chrome/browser/default_browser/model/utils_unittest.mm
index 51f9702..d90c30b 100644
--- a/ios/chrome/browser/default_browser/model/utils_unittest.mm
+++ b/ios/chrome/browser/default_browser/model/utils_unittest.mm
@@ -71,6 +71,9 @@
 // Test key in storage for counting past default browser promo interactions.
 NSString* const kGenericPromoInteractionCount = @"genericPromoInteractionCount";
 
+// Test Key in storage for counting all past default browser promo displays.
+NSString* const kDisplayedFullscreenPromoCount = @"displayedPromoCount";
+
 class DefaultBrowserUtilsTest : public PlatformTest {
  protected:
   void SetUp() override { ClearDefaultBrowserPromoData(); }
@@ -79,13 +82,16 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
+// Overwrite local storage with the provided interaction information.
 void SimulateUserInteractionWithFullscreenPromo(const base::TimeDelta& timeAgo,
-                                                int count) {
+                                                int count,
+                                                int totalCount) {
   NSDictionary<NSString*, NSObject*>* values = @{
     kUserHasInteractedWithFullscreenPromo : @YES,
     kLastTimeUserInteractedWithFullscreenPromo : (base::Time::Now() - timeAgo)
         .ToNSDate(),
-    kGenericPromoInteractionCount : [NSNumber numberWithInt:count]
+    kGenericPromoInteractionCount : [NSNumber numberWithInt:count],
+    kDisplayedFullscreenPromoCount : [NSNumber numberWithInt:totalCount]
   };
   SetValuesInStorage(values);
 }
@@ -360,23 +366,29 @@
                                  {/*disabled=*/feature_engagement::
                                       kDefaultBrowserEligibilitySlidingWindow});
 
-  // Test with multiple interactions.
+  // Test when there are no interaction recorded yet.
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(kMoreThan6Hours, 1);
+
+  // Test that logging first run doesn't affect it.
+  LogUserInteractionWithFirstRunPromo(true);
+  EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
+
+  // Test with multiple interactions.
+  SimulateUserInteractionWithFullscreenPromo(kMoreThan6Hours, 1, 2);
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(kMoreThan14Days, 2);
+  SimulateUserInteractionWithFullscreenPromo(kMoreThan14Days, 2, 3);
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
 
   // Test with a single, more distant interaction.
   ClearDefaultBrowserPromoData();
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(k6Months, 1);
+  SimulateUserInteractionWithFullscreenPromo(k6Months, 1, 2);
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
 
   // Test with a single, even more distant interaction.
   ClearDefaultBrowserPromoData();
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(k2Years, 1);
+  SimulateUserInteractionWithFullscreenPromo(k2Years, 1, 2);
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
 }
 
@@ -391,37 +403,65 @@
       feature_engagement::kDefaultBrowserEligibilitySlidingWindow,
       feature_params);
 
-  // Test with multiple interactions.
+  // Test when there are no interaction recorded yet.
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(kMoreThan6Hours, 1);
+
+  // Test that logging first run doesn't affect it.
+  LogUserInteractionWithFirstRunPromo(true);
+  EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
+
+  // Test with multiple interactions.
+  SimulateUserInteractionWithFullscreenPromo(kMoreThan6Hours, 1, 2);
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(kMoreThan14Days, 2);
+  SimulateUserInteractionWithFullscreenPromo(kMoreThan14Days, 2, 3);
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
 
   // Test with a single, more distant interaction (but still within the sliding
   // window limit).
   ClearDefaultBrowserPromoData();
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(k6Months, 1);
+  SimulateUserInteractionWithFullscreenPromo(k6Months, 1, 2);
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
 
   // Test with a single interaction that's outside the sliding window limit.
   ClearDefaultBrowserPromoData();
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(k2Years, 1);
+  SimulateUserInteractionWithFullscreenPromo(k2Years, 1, 2);
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
 
   // Test with multiple interactions, some within and some outside the sliding
   // window limit.
   ClearDefaultBrowserPromoData();
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(k5Years, 1);
+  SimulateUserInteractionWithFullscreenPromo(k5Years, 1, 2);
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(k2Years, 2);
+  SimulateUserInteractionWithFullscreenPromo(k2Years, 2, 3);
   EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(k6Months, 3);
+  SimulateUserInteractionWithFullscreenPromo(k6Months, 3, 4);
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
-  SimulateUserInteractionWithFullscreenPromo(kMoreThan14Days, 4);
+  SimulateUserInteractionWithFullscreenPromo(kMoreThan14Days, 4, 5);
+  EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
+}
+
+// Tests that sliding window experiment doesn't not affect the cooldown from
+// FRE.
+TEST_F(DefaultBrowserUtilsTest, CooldownFromFRESlidingWindowEnabled) {
+  base::FieldTrialParams feature_params;
+  feature_params["sliding-window-days"] = "365";
+  feature_list_.InitAndEnableFeatureWithParameters(
+      feature_engagement::kDefaultBrowserEligibilitySlidingWindow,
+      feature_params);
+
+  // Test when there are no interaction recorded yet.
+  EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
+
+  // Test that logging first run doesn't affect it.
+  LogUserInteractionWithFirstRunPromo(true);
+  EXPECT_FALSE(HasUserInteractedWithFullscreenPromoBefore());
+
+  // Test that logging a generic promo interaction will affect it.
+  LogUserInteractionWithFullscreenPromo();
+  LogFullscreenDefaultBrowserPromoDisplayed();
   EXPECT_TRUE(HasUserInteractedWithFullscreenPromoBefore());
 }
 
diff --git a/ios/chrome/browser/policy/model/configuration_policy_handler_list_factory.mm b/ios/chrome/browser/policy/model/configuration_policy_handler_list_factory.mm
index eb1c60c4..aa2a6522 100644
--- a/ios/chrome/browser/policy/model/configuration_policy_handler_list_factory.mm
+++ b/ios/chrome/browser/policy/model/configuration_policy_handler_list_factory.mm
@@ -144,6 +144,9 @@
   { policy::key::kDownloadManagerSaveToDriveSettings,
     prefs::kIosSaveToDriveDownloadManagerPolicySettings,
     base::Value::Type::INTEGER },
+  { policy::key::kProductSpecificationsEnabled,
+    commerce::kProductSpecificationsEnabledPrefName,
+    base::Value::Type::BOOLEAN},
 };
 // clang-format on
 
diff --git a/ios/chrome/browser/segmentation_platform/model/segmentation_platform_config.mm b/ios/chrome/browser/segmentation_platform/model/segmentation_platform_config.mm
index f8c9ccef..10a85f6a 100644
--- a/ios/chrome/browser/segmentation_platform/model/segmentation_platform_config.mm
+++ b/ios/chrome/browser/segmentation_platform/model/segmentation_platform_config.mm
@@ -51,7 +51,7 @@
   configs.emplace_back(MostVisitedTilesUser::GetConfig());
 
   // Add new configs here.
-  base::EraseIf(configs, [](const auto& config) { return !config.get(); });
+  std::erase_if(configs, [](const auto& config) { return !config.get(); });
   return configs;
 }
 
diff --git a/ios/chrome/browser/shared/public/commands/BUILD.gn b/ios/chrome/browser/shared/public/commands/BUILD.gn
index 41bb96e..e46887b 100644
--- a/ios/chrome/browser/shared/public/commands/BUILD.gn
+++ b/ios/chrome/browser/shared/public/commands/BUILD.gn
@@ -7,7 +7,7 @@
     "account_picker_commands.h",
     "activity_service_commands.h",
     "application_commands.h",
-    "autofill_bottom_sheet_commands.h",
+    "autofill_commands.h",
     "bookmarks_commands.h",
     "bring_android_tabs_commands.h",
     "browser_commands.h",
diff --git a/ios/chrome/browser/shared/public/commands/autofill_bottom_sheet_commands.h b/ios/chrome/browser/shared/public/commands/autofill_commands.h
similarity index 60%
rename from ios/chrome/browser/shared/public/commands/autofill_bottom_sheet_commands.h
rename to ios/chrome/browser/shared/public/commands/autofill_commands.h
index 9d05328..db870565 100644
--- a/ios/chrome/browser/shared/public/commands/autofill_bottom_sheet_commands.h
+++ b/ios/chrome/browser/shared/public/commands/autofill_commands.h
@@ -2,18 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_AUTOFILL_BOTTOM_SHEET_COMMANDS_H_
-#define IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_AUTOFILL_BOTTOM_SHEET_COMMANDS_H_
+#ifndef IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_AUTOFILL_COMMANDS_H_
+#define IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_AUTOFILL_COMMANDS_H_
 
 #import "components/plus_addresses/plus_address_types.h"
 
 namespace autofill {
+struct AutofillErrorDialogContext;
 struct FormActivityParams;
 struct VirtualCardEnrollUiModel;
 }  // namespace autofill
 
-// Commands related to the passwords bottom sheet.
-@protocol AutofillBottomSheetCommands
+// Commands related to the Autofill flows (passwords, addresses, payments etc).
+@protocol AutofillCommands
 
 // Shows the password suggestion view controller.
 - (void)showPasswordBottomSheet:(const autofill::FormActivityParams&)params;
@@ -28,6 +29,11 @@
 - (void)showVirtualCardEnrollmentBottomSheet:
     (const autofill::VirtualCardEnrollUiModel&)model;
 
+// Commands to manage the Autofill error dialog.
+- (void)showAutofillErrorDialog:
+    (autofill::AutofillErrorDialogContext)errorContext;
+- (void)dismissAutofillErrorDialog;
+
 @end
 
-#endif  // IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_AUTOFILL_BOTTOM_SHEET_COMMANDS_H_
+#endif  // IOS_CHROME_BROWSER_SHARED_PUBLIC_COMMANDS_AUTOFILL_COMMANDS_H_
diff --git a/ios/chrome/browser/shared/ui/table_view/cells/table_view_url_item.mm b/ios/chrome/browser/shared/ui/table_view/cells/table_view_url_item.mm
index 6e0530b4..cc49a9c 100644
--- a/ios/chrome/browser/shared/ui/table_view/cells/table_view_url_item.mm
+++ b/ios/chrome/browser/shared/ui/table_view/cells/table_view_url_item.mm
@@ -22,6 +22,8 @@
 // Default delimiter to use between the hostname and the supplemental URL text
 // if text is specified but not the delimiter.
 const char kDefaultSupplementalURLTextDelimiter[] = "•";
+// The max number of lines for the cell title label.
+const int kMaxNumberOfLinesForCellTitleLabel = 4;
 }  // namespace
 
 #pragma mark - TableViewURLItem
@@ -167,7 +169,9 @@
     // Set font sizes using dynamic type.
     _titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
     _titleLabel.adjustsFontForContentSizeCategory = YES;
-    _titleLabel.numberOfLines = 0;
+    // Sometimes a very long url is used as the cell title, so be sure it will
+    // not stretch the cell to an unlimited number of lines.
+    _titleLabel.numberOfLines = kMaxNumberOfLinesForCellTitleLabel;
     _URLLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
     _URLLabel.adjustsFontForContentSizeCategory = YES;
     _URLLabel.textColor = [UIColor colorNamed:kTextSecondaryColor];
diff --git a/ios/chrome/browser/ui/autofill/BUILD.gn b/ios/chrome/browser/ui/autofill/BUILD.gn
index 4e5c46a..9be2135 100644
--- a/ios/chrome/browser/ui/autofill/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/BUILD.gn
@@ -45,6 +45,7 @@
     "//ios/chrome/browser/shared/model/application_context",
     "//ios/chrome/browser/shared/model/browser_state",
     "//ios/chrome/browser/shared/model/web_state_list:web_state_list",
+    "//ios/chrome/browser/shared/public/commands:commands",
     "//ios/chrome/browser/shared/ui/util",
     "//ios/chrome/browser/signin/model",
     "//ios/chrome/browser/ssl/model",
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
index f86cef9..7fbe57f9 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
@@ -34,6 +34,7 @@
 #include "components/sync/service/sync_service.h"
 #include "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 
+@protocol AutofillCommands;
 @class UIViewController;
 
 namespace web {
@@ -42,6 +43,8 @@
 
 namespace autofill {
 
+struct AutofillErrorDialogContext;
+
 namespace payments {
 class IOSChromePaymentsAutofillClient;
 }
@@ -62,6 +65,11 @@
   // Sets a weak reference to the view controller used to present UI.
   void SetBaseViewController(UIViewController* base_view_controller);
 
+  void set_commands_handler(id<AutofillCommands> commands_handler) {
+    commands_handler_ = commands_handler;
+  }
+  id<AutofillCommands> commands_handler() const { return commands_handler_; }
+
   // AutofillClient:
   version_info::Channel GetChannel() const override;
   bool IsOffTheRecord() override;
@@ -144,6 +152,7 @@
                    FillingProduct main_filling_product,
                    AutofillSuggestionTriggerSource trigger_source) override;
   void HideAutofillPopup(PopupHidingReason reason) override;
+  void ShowAutofillErrorDialog(AutofillErrorDialogContext context) override;
   bool IsAutocompleteEnabled() const override;
   bool IsPasswordManagerEnabled() override;
   void DidFillOrPreviewForm(mojom::ActionPersistence action_persistence,
@@ -200,6 +209,8 @@
 
   // A weak reference to the view controller used to present UI.
   __weak UIViewController* base_view_controller_;
+
+  __weak id<AutofillCommands> commands_handler_;
 };
 
 }  // namespace autofill
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
index 517e40e..30d874b 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
@@ -20,6 +20,7 @@
 #import "components/autofill/core/browser/form_data_importer.h"
 #import "components/autofill/core/browser/logging/log_manager.h"
 #import "components/autofill/core/browser/payments/autofill_credit_card_filling_infobar_delegate_mobile.h"
+#import "components/autofill/core/browser/payments/autofill_error_dialog_context.h"
 #import "components/autofill/core/browser/payments/autofill_save_card_delegate.h"
 #import "components/autofill/core/browser/payments/autofill_save_card_infobar_delegate_mobile.h"
 #import "components/autofill/core/browser/payments/autofill_save_card_ui_info.h"
@@ -59,6 +60,7 @@
 #import "ios/chrome/browser/passwords/model/password_tab_helper.h"
 #import "ios/chrome/browser/plus_addresses/model/plus_address_service_factory.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
+#import "ios/chrome/browser/shared/public/commands/autofill_commands.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"
@@ -214,7 +216,7 @@
 ChromeAutofillClientIOS::GetPaymentsAutofillClient() {
   if (!payments_autofill_client_) {
     payments_autofill_client_ =
-        std::make_unique<payments::IOSChromePaymentsAutofillClient>();
+        std::make_unique<payments::IOSChromePaymentsAutofillClient>(this);
   }
 
   return payments_autofill_client_.get();
@@ -514,6 +516,17 @@
   [bridge_ hideAutofillPopup];
 }
 
+void ChromeAutofillClientIOS::ShowAutofillErrorDialog(
+    AutofillErrorDialogContext context) {
+  // TODO(b/324609813): Move this function from ChromeAutofillClientIOS to
+  // IOSChromePaymentsAutofillClient to avoid this call.
+  if (!payments_autofill_client_) {
+    payments_autofill_client_ =
+        std::make_unique<payments::IOSChromePaymentsAutofillClient>(this);
+  }
+  payments_autofill_client_->ShowAutofillErrorDialog(std::move(context));
+}
+
 bool ChromeAutofillClientIOS::IsAutocompleteEnabled() const {
   return prefs::IsAutocompleteEnabled(GetPrefs());
 }
diff --git a/ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.h b/ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.h
index be4cee0..0d2b89e 100644
--- a/ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.h
+++ b/ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.h
@@ -7,14 +7,23 @@
 
 #include "components/autofill/core/browser/payments/payments_autofill_client.h"
 
-namespace autofill::payments {
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+
+namespace autofill {
+
+class ChromeAutofillClientIOS;
+struct AutofillErrorDialogContext;
+
+namespace payments {
 
 // Chrome iOS implementation of PaymentsAutofillClient. Owned by the
 // ChromeAutofillClientIOS. Created lazily in the ChromeAutofillClientIOS when
 // it is needed.
 class IOSChromePaymentsAutofillClient : public PaymentsAutofillClient {
  public:
-  IOSChromePaymentsAutofillClient();
+  explicit IOSChromePaymentsAutofillClient(
+      autofill::ChromeAutofillClientIOS* client);
   IOSChromePaymentsAutofillClient(const IOSChromePaymentsAutofillClient&) =
       delete;
   IOSChromePaymentsAutofillClient& operator=(
@@ -27,8 +36,15 @@
 
   // PaymentsAutofillClient:
   void CreditCardUploadCompleted(bool card_saved) override;
+
+  void ShowAutofillErrorDialog(AutofillErrorDialogContext error_context);
+
+ private:
+  const raw_ref<autofill::ChromeAutofillClientIOS> client_;
 };
 
-}  // namespace autofill::payments
+}  // namespace payments
 
-#endif  // IOS_CHROME_BROWSER_UI_AUTOFILL_IOS_CHROME_PAYMENTS_AUTOFILL_CLIENT_H_
+}  // namespace autofill
+
+#endif  //  IOS_CHROME_BROWSER_UI_AUTOFILL_IOS_CHROME_PAYMENTS_AUTOFILL_CLIENT_H_
diff --git a/ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.mm b/ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.mm
index b53fa9b..9d177d1 100644
--- a/ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.mm
+++ b/ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.mm
@@ -4,12 +4,19 @@
 
 #import "ios/chrome/browser/ui/autofill/ios_chrome_payments_autofill_client.h"
 
+#import "base/check_deref.h"
+#import "base/memory/weak_ptr.h"
 #import "base/strings/sys_string_conversions.h"
+#import "components/autofill/core/browser/payments/autofill_error_dialog_context.h"
+#import "ios/chrome/browser/shared/public/commands/autofill_commands.h"
+#import "ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h"
 #import "ios/public/provider/chrome/browser/risk_data/risk_data_api.h"
 
 namespace autofill::payments {
 
-IOSChromePaymentsAutofillClient::IOSChromePaymentsAutofillClient() = default;
+IOSChromePaymentsAutofillClient::IOSChromePaymentsAutofillClient(
+    autofill::ChromeAutofillClientIOS* client)
+    : client_(CHECK_DEREF(client)) {}
 
 IOSChromePaymentsAutofillClient::~IOSChromePaymentsAutofillClient() = default;
 
@@ -24,4 +31,10 @@
   NOTIMPLEMENTED();
 }
 
+void IOSChromePaymentsAutofillClient::ShowAutofillErrorDialog(
+    AutofillErrorDialogContext error_context) {
+  [client_->commands_handler()
+      showAutofillErrorDialog:std::move(error_context)];
+}
+
 }  // namespace autofill::payments
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 95ed2ca..a8e27ed 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -10,9 +10,11 @@
 #import <optional>
 
 #import "base/memory/raw_ptr.h"
+#import "base/memory/weak_ptr.h"
 #import "base/metrics/histogram_functions.h"
 #import "base/scoped_observation.h"
 #import "base/strings/sys_string_conversions.h"
+#import "components/autofill/core/browser/payments/autofill_error_dialog_context.h"
 #import "components/commerce/core/shopping_service.h"
 #import "components/content_settings/core/browser/host_content_settings_map.h"
 #import "components/feature_engagement/public/event_constants.h"
@@ -76,7 +78,7 @@
 #import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
 #import "ios/chrome/browser/shared/public/commands/activity_service_commands.h"
 #import "ios/chrome/browser/shared/public/commands/application_commands.h"
-#import "ios/chrome/browser/shared/public/commands/autofill_bottom_sheet_commands.h"
+#import "ios/chrome/browser/shared/public/commands/autofill_commands.h"
 #import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
 #import "ios/chrome/browser/shared/public/commands/feed_commands.h"
@@ -717,6 +719,8 @@
   [self.virtualCardEnrollmentBottomSheetCoordinator stop];
   self.virtualCardEnrollmentBottomSheetCoordinator = nil;
 
+  [self dismissAutofillErrorDialog];
+
   [_sendTabToSelfCoordinator stop];
   _sendTabToSelfCoordinator = nil;
 
@@ -862,7 +866,7 @@
   // handlers.
   NSArray<Protocol*>* protocols = @[
     @protocol(ActivityServiceCommands),
-    @protocol(AutofillBottomSheetCommands),
+    @protocol(AutofillCommands),
     @protocol(BrowserCoordinatorCommands),
     @protocol(DefaultBrowserPromoNonModalCommands),
     @protocol(FeedCommands),
@@ -1370,6 +1374,8 @@
   [self.virtualCardEnrollmentBottomSheetCoordinator stop];
   self.virtualCardEnrollmentBottomSheetCoordinator = nil;
 
+  [self dismissAutofillErrorDialog];
+
   [self.printCoordinator stop];
   self.printCoordinator = nil;
 
@@ -1612,7 +1618,7 @@
   [self.sharingCoordinator start];
 }
 
-#pragma mark - AutofillBottomSheetCommands
+#pragma mark - AutofillCommands
 
 - (void)showPasswordBottomSheet:(const autofill::FormActivityParams&)params {
   self.passwordSuggestionBottomSheetCoordinator =
@@ -1663,6 +1669,13 @@
   [self.virtualCardEnrollmentBottomSheetCoordinator start];
 }
 
+- (void)showAutofillErrorDialog:
+    (autofill::AutofillErrorDialogContext)errorContext {
+}
+
+- (void)dismissAutofillErrorDialog {
+}
+
 #pragma mark - BrowserCoordinatorCommands
 
 - (void)printTabWithBaseViewController:(UIViewController*)baseViewController {
diff --git a/ios/chrome/browser/ui/browser_view/tab_lifecycle_mediator.mm b/ios/chrome/browser/ui/browser_view/tab_lifecycle_mediator.mm
index 0514a26..81e984c 100644
--- a/ios/chrome/browser/ui/browser_view/tab_lifecycle_mediator.mm
+++ b/ios/chrome/browser/ui/browser_view/tab_lifecycle_mediator.mm
@@ -23,7 +23,7 @@
 #import "ios/chrome/browser/prerender/model/prerender_service.h"
 #import "ios/chrome/browser/shared/model/application_context/application_context.h"
 #import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
-#import "ios/chrome/browser/shared/public/commands/autofill_bottom_sheet_commands.h"
+#import "ios/chrome/browser/shared/public/commands/autofill_commands.h"
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
 #import "ios/chrome/browser/shared/public/commands/lens_commands.h"
 #import "ios/chrome/browser/shared/public/commands/mini_map_commands.h"
@@ -94,7 +94,7 @@
   AutofillBottomSheetTabHelper* bottomSheetTabHelper =
       AutofillBottomSheetTabHelper::FromWebState(webState);
   bottomSheetTabHelper->SetAutofillBottomSheetHandler(
-      HandlerForProtocol(_commandDispatcher, AutofillBottomSheetCommands));
+      HandlerForProtocol(_commandDispatcher, AutofillCommands));
 
   if (ios::provider::IsLensSupported()) {
     LensTabHelper* lensTabHelper = LensTabHelper::FromWebState(webState);
@@ -123,8 +123,12 @@
       webContentsHandler);
 
   DCHECK(_baseViewController);
-  AutofillTabHelper::FromWebState(webState)->SetBaseViewController(
-      _baseViewController);
+  AutofillTabHelper* autofillTabHelper =
+      AutofillTabHelper::FromWebState(webState);
+  autofillTabHelper->SetBaseViewController(_baseViewController);
+  id<AutofillCommands> autofillHandler =
+      HandlerForProtocol(_commandDispatcher, AutofillCommands);
+  autofillTabHelper->SetCommandsHandler(autofillHandler);
 
   DCHECK(_printCoordinator);
   PrintTabHelper::FromWebState(webState)->set_printer(_printCoordinator);
@@ -199,7 +203,10 @@
 
   NetExportTabHelper::FromWebState(webState)->SetDelegate(nil);
 
-  AutofillTabHelper::FromWebState(webState)->SetBaseViewController(nil);
+  AutofillTabHelper* autofillTabHelper =
+      AutofillTabHelper::FromWebState(webState);
+  autofillTabHelper->SetBaseViewController(nil);
+  autofillTabHelper->SetCommandsHandler(nil);
 
   PrintTabHelper::FromWebState(webState)->set_printer(nil);
 
diff --git a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
index 11a18b4..ab33c9a 100644
--- a/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_details/password_details_mediator.mm
@@ -232,7 +232,7 @@
     return;
   }
 
-  // Use the iterator before base::Erase() makes it invalid.
+  // Use the iterator before std::erase() makes it invalid.
   self.savedPasswordsPresenter->RemoveCredential(*it);
   // TODO(crbug.com/1359392). Once kPasswordsGrouping launches, the mediator
   // should update the passwords model and receive the updates via
@@ -241,7 +241,7 @@
   // flag is disabled and the password is edited, it's impossible to identify
   // the new object to show (sign-on realm can't be used as an id, there might
   // be multiple credentials; nor username/password since the values changed).
-  base::Erase(_credentials, *it);
+  std::erase(_credentials, *it);
   [self providePasswordsToConsumer];
 
   // Update form managers so the list of password suggestions shown to the user
@@ -420,7 +420,7 @@
   CHECK(self.credentials.size() == 1);
   password_manager::CredentialUIEntry credential = self.credentials[0];
   _manager->UnmuteCredential(credential);
-  base::Erase(_credentials, credential);
+  std::erase(_credentials, credential);
   [self providePasswordsToConsumer];
 }
 
diff --git a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.mm b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.mm
index 417a3e4..24657af 100644
--- a/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.mm
@@ -215,7 +215,7 @@
   // example.com"), so those must be filtered before passing to the exporter.
   std::vector<CredentialUIEntry> passwords =
       _savedPasswordsPresenter->GetSavedCredentials();
-  base::EraseIf(passwords, [](const auto& credential) {
+  std::erase_if(passwords, [](const auto& credential) {
     return credential.blocked_by_user;
   });
   [self.passwordExporter startExportFlow:passwords];
diff --git a/ios/chrome/test/data/policy/policy_test_bundle_data.filelist b/ios/chrome/test/data/policy/policy_test_bundle_data.filelist
index 2d597b07..333bc2e9 100644
--- a/ios/chrome/test/data/policy/policy_test_bundle_data.filelist
+++ b/ios/chrome/test/data/policy/policy_test_bundle_data.filelist
@@ -59,6 +59,7 @@
 //ios/chrome/test/data/policy/pref_mapping/PopupsAllowedForUrls.json
 //ios/chrome/test/data/policy/pref_mapping/PopupsBlockedForUrls.json
 //ios/chrome/test/data/policy/pref_mapping/PrintingEnabled.json
+//ios/chrome/test/data/policy/pref_mapping/ProductSpecificationsEnabled.json
 //ios/chrome/test/data/policy/pref_mapping/RestrictAccountsToPatterns.json
 //ios/chrome/test/data/policy/pref_mapping/SafeBrowsingEnabled.json
 //ios/chrome/test/data/policy/pref_mapping/SafeBrowsingProtectionLevel.json
diff --git a/ios/chrome/test/data/policy/pref_mapping/ProductSpecificationsEnabled.json b/ios/chrome/test/data/policy/pref_mapping/ProductSpecificationsEnabled.json
new file mode 100644
index 0000000..3628605
--- /dev/null
+++ b/ios/chrome/test/data/policy/pref_mapping/ProductSpecificationsEnabled.json
@@ -0,0 +1,40 @@
+[
+  {
+    "os": [
+      "ios"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {},
+        "prefs": {
+          "product_specifications_enabled": {
+            "default_value": true,
+            "location": "user_profile"
+          }
+        }
+      },
+      {
+        "policies": {
+          "ProductSpecificationsEnabled": true
+        },
+        "prefs": {
+          "product_specifications_enabled": {
+              "value": true,
+              "location": "user_profile"
+          }
+        }
+      },
+      {
+        "policies": {
+          "ProductSpecificationsEnabled": false
+        },
+        "prefs": {
+          "product_specifications_enabled": {
+              "value": false,
+              "location": "user_profile"
+          }
+        }
+      }
+    ]
+  }
+]
diff --git a/ios/public/provider/chrome/browser/user_feedback/user_feedback_sender.h b/ios/public/provider/chrome/browser/user_feedback/user_feedback_sender.h
index a8eb3fca..7021938 100644
--- a/ios/public/provider/chrome/browser/user_feedback/user_feedback_sender.h
+++ b/ios/public/provider/chrome/browser/user_feedback/user_feedback_sender.h
@@ -21,6 +21,8 @@
   ParcelTracking,
   // Sent from Unit Conversion.
   UnitConversion,
+  // Sent from a content notification.
+  ContentNotification,
 };
 
 #endif  // IOS_PUBLIC_PROVIDER_CHROME_BROWSER_USER_FEEDBACK_USER_FEEDBACK_SENDER_H_
diff --git a/ios/web/js_features/context_menu/context_menu_java_script_feature.h b/ios/web/js_features/context_menu/context_menu_java_script_feature.h
index 4fb5ba7..340b5f9 100644
--- a/ios/web/js_features/context_menu/context_menu_java_script_feature.h
+++ b/ios/web/js_features/context_menu/context_menu_java_script_feature.h
@@ -40,6 +40,7 @@
   void GetElementAtPoint(WebState* web_state,
                          std::string requestID,
                          CGPoint point,
+                         CGSize web_content_size,
                          ElementDetailsCallback callback);
 
   // JavaScriptFeature:
diff --git a/ios/web/js_features/context_menu/context_menu_java_script_feature.mm b/ios/web/js_features/context_menu/context_menu_java_script_feature.mm
index 30e66f1..fb62843b 100644
--- a/ios/web/js_features/context_menu/context_menu_java_script_feature.mm
+++ b/ios/web/js_features/context_menu/context_menu_java_script_feature.mm
@@ -60,6 +60,7 @@
     WebState* web_state,
     std::string requestID,
     CGPoint point,
+    CGSize web_content_size,
     ElementDetailsCallback callback) {
   callbacks_[requestID] = std::move(callback);
 
@@ -68,6 +69,8 @@
   parameters.Append(requestID);
   parameters.Append(point.x);
   parameters.Append(point.y);
+  parameters.Append(web_content_size.width);
+  parameters.Append(web_content_size.height);
   CallJavaScriptFunction(main_frame, "contextMenu.findElementAtPoint",
                          parameters);
 }
diff --git a/ios/web/js_features/context_menu/context_menu_java_script_feature_unittest.mm b/ios/web/js_features/context_menu/context_menu_java_script_feature_unittest.mm
index de907e05..b3f0406 100644
--- a/ios/web/js_features/context_menu/context_menu_java_script_feature_unittest.mm
+++ b/ios/web/js_features/context_menu/context_menu_java_script_feature_unittest.mm
@@ -34,6 +34,7 @@
   ContextMenuJavaScriptFeature::FromBrowserState(GetBrowserState())
       ->GetElementAtPoint(
           web_state(), request_id, CGPointMake(10.0, 10.0),
+          CGSizeMake(100.0, 100.0),
           base::BindOnce(^(const std::string& callback_request_id,
                            const web::ContextMenuParams& params) {
             EXPECT_EQ(request_id, callback_request_id);
@@ -67,6 +68,7 @@
   ContextMenuJavaScriptFeature::FromBrowserState(GetBrowserState())
       ->GetElementAtPoint(
           web_state(), request_id, CGPointMake(10.0, 10.0),
+          CGSizeMake(100.0, 100.0),
           base::BindOnce(^(const std::string& callback_request_id,
                            const web::ContextMenuParams& params) {
             EXPECT_EQ(request_id, callback_request_id);
diff --git a/ios/web/js_features/context_menu/context_menu_js_unittest.mm b/ios/web/js_features/context_menu/context_menu_js_unittest.mm
index b62ee32..acc8abe 100644
--- a/ios/web/js_features/context_menu/context_menu_js_unittest.mm
+++ b/ios/web/js_features/context_menu/context_menu_js_unittest.mm
@@ -37,6 +37,10 @@
 // The base url for loaded web pages.
 const char kTestUrl[] = "https://chromium.test/";
 
+// A point in the web view's coordinate space on the link returned by
+// `GetHtmlForLink()`.
+const CGPoint kPointOnLink = {5.0, 2.0};
+
 // A point in the web view's coordinate space on the image returned by
 // `GetHtmlForImage()`.
 const CGPoint kPointOnImage = {50.0, 10.0};
@@ -100,22 +104,14 @@
           head ? head : @"", body];
 }
 
-// Returns HTML for a link to `href`, with `text`, `id` and inline `style`.
-NSString* GetHtmlForLink(const char* id,
-                         const char* href,
-                         const char* text,
-                         const char* style) {
-  std::string style_attribute =
-      style ? base::StringPrintf("style=\"%s\" ", style) : "";
-  return [NSString stringWithFormat:@"<a id=\"%s\" %shref=\"%s\">%s</a>", id,
-                                    style_attribute.c_str(), href, text];
-}
-
 // Returns HTML for a link to `href`, display `text`, and inline `style`.
 NSString* GetHtmlForLink(const char* href,
                          const char* text,
                          const char* style) {
-  return GetHtmlForLink("link", href, text, style);
+  std::string style_attribute =
+      style ? base::StringPrintf("style=\"%s\" ", style) : "";
+  return [NSString stringWithFormat:@"<a %shref=\"%s\">%s</a>",
+                                    style_attribute.c_str(), href, text];
 }
 
 // Returns HTML for an SVG shape which links to `href`.
@@ -300,11 +296,11 @@
   // Executes __gCrWeb.findElementAtPoint script with the given `point` in the
   // web view viewport's coordinate space.
   id ExecuteFindElementFromPointJavaScript(CGPoint point) {
-    CGFloat scale = web_view().scrollView.zoomScale;
+    CGSize size = GetWebViewContentSize();
     NSString* script = [NSString
         stringWithFormat:@"__gCrWeb.contextMenu.findElementAtPoint('%"
-                         @"s', %g, %g)",
-                         kRequestId, point.x / scale, point.y / scale];
+                         @"s', %g, %g, %g, %g)",
+                         kRequestId, point.x, point.y, size.width, size.height];
 
     return web::test::ExecuteJavaScript(web_view(), script);
   }
@@ -321,9 +317,7 @@
             elementId];
 
     NSDictionary* body = web::test::ExecuteJavaScript(web_view(), script);
-    return CGPointMake(
-        [body[@"x"] floatValue] * web_view().scrollView.zoomScale,
-        [body[@"y"] floatValue] * web_view().scrollView.zoomScale);
+    return CGPointMake([body[@"x"] floatValue], [body[@"y"] floatValue]);
   }
 
   // Handles script message responses sent from `web_view()`.
@@ -922,7 +916,7 @@
                             .Set(kContextMenuElementHyperlink, link)
                             .Set(kContextMenuElementTagName, "a");
 
-  CheckElementResult(@"link", expected_value);
+  CheckElementResult(kPointOnLink, expected_value);
 }
 
 // Tests that a callout information about a link is displayed when
@@ -943,7 +937,7 @@
                             .Set(kContextMenuElementHyperlink, link)
                             .Set(kContextMenuElementTagName, "a");
 
-  CheckElementResult(@"link", expected_value);
+  CheckElementResult(kPointOnLink, expected_value);
 }
 
 // Tests that no callout information about a link is displayed when
@@ -965,7 +959,7 @@
   ignored_keys.push_back(kContextMenuElementTextOffset);
   ignored_keys.push_back(kContextMenuElementSurroundingTextOffset);
 
-  CheckElementResult(@"link", expected_value, ignored_keys);
+  CheckElementResult(kPointOnLink, expected_value, ignored_keys);
 }
 
 // Tests that -webkit-touch-callout property can be inherited from ancester
@@ -986,7 +980,7 @@
   ignored_keys.push_back(kContextMenuElementTextOffset);
   ignored_keys.push_back(kContextMenuElementSurroundingTextOffset);
 
-  CheckElementResult(@"link", expected_value, ignored_keys);
+  CheckElementResult(kPointOnLink, expected_value, ignored_keys);
 }
 
 // Tests that setting -webkit-touch-callout property can override the value
@@ -1008,7 +1002,7 @@
                             .Set(kContextMenuElementHyperlink, link)
                             .Set(kContextMenuElementTagName, "a");
 
-  CheckElementResult(@"link", expected_value);
+  CheckElementResult(kPointOnLink, expected_value);
 }
 
 }  // namespace web
diff --git a/ios/web/js_features/context_menu/resources/main_frame_context_menu.ts b/ios/web/js_features/context_menu/resources/main_frame_context_menu.ts
index d8b25c4..4b076cf5 100644
--- a/ios/web/js_features/context_menu/resources/main_frame_context_menu.ts
+++ b/ios/web/js_features/context_menu/resources/main_frame_context_menu.ts
@@ -21,9 +21,24 @@
  * @param y - vertical center of the selected point in web view
  *                 coordinates.
  */
-function findElementAtPoint(requestId: string, x: number, y: number) {
+function findElementAtPoint(
+    requestId: string, x: number, y: number, webViewWidth: number,
+    _webViewHeight: number) {
+  const scale = getPageWidth() / webViewWidth;
   gCrWeb.contextMenuAllFrames.findElementAtPointInPageCoordinates(
-      requestId, x, y);
+      requestId, x * scale, y * scale);
+}
+
+/**
+ * Returns maximum width of the web page.
+ */
+function getPageWidth(): number {
+  const documentElement = document.documentElement;
+  const documentBody = document.body;
+  return Math.max(
+      documentElement.clientWidth, documentElement.scrollWidth,
+      documentElement.offsetWidth, documentBody.scrollWidth,
+      documentBody.offsetWidth);
 }
 
 gCrWeb.contextMenu = {findElementAtPoint};
diff --git a/ios/web/web_state/ui/crw_context_menu_controller.mm b/ios/web/web_state/ui/crw_context_menu_controller.mm
index 0ad4f5ee..2f3a798 100644
--- a/ios/web/web_state/ui/crw_context_menu_controller.mm
+++ b/ios/web/web_state/ui/crw_context_menu_controller.mm
@@ -94,9 +94,6 @@
   CGPoint locationInWebView =
       [self.webView.scrollView convertPoint:location fromView:interaction.view];
 
-  locationInWebView.x /= self.webView.scrollView.zoomScale;
-  locationInWebView.y /= self.webView.scrollView.zoomScale;
-
   std::optional<web::ContextMenuParams> optionalParams =
       [self fetchContextMenuParamsAtLocation:locationInWebView];
 
diff --git a/ios/web/web_state/ui/crw_context_menu_element_fetcher.mm b/ios/web/web_state/ui/crw_context_menu_element_fetcher.mm
index 33b6705..f42eb4f 100644
--- a/ios/web/web_state/ui/crw_context_menu_element_fetcher.mm
+++ b/ios/web/web_state/ui/crw_context_menu_element_fetcher.mm
@@ -76,7 +76,7 @@
 
   __weak __typeof(self) weakSelf = self;
   context_menu_feature->GetElementAtPoint(
-      self.webState, requestID, point,
+      self.webState, requestID, point, self.webView.scrollView.contentSize,
       base::BindOnce(^(const std::string& innerRequestID,
                        const web::ContextMenuParams& params) {
         web::ContextMenuParams context_menu_params(params);
diff --git a/ios_internal b/ios_internal
index 4f50ee5..e8610f3 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit 4f50ee554da55ee6d7c1382a8b06233257b9ee81
+Subproject commit e8610f3ba765ba96d259acd21b689db6e0cbd181
diff --git a/media/base/frame_buffer_pool.cc b/media/base/frame_buffer_pool.cc
index 9884126..457784a 100644
--- a/media/base/frame_buffer_pool.cc
+++ b/media/base/frame_buffer_pool.cc
@@ -6,8 +6,9 @@
 
 #include "base/logging.h"
 
+#include <vector>
+
 #include "base/check_op.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
 #include "base/location.h"
@@ -243,7 +244,7 @@
 
 void FrameBufferPool::EraseUnusedResourcesLocked() {
   lock_.AssertAcquired();
-  base::EraseIf(frame_buffers_, [](const std::unique_ptr<FrameBuffer>& buf) {
+  std::erase_if(frame_buffers_, [](const std::unique_ptr<FrameBuffer>& buf) {
     return !IsUsedLocked(buf.get());
   });
 }
@@ -264,7 +265,7 @@
     frame_buffer->last_use_time = now;
   }
 
-  base::EraseIf(frame_buffers_, [now](const std::unique_ptr<FrameBuffer>& buf) {
+  std::erase_if(frame_buffers_, [now](const std::unique_ptr<FrameBuffer>& buf) {
     return !IsUsedLocked(buf.get()) &&
            now - buf->last_use_time > base::Seconds(kStaleFrameLimitSecs);
   });
diff --git a/media/base/frame_buffer_pool.h b/media/base/frame_buffer_pool.h
index 3c989d7..353b9a1 100644
--- a/media/base/frame_buffer_pool.h
+++ b/media/base/frame_buffer_pool.h
@@ -36,6 +36,10 @@
   // Called when a frame buffer allocation is needed. Upon return |fb_priv| will
   // be set to a private value used to identify the buffer in future calls and a
   // buffer of at least |min_size| will be returned.
+  //
+  // WARNING: To release the FrameBuffer, clients must either call Shutdown() or
+  // ReleaseFrameBuffer() in addition to any callbacks returned by
+  // CreateFrameCallback() (if any are created).
   uint8_t* GetFrameBuffer(size_t min_size, void** fb_priv);
 
   // Called when a frame buffer allocation is no longer needed.
diff --git a/media/base/video_frame_converter.cc b/media/base/video_frame_converter.cc
index b75b435..22e5c5c 100644
--- a/media/base/video_frame_converter.cc
+++ b/media/base/video_frame_converter.cc
@@ -104,7 +104,7 @@
   if (tmp_frame) {
     tmp_frame->AddDestructionObserver(frame_pool_->CreateFrameCallback(fb_id));
   }
-
+  frame_pool_->ReleaseFrameBuffer(fb_id);
   return tmp_frame;
 }
 
@@ -155,7 +155,7 @@
     wrapped_frame->AddDestructionObserver(
         frame_pool_->CreateFrameCallback(fb_id));
   }
-
+  frame_pool_->ReleaseFrameBuffer(fb_id);
   return wrapped_frame;
 }
 
diff --git a/media/base/video_frame_converter.h b/media/base/video_frame_converter.h
index 87b6e03e..9f2ccd4 100644
--- a/media/base/video_frame_converter.h
+++ b/media/base/video_frame_converter.h
@@ -45,6 +45,10 @@
   EncoderStatus ConvertAndScale(const VideoFrame& src_frame,
                                 VideoFrame& dest_frame);
 
+  size_t get_pool_size_for_testing() const {
+    return frame_pool_->get_pool_size_for_testing();
+  }
+
  private:
   // Creates a temporary frame backed by `frame_pool_`.
   scoped_refptr<VideoFrame> CreateTempFrame(VideoPixelFormat format,
diff --git a/media/base/video_frame_converter_unittest.cc b/media/base/video_frame_converter_unittest.cc
index adc160a..ee84d7e 100644
--- a/media/base/video_frame_converter_unittest.cc
+++ b/media/base/video_frame_converter_unittest.cc
@@ -165,6 +165,14 @@
     EXPECT_DOUBLE_EQ(ssim, 1.0);
     EXPECT_EQ(psnr, libyuv::kMaxPsnr);
   }
+
+  // Ensure memory pool is functioning correctly by running conversions which
+  // use scratch space twice.
+  if (converter_.get_pool_size_for_testing() > 0) {
+    EXPECT_EQ(converter_.get_pool_size_for_testing(), 1u);
+    ASSERT_TRUE(converter_.ConvertAndScale(*src_frame, *dest_frame).is_ok());
+    EXPECT_EQ(converter_.get_pool_size_for_testing(), 1u);
+  }
 }
 
 INSTANTIATE_TEST_SUITE_P(,
diff --git a/media/gpu/android/maybe_render_early_manager.cc b/media/gpu/android/maybe_render_early_manager.cc
index 7de5cd5..67085360 100644
--- a/media/gpu/android/maybe_render_early_manager.cc
+++ b/media/gpu/android/maybe_render_early_manager.cc
@@ -4,8 +4,9 @@
 
 #include "media/gpu/android/maybe_render_early_manager.h"
 
+#include <vector>
+
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner.h"
@@ -56,7 +57,7 @@
     DCHECK(base::Contains(images_, image));
     // Remember that |image_group_| might not be the same one that |image|
     // belongs to.
-    base::Erase(images_, image);
+    std::erase(images_, image);
     internal::MaybeRenderEarly(&images_);
   }
 
diff --git a/media/gpu/chromeos/video_decoder_pipeline.cc b/media/gpu/chromeos/video_decoder_pipeline.cc
index 3af7a5f..d28fe13 100644
--- a/media/gpu/chromeos/video_decoder_pipeline.cc
+++ b/media/gpu/chromeos/video_decoder_pipeline.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <optional>
+#include <vector>
 
 #include "base/containers/contains.h"
 #include "base/feature_list.h"
@@ -290,35 +291,35 @@
     return std::nullopt;
 
   if (workarounds.disable_accelerated_vp8_decode) {
-    base::EraseIf(configs.value(), [](const auto& config) {
+    std::erase_if(configs.value(), [](const auto& config) {
       return config.profile_min >= VP8PROFILE_MIN &&
              config.profile_max <= VP8PROFILE_MAX;
     });
   }
 
   if (workarounds.disable_accelerated_vp9_decode) {
-    base::EraseIf(configs.value(), [](const auto& config) {
+    std::erase_if(configs.value(), [](const auto& config) {
       return config.profile_min >= VP9PROFILE_PROFILE0 &&
              config.profile_max <= VP9PROFILE_PROFILE0;
     });
   }
 
   if (workarounds.disable_accelerated_vp9_profile2_decode) {
-    base::EraseIf(configs.value(), [](const auto& config) {
+    std::erase_if(configs.value(), [](const auto& config) {
       return config.profile_min >= VP9PROFILE_PROFILE2 &&
              config.profile_max <= VP9PROFILE_PROFILE2;
     });
   }
 
   if (workarounds.disable_accelerated_h264_decode) {
-    base::EraseIf(configs.value(), [](const auto& config) {
+    std::erase_if(configs.value(), [](const auto& config) {
       return config.profile_min >= H264PROFILE_MIN &&
              config.profile_max <= H264PROFILE_MAX;
     });
   }
 
   if (workarounds.disable_accelerated_hevc_decode) {
-    base::EraseIf(configs.value(), [](const auto& config) {
+    std::erase_if(configs.value(), [](const auto& config) {
       return config.profile_min >= HEVCPROFILE_MIN &&
              config.profile_max <= HEVCPROFILE_MAX;
     });
diff --git a/media/gpu/gpu_video_encode_accelerator_factory.cc b/media/gpu/gpu_video_encode_accelerator_factory.cc
index f6c262c..daede9e 100644
--- a/media/gpu/gpu_video_encode_accelerator_factory.cc
+++ b/media/gpu/gpu_video_encode_accelerator_factory.cc
@@ -8,7 +8,6 @@
 #include <vector>
 
 #include "base/command_line.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/memory/ptr_util.h"
@@ -222,27 +221,27 @@
 #endif
 
   if (gpu_workarounds.disable_accelerated_av1_encode) {
-    base::EraseIf(profiles, [](const auto& vea_profile) {
+    std::erase_if(profiles, [](const auto& vea_profile) {
       return vea_profile.profile >= AV1PROFILE_PROFILE_MAIN &&
              vea_profile.profile <= AV1PROFILE_PROFILE_PRO;
     });
   }
 
   if (gpu_workarounds.disable_accelerated_vp8_encode) {
-    base::EraseIf(profiles, [](const auto& vea_profile) {
+    std::erase_if(profiles, [](const auto& vea_profile) {
       return vea_profile.profile == VP8PROFILE_ANY;
     });
   }
 
   if (gpu_workarounds.disable_accelerated_vp9_encode) {
-    base::EraseIf(profiles, [](const auto& vea_profile) {
+    std::erase_if(profiles, [](const auto& vea_profile) {
       return vea_profile.profile >= VP9PROFILE_PROFILE0 &&
              vea_profile.profile <= VP9PROFILE_PROFILE3;
     });
   }
 
   if (gpu_workarounds.disable_accelerated_h264_encode) {
-    base::EraseIf(profiles, [](const auto& vea_profile) {
+    std::erase_if(profiles, [](const auto& vea_profile) {
       return vea_profile.profile >= H264PROFILE_MIN &&
              vea_profile.profile <= H264PROFILE_MAX;
     });
diff --git a/media/gpu/mac/video_toolbox_decompression_metadata.h b/media/gpu/mac/video_toolbox_decompression_metadata.h
index e579ff88..b42fdee 100644
--- a/media/gpu/mac/video_toolbox_decompression_metadata.h
+++ b/media/gpu/mac/video_toolbox_decompression_metadata.h
@@ -45,6 +45,9 @@
   gfx::ColorSpace color_space;
   std::optional<gfx::HDRMetadata> hdr_metadata;
 
+  // The frame should be dropped after decoding. Used to implement Reset().
+  bool discard = false;
+
   // Session metadata is included in case the decoder needs to be reconfigured.
   // TODO(crbug.com/1331597): Pass separately, maybe even independently.
   VideoToolboxDecompressionSessionMetadata session_metadata;
diff --git a/media/gpu/mac/video_toolbox_decompression_session_manager.cc b/media/gpu/mac/video_toolbox_decompression_session_manager.cc
index 6ea5d9c1..f2135b4 100644
--- a/media/gpu/mac/video_toolbox_decompression_session_manager.cc
+++ b/media/gpu/mac/video_toolbox_decompression_session_manager.cc
@@ -66,12 +66,17 @@
 
   pending_decodes_ = {};
 
-  DestroySession();
+  for (auto& it : active_decodes_) {
+    it.second->discard = true;
+  }
+
+  draining_ = false;
 }
 
 size_t VideoToolboxDecompressionSessionManager::NumDecodes() {
   DVLOG(4) << __func__;
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
+  // Note: This counts frames that will be discarded due to a Reset().
   return pending_decodes_.size() + active_decodes_.size();
 }
 
@@ -80,9 +85,9 @@
   DCHECK(task_runner_->RunsTasksInCurrentSequence());
   DCHECK(!has_error_);
 
-  Reset();
-
   has_error_ = true;
+  pending_decodes_ = {};
+  DestroySession();
 
   // We may still be executing inside Decode() and don't want to make a
   // re-entrant call.
@@ -176,7 +181,6 @@
       decoder_config.get(),
       kVTVideoDecoderSpecification_EnableHardwareAcceleratedVideoDecoder,
       kCFBooleanTrue);
-  // TODO(crbug.com/1331597): Use session_metadata.allow_software_decoding.
   CFDictionarySetValue(
       decoder_config.get(),
       kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder,
@@ -306,8 +310,10 @@
     }
   }
 
-  // OnOutput() was posted, so this is never re-entrant.
-  output_cb_.Run(std::move(image), std::move(metadata));
+  if (!metadata->discard) {
+    // OnOutput() was posted, so this is never re-entrant.
+    output_cb_.Run(std::move(image), std::move(metadata));
+  }
 }
 
 void VideoToolboxDecompressionSessionManager::SetDecompressionSessionForTesting(
diff --git a/media/gpu/test/video_encoder/decoder_buffer_validator.cc b/media/gpu/test/video_encoder/decoder_buffer_validator.cc
index 9319e7d..0ceb0a4 100644
--- a/media/gpu/test/video_encoder/decoder_buffer_validator.cc
+++ b/media/gpu/test/video_encoder/decoder_buffer_validator.cc
@@ -4,9 +4,9 @@
 #include "media/gpu/test/video_encoder/decoder_buffer_validator.h"
 
 #include <set>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/logging.h"
 #include "base/numerics/safe_conversions.h"
 #include "build/build_config.h"
@@ -757,7 +757,7 @@
       }
     }
     for (uint8_t p_diff : vp9.p_diffs) {
-      if (!base::Erase(expected_pdiffs, p_diff)) {
+      if (!std::erase(expected_pdiffs, p_diff)) {
         LOG(ERROR)
             << "Frame is referencing buffer not contained in the p_diff.";
         return false;
@@ -925,7 +925,7 @@
       expected_pdiffs.push_back(new_buffer_state.picture_id - ref.picture_id);
     }
     for (uint8_t p_diff : vp9.p_diffs) {
-      if (!base::Erase(expected_pdiffs, p_diff)) {
+      if (!std::erase(expected_pdiffs, p_diff)) {
         LOG(ERROR)
             << "Frame is referencing buffer not contained in the p_diff.";
         return false;
diff --git a/media/gpu/vaapi/vaapi_wrapper.cc b/media/gpu/vaapi/vaapi_wrapper.cc
index 12d0768..184444f9 100644
--- a/media/gpu/vaapi/vaapi_wrapper.cc
+++ b/media/gpu/vaapi/vaapi_wrapper.cc
@@ -19,9 +19,9 @@
 #include <string>
 #include <type_traits>
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/containers/fixed_flat_set.h"
 #include "base/cpu.h"
 #include "base/environment.h"
@@ -3378,7 +3378,7 @@
   DVLOG(2) << "Destroying " << va_surfaces.size() << " surfaces";
 
   // vaDestroySurfaces() makes no guarantees about VA_INVALID_SURFACE.
-  base::Erase(va_surfaces, VA_INVALID_SURFACE);
+  std::erase(va_surfaces, VA_INVALID_SURFACE);
   if (va_surfaces.empty())
     return;
 
diff --git a/media/renderers/video_resource_updater.cc b/media/renderers/video_resource_updater.cc
index b87a4c9..a0cd5cb9 100644
--- a/media/renderers/video_resource_updater.cc
+++ b/media/renderers/video_resource_updater.cc
@@ -8,10 +8,10 @@
 #include <stdint.h>
 
 #include <string>
+#include <vector>
 
 #include "base/atomic_sequence_num.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
@@ -1848,7 +1848,7 @@
                     subplane_si_format.value_or(output_si_format)) ||
                !base::Contains(outplane_plane_sizes, resource->resource_size());
       };
-  base::EraseIf(all_resources_, can_delete_resource_fn);
+  std::erase_if(all_resources_, can_delete_resource_fn);
 
   // Recycle or allocate resources for each video plane.
   std::vector<PlaneResource*> plane_resources;
diff --git a/media/video/picture.h b/media/video/picture.h
index 4541758b..c0a79a0d 100644
--- a/media/video/picture.h
+++ b/media/video/picture.h
@@ -51,7 +51,7 @@
  private:
   int32_t id_;
   gfx::Size size_;
-  uint32_t service_texture_id_;
+  uint32_t service_texture_id_ = 0;
   uint32_t texture_target_ = 0;
   VideoPixelFormat pixel_format_ = PIXEL_FORMAT_UNKNOWN;
 };
diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
index 2826855..75f98ab 100644
--- a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
+++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
@@ -9,10 +9,10 @@
 #include <optional>
 #include <string_view>
 #include <tuple>
+#include <vector>
 
 #include "base/check.h"
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/debug/alias.h"
 #include "base/functional/bind.h"
 #include "base/location.h"
@@ -443,7 +443,7 @@
 
   {
     base::AutoLock l(sync_calls->lock);
-    base::Erase(sync_calls->pending_responses, response.get());
+    std::erase(sync_calls->pending_responses, response.get());
   }
 
   if (response->received)
diff --git a/net/base/url_search_params.cc b/net/base/url_search_params.cc
index 9215bc4..e20d42f 100644
--- a/net/base/url_search_params.cc
+++ b/net/base/url_search_params.cc
@@ -10,7 +10,6 @@
 #include <utility>
 #include <vector>
 
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/strings/utf_string_conversions.h"
 #include "net/base/url_util.h"
 #include "url/gurl.h"
@@ -46,13 +45,13 @@
 
 void UrlSearchParams::DeleteAllWithNames(
     const base::flat_set<std::string>& names) {
-  base::EraseIf(params_,
+  std::erase_if(params_,
                 [&](const auto& pair) { return names.contains(pair.first); });
 }
 
 void UrlSearchParams::DeleteAllExceptWithNames(
     const base::flat_set<std::string>& names) {
-  base::EraseIf(params_,
+  std::erase_if(params_,
                 [&](const auto& pair) { return !names.contains(pair.first); });
 }
 
diff --git a/net/cert/nss_cert_database_chromeos.cc b/net/cert/nss_cert_database_chromeos.cc
index 7c68834..9e140a0a 100644
--- a/net/cert/nss_cert_database_chromeos.cc
+++ b/net/cert/nss_cert_database_chromeos.cc
@@ -10,8 +10,8 @@
 #include <algorithm>
 #include <memory>
 #include <utility>
+#include <vector>
 
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/location.h"
@@ -73,7 +73,7 @@
   NSSCertDatabase::ListModules(modules, need_rw);
 
   const NSSProfileFilterChromeOS& profile_filter = profile_filter_;
-  base::EraseIf(*modules, [&profile_filter](crypto::ScopedPK11Slot& module) {
+  std::erase_if(*modules, [&profile_filter](crypto::ScopedPK11Slot& module) {
     return !profile_filter.IsModuleAllowed(module.get());
   });
 }
@@ -130,7 +130,7 @@
       crypto::ScopedPK11Slot(), add_certs_info, nss_roots_handling));
 
   // Filter certificate information according to user profile.
-  base::EraseIf(certs_info, [&profile_filter](CertInfo& cert_info) {
+  std::erase_if(certs_info, [&profile_filter](CertInfo& cert_info) {
     return !profile_filter.IsCertAllowed(cert_info.cert.get());
   });
 
diff --git a/net/dns/address_sorter_posix.cc b/net/dns/address_sorter_posix.cc
index 0f4afb7..6ff0704 100644
--- a/net/dns/address_sorter_posix.cc
+++ b/net/dns/address_sorter_posix.cc
@@ -29,8 +29,8 @@
 #include <netinet/in_var.h>
 #endif  // BUILDFLAG(IS_IOS)
 #endif
+#include <vector>
 
-#include "base/containers/cxx20_erase_vector.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/logging.h"
 #include "net/base/ip_endpoint.h"
@@ -326,7 +326,7 @@
                      info.src.prefix_length);
       }
     }
-    base::EraseIf(sort_list_, [](auto& element) { return element.failed; });
+    std::erase_if(sort_list_, [](auto& element) { return element.failed; });
     std::stable_sort(sort_list_.begin(), sort_list_.end(), CompareDestinations);
 
     std::vector<IPEndPoint> sorted_result;
diff --git a/net/quic/dedicated_web_transport_http3_client.cc b/net/quic/dedicated_web_transport_http3_client.cc
index 96e7abe..248cbb0 100644
--- a/net/quic/dedicated_web_transport_http3_client.cc
+++ b/net/quic/dedicated_web_transport_http3_client.cc
@@ -4,8 +4,9 @@
 
 #include "net/quic/dedicated_web_transport_http3_client.h"
 
+#include <vector>
+
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/feature_list.h"
 #include "base/memory/raw_ptr.h"
 #include "base/metrics/field_trial_params.h"
@@ -937,7 +938,7 @@
     retried_with_new_version_ = true;
     DCHECK(original_supported_versions_.empty());
     original_supported_versions_ = supported_versions_;
-    base::EraseIf(
+    std::erase_if(
         supported_versions_, [this](const quic::ParsedQuicVersion& version) {
           return !base::Contains(
               session_->connection()->server_supported_versions(), version);
diff --git a/remoting/host/linux/wayland_display.cc b/remoting/host/linux/wayland_display.cc
index 76f5c1c..304c008 100644
--- a/remoting/host/linux/wayland_display.cc
+++ b/remoting/host/linux/wayland_display.cc
@@ -4,8 +4,9 @@
 
 #include "remoting/host/linux/wayland_display.h"
 
+#include <vector>
+
 #include "base/check.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/hash/hash.h"
 #include "remoting/base/logging.h"
 
@@ -63,7 +64,7 @@
 }
 
 bool WaylandDisplay::HandleGlobalRemoveDisplayEvent(uint32_t name) {
-  size_t num_removed = base::EraseIf(
+  size_t num_removed = std::erase_if(
       display_info_,
       [name](const auto& display_info) { return display_info.id == name; });
   DCHECK(num_removed <= 1)
diff --git a/sandbox/policy/sandbox.cc b/sandbox/policy/sandbox.cc
index 217e07c..b6495ae 100644
--- a/sandbox/policy/sandbox.cc
+++ b/sandbox/policy/sandbox.cc
@@ -23,8 +23,7 @@
 #endif  // BUILDFLAG(IS_MAC)
 
 #if BUILDFLAG(IS_WIN)
-#include "base/debug/alias.h"
-#include "base/notreached.h"
+#include "base/check_op.h"
 #include "base/process/process_info.h"
 #include "sandbox/policy/win/sandbox_win.h"
 #include "sandbox/win/src/sandbox.h"
@@ -61,13 +60,9 @@
       // will be broken. This has to run before threads and windows are created.
       ResultCode result = broker_services->CreateAlternateDesktop(
           Desktop::kAlternateWinstation);
-      if (result != SBOX_ALL_OK) {
-        // TODO(crbug.com/1396219) Gather some extra data when this fails.
-        DWORD gle = GetLastError();
-        base::debug::Alias(&result);
-        base::debug::Alias(&gle);
-        NOTREACHED_NORETURN();
-      }
+      // This failure is usually caused by third-party software or by the host
+      // system exhausting its desktop heap.
+      CHECK(result == SBOX_ALL_OK);
     }
     return true;
   }
diff --git a/sql/BUILD.gn b/sql/BUILD.gn
index 5e46b88..02dae9c 100644
--- a/sql/BUILD.gn
+++ b/sql/BUILD.gn
@@ -19,24 +19,6 @@
     "internal_api_token.h",
     "meta_table.cc",
     "meta_table.h",
-    "recover_module/btree.cc",
-    "recover_module/btree.h",
-    "recover_module/cursor.cc",
-    "recover_module/cursor.h",
-    "recover_module/integers.cc",
-    "recover_module/integers.h",
-    "recover_module/module.cc",
-    "recover_module/module.h",
-    "recover_module/pager.cc",
-    "recover_module/pager.h",
-    "recover_module/parsing.cc",
-    "recover_module/parsing.h",
-    "recover_module/payload.cc",
-    "recover_module/payload.h",
-    "recover_module/record.cc",
-    "recover_module/record.h",
-    "recover_module/table.cc",
-    "recover_module/table.h",
     "recovery.cc",
     "recovery.h",
     "sandboxed_vfs.cc",
@@ -114,7 +96,6 @@
     "database_options_unittest.cc",
     "database_unittest.cc",
     "meta_table_unittest.cc",
-    "recover_module/module_unittest.cc",
     "recovery_unittest.cc",
     "sql_memory_dump_provider_unittest.cc",
     "sqlite_features_unittest.cc",
@@ -143,10 +124,8 @@
   ]
 }
 
-# Why "built in"? Chromium used to have bespoke recovery code before SQLite
-# had a built-in recovery module. See crbug.com/1513310
-fuzzer_test("sql_built_in_recovery_fuzzer") {
-  sources = [ "built_in_recovery_fuzzer.cc" ]
+fuzzer_test("sql_recovery_fuzzer") {
+  sources = [ "recovery_fuzzer.cc" ]
   deps = [
     ":sql",
     "//base",
@@ -158,5 +137,5 @@
 
 group("fuzzers") {
   testonly = true
-  deps = [ "//sql/fuzzers:sql_built_in_recovery_lpm_fuzzer" ]
+  deps = [ "//sql/fuzzers:sql_recovery_lpm_fuzzer" ]
 }
diff --git a/sql/fuzzers/BUILD.gn b/sql/fuzzers/BUILD.gn
index fffddb2..9c0f489 100644
--- a/sql/fuzzers/BUILD.gn
+++ b/sql/fuzzers/BUILD.gn
@@ -5,8 +5,8 @@
 import("//testing/libfuzzer/fuzzer_test.gni")
 import("//third_party/libprotobuf-mutator/fuzzable_proto_library.gni")
 
-fuzzer_test("sql_built_in_recovery_lpm_fuzzer") {
-  sources = [ "built_in_recovery_lpm_fuzzer.cc" ]
+fuzzer_test("sql_recovery_lpm_fuzzer") {
+  sources = [ "recovery_lpm_fuzzer.cc" ]
   deps = [
     ":sql_disk_corruption_proto",
     "//base",
diff --git a/sql/fuzzers/built_in_recovery_lpm_fuzzer.cc b/sql/fuzzers/recovery_lpm_fuzzer.cc
similarity index 97%
rename from sql/fuzzers/built_in_recovery_lpm_fuzzer.cc
rename to sql/fuzzers/recovery_lpm_fuzzer.cc
index 34c4ab7..fb9be6d 100644
--- a/sql/fuzzers/built_in_recovery_lpm_fuzzer.cc
+++ b/sql/fuzzers/recovery_lpm_fuzzer.cc
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// This fuzzer exercises BuiltInRecovery like sql_built_in_recovery_fuzzer, but
-// employs a different strategy for generating database files. Rather than
-// directly interpreting the fuzzer input as a SQLite database file, this fuzzer
+// This fuzzer exercises recovery like sql_recovery_fuzzer, but employs a
+// different strategy for generating database files. Rather than directly
+// interpreting the fuzzer input as a SQLite database file, this fuzzer
 // constructs a DB from fuzzer-derived SQL statements and then mutates the file
 // with fuzzer-derived XOR masks before exercising recovery.
 
diff --git a/sql/recover_module/README.md b/sql/recover_module/README.md
deleted file mode 100644
index fb1e0d6..0000000
--- a/sql/recover_module/README.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# SQLite Data Recovery
-
-This directory implements data recovery heuristics for SQLite databases whose
-files were corrupted on disk. The recovery code walks through the B-tree
-holding a SQLite table and recovers all records that seem healthy. Even if
-recovery succeeds, a recovered table may be missing records, and existing
-records may have corrupted values inside them. Any constraints imposed by Chrome
-or by SQLite may be broken.
-
-
-## Usage
-
-The default approach for handling corruption in SQLite databases is to
-immediately stop using the database, delete it, and start over with a new
-database. The recovery method implemented here is intended for databases used by
-Chrome features that handle high-value user data, such as History and Bookmarks.
-These features carefully handle data inconsistency edge cases, and their
-database schemas are resilient to partial data loss.
-
-The code is plugged into the rest of Chrome via
-[SQLite's virtual table module API](https://sqlite.org/vtab.html). The example
-below covers a typical recovery scenario.
-
-```sql
--- Feature table schema.
-CREATE TABLE data(name TEXT PRIMARY KEY, value TEXT NOT NULL);
-
--- Recover in another database. The corrupted one is unreliable.
-ATTACH DATABASE '/tmp/db.db' as recovery;
--- Re-create the feature table's schema.
-CREATE TABLE recovery.feature(name TEXT PRIMARY KEY, value TEXT NOT NULL);
--- Start reading the corrupted data.
-CREATE VIRTUAL TABLE temp.recover_feature USING recover(
-    main.feature, -- The corrupted database.
-    -- Recovery will skip row values that don't have the TEXT type.
-    name TEXT STRICT NOT NULL,
-    -- Recovery will include any row value coercible to TEXT.
-    value TEXT NOT NULL);
--- Data recovered from corrupted databases may not meet schema constraints, so
--- recovery insertions must use "OR REPLACE"  or "OR IGNORE".
-INSERT OR REPLACE INTO recovery.feature(rowid, name, value)
-SELECT rowid, name, value FROM temp.recover_feature;
--- Cleanup after the recovery operation.
-DROP TABLE temp.recover_feature;
-DETACH DATABASE recovery;
--- Replace the corrupted database file with the recovered one.
-```
-
-The feature invoking the recovery virtual table must know the schema of the
-database being recovered. A generic high-level recovery layer should first
-recover
-[the `sqlite_schema` table](https://www.sqlite.org/fileformat.html#storage_of_the_sql_database_schema),
-which has a well known format, then use its contents to recover the schema of
-any other table. This recovery module already relies on the integrity of the
-`sqlite_schema` table.
-
-The column definitions in the virtual table creation statement must follow
-the syntax _column\_name_ _type\_name_ [`STRICT`] [`NOT NULL`]. _type\_name_ is
-[the SQLite data types](https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes),
-or the special types `ANY` or `ROWID`.
-
-The `ANY` type can be used to recover values of all types.
-
-The `ROWID` type must be used for columns that alias
-[rowid](https://www.sqlite.org/lang_createtable.html#rowid). This typically only
-happens when a column is declared as `INTEGER PRIMARY KEY`.
-Designating `ROWID` columns is essential for decoding records correctly.
-
-TODO(pwnall): Look into removing `STRICT`, if it's not used.
-
-
-## Limitations
-
-The current implementation only handles [table
-B-trees](https://www.sqlite.org/fileformat.html#b_tree_pages). It cannot
-recover [WITHOUT ROWID](https://www.sqlite.org/rowidtable.html) tables, which
-are stored in index B-trees.
-
-
-## Code Map
-
-The code is structured as follows.
-
-* integers.{cc,h} decodes the integer formats used by SQLite.
-* btree.{cc,h} decodes the cells in SQLite's B-tree pages.
-* payload.{cc,h} reads record payloads from B-tree pages and overflow pages.
-* record.{cc,h} decodes column values from record.
-* cursor.{cc,h} implements a SQLite virtual table cursor.
-* table.{cc,h} implements one recovery virtual table.
-* parsing.{cc,h} parses the SQL strings passed in via `CREATE VIRTUAL TABLE`
-  and implements the constraints explained above.
-* module.{cc,h} implements the SQLite virtual table interface.
-
-The feature is tested by integration tests that issue SQLite queries.
diff --git a/sql/recover_module/btree.cc b/sql/recover_module/btree.cc
deleted file mode 100644
index ad985a3..0000000
--- a/sql/recover_module/btree.cc
+++ /dev/null
@@ -1,253 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/btree.h"
-
-#include <algorithm>
-#include <limits>
-#include <ostream>
-#include <type_traits>
-
-#include "base/check_op.h"
-#include "sql/recover_module/integers.h"
-#include "sql/recover_module/pager.h"
-#include "third_party/sqlite/sqlite3.h"
-
-namespace sql {
-namespace recover {
-
-namespace {
-
-// The SQLite database format is documented at the following URLs.
-//   https://www.sqlite.org/fileformat.html
-//   https://www.sqlite.org/fileformat2.html
-constexpr uint8_t kInnerTablePageType = 0x05;
-constexpr uint8_t kLeafTablePageType = 0x0D;
-
-// Offset from the page header to the page type byte.
-constexpr int kPageTypePageOffset = 0;
-// Offset from the page header to the 2-byte cell count.
-constexpr int kCellCountPageOffset = 3;
-// Offset from an inner page header to the 4-byte last child page ID.
-constexpr int kLastChildIdInnerPageOffset = 8;
-// Offset from an inner page header to the cell pointer array.
-constexpr int kFirstCellOfsetInnerPageOffset = 12;
-// Offset from a leaf page header to the cell pointer array.
-constexpr int kFirstCellOfsetLeafPageOffset = 8;
-
-}  // namespace
-
-#if !DCHECK_IS_ON()
-// In DCHECKed builds, the decoder contains a sequence checker, which has a
-// non-trivial destructor.
-static_assert(std::is_trivially_destructible<InnerPageDecoder>::value,
-              "Move the destructor to the .cc file if it's non-trival");
-#endif  // !DCHECK_IS_ON()
-
-InnerPageDecoder::InnerPageDecoder(DatabasePageReader* db_reader) noexcept
-    : page_id_(db_reader->page_id()),
-      db_reader_(db_reader),
-      cell_count_(ComputeCellCount(db_reader)),
-      next_read_index_(0) {
-  DCHECK(IsOnValidPage(db_reader));
-  DCHECK(DatabasePageReader::IsValidPageId(page_id_));
-}
-
-int InnerPageDecoder::TryAdvance() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(CanAdvance());
-
-  const int sqlite_status = db_reader_->ReadPage(page_id_);
-  if (sqlite_status != SQLITE_OK) {
-    // TODO(pwnall): UMA the error code.
-
-    next_read_index_ = cell_count_ + 1;  // End the reading process.
-    return DatabasePageReader::kHighestInvalidPageId;
-  }
-
-  const uint8_t* const page_data = db_reader_->page_data();
-  const int read_index = next_read_index_;
-  next_read_index_ += 1;
-  if (read_index == cell_count_)
-    return LoadBigEndianInt32(page_data + kLastChildIdInnerPageOffset);
-
-  const int cell_pointer_offset =
-      kFirstCellOfsetInnerPageOffset + (read_index << 1);
-  DCHECK_LE(cell_pointer_offset + 2, db_reader_->page_size())
-      << "ComputeCellCount() used an incorrect upper bound";
-  const int cell_pointer = LoadBigEndianUint16(page_data + cell_pointer_offset);
-
-  static_assert(std::numeric_limits<uint16_t>::max() + 4 <
-                    std::numeric_limits<int>::max(),
-                "The addition below may overflow");
-  if (cell_pointer + 4 >= db_reader_->page_size()) {
-    // Each cell needs 1 byte for the rowid varint, in addition to the 4 bytes
-    // for the child page number that will be read below. Skip cells that
-    // obviously go over the page end.
-    return DatabasePageReader::kHighestInvalidPageId;
-  }
-  if (cell_pointer < kFirstCellOfsetInnerPageOffset) {
-    // The pointer points into the cell's header.
-    return DatabasePageReader::kHighestInvalidPageId;
-  }
-
-  return LoadBigEndianInt32(page_data + cell_pointer);
-}
-
-// static
-bool InnerPageDecoder::IsOnValidPage(DatabasePageReader* db_reader) {
-  static_assert(kPageTypePageOffset < DatabasePageReader::kMinUsablePageSize,
-                "The check below may perform an out-of-bounds memory access");
-  return db_reader->page_data()[kPageTypePageOffset] == kInnerTablePageType;
-}
-
-// static
-int InnerPageDecoder::ComputeCellCount(DatabasePageReader* db_reader) {
-  // The B-tree page header stores the cell count.
-  int header_count =
-      LoadBigEndianUint16(db_reader->page_data() + kCellCountPageOffset);
-  static_assert(
-      kCellCountPageOffset + 2 <= DatabasePageReader::kMinUsablePageSize,
-      "The read above may be out of bounds");
-
-  // However, the data may be corrupted. So, use an upper bound based on the
-  // fact that the cell pointer array should never extend past the end of the
-  // page.
-  //
-  // The page size is always even, because it is either a power of two, for
-  // most pages, or a power of two minus 100, for the first database page. The
-  // cell pointer array starts at offset 12. So, each cell pointer must be
-  // separated from the page buffer's end by an even number of bytes.
-  DCHECK((db_reader->page_size() - kFirstCellOfsetInnerPageOffset) % 2 == 0);
-  int upper_bound =
-      (db_reader->page_size() - kFirstCellOfsetInnerPageOffset) >> 1;
-  static_assert(
-      kFirstCellOfsetInnerPageOffset <= DatabasePageReader::kMinUsablePageSize,
-      "The |upper_bound| computation above may overflow");
-
-  return std::min(header_count, upper_bound);
-}
-
-#if !DCHECK_IS_ON()
-// In DCHECKed builds, the decoder contains a sequence checker, which has a
-// non-trivial destructor.
-static_assert(std::is_trivially_destructible<LeafPageDecoder>::value,
-              "Move the destructor to the .cc file if it's non-trival");
-#endif  // !DCHECK_IS_ON()
-
-LeafPageDecoder::LeafPageDecoder(DatabasePageReader* db_reader) noexcept
-    : page_id_(db_reader->page_id()),
-      db_reader_(db_reader),
-      cell_count_(ComputeCellCount(db_reader)),
-      next_read_index_(0),
-      last_record_size_(0) {
-  DCHECK(IsOnValidPage(db_reader));
-  DCHECK(DatabasePageReader::IsValidPageId(page_id_));
-}
-
-bool LeafPageDecoder::TryAdvance() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(CanAdvance());
-
-#if DCHECK_IS_ON()
-  // DCHECKs use last_record_size == 0 to check for incorrect access to the
-  // decoder's state.
-  last_record_size_ = 0;
-#endif  // DCHECK_IS_ON()
-
-  const int sqlite_status = db_reader_->ReadPage(page_id_);
-  if (sqlite_status != SQLITE_OK) {
-    // TODO(pwnall): UMA the error code.
-
-    next_read_index_ = cell_count_;  // End the reading process.
-    return false;
-  }
-
-  const uint8_t* page_data = db_reader_->page_data();
-  const int read_index = next_read_index_;
-  next_read_index_ += 1;
-
-  const int cell_pointer_offset =
-      kFirstCellOfsetLeafPageOffset + (read_index << 1);
-  DCHECK_LE(cell_pointer_offset + 2, db_reader_->page_size())
-      << "ComputeCellCount() used an incorrect upper bound";
-  const int cell_pointer = LoadBigEndianUint16(page_data + cell_pointer_offset);
-
-  static_assert(std::numeric_limits<uint16_t>::max() + 3 <
-                    std::numeric_limits<int>::max(),
-                "The addition below may overflow");
-  if (cell_pointer + 3 >= db_reader_->page_size()) {
-    // Each cell needs at least 1 byte for page type varint, 1 byte for the
-    // rowid varint, and 1 byte for the record header size varint. Skip cells
-    // that obviously go over the page end.
-    return false;
-  }
-  if (cell_pointer < kFirstCellOfsetLeafPageOffset) {
-    // The pointer points into the cell's header.
-    return false;
-  }
-
-  const uint8_t* const cell_start = page_data + cell_pointer;
-  const uint8_t* const page_end = page_data + db_reader_->page_size();
-  DCHECK_LT(cell_start, page_end) << "Failed to skip over empty cells";
-
-  const uint8_t* rowid_start;
-  std::tie(last_record_size_, rowid_start) = ParseVarint(cell_start, page_end);
-  if (rowid_start == page_end) {
-    // The value size varint extended to the end of the page, so the rowid
-    // varint starts past the page end.
-    return false;
-  }
-  if (last_record_size_ <= 0) {
-    // Each payload needs at least one varint. Skip empty payloads.
-#if DCHECK_IS_ON()
-    // DCHECKs use last_record_size == 0 to check for incorrect access to the
-    // decoder's state.
-    last_record_size_ = 0;
-#endif  // DCHECK_IS_ON()
-    return false;
-  }
-
-  const uint8_t* record_start;
-  std::tie(last_record_rowid_, record_start) =
-      ParseVarint(rowid_start, page_end);
-  if (record_start == page_end) {
-    // The rowid varint extended to the end of the page, so the record starts
-    // past the page end. Records need at least 1 byte for their header size
-    // varint, so this suggests corruption.
-    last_record_size_ = 0;
-    return false;
-  }
-
-  last_record_offset_ = record_start - page_data;
-  return true;
-}
-
-// static
-bool LeafPageDecoder::IsOnValidPage(DatabasePageReader* db_reader) {
-  static_assert(kPageTypePageOffset < DatabasePageReader::kMinUsablePageSize,
-                "The check below may perform an out-of-bounds memory access");
-  return db_reader->page_data()[kPageTypePageOffset] == kLeafTablePageType;
-}
-
-// static
-int LeafPageDecoder::ComputeCellCount(DatabasePageReader* db_reader) {
-  // See InnerPageDecoder::ComputeCellCount() for the reasoning behind the code.
-  int header_count =
-      LoadBigEndianUint16(db_reader->page_data() + kCellCountPageOffset);
-  static_assert(
-      kCellCountPageOffset + 2 <= DatabasePageReader::kMinUsablePageSize,
-      "The read above may be out of bounds");
-
-  int upper_bound =
-      (db_reader->page_size() - kFirstCellOfsetLeafPageOffset) >> 1;
-  static_assert(
-      kFirstCellOfsetLeafPageOffset <= DatabasePageReader::kMinUsablePageSize,
-      "The |upper_bound| computation above may overflow");
-
-  return std::min(header_count, upper_bound);
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/btree.h b/sql/recover_module/btree.h
deleted file mode 100644
index 155be1e..0000000
--- a/sql/recover_module/btree.h
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_BTREE_H_
-#define SQL_RECOVER_MODULE_BTREE_H_
-
-#include <cstdint>
-#include <ostream>
-
-#include "base/check.h"
-#include "base/memory/raw_ptr_exclusion.h"
-#include "base/sequence_checker.h"
-
-namespace sql {
-namespace recover {
-
-class DatabasePageReader;
-
-// Streaming decoder for inner pages in SQLite table B-trees.
-//
-// The decoder outputs the page IDs of the inner page's children pages.
-//
-// An instance can only be used to decode a single page. Instances are not
-// thread-safe.
-class InnerPageDecoder {
- public:
-  // Creates a decoder for a DatabasePageReader's last read page.
-  //
-  // |db_reader| must have been used to read an inner page of a table B-tree.
-  // |db_reader| must outlive this instance.
-  explicit InnerPageDecoder(DatabasePageReader* db_reader) noexcept;
-  ~InnerPageDecoder() noexcept = default;
-
-  InnerPageDecoder(const InnerPageDecoder&) = delete;
-  InnerPageDecoder& operator=(const InnerPageDecoder&) = delete;
-
-  // The ID of the database page decoded by this instance.
-  int page_id() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return page_id_;
-  }
-
-  // Returns true iff TryAdvance() may be called.
-  bool CanAdvance() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    // The <= below is not a typo. Inner nodes store the right-most child
-    // pointer in their headers, so their child count is (cell_count + 1).
-    return next_read_index_ <= cell_count_;
-  }
-
-  // Advances the reader and returns the last read value.
-  //
-  // May return an invalid page ID if database was corrupted or the read failed.
-  // The caller must use DatabasePageReader::IsValidPageId() to verify the
-  // returned page ID. The caller should continue attempting to read as long as
-  // CanAdvance() returns true.
-  int TryAdvance();
-
-  // True if the given reader may point to an inner page in a table B-tree.
-  //
-  // The last ReadPage() call on |db_reader| must have succeeded.
-  static bool IsOnValidPage(DatabasePageReader* db_reader);
-
- private:
-  // Returns the number of cells in the B-tree page.
-  //
-  // Checks for database corruption. The caller can assume that the cell pointer
-  // array with the returned size will not extend past the page buffer.
-  static int ComputeCellCount(DatabasePageReader* db_reader);
-
-  // The number of the B-tree page this reader is reading.
-  const int page_id_;
-  // Used to read the tree page.
-  //
-  // Raw pointer usage is acceptable because this instance's owner is expected
-  // to ensure that the DatabasePageReader outlives this.
-  // This field is not a raw_ptr<> because it caused a
-  // std::is_trivially_destructible static_assert failure.
-  RAW_PTR_EXCLUSION DatabasePageReader* const db_reader_;
-  // Caches the ComputeCellCount() value for this reader's page.
-  const int cell_count_ = ComputeCellCount(db_reader_);
-
-  // The reader's cursor state.
-  //
-  // Each B-tree page has a header and many cells. In an inner B-tree page, each
-  // cell points to a child page, and the header points to the last child page.
-  // So, an inner page with N cells has N+1 children, and |next_read_index_|
-  // takes values between 0 and |cell_count_| + 1.
-  int next_read_index_ = 0;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-};
-
-// Streaming decoder for leaf pages in SQLite table B-trees.
-//
-// Conceptually, the decoder outputs (rowid, record size, record offset) tuples
-// for all the values stored in the leaf page. The tuple members can be accessed
-// via last_record_{rowid, size, offset}() methods.
-//
-// An instance can only be used to decode a single page. Instances are not
-// thread-safe.
-class LeafPageDecoder {
- public:
-  // Creates a decoder for a DatabasePageReader's last read page.
-  //
-  // |db_reader| must have been used to read an inner page of a table B-tree.
-  // |db_reader| must outlive this instance.
-  explicit LeafPageDecoder(DatabasePageReader* db_reader) noexcept;
-  ~LeafPageDecoder() noexcept = default;
-
-  LeafPageDecoder(const LeafPageDecoder&) = delete;
-  LeafPageDecoder& operator=(const LeafPageDecoder&) = delete;
-
-  // The rowid of the most recent record read by TryAdvance().
-  //
-  // Must only be called after a successful call to TryAdvance().
-  int64_t last_record_rowid() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    DCHECK(last_record_size_ != 0)
-        << "TryAdvance() not called / did not succeed";
-    return last_record_rowid_;
-  }
-
-  // The size of the most recent record read by TryAdvance().
-  //
-  // Must only be called after a successful call to TryAdvance().
-  int64_t last_record_size() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    DCHECK(last_record_size_ != 0)
-        << "TryAdvance() not called / did not succeed";
-    return last_record_size_;
-  }
-
-  // The page offset of the most recent record read by TryAdvance().
-  //
-  // Must only be called after a successful call to TryAdvance().
-  int64_t last_record_offset() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    DCHECK(last_record_size_ != 0)
-        << "TryAdvance() not called / did not succeed";
-    return last_record_offset_;
-  }
-
-  // Returns true iff TryAdvance() may be called.
-  bool CanAdvance() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return next_read_index_ < cell_count_;
-  }
-
-  // Advances the reader and returns the last read value.
-  //
-  // Returns false if the read fails. The caller should continue attempting to
-  // read as long as CanAdvance() returns true.
-  bool TryAdvance();
-
-  // True if the given reader may point to an inner page in a table B-tree.
-  //
-  // The last ReadPage() call on |db_reader| must have succeeded.
-  static bool IsOnValidPage(DatabasePageReader* db_reader);
-
- private:
-  // Returns the number of cells in the B-tree page.
-  //
-  // Checks for database corruption. The caller can assume that the cell pointer
-  // array with the returned size will not extend past the page buffer.
-  static int ComputeCellCount(DatabasePageReader* db_reader);
-
-  // The number of the B-tree page this reader is reading.
-  const int64_t page_id_;
-  // Used to read the tree page.
-  //
-  // Raw pointer usage is acceptable because this instance's owner is expected
-  // to ensure that the DatabasePageReader outlives this.
-  // This field is not a raw_ptr<> because it caused a
-  // std::is_trivially_destructible static_assert failure.
-  RAW_PTR_EXCLUSION DatabasePageReader* const db_reader_;
-  // Caches the ComputeCellCount() value for this reader's page.
-  const int cell_count_ = ComputeCellCount(db_reader_);
-
-  // The reader's cursor state.
-  //
-  // Each B-tree cell contains a value. So, this member takes values in
-  // [0, cell_count_).
-  int next_read_index_ = 0;
-
-  int64_t last_record_size_ = 0;
-  int64_t last_record_rowid_ = 0;
-  int last_record_offset_ = 0;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-};
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_BTREE_H_
diff --git a/sql/recover_module/cursor.cc b/sql/recover_module/cursor.cc
deleted file mode 100644
index 8983af5..0000000
--- a/sql/recover_module/cursor.cc
+++ /dev/null
@@ -1,164 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/cursor.h"
-
-#include <ostream>
-
-#include "base/containers/span.h"
-#include "sql/recover_module/table.h"
-
-namespace sql {
-namespace recover {
-
-VirtualCursor::VirtualCursor(VirtualTable* table)
-    : table_(table),
-      db_reader_(table),
-      payload_reader_(&db_reader_),
-      record_reader_(&payload_reader_, table->column_specs().size()) {
-  DCHECK(table_ != nullptr);
-}
-
-VirtualCursor::~VirtualCursor() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  table_->WillDeleteCursor(this);
-}
-
-int VirtualCursor::First() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  inner_decoders_.clear();
-  leaf_decoder_ = nullptr;
-
-  AppendPageDecoder(table_->root_page_id());
-  return Next();
-}
-
-int VirtualCursor::Next() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  record_reader_.Reset();
-
-  while (!inner_decoders_.empty() || leaf_decoder_.get()) {
-    if (leaf_decoder_.get()) {
-      if (!leaf_decoder_->CanAdvance()) {
-        // The leaf has been exhausted. Remove it from the DFS stack.
-        leaf_decoder_ = nullptr;
-        continue;
-      }
-      if (!leaf_decoder_->TryAdvance())
-        continue;
-
-      if (!payload_reader_.Initialize(leaf_decoder_->last_record_size(),
-                                      leaf_decoder_->last_record_offset())) {
-        continue;
-      }
-      if (!record_reader_.Initialize())
-        continue;
-
-      // Found a healthy record.
-      if (!IsAcceptableRecord()) {
-        record_reader_.Reset();
-        continue;
-      }
-      return SQLITE_OK;
-    }
-
-    // Try advancing the bottom-most inner node.
-    DCHECK(!inner_decoders_.empty());
-    InnerPageDecoder* inner_decoder = inner_decoders_.back().get();
-    if (!inner_decoder->CanAdvance()) {
-      // The inner node's sub-tree has been visited. Remove from the DFS stack.
-      inner_decoders_.pop_back();
-      continue;
-    }
-    int next_page_id = inner_decoder->TryAdvance();
-    if (!DatabasePageReader::IsValidPageId(next_page_id)) {
-      continue;
-    }
-    AppendPageDecoder(next_page_id);
-  }
-
-  // The cursor reached the end of the table.
-  return SQLITE_OK;
-}
-
-int VirtualCursor::ReadColumn(int column_index,
-                              sqlite3_context* result_context) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_GE(column_index, 0);
-  DCHECK_LT(column_index, static_cast<int>(table_->column_specs().size()));
-  DCHECK(record_reader_.IsInitialized());
-
-  if (table_->column_specs()[column_index].type == ModuleColumnType::kRowId) {
-    sqlite3_result_int64(result_context, RowId());
-    return SQLITE_OK;
-  }
-
-  if (record_reader_.ReadValue(column_index, result_context))
-    return SQLITE_OK;
-  return SQLITE_ERROR;
-}
-
-int64_t VirtualCursor::RowId() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(record_reader_.IsInitialized());
-  DCHECK(leaf_decoder_.get());
-  return leaf_decoder_->last_record_rowid();
-}
-
-void VirtualCursor::AppendPageDecoder(int page_id) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(leaf_decoder_.get() == nullptr)
-      << __func__
-      << " must only be called when the current path has no leaf decoder";
-
-  if (db_reader_.ReadPage(page_id) != SQLITE_OK)
-    return;
-
-  if (LeafPageDecoder::IsOnValidPage(&db_reader_)) {
-    leaf_decoder_ = std::make_unique<LeafPageDecoder>(&db_reader_);
-    return;
-  }
-
-  if (InnerPageDecoder::IsOnValidPage(&db_reader_)) {
-    // Detect cycles.
-    for (const auto& decoder : inner_decoders_) {
-      if (decoder->page_id() == page_id)
-        return;
-    }
-
-    // Give up on overly deep tree branches.
-    //
-    // SQLite supports up to 2^31 pages. SQLite ensures that inner nodes can
-    // hold at least 4 child pointers, even in the presence of very large keys.
-    // So, even poorly balanced trees should not exceed 100 nodes in depth.
-    // InnerPageDecoder instances take up 32 bytes on 64-bit platforms.
-    //
-    // The depth limit below balances recovering broken trees with avoiding
-    // excessive memory consumption.
-    constexpr int kMaxTreeDepth = 10000;
-    if (inner_decoders_.size() == kMaxTreeDepth)
-      return;
-
-    inner_decoders_.emplace_back(
-        std::make_unique<InnerPageDecoder>(&db_reader_));
-    return;
-  }
-}
-
-bool VirtualCursor::IsAcceptableRecord() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(record_reader_.IsInitialized());
-
-  const std::vector<RecoveredColumnSpec>& column_specs = table_->column_specs();
-  const int column_count = static_cast<int>(column_specs.size());
-  for (int column_index = 0; column_index < column_count; ++column_index) {
-    ValueType value_type = record_reader_.GetValueType(column_index);
-    if (!column_specs[column_index].IsAcceptableValue(value_type))
-      return false;
-  }
-  return true;
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/cursor.h b/sql/recover_module/cursor.h
deleted file mode 100644
index 4cb0655..0000000
--- a/sql/recover_module/cursor.h
+++ /dev/null
@@ -1,139 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_CURSOR_H_
-#define SQL_RECOVER_MODULE_CURSOR_H_
-
-#include <cstddef>
-#include <cstdint>
-#include <memory>
-#include <utility>
-
-#include "base/check_op.h"
-#include "base/memory/raw_ptr.h"
-#include "base/sequence_checker.h"
-#include "sql/recover_module/btree.h"
-#include "sql/recover_module/pager.h"
-#include "sql/recover_module/parsing.h"
-#include "sql/recover_module/payload.h"
-#include "sql/recover_module/record.h"
-#include "third_party/sqlite/sqlite3.h"
-
-namespace sql {
-namespace recover {
-
-class VirtualTable;
-
-// Represents a virtual table cursor created by SQLite in a recovery table.
-//
-// Instances are allocated on the heap using the C++ new operator, and passed to
-// SQLite via pointers to the sqlite_vtab members. SQLite is responsible for
-// managing the instances' lifetimes. SQLite will call xClose() for every
-// successful xOpen().
-//
-// Instances are not thread-safe. This should be fine, as long as each SQLite
-// statement that reads from a virtual table is only used on one sequence. This
-// assumption is verified by a sequence checker.
-//
-// If it turns out that VirtualCursor needs to be thread-safe, the best solution
-// is to add a base::Lock to VirtualCursor, and keep all underlying classes not
-// thread-safe.
-class VirtualCursor {
- public:
-  // Creates a cursor that iterates over |table|.
-  //
-  // |table| must outlive this instance. SQLite is trusted to call xClose() for
-  // this cursor before calling xDestroy() / xDisconnect() for the virtual table
-  // related to the cursor.
-  explicit VirtualCursor(VirtualTable* table);
-  ~VirtualCursor();
-
-  VirtualCursor(const VirtualCursor&) = delete;
-  VirtualCursor& operator=(const VirtualCursor&) = delete;
-
-  // Returns the embedded SQLite virtual table cursor.
-  //
-  // This getter is not const because SQLite wants a non-const pointer to the
-  // structure.
-  sqlite3_vtab_cursor* SqliteCursor() { return &sqlite_cursor_; }
-
-  // The VirtualCursor instance that embeds a given SQLite virtual table cursor.
-  //
-  // |sqlite_cursor| must have been returned by VirtualTable::SqliteCursor().
-  static inline VirtualCursor* FromSqliteCursor(
-      sqlite3_vtab_cursor* sqlite_cursor) {
-    VirtualCursor* result = reinterpret_cast<VirtualCursor*>(
-        (reinterpret_cast<char*>(sqlite_cursor) -
-         offsetof(VirtualCursor, sqlite_cursor_)));
-    CHECK_EQ(sqlite_cursor, &result->sqlite_cursor_);
-    return result;
-  }
-
-  // Seeks the cursor to the first readable row. Returns a SQLite status code.
-  int First();
-
-  // Seeks the cursor to the next row. Returns a SQLite status code.
-  int Next();
-
-  // Returns true if the cursor points to a valid row, false otherwise.
-  bool IsValid() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    return record_reader_.IsInitialized();
-  }
-
-  // Reports a value in the record to SQLite. |column_index| is 0-based.
-  //
-  // Returns a SQLite error code. This method can fail can happen if a value is
-  // stored across overflow pages, and reading one of the overflow pages results
-  // in an I/O error.
-  int ReadColumn(int column_index, sqlite3_context* result_context);
-
-  // Returns the rowid of the current row. The cursor must point to a valid row.
-  int64_t RowId();
-
- private:
-  // Appends a decoder for the given page at the end of the current chain.
-  //
-  // No modification is performed in case of failures due to I/O errors or
-  // database corruption.
-  void AppendPageDecoder(int page_id);
-
-  // True if the current record is acceptable given the recovery schema.
-  bool IsAcceptableRecord();
-
-  // SQLite handle for this cursor. The struct is populated and used by SQLite.
-  sqlite3_vtab_cursor sqlite_cursor_;
-
-  // The table this cursor was created for.
-  //
-  // Raw pointer usage is acceptable because SQLite will ensure that the
-  // VirtualTable, which is passed around as a sqlite3_vtab*, will outlive this
-  // cursor, which is passed around as a sqlite3_cursor*.
-  const raw_ptr<VirtualTable> table_;
-
-  // Reads database pages for this cursor.
-  DatabasePageReader db_reader_;
-
-  // Reads record payloads for this cursor.
-  LeafPayloadReader payload_reader_;
-
-  // Reads record rows for this cursor.
-  RecordReader record_reader_;
-
-  // Decoders for the current chain of inner pages.
-  //
-  // The current chain of pages consists of the inner page decoders here and the
-  // decoder in |leaf_decoder_|.
-  std::vector<std::unique_ptr<InnerPageDecoder>> inner_decoders_;
-
-  // Decodes the leaf page containing records.
-  std::unique_ptr<LeafPageDecoder> leaf_decoder_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-};
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_CURSOR_H_
diff --git a/sql/recover_module/integers.cc b/sql/recover_module/integers.cc
deleted file mode 100644
index 14566857..0000000
--- a/sql/recover_module/integers.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/integers.h"
-
-#include "base/check_op.h"
-
-namespace sql {
-namespace recover {
-
-std::pair<int64_t, const uint8_t*> ParseVarint(const uint8_t* buffer,
-                                               const uint8_t* buffer_end) {
-  DCHECK(buffer != nullptr);
-  DCHECK(buffer_end != nullptr);
-
-  DCHECK_LT(buffer, buffer_end);
-  const uint8_t* const regular_buffer_end =
-      (buffer_end - buffer > kMaxVarintSize - 1) ? buffer + kMaxVarintSize - 1
-                                                 : buffer_end;
-
-  uint64_t value = 0;
-  uint8_t last_byte;
-  while (buffer < regular_buffer_end) {
-    last_byte = *buffer;
-    ++buffer;
-    value = (value << 7) | (last_byte & 0x7f);
-    if ((last_byte & 0x80) == 0)
-      break;
-  }
-  if (buffer < buffer_end && (last_byte & 0x80) != 0) {
-    value = (value << 8) | *buffer;
-    ++buffer;
-  }
-  return {value, buffer};
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/integers.h b/sql/recover_module/integers.h
deleted file mode 100644
index fb4133e..0000000
--- a/sql/recover_module/integers.h
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_INTEGERS_H_
-#define SQL_RECOVER_MODULE_INTEGERS_H_
-
-#include <cstdint>
-#include <limits>
-#include <utility>
-
-namespace sql {
-namespace recover {
-
-// Reads an unsigned 16-bit big-endian integer from the given buffer.
-//
-// |buffer| must point to at least two consecutive bytes of valid memory.
-//
-// The return type was chosen because it suits the callers best.
-inline int LoadBigEndianUint16(const uint8_t* buffer) {
-  static_assert(
-      std::numeric_limits<uint16_t>::max() <= std::numeric_limits<int>::max(),
-      "The value may overflow the return type");
-  return (static_cast<int>(buffer[0]) << 8) | static_cast<int>(buffer[1]);
-}
-
-// Reads a signed 32-bit big-endian integer from the given buffer.
-//
-// |buffer| must point to at least four consecutive bytes of valid memory.
-inline int32_t LoadBigEndianInt32(const uint8_t* buffer) {
-  // The code gets optimized to mov + bswap on x86_64, and to ldr + rev on ARM.
-  return (static_cast<int32_t>(buffer[0]) << 24) |
-         (static_cast<int32_t>(buffer[1]) << 16) |
-         (static_cast<int32_t>(buffer[2]) << 8) |
-         static_cast<int32_t>(buffer[3]);
-}
-
-// Reads a signed 64-bit big-endian integer from the given buffer.
-//
-// |buffer| must point to at least eight consecutive bytes of valid memory.
-inline int64_t LoadBigEndianInt64(const uint8_t* buffer) {
-  // The code gets optimized to mov + bswap on x86_64, and to ldr + rev on ARM.
-  return (static_cast<int64_t>(buffer[0]) << 56) |
-         (static_cast<int64_t>(buffer[1]) << 48) |
-         (static_cast<int64_t>(buffer[2]) << 40) |
-         (static_cast<int64_t>(buffer[3]) << 32) |
-         (static_cast<int64_t>(buffer[4]) << 24) |
-         (static_cast<int64_t>(buffer[5]) << 16) |
-         (static_cast<int64_t>(buffer[6]) << 8) |
-         static_cast<int64_t>(buffer[7]);
-}
-
-// Reads a SQLite varint.
-//
-// SQLite varints decode to 64-bit integers, and take up at most 9 bytes.
-// If present, the 9th byte holds bits 56-63 of the integer. This deviates from
-// Google (protobuf, leveldb) varint encoding, where the last varint byte's top
-// bit is always 0.
-//
-// The implementation assumes that |buffer| and |buffer_end| point into the same
-// array of bytes, and that |buffer| < |buffer_end|. The implementation will
-// never compute a pointer value larger than |buffer_end|.
-//
-// Returns the parsed number and a pointer to the first byte past the number.
-// Per the rules above, the returned pointer is guaranteed to be between
-// |buffer| and |buffer_end|. The returned pointer is also guaranteed to be at
-// most |kMaxVarintSize| bytes past |buffer|.
-std::pair<int64_t, const uint8_t*> ParseVarint(const uint8_t* buffer,
-                                               const uint8_t* buffer_end);
-
-// The maximum number of bytes used to store a SQLite varint.
-constexpr int kMaxVarintSize = 9;
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_INTEGERS_H_
diff --git a/sql/recover_module/module.cc b/sql/recover_module/module.cc
deleted file mode 100644
index 9795b713..0000000
--- a/sql/recover_module/module.cc
+++ /dev/null
@@ -1,281 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/module.h"
-
-#include <cstddef>
-#include <cstdint>
-#include <ostream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/check_op.h"
-#include "base/strings/strcat.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/string_util.h"
-#include "sql/recover_module/cursor.h"
-#include "sql/recover_module/parsing.h"
-#include "sql/recover_module/table.h"
-#include "third_party/sqlite/sqlite3.h"
-
-// https://sqlite.org/vtab.html documents SQLite's virtual table module API.
-
-namespace sql {
-namespace recover {
-
-namespace {
-
-// SQLite module argument constants.
-static constexpr int kModuleNameArgument = 0;
-static constexpr int kVirtualTableDbNameArgument = 1;
-static constexpr int kVirtualTableNameArgument = 2;
-static constexpr int kBackingTableSpecArgument = 3;
-static constexpr int kFirstColumnArgument = 4;
-
-// Returns an empty vector on parse errors.
-std::vector<RecoveredColumnSpec> ParseColumnSpecs(int argc,
-                                                  const char* const* argv) {
-  std::vector<RecoveredColumnSpec> result;
-  DCHECK_GE(argc, kFirstColumnArgument);
-  result.reserve(argc - kFirstColumnArgument + 1);
-
-  for (int i = kFirstColumnArgument; i < argc; ++i) {
-    result.emplace_back(ParseColumnSpec(argv[i]));
-    if (!result.back().IsValid()) {
-      result.clear();
-      break;
-    }
-  }
-  return result;
-}
-
-int ModuleCreate(sqlite3* sqlite_db,
-                 void* /* client_data */,
-                 int argc,
-                 const char* const* argv,
-                 sqlite3_vtab** result_sqlite_table,
-                 char** /* error_string */) {
-  DCHECK(sqlite_db != nullptr);
-  if (argc <= kFirstColumnArgument) {
-    // The recovery module needs at least one column specification.
-    return SQLITE_ERROR;
-  }
-  DCHECK(argv != nullptr);
-  DCHECK(result_sqlite_table != nullptr);
-
-  // This module is always expected to be registered as "recover".
-  DCHECK_EQ("recover", base::StringPiece(argv[kModuleNameArgument]));
-
-  base::StringPiece db_name(argv[kVirtualTableDbNameArgument]);
-  if (db_name != "temp") {
-    // Refuse to create tables outside the "temp" database.
-    //
-    // This check is overly strict. The virtual table can be safely used on any
-    // temporary database (ATTACH '' AS other_temp). However, there is no easy
-    // way to determine if an attachment point corresponds to a temporary
-    // database, and "temp" is sufficient for Chrome's purposes.
-    return SQLITE_ERROR;
-  }
-
-  base::StringPiece table_name(argv[kVirtualTableNameArgument]);
-  if (!base::StartsWith(table_name, "recover_")) {
-    // In the future, we may deploy UMA metrics that use the virtual table name
-    // to attribute recovery events to Chrome features. In preparation for that
-    // future, require all recovery table names to start with "recover_".
-    return SQLITE_ERROR;
-  }
-
-  TargetTableSpec backing_table_spec =
-      ParseTableSpec(argv[kBackingTableSpecArgument]);
-  if (!backing_table_spec.IsValid()) {
-    // The parser concluded that the string specifying the backing table is
-    // invalid. This is definitely an error in the SQL using the virtual table.
-    return SQLITE_ERROR;
-  }
-
-  std::vector<RecoveredColumnSpec> column_specs = ParseColumnSpecs(argc, argv);
-  if (column_specs.empty()) {
-    // The column specifications were invalid.
-    return SQLITE_ERROR;
-  }
-
-  auto [sqlite_status, table] = VirtualTable::Create(
-      sqlite_db, std::move(backing_table_spec), std::move(column_specs));
-  if (sqlite_status != SQLITE_OK)
-    return sqlite_status;
-
-  {
-    std::string create_table_sql = table->ToCreateTableSql();
-    sqlite3_declare_vtab(sqlite_db, create_table_sql.c_str());
-  }
-  *result_sqlite_table = table->SqliteTable();
-  table.release();  // SQLite manages the lifetime of the table.
-  return SQLITE_OK;
-}
-
-int ModuleConnect(sqlite3* sqlite_db,
-                  void* client_data,
-                  int argc,
-                  const char* const* argv,
-                  sqlite3_vtab** result_sqlite_table,
-                  char** error_string) {
-  // TODO(pwnall): Figure out if it's acceptable to have "recover" be an
-  //               eponymous table. If so, use ModuleCreate instead of
-  //               ModuleConnect in the entry point table.
-  return ModuleCreate(sqlite_db, client_data, argc, argv, result_sqlite_table,
-                      error_string);
-}
-
-int ModuleBestIndex(sqlite3_vtab* sqlite_table,
-                    sqlite3_index_info* index_info) {
-  DCHECK(sqlite_table != nullptr);
-  DCHECK(index_info != nullptr);
-
-  // The sqlite3_index_info structure is also documented at
-  //   https://www.sqlite.org/draft/c3ref/index_info.html
-  for (int i = 0; i < index_info->nConstraint; ++i) {
-    if (index_info->aConstraint[i].usable == static_cast<char>(false))
-      continue;
-    // True asks SQLite to evaluate the constraint and pass the result to any
-    // follow-up xFilter() calls, via argc/argv.
-    index_info->aConstraintUsage[i].argvIndex = 0;
-    // True indicates that the virtual table will check the constraint.
-    index_info->aConstraintUsage[i].omit = false;
-  }
-  index_info->orderByConsumed = static_cast<int>(false);
-
-  // SQLite saves the sqlite_idx_info fields set here and passes the values to
-  // xFilter().
-  index_info->idxStr = nullptr;
-  index_info->idxNum = 0;
-  index_info->needToFreeIdxStr = static_cast<int>(false);
-
-  return SQLITE_OK;
-}
-
-int ModuleDisconnect(sqlite3_vtab* sqlite_table) {
-  DCHECK(sqlite_table != nullptr);
-
-  // SQLite takes ownership of the VirtualTable (which is passed around as a
-  // sqlite_table) in ModuleCreate() / ModuleConnect(). SQLite then calls
-  // ModuleDestroy() / ModuleDisconnect() to relinquish ownership of the
-  // VirtualTable. At this point, the table will not be used again, and can be
-  // destroyed.
-  VirtualTable* const table = VirtualTable::FromSqliteTable(sqlite_table);
-  delete table;
-  return SQLITE_OK;
-}
-
-int ModuleDestroy(sqlite3_vtab* sqlite_table) {
-  return ModuleDisconnect(sqlite_table);
-}
-
-int ModuleOpen(sqlite3_vtab* sqlite_table,
-               sqlite3_vtab_cursor** result_sqlite_cursor) {
-  DCHECK(sqlite_table != nullptr);
-  DCHECK(result_sqlite_cursor != nullptr);
-
-  VirtualTable* const table = VirtualTable::FromSqliteTable(sqlite_table);
-  VirtualCursor* const cursor = table->CreateCursor();
-  *result_sqlite_cursor = cursor->SqliteCursor();
-  return SQLITE_OK;
-}
-
-int ModuleClose(sqlite3_vtab_cursor* sqlite_cursor) {
-  DCHECK(sqlite_cursor != nullptr);
-
-  // SQLite takes ownership of the VirtualCursor (which is passed around as a
-  // sqlite_cursor) in ModuleOpen(). SQLite then calls ModuleClose() to
-  // relinquish ownership of the VirtualCursor. At this point, the cursor will
-  // not be used again, and can be destroyed.
-  VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
-  delete cursor;
-  return SQLITE_OK;
-}
-
-int ModuleFilter(sqlite3_vtab_cursor* sqlite_cursor,
-                 int /* best_index_num */,
-                 const char* /* best_index_str */,
-                 int /* argc */,
-                 sqlite3_value** /* argv */) {
-  DCHECK(sqlite_cursor != nullptr);
-
-  VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
-  return cursor->First();
-}
-
-int ModuleNext(sqlite3_vtab_cursor* sqlite_cursor) {
-  DCHECK(sqlite_cursor != nullptr);
-
-  VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
-  return cursor->Next();
-}
-
-int ModuleEof(sqlite3_vtab_cursor* sqlite_cursor) {
-  DCHECK(sqlite_cursor != nullptr);
-
-  VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
-  return cursor->IsValid() ? 0 : 1;
-}
-
-int ModuleColumn(sqlite3_vtab_cursor* sqlite_cursor,
-                 sqlite3_context* result_context,
-                 int column_index) {
-  DCHECK(sqlite_cursor != nullptr);
-  DCHECK(result_context != nullptr);
-
-  VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
-  DCHECK(cursor->IsValid()) << "SQLite called xRowid() without a valid cursor";
-  return cursor->ReadColumn(column_index, result_context);
-}
-
-int ModuleRowid(sqlite3_vtab_cursor* sqlite_cursor,
-                sqlite3_int64* result_rowid) {
-  DCHECK(sqlite_cursor != nullptr);
-  DCHECK(result_rowid != nullptr);
-
-  VirtualCursor* const cursor = VirtualCursor::FromSqliteCursor(sqlite_cursor);
-  DCHECK(cursor->IsValid()) << "SQLite called xRowid() without a valid cursor";
-  *result_rowid = cursor->RowId();
-  return SQLITE_OK;
-}
-
-// SQLite module API version supported by this implementation.
-constexpr int kSqliteModuleApiVersion = 1;
-
-// Entry points to the SQLite module.
-constexpr sqlite3_module kSqliteModule = {
-    kSqliteModuleApiVersion,
-    &ModuleCreate,
-    &ModuleConnect,
-    &ModuleBestIndex,
-    &ModuleDisconnect,
-    &ModuleDestroy,
-    &ModuleOpen,
-    &ModuleClose,
-    &ModuleFilter,
-    &ModuleNext,
-    &ModuleEof,
-    &ModuleColumn,
-    &ModuleRowid,
-    /* xUpdate= */ nullptr,
-    /* xBegin= */ nullptr,
-    /* xSync= */ nullptr,
-    /* xCommit= */ nullptr,
-    /* xRollback= */ nullptr,
-    /* xFindFunction= */ nullptr,
-    /* xRename= */ nullptr,
-};
-
-}  // namespace
-
-int RegisterRecoverExtension(sqlite3* db) {
-  return sqlite3_create_module_v2(db, "recover", &kSqliteModule,
-                                  /* pClientData= */ nullptr,
-                                  /* xDestroy= */ nullptr);
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/module.h b/sql/recover_module/module.h
deleted file mode 100644
index a4d40ee4..0000000
--- a/sql/recover_module/module.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_MODULE_H_
-#define SQL_RECOVER_MODULE_MODULE_H_
-
-struct sqlite3;
-
-namespace sql {
-namespace recover {
-
-// Registers the "recover" virtual table with a SQLite connection.
-//
-// Returns a SQLite error code.
-int RegisterRecoverExtension(sqlite3* db);
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_MODULE_H_
diff --git a/sql/recover_module/module_unittest.cc b/sql/recover_module/module_unittest.cc
deleted file mode 100644
index c554db5..0000000
--- a/sql/recover_module/module_unittest.cc
+++ /dev/null
@@ -1,1126 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <random>
-#include <string>
-#include <tuple>
-#include <vector>
-
-#include "base/files/scoped_temp_dir.h"
-#include "base/strings/stringprintf.h"
-#include "sql/database.h"
-#include "sql/statement.h"
-#include "sql/test/database_test_peer.h"
-#include "sql/test/scoped_error_expecter.h"
-#include "sql/test/test_helpers.h"
-#include "sql/transaction.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/sqlite/sqlite3.h"
-
-namespace sql {
-namespace recover {
-
-class RecoverModuleTest : public testing::Test {
- public:
-  ~RecoverModuleTest() override = default;
-
-  void SetUp() override {
-    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-    ASSERT_TRUE(
-        db_.Open(temp_dir_.GetPath().AppendASCII("recovery_test.sqlite")));
-    ASSERT_TRUE(DatabaseTestPeer::EnableRecoveryExtension(&db_));
-  }
-
- protected:
-  base::ScopedTempDir temp_dir_;
-  sql::Database db_{sql::DatabaseOptions{
-      .enable_virtual_tables_discouraged = true,
-  }};
-};
-
-TEST_F(RecoverModuleTest, CreateVtable) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  EXPECT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                  "USING recover(backing, t TEXT)"));
-}
-TEST_F(RecoverModuleTest, CreateVtableWithDatabaseSpecifier) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  EXPECT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                  "USING recover(main.backing, t TEXT)"));
-}
-TEST_F(RecoverModuleTest, CreateVtableOnSqliteSchema) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  EXPECT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing USING recover("
-                  "sqlite_schema, type TEXT, name TEXT, tbl_name TEXT, "
-                  "rootpage INTEGER, sql TEXT)"));
-}
-
-TEST_F(RecoverModuleTest, CreateVtableFailsOnNonTempTable) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(db_.Execute(
-        "CREATE VIRTUAL TABLE recover_backing USING recover(backing, t TEXT)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingTable) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_CORRUPT);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_missing "
-                    "USING recover(missing, t TEXT)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingDatabase) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_CORRUPT);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(db.backing, t TEXT)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, CreateVtableFailsOnTableWithInvalidQualifier) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_CORRUPT);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(backing invalid, t TEXT)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingTableName) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(main., t TEXT)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingSchemaSpec) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(backing)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, CreateVtableFailsOnMissingDbName) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(.backing)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-
-TEST_F(RecoverModuleTest, ColumnTypeMappingAny) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  EXPECT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                  "USING recover(backing, t ANY)"));
-
-  sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "t");
-  EXPECT_EQ("(nullptr)", column_info.data_type);
-  EXPECT_EQ("BINARY", column_info.collation_sequence);
-  EXPECT_FALSE(column_info.has_non_null_constraint);
-  EXPECT_FALSE(column_info.is_in_primary_key);
-  EXPECT_FALSE(column_info.is_auto_incremented);
-}
-TEST_F(RecoverModuleTest, ColumnTypeMappingAnyNotNull) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  EXPECT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                  "USING recover(backing, t ANY NOT NULL)"));
-
-  sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "t");
-  EXPECT_EQ("(nullptr)", column_info.data_type);
-  EXPECT_EQ("BINARY", column_info.collation_sequence);
-  EXPECT_TRUE(column_info.has_non_null_constraint);
-  EXPECT_FALSE(column_info.is_in_primary_key);
-  EXPECT_FALSE(column_info.is_auto_incremented);
-}
-TEST_F(RecoverModuleTest, ColumnTypeMappingAnyStrict) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(backing, t ANY STRICT)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-
-TEST_F(RecoverModuleTest, ColumnTypeExtraKeyword) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(backing, t INTEGER SOMETHING)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, ColumnTypeNotNullExtraKeyword) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(backing, t INTEGER NOT NULL SOMETHING)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, ColumnTypeDoubleTypes) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing "
-                    "USING recover(backing, t INTEGER FLOAT)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-TEST_F(RecoverModuleTest, ColumnTypeNotNullDoubleTypes) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE backing(t TEXT)"));
-  {
-    sql::test::ScopedErrorExpecter error_expecter;
-    error_expecter.ExpectError(SQLITE_ERROR);
-    EXPECT_FALSE(
-        db_.Execute("CREATE VIRTUAL TABLE temp.recover_backing USING recover("
-                    "backing, t INTEGER NOT NULL TEXT)"));
-    EXPECT_TRUE(error_expecter.SawExpectedErrors());
-  }
-}
-
-class RecoverModuleColumnTypeMappingTest
-    : public RecoverModuleTest,
-      public ::testing::WithParamInterface<
-          std::tuple<const char*, const char*, bool>> {
- public:
-  ~RecoverModuleColumnTypeMappingTest() override = default;
-
-  void SetUp() override {
-    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
-    ASSERT_TRUE(
-        db_.Open(temp_dir_.GetPath().AppendASCII("recovery_test.sqlite")));
-    ASSERT_TRUE(DatabaseTestPeer::EnableRecoveryExtension(&db_));
-
-    std::string sql =
-        base::StringPrintf("CREATE TABLE backing(data %s)", SchemaType());
-    ASSERT_TRUE(db_.Execute(sql.c_str()));
-  }
-
-  void CreateRecoveryTable(const char* suffix) {
-    std::string sql = base::StringPrintf(
-        "CREATE VIRTUAL TABLE temp.recover_backing "
-        "USING recover(backing, data %s%s)",
-        SchemaType(), suffix);
-    ASSERT_TRUE(db_.Execute(sql.c_str()));
-  }
-
-  const char* SchemaType() const { return std::get<0>(GetParam()); }
-  const char* ExpectedType() const { return std::get<1>(GetParam()); }
-  bool IsAlwaysNonNull() const { return std::get<2>(GetParam()); }
-
- protected:
-  base::ScopedTempDir temp_dir_;
-  sql::Database db_;
-};
-TEST_P(RecoverModuleColumnTypeMappingTest, Unqualified) {
-  CreateRecoveryTable("");
-  sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data");
-  EXPECT_EQ(ExpectedType(), column_info.data_type);
-  EXPECT_EQ("BINARY", column_info.collation_sequence);
-  EXPECT_EQ(IsAlwaysNonNull(), column_info.has_non_null_constraint);
-  EXPECT_FALSE(column_info.is_in_primary_key);
-  EXPECT_FALSE(column_info.is_auto_incremented);
-}
-TEST_P(RecoverModuleColumnTypeMappingTest, NotNull) {
-  CreateRecoveryTable(" NOT NULL");
-  sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data");
-  EXPECT_EQ(ExpectedType(), column_info.data_type);
-  EXPECT_EQ("BINARY", column_info.collation_sequence);
-  EXPECT_TRUE(column_info.has_non_null_constraint);
-  EXPECT_FALSE(column_info.is_in_primary_key);
-  EXPECT_FALSE(column_info.is_auto_incremented);
-}
-TEST_P(RecoverModuleColumnTypeMappingTest, Strict) {
-  CreateRecoveryTable(" STRICT");
-  sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data");
-  EXPECT_EQ(ExpectedType(), column_info.data_type);
-  EXPECT_EQ("BINARY", column_info.collation_sequence);
-  EXPECT_EQ(IsAlwaysNonNull(), column_info.has_non_null_constraint);
-  EXPECT_FALSE(column_info.is_in_primary_key);
-  EXPECT_FALSE(column_info.is_auto_incremented);
-}
-TEST_P(RecoverModuleColumnTypeMappingTest, StrictNotNull) {
-  CreateRecoveryTable(" STRICT NOT NULL");
-  sql::test::ColumnInfo column_info =
-      sql::test::ColumnInfo::Create(&db_, "temp", "recover_backing", "data");
-  EXPECT_EQ(ExpectedType(), column_info.data_type);
-  EXPECT_EQ("BINARY", column_info.collation_sequence);
-  EXPECT_TRUE(column_info.has_non_null_constraint);
-  EXPECT_FALSE(column_info.is_in_primary_key);
-  EXPECT_FALSE(column_info.is_auto_incremented);
-}
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    RecoverModuleColumnTypeMappingTest,
-    ::testing::Values(std::make_tuple("TEXT", "TEXT", false),
-                      std::make_tuple("INTEGER", "INTEGER", false),
-                      std::make_tuple("FLOAT", "FLOAT", false),
-                      std::make_tuple("BLOB", "BLOB", false),
-                      std::make_tuple("NUMERIC", "NUMERIC", false),
-                      std::make_tuple("ROWID", "INTEGER", true)));
-
-namespace {
-
-void GenerateAlteredTable(sql::Database* db) {
-  ASSERT_TRUE(db->Execute("CREATE TABLE altered(t TEXT)"));
-  ASSERT_TRUE(db->Execute("INSERT INTO altered VALUES('a')"));
-  ASSERT_TRUE(db->Execute("INSERT INTO altered VALUES('b')"));
-  ASSERT_TRUE(db->Execute("INSERT INTO altered VALUES('c')"));
-  ASSERT_TRUE(db->Execute(
-      "ALTER TABLE altered ADD COLUMN i INTEGER NOT NULL DEFAULT 10"));
-  ASSERT_TRUE(db->Execute("INSERT INTO altered VALUES('d', 5)"));
-}
-
-}  // namespace
-
-TEST_F(RecoverModuleTest, ReadFromAlteredTableNullDefaults) {
-  GenerateAlteredTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_altered "
-                  "USING recover(altered, t TEXT, i INTEGER)"));
-
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT t, i FROM recover_altered ORDER BY rowid"));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ("a", statement.ColumnString(0));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(1));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ("b", statement.ColumnString(0));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(1));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ("c", statement.ColumnString(0));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(1));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ("d", statement.ColumnString(0));
-  EXPECT_EQ(5, statement.ColumnInt(1));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, ReadFromAlteredTableSkipsNulls) {
-  GenerateAlteredTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_altered "
-                  "USING recover(altered, t TEXT, i INTEGER NOT NULL)"));
-
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT t, i FROM recover_altered ORDER BY rowid"));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ("d", statement.ColumnString(0));
-  EXPECT_EQ(5, statement.ColumnInt(1));
-  EXPECT_FALSE(statement.Step());
-}
-
-namespace {
-
-void GenerateSizedTable(sql::Database* db,
-                        int row_count,
-                        const std::string& prefix) {
-  ASSERT_TRUE(db->Execute("CREATE TABLE sized(t TEXT, i INTEGER)"));
-
-  sql::Transaction transaction(db);
-  ASSERT_TRUE(transaction.Begin());
-  sql::Statement statement(
-      db->GetUniqueStatement("INSERT INTO sized VALUES(?, ?)"));
-
-  for (int i = 0; i < row_count; ++i) {
-    statement.BindString(0, base::StringPrintf("%s%d", prefix.c_str(), i));
-    statement.BindInt(1, i);
-    ASSERT_TRUE(statement.Run());
-    statement.Reset(/* clear_bound_vars= */ true);
-  }
-  ASSERT_TRUE(transaction.Commit());
-}
-
-}  // namespace
-
-TEST_F(RecoverModuleTest, LeafNodes) {
-  GenerateSizedTable(&db_, 10, "Leaf-node-generating line ");
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized "
-                  "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
-
-  sql::Statement statement(
-      db_.GetUniqueStatement("SELECT t, i FROM recover_sized ORDER BY rowid"));
-  for (int i = 0; i < 10; ++i) {
-    ASSERT_TRUE(statement.Step());
-    EXPECT_EQ(base::StringPrintf("Leaf-node-generating line %d", i),
-              statement.ColumnString(0));
-    EXPECT_EQ(i, statement.ColumnInt(1));
-  }
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, EmptyTable) {
-  GenerateSizedTable(&db_, 0, "");
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized "
-                  "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, t, i FROM recover_sized ORDER BY rowid"));
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, SingleLevelInteriorNodes) {
-  GenerateSizedTable(&db_, 100, "Interior-node-generating line ");
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized "
-                  "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
-
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, t, i FROM recover_sized ORDER BY rowid"));
-  for (int i = 0; i < 100; ++i) {
-    ASSERT_TRUE(statement.Step());
-    EXPECT_EQ(i + 1, statement.ColumnInt(0));
-    EXPECT_EQ(base::StringPrintf("Interior-node-generating line %d", i),
-              statement.ColumnString(1));
-    EXPECT_EQ(i, statement.ColumnInt(2));
-  }
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, MultiLevelInteriorNodes) {
-  GenerateSizedTable(&db_, 5000, "Interior-node-generating line ");
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_sized "
-                  "USING recover(sized, t TEXT, i INTEGER NOT NULL)"));
-
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, t, i FROM recover_sized ORDER BY rowid"));
-  for (int i = 0; i < 5000; ++i) {
-    ASSERT_TRUE(statement.Step());
-    EXPECT_EQ(i + 1, statement.ColumnInt(0));
-    EXPECT_EQ(base::StringPrintf("Interior-node-generating line %d", i),
-              statement.ColumnString(1));
-    EXPECT_EQ(i, statement.ColumnInt(2));
-  }
-  EXPECT_FALSE(statement.Step());
-}
-
-namespace {
-
-void GenerateTypesTable(sql::Database* db) {
-  ASSERT_TRUE(db->Execute("CREATE TABLE types(rowtype TEXT, value)"));
-
-  ASSERT_TRUE(db->Execute("INSERT INTO types VALUES('NULL', NULL)"));
-  ASSERT_TRUE(db->Execute("INSERT INTO types VALUES('INTEGER', 17)"));
-  ASSERT_TRUE(db->Execute("INSERT INTO types VALUES('FLOAT', 3.1415927)"));
-  ASSERT_TRUE(db->Execute("INSERT INTO types VALUES('TEXT', 'This is text')"));
-  ASSERT_TRUE(db->Execute(
-      "INSERT INTO types VALUES('BLOB', CAST('This is a blob' AS BLOB))"));
-}
-
-}  // namespace
-
-TEST_F(RecoverModuleTest, Any) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value ANY)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(1, statement.ColumnInt(0));
-  EXPECT_EQ("NULL", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(2, statement.ColumnInt(0));
-  EXPECT_EQ("INTEGER", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2));
-  EXPECT_EQ(17, statement.ColumnInt(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(3, statement.ColumnInt(0));
-  EXPECT_EQ("FLOAT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2));
-  EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(4, statement.ColumnInt(0));
-  EXPECT_EQ("TEXT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2));
-  EXPECT_EQ("This is text", statement.ColumnString(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(5, statement.ColumnInt(0));
-  EXPECT_EQ("BLOB", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2));
-  std::string blob_text;
-  ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text));
-  EXPECT_EQ("This is a blob", blob_text);
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, Integers) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value INTEGER)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(1, statement.ColumnInt(0));
-  EXPECT_EQ("NULL", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(2, statement.ColumnInt(0));
-  EXPECT_EQ("INTEGER", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2));
-  EXPECT_EQ(17, statement.ColumnInt(2));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, NonNullIntegers) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(db_.Execute(
-      "CREATE VIRTUAL TABLE temp.recover_types "
-      "USING recover(types, rowtype TEXT, value INTEGER NOT NULL)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(2, statement.ColumnInt(0));
-  EXPECT_EQ("INTEGER", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2));
-  EXPECT_EQ(17, statement.ColumnInt(2));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, Floats) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value FLOAT)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(1, statement.ColumnInt(0));
-  EXPECT_EQ("NULL", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(2, statement.ColumnInt(0));
-  EXPECT_EQ("INTEGER", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2));
-  EXPECT_EQ(17, statement.ColumnInt(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(3, statement.ColumnInt(0));
-  EXPECT_EQ("FLOAT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2));
-  EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, NonNullFloats) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value FLOAT NOT NULL)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(2, statement.ColumnInt(0));
-  EXPECT_EQ("INTEGER", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2));
-  EXPECT_EQ(17, statement.ColumnInt(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(3, statement.ColumnInt(0));
-  EXPECT_EQ("FLOAT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2));
-  EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, FloatsStrict) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value FLOAT STRICT)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(1, statement.ColumnInt(0));
-  EXPECT_EQ("NULL", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(3, statement.ColumnInt(0));
-  EXPECT_EQ("FLOAT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2));
-  EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, NonNullFloatsStrict) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(db_.Execute(
-      "CREATE VIRTUAL TABLE temp.recover_types "
-      "USING recover(types, rowtype TEXT, value FLOAT STRICT NOT NULL)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(3, statement.ColumnInt(0));
-  EXPECT_EQ("FLOAT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2));
-  EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, Texts) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value TEXT)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(1, statement.ColumnInt(0));
-  EXPECT_EQ("NULL", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(4, statement.ColumnInt(0));
-  EXPECT_EQ("TEXT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2));
-  EXPECT_EQ("This is text", statement.ColumnString(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(5, statement.ColumnInt(0));
-  EXPECT_EQ("BLOB", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2));
-  std::string blob_text;
-  ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text));
-  EXPECT_EQ("This is a blob", blob_text);
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, NonNullTexts) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value TEXT NOT NULL)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(4, statement.ColumnInt(0));
-  EXPECT_EQ("TEXT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2));
-  EXPECT_EQ("This is text", statement.ColumnString(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(5, statement.ColumnInt(0));
-  EXPECT_EQ("BLOB", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2));
-  std::string blob_text;
-  ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text));
-  EXPECT_EQ("This is a blob", blob_text);
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, TextsStrict) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value TEXT STRICT)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(1, statement.ColumnInt(0));
-  EXPECT_EQ("NULL", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(4, statement.ColumnInt(0));
-  EXPECT_EQ("TEXT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2));
-  EXPECT_EQ("This is text", statement.ColumnString(2));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, NonNullTextsStrict) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(db_.Execute(
-      "CREATE VIRTUAL TABLE temp.recover_types "
-      "USING recover(types, rowtype TEXT, value TEXT STRICT NOT NULL)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(4, statement.ColumnInt(0));
-  EXPECT_EQ("TEXT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2));
-  EXPECT_EQ("This is text", statement.ColumnString(2));
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, Blobs) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value BLOB)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(1, statement.ColumnInt(0));
-  EXPECT_EQ("NULL", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(5, statement.ColumnInt(0));
-  EXPECT_EQ("BLOB", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2));
-  std::string blob_text;
-  ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text));
-  EXPECT_EQ("This is a blob", blob_text);
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, NonNullBlobs) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value BLOB NOT NULL)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(5, statement.ColumnInt(0));
-  EXPECT_EQ("BLOB", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2));
-  std::string blob_text;
-  ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text));
-  EXPECT_EQ("This is a blob", blob_text);
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, AnyNonNull) {
-  GenerateTypesTable(&db_);
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_types "
-                  "USING recover(types, rowtype TEXT, value ANY NOT NULL)"));
-  sql::Statement statement(db_.GetUniqueStatement(
-      "SELECT rowid, rowtype, value FROM recover_types"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(2, statement.ColumnInt(0));
-  EXPECT_EQ("INTEGER", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kInteger, statement.GetColumnType(2));
-  EXPECT_EQ(17, statement.ColumnInt(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(3, statement.ColumnInt(0));
-  EXPECT_EQ("FLOAT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kFloat, statement.GetColumnType(2));
-  EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(4, statement.ColumnInt(0));
-  EXPECT_EQ("TEXT", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kText, statement.GetColumnType(2));
-  EXPECT_EQ("This is text", statement.ColumnString(2));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(5, statement.ColumnInt(0));
-  EXPECT_EQ("BLOB", statement.ColumnString(1));
-  EXPECT_EQ(sql::ColumnType::kBlob, statement.GetColumnType(2));
-  std::string blob_text;
-  ASSERT_TRUE(statement.ColumnBlobAsString(2, &blob_text));
-  EXPECT_EQ("This is a blob", blob_text);
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, RowidAlias) {
-  GenerateTypesTable(&db_);
-
-  // The id column is an alias for rowid, and its values get serialized as NULL.
-  ASSERT_TRUE(db_.Execute(
-      "CREATE TABLE types2(id INTEGER PRIMARY KEY, rowtype TEXT, value)"));
-  ASSERT_TRUE(
-      db_.Execute("INSERT INTO types2(id, rowtype, value) "
-                  "SELECT rowid, rowtype, value FROM types WHERE true"));
-  ASSERT_TRUE(db_.Execute(
-      "CREATE VIRTUAL TABLE temp.recover_types2 "
-      "USING recover(types2, id ROWID NOT NULL, rowtype TEXT, value ANY)"));
-
-  sql::Statement statement(
-      db_.GetUniqueStatement("SELECT id, rowid, rowtype, value FROM types2"));
-
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(1, statement.ColumnInt(0));
-  EXPECT_EQ(1, statement.ColumnInt(1));
-  EXPECT_EQ("NULL", statement.ColumnString(2));
-  EXPECT_EQ(sql::ColumnType::kNull, statement.GetColumnType(3));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(2, statement.ColumnInt(0));
-  EXPECT_EQ(2, statement.ColumnInt(1));
-  EXPECT_EQ("INTEGER", statement.ColumnString(2));
-  EXPECT_EQ(17, statement.ColumnInt(3));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(3, statement.ColumnInt(0));
-  EXPECT_EQ(3, statement.ColumnInt(1));
-  EXPECT_EQ("FLOAT", statement.ColumnString(2));
-  EXPECT_DOUBLE_EQ(3.1415927, statement.ColumnDouble(3));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(4, statement.ColumnInt(0));
-  EXPECT_EQ(4, statement.ColumnInt(1));
-  EXPECT_EQ("TEXT", statement.ColumnString(2));
-  EXPECT_EQ("This is text", statement.ColumnString(3));
-  ASSERT_TRUE(statement.Step());
-  EXPECT_EQ(5, statement.ColumnInt(0));
-  EXPECT_EQ(5, statement.ColumnInt(1));
-  EXPECT_EQ("BLOB", statement.ColumnString(2));
-  std::string blob_text;
-  ASSERT_TRUE(statement.ColumnBlobAsString(3, &blob_text));
-  EXPECT_EQ("This is a blob", blob_text);
-
-  EXPECT_FALSE(statement.Step());
-}
-
-TEST_F(RecoverModuleTest, IntegerEncodings) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE integers(value)"));
-
-  const std::vector<int64_t> values = {
-      // Encoded directly in type info.
-      0,
-      1,
-      // 8-bit signed.
-      2,
-      -2,
-      127,
-      -128,
-      // 16-bit signed.
-      12345,
-      -12345,
-      32767,
-      -32768,
-      // 24-bit signed.
-      1234567,
-      -1234567,
-      8388607,
-      -8388608,
-      // 32-bit signed.
-      1234567890,
-      -1234567890,
-      2147483647,
-      -2147483647,
-      // 48-bit signed.
-      123456789012345,
-      -123456789012345,
-      140737488355327,
-      -140737488355327,
-      // 64-bit signed.
-      1234567890123456789,
-      -1234567890123456789,
-      9223372036854775807,
-      -9223372036854775807,
-  };
-  sql::Statement insert(
-      db_.GetUniqueStatement("INSERT INTO integers VALUES(?)"));
-  for (int64_t value : values) {
-    insert.BindInt64(0, value);
-    ASSERT_TRUE(insert.Run());
-    insert.Reset(/* clear_bound_vars= */ true);
-  }
-
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_integers "
-                  "USING recover(integers, value INTEGER)"));
-  sql::Statement select(
-      db_.GetUniqueStatement("SELECT rowid, value FROM recover_integers"));
-  for (size_t i = 0; i < values.size(); ++i) {
-    ASSERT_TRUE(select.Step()) << "Was attemping to read " << values[i];
-    EXPECT_EQ(static_cast<int>(i + 1), select.ColumnInt(0));
-    EXPECT_EQ(values[i], select.ColumnInt64(1));
-  }
-  EXPECT_FALSE(select.Step());
-}
-
-TEST_F(RecoverModuleTest, VarintEncodings) {
-  const std::vector<int64_t> values = {
-      // 1-byte varints.
-      0x00, 0x01, 0x02, 0x7e, 0x7f,
-      // 2-byte varints
-      0x80, 0x81, 0xff, 0x0100, 0x0101, 0x1234, 0x1ffe, 0x1fff, 0x3ffe, 0x3fff,
-      // 3-byte varints
-      0x4000, 0x4001, 0x0ffffe, 0x0fffff, 0x123456, 0x1fedcb, 0x1ffffe,
-      0x1fffff,
-      // 4-byte varints
-      0x200000, 0x200001, 0x123456, 0xfedcba, 0xfffffe, 0xffffff, 0x01234567,
-      0x0fedcba9, 0x0ffffffe, 0x0fffffff,
-      // 5-byte varints
-      0x10000000, 0x10000001, 0x12345678, 0xfedcba98, 0x01'23456789,
-      0x07'fffffffe, 0x07'ffffffff,
-      // 6-byte varints
-      0x08'00000000, 0x08'00000001, 0x12'3456789a, 0xfe'dcba9876,
-      0x0123'456789ab, 0x03ff'fffffffe, 0x03ff'ffffffff,
-      // 7-byte varints
-      0x0400'00000000, 0x0400'00000001, 0xfedc'ba987654, 0x012345'6789abcd,
-      0x01ffff'fffffffe, 0x01ffff'ffffffff,
-      // 8-byte varints
-      0x020000'00000000, 0x020000'00000001, 0x0fedcb'a9876543,
-      0x123456'789abcde, 0xfedcba'98765432, 0xffffff'fffffffe,
-      0xffffff'ffffffff,
-      // 9-byte positive varints
-      0x01000000'00000000, 0x01000000'00000001, 0x12345678'9abcdef0,
-      0x7fedcba9'87654321, 0x7fffffff'fffffffe, 0x7fffffff'ffffffff,
-      // 9-byte negative varints
-      -0x01, -0x02, -0x7e, -0x7f, -0x80, -0x81, -0x12345678'9abcdef0,
-      -0x7fedcba9'87654321, -0x7fffffff'ffffffff,
-      -0x7fffffff'ffffffff - 1,  // -0x80000000'00000000 is not a valid literal
-  };
-
-  ASSERT_TRUE(db_.Execute("CREATE TABLE varints(value INTEGER PRIMARY KEY)"));
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_varints "
-                  "USING recover(varints, value ROWID)"));
-
-  for (int64_t value : values) {
-    sql::Statement insert(
-        db_.GetUniqueStatement("INSERT INTO varints VALUES(?)"));
-    insert.BindInt64(0, value);
-    ASSERT_TRUE(insert.Run());
-
-    sql::Statement select(
-        db_.GetUniqueStatement("SELECT rowid, value FROM recover_varints"));
-    ASSERT_TRUE(select.Step()) << "Was attemping to read " << value;
-    EXPECT_EQ(value, select.ColumnInt64(0));
-    EXPECT_EQ(value, select.ColumnInt64(1));
-    EXPECT_FALSE(select.Step());
-
-    ASSERT_TRUE(db_.Execute("DELETE FROM varints"));
-  }
-}
-
-TEST_F(RecoverModuleTest, TextEncodings) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE encodings(t TEXT)"));
-
-  const std::vector<std::string> values = {
-      "",        "a",       "ö",       "Mjollnir", "Mjölnir", "Mjǫlnir",
-      "Mjölner", "Mjølner", "ハンマー",
-  };
-
-  sql::Statement insert(
-      db_.GetUniqueStatement("INSERT INTO encodings VALUES(?)"));
-  for (const std::string& value : values) {
-    insert.BindString(0, value);
-    ASSERT_TRUE(insert.Run());
-    insert.Reset(/* clear_bound_vars= */ true);
-  }
-
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_encodings "
-                  "USING recover(encodings, t TEXT)"));
-  sql::Statement select(
-      db_.GetUniqueStatement("SELECT rowid, t FROM recover_encodings"));
-  for (size_t i = 0; i < values.size(); ++i) {
-    ASSERT_TRUE(select.Step());
-    EXPECT_EQ(static_cast<int>(i + 1), select.ColumnInt(0));
-    EXPECT_EQ(values[i], select.ColumnString(1));
-  }
-  EXPECT_FALSE(select.Step());
-}
-
-TEST_F(RecoverModuleTest, BlobEncodings) {
-  ASSERT_TRUE(db_.Execute("CREATE TABLE blob_encodings(t BLOB)"));
-
-  const std::vector<std::vector<uint8_t>> values = {
-      {},           {0x00},       {0x01},
-      {0x42},       {0xff},       {0x00, 0x00},
-      {0x00, 0x01}, {0x00, 0xff}, {0x42, 0x43, 0x44, 0x45, 0x46},
-  };
-
-  sql::Statement insert(
-      db_.GetUniqueStatement("INSERT INTO blob_encodings VALUES(?)"));
-  for (const std::vector<uint8_t>& value : values) {
-    insert.BindBlob(0, value);
-    ASSERT_TRUE(insert.Run());
-    insert.Reset(/* clear_bound_vars= */ true);
-  }
-
-  ASSERT_TRUE(
-      db_.Execute("CREATE VIRTUAL TABLE temp.recover_blob_encodings "
-                  "USING recover(blob_encodings, t BLOB)"));
-  sql::Statement select(
-      db_.GetUniqueStatement("SELECT rowid, t FROM recover_blob_encodings"));
-  for (size_t i = 0; i < values.size(); ++i) {
-    ASSERT_TRUE(select.Step());
-    EXPECT_EQ(static_cast<int>(i + 1), select.ColumnInt(0));
-
-    std::vector<uint8_t> column_value;
-    EXPECT_TRUE(select.ColumnBlobAsVector(1, &column_value));
-    EXPECT_EQ(values[i], column_value);
-  }
-  EXPECT_FALSE(select.Step());
-}
-
-namespace {
-
-std::string RandomString(int size) {
-  std::mt19937 rng;
-  std::uniform_int_distribution<int> random_char(32, 127);
-  std::string large_value;
-  large_value.reserve(size);
-  for (int i = 0; i < size; ++i)
-    large_value.push_back(random_char(rng));
-  return large_value;
-}
-
-void CheckLargeValueRecovery(sql::Database* db, int value_size) {
-  const std::string large_value = RandomString(value_size);
-
-  ASSERT_TRUE(db->Execute("CREATE TABLE overflow(t TEXT)"));
-  sql::Statement insert(
-      db->GetUniqueStatement("INSERT INTO overflow VALUES(?)"));
-  insert.BindString(0, large_value);
-  ASSERT_TRUE(insert.Run());
-
-  ASSERT_TRUE(db->Execute("VACUUM"));
-
-  ASSERT_TRUE(
-      db->Execute("CREATE VIRTUAL TABLE temp.recover_overflow "
-                  "USING recover(overflow, t TEXT)"));
-  sql::Statement select(
-      db->GetUniqueStatement("SELECT rowid, t FROM recover_overflow"));
-  ASSERT_TRUE(select.Step());
-  EXPECT_EQ(1, select.ColumnInt(0));
-  EXPECT_EQ(large_value, select.ColumnString(1));
-}
-
-bool HasEnabledAutoVacuum(sql::Database* db) {
-  sql::Statement pragma(db->GetUniqueStatement("PRAGMA auto_vacuum"));
-  EXPECT_TRUE(pragma.Step());
-  return pragma.ColumnInt(0) != 0;
-}
-
-// The overhead in the table page is:
-// * 35 bytes - the cell size cutoff that causes a cell's payload to overflow
-// * 1 byte - record header size
-// * 2-3 bytes - type ID for the text column
-//   - texts of 58-8185 bytes use 2 bytes
-//   - texts of 8186-1048569 bytes  use 3 bytes
-//
-// The overhead below assumes a 2-byte string type ID.
-constexpr int kRecordOverhead = 38;
-
-// Each overflow page uses 4 bytes to store the pointer to the next page.
-constexpr int kOverflowOverhead = 4;
-
-}  // namespace
-
-TEST_F(RecoverModuleTest, ValueWithoutOverflow) {
-  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
-  ASSERT_EQ(2 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
-      << "Database should have a root page and a leaf page";
-}
-
-TEST_F(RecoverModuleTest, ValueWithOneByteOverflow) {
-  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead + 1);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
-  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
-      << "Database should have a root page, a leaf page, and 1 overflow page";
-}
-
-TEST_F(RecoverModuleTest, ValueWithOneOverflowPage) {
-  CheckLargeValueRecovery(
-      &db_, db_.page_size() - kRecordOverhead + db_.page_size() / 2);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
-  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
-      << "Database should have a root page, a leaf page, and 1 overflow page";
-}
-
-TEST_F(RecoverModuleTest, ValueWithOneFullOverflowPage) {
-  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead +
-                                    db_.page_size() - kOverflowOverhead);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
-  ASSERT_EQ(3 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
-      << "Database should have a root page, a leaf page, and 1 overflow page";
-}
-
-TEST_F(RecoverModuleTest, ValueWithOneByteSecondOverflowPage) {
-  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead +
-                                    db_.page_size() - kOverflowOverhead + 1);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
-  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
-      << "Database should have a root page, a leaf page, and 2 overflow pages";
-}
-
-TEST_F(RecoverModuleTest, ValueWithTwoOverflowPages) {
-  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead +
-                                    db_.page_size() - kOverflowOverhead +
-                                    db_.page_size() / 2);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
-  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
-      << "Database should have a root page, a leaf page, and 2 overflow pages";
-}
-
-TEST_F(RecoverModuleTest, ValueWithTwoFullOverflowPages) {
-  // This value is large enough that the varint encoding of its type ID takes up
-  // 3 bytes, instead of 2.
-  CheckLargeValueRecovery(&db_, db_.page_size() - kRecordOverhead +
-                                    (db_.page_size() - kOverflowOverhead) * 2 -
-                                    1);
-  int auto_vacuum_pages = HasEnabledAutoVacuum(&db_) ? 1 : 0;
-  ASSERT_EQ(4 + auto_vacuum_pages, sql::test::GetPageCount(&db_))
-      << "Database should have a root page, a leaf page, and 2 overflow pages";
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/pager.cc b/sql/recover_module/pager.cc
deleted file mode 100644
index 7f5b368..0000000
--- a/sql/recover_module/pager.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/pager.h"
-
-#include <limits>
-
-#include "base/logging.h"
-#include "sql/recover_module/table.h"
-#include "third_party/sqlite/sqlite3.h"
-
-namespace sql {
-namespace recover {
-
-constexpr int DatabasePageReader::kHighestInvalidPageId;
-constexpr int DatabasePageReader::kMinPageSize;
-constexpr int DatabasePageReader::kMaxPageSize;
-constexpr int DatabasePageReader::kDatabaseHeaderSize;
-constexpr int DatabasePageReader::kMinUsablePageSize;
-constexpr int DatabasePageReader::kMaxPageId;
-
-static_assert(DatabasePageReader::kMaxPageId <= std::numeric_limits<int>::max(),
-              "ints are not appropriate for representing page IDs");
-
-DatabasePageReader::DatabasePageReader(VirtualTable* table)
-    : page_data_(std::make_unique<uint8_t[]>(table->page_size())),
-      table_(table) {
-  CHECK(table != nullptr);
-  CHECK(IsValidPageSize(table->page_size()));
-}
-
-DatabasePageReader::~DatabasePageReader() = default;
-
-int DatabasePageReader::ReadPage(int page_id) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  CHECK(IsValidPageId(page_id));
-  CHECK_LE(page_id, kMaxPageId);
-
-  if (page_id_ == page_id)
-    return SQLITE_OK;
-
-  sqlite3_file* const sqlite_file = table_->SqliteFile();
-  const int page_size = table_->page_size();
-  const int page_offset = (page_id == 1) ? kDatabaseHeaderSize : 0;
-  const int read_size = page_size - page_offset;
-  static_assert(kMinPageSize >= kDatabaseHeaderSize,
-                "The |read_size| computation above may overflow");
-
-  page_size_ = read_size;
-  CHECK_GE(page_size_, kMinUsablePageSize);
-  CHECK_LE(page_size_, kMaxPageSize);
-
-  const int64_t read_offset =
-      static_cast<int64_t>(page_id - 1) * page_size + page_offset;
-  static_assert(static_cast<int64_t>(kMaxPageId - 1) * kMaxPageSize +
-                        kDatabaseHeaderSize <=
-                    std::numeric_limits<int64_t>::max(),
-                "The |read_offset| computation above may overflow");
-
-  int sqlite_status =
-      RawRead(sqlite_file, read_size, read_offset, page_data_.get());
-
-  // |page_id_| needs to be set to kHighestInvalidPageId if the read failed.
-  // Otherwise, future ReadPage() calls with the previous |page_id_| value
-  // would return SQLITE_OK, but the page data buffer might be trashed.
-  page_id_ = (sqlite_status == SQLITE_OK) ? page_id : kHighestInvalidPageId;
-  return sqlite_status;
-}
-
-// static
-int DatabasePageReader::RawRead(sqlite3_file* sqlite_file,
-                                int read_size,
-                                int64_t read_offset,
-                                uint8_t* result_buffer) {
-  CHECK(sqlite_file != nullptr);
-  CHECK_GE(read_size, 0);
-  CHECK_GE(read_offset, 0);
-  CHECK(result_buffer != nullptr);
-
-  // Retry the I/O operations a few times if they fail. This is especially
-  // useful when recovering from database corruption.
-  static constexpr int kRetryCount = 10;
-
-  int sqlite_status;
-  bool got_lock = false;
-  for (int i = kRetryCount; i > 0; --i) {
-    sqlite_status =
-        sqlite_file->pMethods->xLock(sqlite_file, SQLITE_LOCK_SHARED);
-    if (sqlite_status == SQLITE_OK) {
-      got_lock = true;
-      break;
-    }
-  }
-
-  // Try reading even if we don't have a shared lock on the database. If the
-  // read fails, the database page is completely skipped, so any data we might
-  // get from the read is better than nothing.
-  for (int i = kRetryCount; i > 0; --i) {
-    sqlite_status = sqlite_file->pMethods->xRead(sqlite_file, result_buffer,
-                                                 read_size, read_offset);
-    if (sqlite_status == SQLITE_OK)
-      break;
-    if (sqlite_status == SQLITE_IOERR_SHORT_READ) {
-      // The read succeeded, but hit EOF. The extra bytes in the page buffer
-      // are set to zero. This is acceptable for our purposes.
-      sqlite_status = SQLITE_OK;
-      break;
-    }
-  }
-
-  if (got_lock) {
-    // TODO(pwnall): This logic was ported from the old C-in-SQLite-style patch.
-    //               Dropping the lock here is incorrect, because the file
-    //               descriptor is shared with the SQLite pager, which may
-    //               expect to be holding a lock.
-    sqlite_file->pMethods->xUnlock(sqlite_file, SQLITE_LOCK_NONE);
-  }
-  return sqlite_status;
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/pager.h b/sql/recover_module/pager.h
deleted file mode 100644
index d60c5a4..0000000
--- a/sql/recover_module/pager.h
+++ /dev/null
@@ -1,155 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_PAGER_H_
-#define SQL_RECOVER_MODULE_PAGER_H_
-
-#include <cstdint>
-#include <memory>
-#include <ostream>
-
-#include "base/check_op.h"
-#include "base/memory/raw_ptr.h"
-#include "base/sequence_checker.h"
-
-struct sqlite3_file;
-
-namespace sql {
-namespace recover {
-
-class VirtualTable;
-
-// Page reader for SQLite database files.
-//
-// Contains logic for retrying reads on I/O errors. Caches the last read page,
-// to facilitate layering in higher-level code.
-//
-// Instances should be members of high-level constructs such as tables or
-// cursors. Instances are not thread-safe.
-class DatabasePageReader {
- public:
-  // Guaranteed to be an invalid page number. NB: use `IsValidPageId()` to
-  // validate a page id.
-  static constexpr int kHighestInvalidPageId = 0;
-
-  // Minimum database page size supported by SQLite.
-  static constexpr int kMinPageSize = 512;
-  // Maximum database page size supported by SQLite.
-  static constexpr int kMaxPageSize = 65536;
-
-  // The size of the header at the beginning of a SQLite database file.
-  static constexpr int kDatabaseHeaderSize = 100;
-
-  // Minimum usable size of a SQLite database page.
-  //
-  // This differs from |kMinPageSize| because the first page in a SQLite
-  // database starts with the database header. That page's header starts right
-  // after the database header.
-  static constexpr int kMinUsablePageSize = kMinPageSize - kDatabaseHeaderSize;
-
-  // Largest valid page ID in a SQLite database.
-  //
-  // This is the maximum value of SQLITE_MAX_PAGE_COUNT plus 1, because page IDs
-  // start at 1. The numerical value, which is the same as
-  // std::numeric_limits<int32_t>::max() - 1, is quoted from
-  // https://www.sqlite.org/limits.html.
-  static constexpr int kMaxPageId = 2147483646 + 1;
-
-  // Creates a reader that uses the SQLite VFS backing |table|.
-  //
-  // |table| must outlive this instance.
-  explicit DatabasePageReader(VirtualTable* table);
-  ~DatabasePageReader();
-
-  DatabasePageReader(const DatabasePageReader&) = delete;
-  DatabasePageReader& operator=(const DatabasePageReader&) = delete;
-
-  // The page data read by the last ReadPage() call.
-  //
-  // The page data is undefined if the last ReadPage() call failed, or if
-  // ReadPage() was never called.
-  const uint8_t* page_data() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    CHECK(IsValidPageId(page_id_))
-        << "Successful ReadPage() required before accessing pager state";
-    return page_data_.get();
-  }
-
-  // The number of bytes in the page read by the last ReadPage() call.
-  //
-  // The result is guaranteed to be in [kMinUsablePageSize, kMaxPageSize].
-  //
-  // In general, pages have the same size. However, the first page in each
-  // database is smaller, because it starts after the database header.
-  //
-  // The result is undefined if the last ReadPage() call failed, or if
-  // ReadPage() was never called.
-  int page_size() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    CHECK(IsValidPageId(page_id_))
-        << "Successful ReadPage() required before accessing pager state";
-    CHECK_GE(page_size_, kMinUsablePageSize);
-    CHECK_LE(page_size_, kMaxPageSize);
-    return page_size_;
-  }
-
-  // Returns the |page_id| argument for the last successful ReadPage() call.
-  //
-  // The result is undefined if the last ReadPage() call failed, or if
-  // ReadPage() was never called.
-  int page_id() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    CHECK(IsValidPageId(page_id_))
-        << "Successful ReadPage() required before accessing pager state";
-    return page_id_;
-  }
-
-  // Reads a database page. Returns a SQLite status code.
-  //
-  // SQLite uses 1-based indexing for its page numbers.
-  //
-  // This method is idempotent, because it caches its result.
-  int ReadPage(int page_id);
-
-  // True if the given database page size is supported by SQLite.
-  static constexpr bool IsValidPageSize(int page_size) noexcept {
-    // SQLite page sizes must be powers of two.
-    return page_size >= kMinPageSize && page_size <= kMaxPageSize &&
-           (page_size & (page_size - 1)) == 0;
-  }
-
-  // True if the given number is a valid SQLite database page ID.
-  //
-  // Valid page IDs are positive 32-bit integers.
-  static constexpr bool IsValidPageId(int64_t page_id) noexcept {
-    return page_id > kHighestInvalidPageId && page_id <= kMaxPageId;
-  }
-
-  // Low-level read wrapper. Returns a SQLite error code.
-  //
-  // |read_size| and |read_offset| are expressed in bytes.
-  static int RawRead(sqlite3_file* sqlite_file,
-                     int read_size,
-                     int64_t read_offset,
-                     uint8_t* buffer);
-
- private:
-  // Points to the last page successfully read by ReadPage().
-  // Set to kHighestInvalidPageId if the last read was unsuccessful.
-  int page_id_ = kHighestInvalidPageId;
-  // Stores the bytes of the last page successfully read by ReadPage().
-  // The content is undefined if the last call to ReadPage() did not succeed.
-  const std::unique_ptr<uint8_t[]> page_data_;
-  // Raw pointer usage is acceptable because this instance's owner is expected
-  // to ensure that the VirtualTable outlives this.
-  const raw_ptr<VirtualTable> table_;
-  int page_size_ = 0;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-};
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_PAGER_H_
diff --git a/sql/recover_module/parsing.cc b/sql/recover_module/parsing.cc
deleted file mode 100644
index b1c7289..0000000
--- a/sql/recover_module/parsing.cc
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/parsing.h"
-
-#include <cstddef>
-#include <ostream>
-#include <tuple>
-#include <utility>
-
-#include <optional>
-#include "base/check.h"
-#include "base/notreached.h"
-#include "base/strings/strcat.h"
-#include "base/strings/string_piece.h"
-#include "sql/recover_module/record.h"
-
-namespace sql {
-namespace recover {
-
-namespace {
-
-// This module defines whitespace as space (0x20).
-constexpr bool IsWhiteSpace(char character) {
-  return (character == ' ');
-}
-
-// Splits a token out of a SQL string representing a column type.
-//
-// Tokens are separated by space (0x20) characters.
-//
-// The SQL must not start with a space character.
-//
-// Returns the token and the rest of the SQL string. Consumes the space after
-// the returned token -- the rest of the SQL string will not start with space.
-std::pair<base::StringPiece, base::StringPiece> SplitToken(
-    base::StringPiece sql) {
-  DCHECK(sql.empty() || !IsWhiteSpace(sql[0]));
-
-  size_t token_end = 0;
-  while (token_end < sql.length() && !IsWhiteSpace(sql[token_end]))
-    ++token_end;
-
-  size_t split = token_end;
-  while (split < sql.length() && IsWhiteSpace(sql[split]))
-    ++split;
-
-  return {sql.substr(0, token_end), sql.substr(split)};
-}
-
-// Column types.
-constexpr base::StringPiece kIntegerSql("INTEGER");
-constexpr base::StringPiece kFloatSql("FLOAT");
-constexpr base::StringPiece kTextSql("TEXT");
-constexpr base::StringPiece kBlobSql("BLOB");
-constexpr base::StringPiece kNumericSql("NUMERIC");
-constexpr base::StringPiece kRowidSql("ROWID");
-constexpr base::StringPiece kAnySql("ANY");
-
-// SQL keywords recognized by the recovery module.
-constexpr base::StringPiece kStrictSql("STRICT");
-constexpr base::StringPiece kNonNullSql1("NOT");
-constexpr base::StringPiece kNonNullSql2("NULL");
-
-std::optional<ModuleColumnType> ParseColumnType(
-    base::StringPiece column_type_sql) {
-  if (column_type_sql == kIntegerSql)
-    return ModuleColumnType::kInteger;
-  if (column_type_sql == kFloatSql)
-    return ModuleColumnType::kFloat;
-  if (column_type_sql == kTextSql)
-    return ModuleColumnType::kText;
-  if (column_type_sql == kBlobSql)
-    return ModuleColumnType::kBlob;
-  if (column_type_sql == kNumericSql)
-    return ModuleColumnType::kNumeric;
-  if (column_type_sql == kRowidSql)
-    return ModuleColumnType::kRowId;
-  if (column_type_sql == kAnySql)
-    return ModuleColumnType::kAny;
-
-  return std::nullopt;
-}
-
-// Returns a view into a SQL string representing the column type.
-//
-// The backing string is guaranteed to live for as long as the process runs.
-base::StringPiece ColumnTypeToSql(ModuleColumnType column_type) {
-  switch (column_type) {
-    case ModuleColumnType::kInteger:
-      return kIntegerSql;
-    case ModuleColumnType::kFloat:
-      return kFloatSql;
-    case ModuleColumnType::kText:
-      return kTextSql;
-    case ModuleColumnType::kBlob:
-      return kBlobSql;
-    case ModuleColumnType::kNumeric:
-      return kNumericSql;
-    case ModuleColumnType::kRowId:
-      return kIntegerSql;  // rowids are ints.
-    case ModuleColumnType::kAny:
-      return base::StringPiece();
-  }
-  NOTREACHED();
-}
-
-}  // namespace
-
-std::string RecoveredColumnSpec::ToCreateTableSql() const {
-  base::StringPiece not_null_sql = (is_non_null) ? " NOT NULL" : "";
-  return base::StrCat(
-      {this->name, " ", ColumnTypeToSql(this->type), not_null_sql});
-}
-
-bool RecoveredColumnSpec::IsAcceptableValue(ValueType value_type) const {
-  if (value_type == ValueType::kNull)
-    return !is_non_null || type == ModuleColumnType::kRowId;
-  if (type == ModuleColumnType::kAny)
-    return true;
-
-  if (value_type == ValueType::kInteger) {
-    return type == ModuleColumnType::kInteger ||
-           (!is_strict && type == ModuleColumnType::kFloat);
-  }
-  if (value_type == ValueType::kFloat) {
-    return type == ModuleColumnType::kFloat ||
-           (!is_strict && type == ModuleColumnType::kFloat);
-  }
-  if (value_type == ValueType::kText)
-    return type == ModuleColumnType::kText;
-  if (value_type == ValueType::kBlob) {
-    return type == ModuleColumnType::kBlob ||
-           (!is_strict && type == ModuleColumnType::kText);
-  }
-  NOTREACHED() << "Unimplemented value type";
-  return false;
-}
-
-RecoveredColumnSpec ParseColumnSpec(const char* sqlite_arg) {
-  // The result is invalid until its |name| member is set.
-  RecoveredColumnSpec result;
-
-  auto [column_name, sql] = SplitToken(sqlite_arg);
-  if (column_name.empty()) {
-    // Empty column names are invalid.
-    DCHECK(!result.IsValid());
-    return result;
-  }
-
-  base::StringPiece column_type_sql;
-  std::tie(column_type_sql, sql) = SplitToken(sql);
-  std::optional<ModuleColumnType> column_type =
-      ParseColumnType(column_type_sql);
-  if (!column_type.has_value()) {
-    // Invalid column type.
-    DCHECK(!result.IsValid());
-    return result;
-  }
-  result.type = column_type.value();
-
-  // Consume keywords.
-  result.is_non_null = result.type == ModuleColumnType::kRowId;
-  while (!sql.empty()) {
-    base::StringPiece token;
-    std::tie(token, sql) = SplitToken(sql);
-
-    if (token == kStrictSql) {
-      if (result.type == ModuleColumnType::kAny) {
-        // STRICT is incompatible with ANY.
-        DCHECK(!result.IsValid());
-        return result;
-      }
-
-      result.is_strict = true;
-      continue;
-    }
-
-    if (token != kNonNullSql1) {
-      // Invalid SQL keyword.
-      DCHECK(!result.IsValid());
-      return result;
-    }
-    std::tie(token, sql) = SplitToken(sql);
-    if (token != kNonNullSql2) {
-      // Invalid SQL keyword.
-      DCHECK(!result.IsValid());
-      return result;
-    }
-    result.is_non_null = true;
-  }
-
-  result.name = std::string(column_name);
-  return result;
-}
-
-TargetTableSpec ParseTableSpec(const char* sqlite_arg) {
-  base::StringPiece sql(sqlite_arg);
-
-  size_t separator_pos = sql.find('.');
-  if (separator_pos == base::StringPiece::npos) {
-    // The default attachment point name is "main".
-    return TargetTableSpec{"main", sqlite_arg};
-  }
-
-  if (separator_pos == 0) {
-    // Empty attachment point names are invalid.
-    return TargetTableSpec();
-  }
-
-  base::StringPiece db_name = sql.substr(0, separator_pos);
-  base::StringPiece table_name = sql.substr(separator_pos + 1);
-  return TargetTableSpec{std::string(db_name), std::string(table_name)};
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/parsing.h b/sql/recover_module/parsing.h
deleted file mode 100644
index 71b3a0cf..0000000
--- a/sql/recover_module/parsing.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_PARSING_H_
-#define SQL_RECOVER_MODULE_PARSING_H_
-
-#include <string>
-
-namespace sql {
-namespace recover {
-
-enum class ValueType;
-
-// The declared data type of a virtual table column.
-enum class ModuleColumnType {
-  kInteger,
-  kFloat,
-  kText,
-  kBlob,
-  kNumeric,
-  kRowId,
-  kAny,
-};
-
-// User-supplied specification for recovering a column in a corrupted table.
-struct RecoveredColumnSpec {
-  // False if this represents a parsing error.
-  bool IsValid() const { return !name.empty(); }
-  // Column description suitable for use in a CREATE TABLE statement.
-  std::string ToCreateTableSql() const;
-  // True if the given value type is admitted by this column specification.
-  bool IsAcceptableValue(ValueType value_type) const;
-
-  // Column name reported to the SQLite engine.
-  //
-  // The empty string is (ab)used for representing invalid column information,
-  // which can be used to communicate parsing errors.
-  std::string name;
-  // The column's canonical type.
-  ModuleColumnType type;
-  // If true, recovery will skip over null values in this column.
-  bool is_non_null = false;
-  // If true, recovery will accept values in this column with compatible types.
-  bool is_strict = false;
-};
-
-// Parses a SQLite module argument that holds a table column specification.
-//
-// Returns an invalid specification (IsValid() returns false) on parsing errors.
-RecoveredColumnSpec ParseColumnSpec(const char* sqlite_arg);
-
-// User-supplied SQL table identifier.
-//
-// This points to the table whose data is being recovered.
-struct TargetTableSpec {
-  // False if this represents a parsing error.
-  bool IsValid() const { return !table_name.empty(); }
-
-  // The name of the attachment point of the database containing the table.
-  std::string db_name;
-  // The name of the table. Uniquely identifies a table in a database.
-  std::string table_name;
-};
-
-// Parses a SQLite module argument that points to a table.
-TargetTableSpec ParseTableSpec(const char* sqlite_arg);
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_PARSING_H_
diff --git a/sql/recover_module/payload.cc b/sql/recover_module/payload.cc
deleted file mode 100644
index a114b75..0000000
--- a/sql/recover_module/payload.cc
+++ /dev/null
@@ -1,344 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/payload.h"
-
-#include <algorithm>
-#include <cstddef>
-#include <limits>
-#include <ostream>
-#include <type_traits>
-
-#include "base/check_op.h"
-#include "base/logging.h"
-#include "sql/recover_module/btree.h"
-#include "sql/recover_module/integers.h"
-#include "sql/recover_module/pager.h"
-#include "third_party/sqlite/sqlite3.h"
-
-namespace sql {
-namespace recover {
-
-namespace {
-
-// The size of page IDs pointing to overflow pages.
-constexpr int kPageIdSize = sizeof(int32_t);
-
-// The largest page header size. Inner B-tree pages use this size.
-constexpr int kMaxPageOverhead = 12;
-
-// Maximum number of bytes in a cell used by header and trailer.
-//
-// The maximum overhead is incurred by cells in leaf table B-tree pages.
-// * 2-byte cell pointer, in the cell pointer array
-// * 9-byte row ID, for the maximum varint size
-// * 8-byte payload size, (varint size for maximum payload size of 2 ** 48)
-// * 4-byte first overflow page ID
-constexpr int kMaxCellOverhead = 23;
-
-// Knob used to trade off between having more rows in a tree page and
-// avoiding the use of overflow pages for large values. The knob value is
-// stored in the database header.
-//
-// In SQLite 3.6 and above, the knob was fixed to the value below.
-static constexpr int kLeafPayloadFraction = 32;
-
-// Denominator used by all load fractions in SQLite's B-tree logic.
-static constexpr int kPayloadFractionDenominator = 255;
-
-// The maximum size of a payload on a leaf page.
-//
-// Records whose size exceeds this limit spill over to overflow pages.
-//
-// The return value is guaranteed to be at least
-// LeafPayloadReader::kMinInlineSize, and at most the database page size.
-int MaxInlinePayloadSize(int page_size) {
-  DCHECK_GE(page_size, DatabasePageReader::kMinUsablePageSize);
-  DCHECK_LE(page_size, DatabasePageReader::kMaxPageSize);
-
-  const int max_inline_payload_size =
-      page_size - kMaxPageOverhead - kMaxCellOverhead;
-  DCHECK_GE(max_inline_payload_size, LeafPayloadReader::kMinInlineSize);
-  static_assert(
-      DatabasePageReader::kMinPageSize - kMaxPageOverhead - kMaxCellOverhead >
-          LeafPayloadReader::kMinInlineSize,
-      "The DCHECK above may fail");
-
-  return max_inline_payload_size;
-}
-
-// The minimum size of a payload on a B-tree page.
-//
-// Records that spill over to overflow pages are guaranteed to have at least
-// these many bytes stored in the B-tree page.
-//
-// The return value is guaranteed to be at least
-// LeafPayloadReader::kMinInlineSize, and at most
-// MaxInlinePayloadSize(page_size).
-int MinInlinePayloadSize(int page_size) {
-  DCHECK_GE(page_size, DatabasePageReader::kMinUsablePageSize);
-  DCHECK_LE(page_size, DatabasePageReader::kMaxPageSize);
-
-  const int min_inline_payload_size =
-      ((page_size - kMaxPageOverhead) * kLeafPayloadFraction) /
-          kPayloadFractionDenominator -
-      kMaxCellOverhead;
-  static_assert((DatabasePageReader::kMaxPageSize - kMaxPageOverhead) *
-                        kLeafPayloadFraction <=
-                    std::numeric_limits<int>::max(),
-                "The |min_inline_payload_size| computation above may overflow");
-  DCHECK_GE(min_inline_payload_size, LeafPayloadReader::kMinInlineSize);
-  static_assert(((DatabasePageReader::kMinUsablePageSize - kMaxPageOverhead) *
-                 kLeafPayloadFraction) /
-                            kPayloadFractionDenominator -
-                        kMaxCellOverhead >=
-                    LeafPayloadReader::kMinInlineSize,
-                "The DCHECK above may fail");
-
-  // The minimum inline payload size is ((P - 12) * 32) / 255 - 23. This is
-  // smaller or equal to ((P - 12) * 255) / 255 - 23, which is P - 35. This is
-  // the maximum payload size.
-  DCHECK_LE(min_inline_payload_size, MaxInlinePayloadSize(page_size));
-
-  return min_inline_payload_size;
-}
-
-// The maximum size of a payload on an overflow page.
-//
-// The return value is guaranteed to be positive, and at most equal to the page
-// size.
-int MaxOverflowPayloadSize(int page_size) {
-  DCHECK_GE(page_size, DatabasePageReader::kMinUsablePageSize);
-  DCHECK_LE(page_size, DatabasePageReader::kMaxPageSize);
-
-  // Each overflow page starts with a 32-bit integer pointing to the next
-  // overflow page. The rest of the page stores payload bytes.
-  const int max_overflow_payload_size = page_size - 4;
-
-  DCHECK_GT(max_overflow_payload_size, 0);
-  static_assert(DatabasePageReader::kMinUsablePageSize > 4,
-                "The DCHECK above may fail");
-  DCHECK_LE(max_overflow_payload_size, DatabasePageReader::kMaxPageSize);
-  return max_overflow_payload_size;
-}
-
-}  // namespace
-
-LeafPayloadReader::LeafPayloadReader(DatabasePageReader* db_reader)
-    : db_reader_(db_reader) {}
-
-LeafPayloadReader::~LeafPayloadReader() = default;
-
-bool LeafPayloadReader::Initialize(int64_t payload_size, int payload_offset) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  payload_size_ = payload_size;
-  inline_payload_offset_ = payload_offset;
-  page_id_ = db_reader_->page_id();
-
-  const int page_size = db_reader_->page_size();
-
-  const int max_inline_payload_size = MaxInlinePayloadSize(page_size);
-  if (payload_size <= max_inline_payload_size) {
-    // The payload fits inside the page.
-    inline_payload_size_ = static_cast<int>(payload_size);
-    overflow_page_count_ = 0;
-  } else {
-    const int min_inline_payload_size = MinInlinePayloadSize(page_size);
-
-    // The payload size is bigger than the maximum inline payload size, so it
-    // must be bigger than the minimum payload size. This check verifies that
-    // the subtractions below have non-negative results.
-    CHECK_GT(payload_size, min_inline_payload_size);
-
-    // Payload sizes are upper-bounded by the page size.
-    static_assert(
-        DatabasePageReader::kMaxPageSize * 2 <= std::numeric_limits<int>::max(),
-        "The additions below may overflow");
-
-    // Ideally, all bytes in the overflow pages would be used by the payload.
-    // Check if this can be accomplished within the other payload constraints.
-    max_overflow_payload_size_ = MaxOverflowPayloadSize(page_size);
-    const int64_t efficient_overflow_page_count =
-        (payload_size - min_inline_payload_size) / max_overflow_payload_size_;
-    const int efficient_overflow_spill =
-        (payload_size - min_inline_payload_size) % max_overflow_payload_size_;
-    const int efficient_inline_payload_size =
-        min_inline_payload_size + efficient_overflow_spill;
-
-    if (efficient_inline_payload_size <= max_inline_payload_size) {
-      inline_payload_size_ = efficient_inline_payload_size;
-      overflow_page_count_ = efficient_overflow_page_count;
-      DCHECK_EQ(
-          0, (payload_size - inline_payload_size_) % max_overflow_payload_size_)
-          << "Overflow pages not fully packed";
-    } else {
-      inline_payload_size_ = min_inline_payload_size;
-      overflow_page_count_ = efficient_overflow_page_count + 1;
-    }
-
-    CHECK_LE(inline_payload_size_, max_inline_payload_size);
-    if (overflow_page_count_ != (payload_size - inline_payload_size_ +
-                                 (max_overflow_payload_size_ - 1)) /
-                                    max_overflow_payload_size_) {
-      LOG(ERROR) << "Incorrect overflow page count calculation";
-      page_id_ = DatabasePageReader::kHighestInvalidPageId;
-      return false;
-    }
-  }
-
-  CHECK_LE(inline_payload_size_, payload_size);
-  CHECK_LE(inline_payload_size_, page_size);
-
-  const int first_overflow_page_id_size =
-      (overflow_page_count_ == 0) ? 0 : kPageIdSize;
-
-  if (inline_payload_offset_ + inline_payload_size_ +
-          first_overflow_page_id_size >
-      page_size) {
-    // Corruption can result in overly large payload sizes. Reject the obvious
-    // case where the in-page payload extends past the end of the page.
-    page_id_ = DatabasePageReader::kHighestInvalidPageId;
-    return false;
-  }
-
-  overflow_page_ids_.clear();
-  overflow_page_ids_.reserve(overflow_page_count_);
-  return true;
-}
-
-bool LeafPayloadReader::IsInitialized() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return DatabasePageReader::IsValidPageId(page_id_);
-}
-
-bool LeafPayloadReader::ReadPayload(int64_t offset,
-                                    int64_t size,
-                                    uint8_t* buffer) {
-  DCHECK(IsInitialized())
-      << "Initialize() not called, or last call did not succeed";
-  DCHECK_GE(offset, 0);
-  DCHECK_LT(offset, payload_size_);
-  DCHECK_GT(size, 0);
-  DCHECK_LE(offset + size, payload_size_);
-  DCHECK(buffer != nullptr);
-
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(IsInitialized())
-      << "Initialize() not called, or last call did not succeed";
-
-  if (offset < inline_payload_size_) {
-    // The read overlaps the payload bytes stored in the B-tree page.
-    if (db_reader_->ReadPage(page_id_) != SQLITE_OK)
-      return false;
-    const int page_size = db_reader_->page_size();
-
-    // The static_cast is safe because inline_payload_size_ is smaller than a
-    // SQLite page size, which is a 32-bit integer.
-    const int read_offset = inline_payload_offset_ + static_cast<int>(offset);
-    DCHECK_LE(read_offset, page_size);
-
-    const int read_size =
-        (static_cast<int>(offset) + size <= inline_payload_size_)
-            ? static_cast<int>(size)
-            : inline_payload_size_ - static_cast<int>(offset);
-    DCHECK_LE(read_offset + read_size, page_size);
-    std::copy(db_reader_->page_data() + read_offset,
-              db_reader_->page_data() + read_offset + read_size, buffer);
-
-    if (read_size == size) {
-      // The read is entirely inside the B-tree page.
-      return true;
-    }
-
-    offset += read_size;
-    DCHECK_EQ(offset, inline_payload_size_);
-    DCHECK_GT(size, read_size);
-    size -= read_size;
-    buffer += read_size;
-  }
-
-  // The read is entirely in overflow pages.
-  DCHECK_GE(offset, inline_payload_size_);
-  if (max_overflow_payload_size_ <= 0) {
-    // `max_overflow_payload_size_` should have been set in Initialize() if it's
-    // to be used here. See https://crbug.com/1417151.
-    return false;
-  }
-  while (size > 0) {
-    const int overflow_page_index =
-        (offset - inline_payload_size_) / max_overflow_payload_size_;
-    DCHECK_LT(overflow_page_index, overflow_page_count_);
-    const int overflow_page_offset =
-        (offset - inline_payload_size_) % max_overflow_payload_size_;
-
-    while (overflow_page_ids_.size() <=
-           static_cast<size_t>(overflow_page_index)) {
-      if (!PopulateNextOverflowPageId())
-        return false;
-    }
-
-    const int page_id = overflow_page_ids_[overflow_page_index];
-    if (db_reader_->ReadPage(page_id) != SQLITE_OK)
-      return false;
-    const int page_size = db_reader_->page_size();
-
-    const int read_offset = kPageIdSize + overflow_page_offset;
-    DCHECK_LE(read_offset, page_size);
-
-    const int read_size = std::min<int64_t>(page_size - read_offset, size);
-    DCHECK_LE(read_offset + read_size, page_size);
-    std::copy(db_reader_->page_data() + read_offset,
-              db_reader_->page_data() + read_offset + read_size, buffer);
-
-    offset += read_size;
-    DCHECK_GE(size, read_size);
-    size -= read_size;
-    buffer += read_size;
-  }
-
-  return true;
-}
-
-const uint8_t* LeafPayloadReader::ReadInlinePayload() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(IsInitialized())
-      << "Initialize() not called, or last call did not succeed";
-
-  if (db_reader_->ReadPage(page_id_) != SQLITE_OK)
-    return nullptr;
-  return db_reader_->page_data() + inline_payload_offset_;
-}
-
-bool LeafPayloadReader::PopulateNextOverflowPageId() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK_LT(overflow_page_ids_.size(),
-            static_cast<size_t>(overflow_page_count_));
-
-  int page_id_offset;
-  if (overflow_page_ids_.empty()) {
-    // The first overflow page ID is right after the payload's inline bytes.
-    page_id_offset = inline_payload_offset_ + inline_payload_size_;
-    if (db_reader_->ReadPage(page_id_) != SQLITE_OK)
-      return false;
-  } else {
-    // Overflow pages start with the ID of the next overflow page.
-    page_id_offset = 0;
-    if (db_reader_->ReadPage(overflow_page_ids_.back()) != SQLITE_OK)
-      return false;
-  }
-
-  DCHECK_LE(page_id_offset + kPageIdSize, db_reader_->page_size());
-  const int next_page_id =
-      LoadBigEndianInt32(db_reader_->page_data() + page_id_offset);
-  if (!DatabasePageReader::IsValidPageId(next_page_id)) {
-    // The overflow page is corrupted.
-    return false;
-  }
-
-  overflow_page_ids_.push_back(next_page_id);
-  return true;
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/payload.h b/sql/recover_module/payload.h
deleted file mode 100644
index 8355209..0000000
--- a/sql/recover_module/payload.h
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_PAYLOAD_H_
-#define SQL_RECOVER_MODULE_PAYLOAD_H_
-
-#include <cstdint>
-#include <ostream>
-#include <vector>
-
-#include "base/check_op.h"
-#include "base/memory/raw_ptr.h"
-#include "base/sequence_checker.h"
-#include "sql/recover_module/pager.h"
-
-namespace sql {
-namespace recover {
-
-class DatabasePageReader;
-
-// Reads payloads (records) across B-tree pages and overflow pages.
-//
-// Instances are designed to be reused for reading multiple payloads. Instances
-// are not thread-safe.
-//
-// Reading a payload is started by calling Initialize() with the information
-// from LeafPageDecoder. If the call succeeds, ReadPayload() can be called
-// repeatedly.
-class LeafPayloadReader {
- public:
-  // Number of payload bytes guaranteed to be on the B-tree page.
-  //
-  // The value is derived from the minimum SQLite usable page size, which is
-  // 380 bytes, and the formula for the minimum payload size given a usable page
-  // size.
-  static constexpr int kMinInlineSize = ((380 - 12) * 32) / 255 - 23;
-
-  explicit LeafPayloadReader(DatabasePageReader* db_reader);
-  ~LeafPayloadReader();
-
-  LeafPayloadReader(const LeafPayloadReader&) = delete;
-  LeafPayloadReader& operator=(const LeafPayloadReader&) = delete;
-
-  // Sets up the reader for a new payload.
-  //
-  // The DatabasePageReader passed to the constructor must be focused on the
-  // page containing the payload.
-  //
-  // This method must complete successfully before any other method on this
-  // class can be called.
-  bool Initialize(int64_t payload_size, int payload_offset);
-
-  // True if the last call to Initialize succeeded.
-  bool IsInitialized() const;
-
-  // The number of payload bytes that are stored on the B-tree page.
-  //
-  // The return value is guaranteed to be non-negative and at most
-  // payload_size().
-  int inline_payload_size() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    DCHECK(IsInitialized())
-        << "Initialize() not called, or last call did not succeed";
-    DCHECK_LE(inline_payload_size_, payload_size_);
-    return inline_payload_size_;
-  }
-
-  // Total payload size, in bytes.
-  //
-  // This includes the bytes stored in the B-tree page, as well as any bytes
-  // stored in overflow pages.
-  //
-  // The return value is guaranteed to be at least inline_payload_size().
-  int payload_size() const {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    DCHECK(IsInitialized())
-        << "Initialize() not called, or last call did not succeed";
-    DCHECK_LE(inline_payload_size_, payload_size_);
-    return payload_size_;
-  }
-
-  // Copies a subset of the payload into a given buffer.
-  //
-  // Returns true if the read succeeds.
-  //
-  // May only be called after a previous call to Initialize() that returns true.
-  bool ReadPayload(int64_t offset, int64_t size, uint8_t* buffer);
-
-  // Pulls the B-tree containing the payload into the database reader's cache.
-  //
-  // Returns a pointer to the beginning of the payload bytes. The pointer is
-  // inside the database reader's buffer, and may get invalidated if the
-  // database reader is used.
-  //
-  // Returns null if the read operation fails.
-  //
-  // May only be called after a previous call to Initialize() that returns true.
-  const uint8_t* ReadInlinePayload();
-
- private:
-  // Extends the cached list of overflow page IDs by one page.
-  //
-  // Returns false if the operation failed. Failures are due to read errors or
-  // database corruption.
-  bool PopulateNextOverflowPageId();
-
-  // Used to read the pages containing the payload.
-  //
-  // Raw pointer usage is acceptable because this instance's owner is expected
-  // to ensure that the DatabasePageReader outlives this.
-  const raw_ptr<DatabasePageReader> db_reader_;
-
-  // Total size of the current payload.
-  int64_t payload_size_ = 0;
-
-  // The ID of the B-tree page containing the current payload's inline bytes.
-  //
-  // Set to kHighestInvalidPageId if the reader wasn't successfully initialized.
-  int page_id_ = DatabasePageReader::kHighestInvalidPageId;
-
-  // The start of the current payload's inline bytes on the B-tree page.
-  //
-  // Large payloads extend past the B-tree page containing the payload, via
-  // overflow pages.
-  int inline_payload_offset_ = 0;
-
-  // Number of bytes in the current payload stored in its B-tree page.
-  //
-  // The rest of the payload is stored on overflow pages.
-  int inline_payload_size_ = 0;
-
-  // Number of overflow pages used by the payload.
-  int overflow_page_count_ = 0;
-
-  // Number of bytes in each overflow page that stores the payload.
-  int max_overflow_payload_size_ = 0;
-
-  // Page IDs for all the payload's overflow pages, in order.
-  //
-  // This list is populated on-demand.
-  std::vector<int> overflow_page_ids_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-};
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_PAYLOAD_H_
diff --git a/sql/recover_module/record.cc b/sql/recover_module/record.cc
deleted file mode 100644
index 781e39c0..0000000
--- a/sql/recover_module/record.cc
+++ /dev/null
@@ -1,327 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/record.h"
-
-#include <cstddef>
-#include <limits>
-#include <ostream>
-#include <type_traits>
-
-#include "base/check_op.h"
-#include "base/notreached.h"
-#include "sql/recover_module/integers.h"
-#include "sql/recover_module/payload.h"
-#include "third_party/sqlite/sqlite3.h"
-
-namespace sql {
-namespace recover {
-
-RecordReader::RecordReader(LeafPayloadReader* payload_reader, int column_count)
-    : payload_reader_(payload_reader), column_count_(column_count) {
-  DCHECK(payload_reader != nullptr);
-  DCHECK_GT(column_count, 0);
-  value_headers_.reserve(column_count);
-}
-
-RecordReader::~RecordReader() = default;
-
-namespace {
-
-// Value type indicating a null.
-constexpr int kNullType = 0;
-// Value type indicating a 1-byte signed integer.
-constexpr int kInt1Type = 1;
-// Value type indicating a 2-byte signed big-endian integer.
-constexpr int kInt2Type = 2;
-// Value type indicating a 3-byte signed big-endian integer.
-constexpr int kInt3Type = 3;
-// Value type indicating a 4-byte signed big-endian integer.
-constexpr int kInt4Type = 4;
-// Value type indicating a 6-byte signed big-endian integer.
-constexpr int kInt6Type = 5;
-// Value type indicating an 8-byte signed big-endian integer.
-constexpr int kInt8Type = 6;
-// Value type indicating a big-endian IEEE 754 64-bit floating point number.
-constexpr int kDoubleType = 7;
-// Value type indicating the integer 0 (zero).
-constexpr int kIntZeroType = 8;
-// Value type indicating the integer 1 (one).
-constexpr int kIntOneType = 9;
-// Value types greater than or equal to this indicate blobs or text.
-constexpr int kMinBlobOrStringType = 12;
-
-// The return value of ParseHeaderType below.
-struct ParsedHeaderType {
-  // True for the special value used to communicate a parsing error.
-  bool IsInvalid() const {
-    return type == ValueType::kNull && has_inline_value;
-  }
-
-  const ValueType type;
-  const int64_t size;
-  const int8_t inline_value;
-  const bool has_inline_value;
-};
-
-// Decodes a type identifier in a SQLite record header.
-//
-// The type identifier includes the type and the size.
-//
-// Returns {kNull, 1} when parsing fails. Null values never require any extra
-// bytes, so this special return value will never occur during normal
-// processing.
-ParsedHeaderType ParseHeaderType(int64_t encoded_type) {
-  static constexpr int8_t kNoInlineValue = 0;
-
-  if (encoded_type == kNullType)
-    return {ValueType::kNull, 0, kNoInlineValue, false};
-  if (encoded_type == kInt1Type)
-    return {ValueType::kInteger, 1, kNoInlineValue, false};
-  if (encoded_type == kInt2Type)
-    return {ValueType::kInteger, 2, kNoInlineValue, false};
-  if (encoded_type == kInt3Type)
-    return {ValueType::kInteger, 3, kNoInlineValue, false};
-  if (encoded_type == kInt4Type)
-    return {ValueType::kInteger, 4, kNoInlineValue, false};
-  if (encoded_type == kInt6Type)
-    return {ValueType::kInteger, 6, kNoInlineValue, false};
-  if (encoded_type == kInt8Type)
-    return {ValueType::kInteger, 8, kNoInlineValue, false};
-  if (encoded_type == kDoubleType)
-    return {ValueType::kFloat, 8, kNoInlineValue, false};
-  if (encoded_type == kIntZeroType)
-    return {ValueType::kInteger, 0, 0, true};
-  if (encoded_type == kIntOneType)
-    return {ValueType::kInteger, 0, 1, true};
-
-  if (encoded_type < kMinBlobOrStringType) {
-    // Types between |kIntOneType| and |kMinBlobOrStringType| are reserved for
-    // SQLite internal usage, and should not appear in persistent databases.
-    // This shows database corruption.
-    return {ValueType::kNull, 0, kNoInlineValue, true};
-  }
-
-  // Blobs and texts take alternating numbers starting at 12.
-  encoded_type -= kMinBlobOrStringType;
-  const ValueType value_type =
-      (encoded_type & 1) == 0 ? ValueType::kBlob : ValueType::kText;
-  const int64_t value_size = encoded_type >> 1;
-  return {value_type, value_size, kNoInlineValue, false};
-}
-
-}  // namespace
-
-bool RecordReader::Initialize() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  // The size of |value_headers_| is used in DCHECKs to track whether
-  // Initialize() succeeded.
-  value_headers_.clear();
-
-  int64_t next_value_offset = InitializeHeaderBuffer();
-  if (next_value_offset == 0)
-    return false;
-
-  const uint8_t* header_pointer = header_buffer_.data();
-  const uint8_t* header_end = header_buffer_.data() + header_buffer_.size();
-
-  for (int i = 0; i < column_count_; ++i) {
-    int64_t encoded_type;
-    if (header_pointer == header_end) {
-      // SQLite versions built with SQLITE_ENABLE_NULL_TRIM don't store trailing
-      // null type IDs in the header.
-      encoded_type = kNullType;
-    } else {
-      std::tie(encoded_type, header_pointer) =
-          ParseVarint(header_pointer, header_end);
-    }
-
-    ParsedHeaderType parsed_type = ParseHeaderType(encoded_type);
-    if (parsed_type.IsInvalid()) {
-      // Parsing failed. The record is corrupted.
-      return false;
-    }
-    value_headers_.emplace_back(next_value_offset, parsed_type.size,
-                                parsed_type.type, parsed_type.inline_value,
-                                parsed_type.has_inline_value);
-
-    next_value_offset += parsed_type.size;
-  }
-
-  DCHECK_EQ(value_headers_.size(), static_cast<size_t>(column_count_));
-  return true;
-}
-
-bool RecordReader::IsInitialized() const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  return value_headers_.size() == static_cast<size_t>(column_count_) &&
-         payload_reader_->IsInitialized();
-}
-
-ValueType RecordReader::GetValueType(int column_index) const {
-  DCHECK(IsInitialized());
-  DCHECK_GE(column_index, 0);
-  DCHECK_LT(static_cast<size_t>(column_index), value_headers_.size());
-
-  return value_headers_[column_index].type;
-}
-
-namespace {
-
-// Deallocates buffers passed to sqlite3_result_{blob,text}64().
-void ValueBytesDeleter(void* buffer) {
-  DCHECK(buffer != nullptr);
-  uint8_t* value_bytes = reinterpret_cast<uint8_t*>(buffer);
-  delete[] value_bytes;
-}
-
-}  // namespace
-
-bool RecordReader::ReadValue(int column_index,
-                             sqlite3_context* receiver) const {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(IsInitialized());
-  DCHECK_GE(column_index, 0);
-  DCHECK_LT(static_cast<size_t>(column_index), value_headers_.size());
-  DCHECK(receiver != nullptr);
-
-  const ValueHeader& header = value_headers_[column_index];
-
-  const int64_t offset = header.offset;
-  const int64_t size = header.size;
-
-  if (header.type == ValueType::kNull) {
-    DCHECK_EQ(size, 0);
-    DCHECK(!header.has_inline_value);
-
-    sqlite3_result_null(receiver);
-    return true;
-  }
-
-  if (header.type == ValueType::kInteger) {
-    if (header.has_inline_value) {
-      sqlite3_result_int(receiver, header.inline_value);
-      return true;
-    }
-
-    uint8_t value_bytes[8];
-    DCHECK_GT(size, 0);
-    DCHECK_LE(size, static_cast<int64_t>(sizeof(value_bytes)));
-    // SQLite integers are big-endian, so the least significant bytes are at the
-    // end of the integer's buffer.
-    uint8_t* const first_read_byte = value_bytes + 8 - size;
-    if (!payload_reader_->ReadPayload(offset, size, first_read_byte))
-      return false;
-
-    // Sign-extend the number.
-    const uint8_t sign_byte = (*first_read_byte & 0x80) ? 0xff : 0;
-    for (uint8_t* sign_extended_byte = &value_bytes[0];
-         sign_extended_byte < first_read_byte; ++sign_extended_byte) {
-      *sign_extended_byte = sign_byte;
-    }
-
-    const int64_t value = LoadBigEndianInt64(value_bytes);
-    sqlite3_result_int64(receiver, value);
-    return true;
-  }
-
-  if (header.type == ValueType::kFloat) {
-    DCHECK_EQ(header.size, static_cast<int64_t>(sizeof(double)));
-    DCHECK(!header.has_inline_value);
-
-    union {
-      double fp;
-      int64_t integer;
-      uint8_t bytes[8];
-    } value;
-    static_assert(sizeof(double) == 8,
-                  "double is not the correct type to represent SQLite floats");
-    if (!payload_reader_->ReadPayload(header.offset, sizeof(double),
-                                      reinterpret_cast<uint8_t*>(&value))) {
-      return false;
-    }
-    // SQLite's doubles are big-endian.
-    value.integer = LoadBigEndianInt64(value.bytes);
-    sqlite3_result_double(receiver, value.fp);
-    return true;
-  }
-
-  if (header.type == ValueType::kBlob || header.type == ValueType::kText) {
-    DCHECK_GE(header.size, 0);
-    DCHECK(!header.has_inline_value);
-
-    uint8_t* const value_bytes = new uint8_t[size];
-    if (size > 0 && !payload_reader_->ReadPayload(offset, size, value_bytes)) {
-      delete[] value_bytes;
-      return false;
-    }
-    if (header.type == ValueType::kBlob) {
-      sqlite3_result_blob64(receiver, value_bytes, static_cast<uint64_t>(size),
-                            &ValueBytesDeleter);
-    } else {
-      DCHECK_EQ(header.type, ValueType::kText);
-
-      const unsigned char encoding = SQLITE_UTF8;
-      sqlite3_result_text64(receiver, reinterpret_cast<char*>(value_bytes),
-                            static_cast<uint64_t>(size), &ValueBytesDeleter,
-                            encoding);
-    }
-    return true;
-  }
-
-  NOTREACHED() << "Invalid value type";
-  return false;
-}
-
-void RecordReader::Reset() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  value_headers_.clear();
-}
-
-int64_t RecordReader::InitializeHeaderBuffer() {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  const uint8_t* const inline_payload_start =
-      payload_reader_->ReadInlinePayload();
-  if (inline_payload_start == nullptr) {
-    // Read failure.
-    return 0;
-  }
-
-  const int64_t inline_payload_size = payload_reader_->inline_payload_size();
-  const uint8_t* const inline_payload_end =
-      inline_payload_start + inline_payload_size;
-  int64_t header_size;
-  const uint8_t* payload_header_start;
-  std::tie(header_size, payload_header_start) =
-      ParseVarint(inline_payload_start, inline_payload_end);
-
-  if (header_size < 0 || header_size > payload_reader_->payload_size()) {
-    // The header is bigger than the entire record. This record is corrupted.
-    return 0;
-  }
-
-  int header_size_size = payload_header_start - inline_payload_start;
-  static_assert(std::numeric_limits<int>::max() > kMaxVarintSize,
-                "The |header_size_size| computation above may overflow");
-
-  // The header size varint is included in the header size computation.
-  const int64_t header_data_size = header_size - header_size_size;
-  if (header_data_size < 0) {
-    return 0;
-  }
-
-  header_buffer_.resize(header_data_size);
-  if (!payload_reader_->ReadPayload(header_size_size, header_data_size,
-                                    header_buffer_.data())) {
-    // Read failure.
-    return 0;
-  }
-
-  return header_size;
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/record.h b/sql/recover_module/record.h
deleted file mode 100644
index c039ae669..0000000
--- a/sql/recover_module/record.h
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_RECORD_H_
-#define SQL_RECOVER_MODULE_RECORD_H_
-
-#include <cstdint>
-#include <vector>
-
-#include "base/memory/raw_ptr.h"
-#include "base/sequence_checker.h"
-
-struct sqlite3_context;
-
-namespace sql {
-namespace recover {
-
-// The effective type of a column's value in a SQLite row.
-enum class ValueType {
-  kNull,
-  kInteger,
-  kFloat,
-  kText,
-  kBlob,
-};
-
-class LeafPayloadReader;
-
-// Reads records from SQLite B-trees.
-//
-// Instances are designed to be reused for reading multiple records. Instances
-// are not thread-safe.
-//
-// A record is a list of column values. SQLite uses "manifest typing", meaning
-// that values don't necessarily match the column types declared in the
-// table/index schema.
-//
-// Reading a record is started by calling Initialize(). Afterwards,
-// GetValueType() can be used to validate the types of the record's values, and
-// ReadValue() can be used to read the values into a SQLite user-defined
-// function context.
-class RecordReader {
- public:
-  struct ValueHeader {
-    explicit ValueHeader(int64_t offset,
-                         int64_t size,
-                         ValueType type,
-                         int8_t inline_value,
-                         bool has_inline_value)
-        : offset(offset),
-          size(size),
-          type(type),
-          inline_value(inline_value),
-          has_inline_value(has_inline_value) {}
-
-    // The position of the first byte used to encode the value, in the record.
-    int64_t offset;
-    // The number of bytes used to encode the value.
-    int64_t size;
-    // The SQLite type for the value.
-    ValueType type;
-    // The value encoded directly in the type, if |has_inline_value| is true.
-    int8_t inline_value;
-    // True if |inline_value| is defined.
-    bool has_inline_value;
-  };
-
-  // Creates an uninitialized record reader from a SQLite table B-tree.
-  //
-  // |payload_reader_| must outlive this instance, and should always point to
-  // leaf pages in the same tree. |column_count| must match the number of
-  // columns in the table's schema.
-  //
-  // The underlying table should not be modified while the record is
-  // initialized.
-  explicit RecordReader(LeafPayloadReader* payload_reader_, int column_count);
-  ~RecordReader();
-
-  RecordReader(const RecordReader&) = delete;
-  RecordReader& operator=(const RecordReader&) = delete;
-
-  // Sets up the reader for a new payload.
-  //
-  // The LeafPayloadReader passed to the constructor must be focused on the
-  // page containing the payload.
-  //
-  // This method must complete successfully before any other method on this
-  // class can be called.
-  bool Initialize();
-
-  // True if the last call to Initialize succeeded.
-  bool IsInitialized() const;
-
-  // The type of a value in the record. |column_index| is 0-based.
-  ValueType GetValueType(int column_index) const;
-
-  // Reads a value in the record into a SQLite user-defined function context.
-  //
-  // |column_index| is 0-based.
-  //
-  // The value is reported by calling a sqlite3_result_*() function on
-  // |receiver|. SQLite's result-reporting API is documented at
-  // https://www.sqlite.org/c3ref/result_blob.html
-  //
-  // Returns false if the reading value fails. This can happen if a value is
-  // stored across overflow pages, and reading one of the overflow pages results
-  // in an I/O error.
-  bool ReadValue(int column_index, sqlite3_context* receiver) const;
-
-  // Resets the reader.
-  //
-  // This method is idempotent. After it is called, IsInitialized() will return
-  // false.
-  void Reset();
-
- private:
-  // Reads the record's header into |header_buffer_|.
-  //
-  // Returns the size of the record's header, or 0 (zero) in case of failure.
-  // No valid record header has 0 bytes, because the record header includes at
-  // least one varint.
-  //
-  // On success, |header_buffer_|'s size will be set correctly.
-  int64_t InitializeHeaderBuffer();
-
-  // Stores decoded type IDs from the record's header.
-  std::vector<ValueHeader> value_headers_;
-
-  // Stores the header of the record being read.
-  //
-  // The header is only used during Initialize(). This buffer is reused across
-  // multiple Initialize() calls to reduce heap churn.
-  std::vector<uint8_t> header_buffer_;
-
-  // Brings the record's bytes from the SQLite database pages.
-  //
-  // Raw pointer usage is acceptable because this instance's owner is expected
-  // to ensure that the LeafPayloadReader outlives this.
-  const raw_ptr<LeafPayloadReader> payload_reader_;
-
-  // The number of columns in the table schema. No payload should have more than
-  // this number of columns.
-  const int column_count_;
-
-  SEQUENCE_CHECKER(sequence_checker_);
-};
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_RECORD_H_
diff --git a/sql/recover_module/table.cc b/sql/recover_module/table.cc
deleted file mode 100644
index 7e852bf2..0000000
--- a/sql/recover_module/table.cc
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "sql/recover_module/table.h"
-
-#include <optional>
-#include "base/check_op.h"
-#include "base/strings/strcat.h"
-#include "base/strings/string_piece.h"
-#include "base/strings/string_util.h"
-#include "sql/recover_module/cursor.h"
-#include "sql/recover_module/integers.h"
-#include "sql/recover_module/pager.h"
-
-namespace sql {
-namespace recover {
-
-// Returns the number of the page holding the root page of a table's B-tree.
-//
-// Returns a null optional if the operation fails in any way. The failure is
-// most likely due to an incorrect table spec (missing attachment or table).
-// Corrupted SQLite metadata can cause failures here.
-std::optional<int> GetTableRootPageId(sqlite3* sqlite_db,
-                                      const TargetTableSpec& table) {
-  if (table.table_name == "sqlite_schema") {
-    // The sqlite_schema table is always rooted at the first page.
-    // SQLite page IDs use 1-based indexing.
-    return std::optional<int64_t>(1);
-  }
-
-  std::string select_sql =
-      base::StrCat({"SELECT rootpage FROM ", table.db_name,
-                    ".sqlite_schema WHERE type='table' AND tbl_name=?"});
-  sqlite3_stmt* sqlite_statement;
-  if (sqlite3_prepare_v3(sqlite_db, select_sql.c_str(), select_sql.size() + 1,
-                         SQLITE_PREPARE_NO_VTAB, &sqlite_statement,
-                         nullptr) != SQLITE_OK) {
-    // The sqlite_schema table is missing or its schema is corrupted.
-    return std::nullopt;
-  }
-
-  if (sqlite3_bind_text(sqlite_statement, 1, table.table_name.c_str(),
-                        static_cast<int>(table.table_name.size()),
-                        SQLITE_STATIC) != SQLITE_OK) {
-    // Binding the table name failed. This shouldn't happen.
-    sqlite3_finalize(sqlite_statement);
-    return std::nullopt;
-  }
-
-  if (sqlite3_step(sqlite_statement) != SQLITE_ROW) {
-    // The database attachment point or table does not exist.
-    sqlite3_finalize(sqlite_statement);
-    return std::nullopt;
-  }
-
-  int64_t root_page = sqlite3_column_int64(sqlite_statement, 0);
-  sqlite3_finalize(sqlite_statement);
-
-  if (!DatabasePageReader::IsValidPageId(root_page)) {
-    // Database corruption.
-    return std::nullopt;
-  }
-
-  static_assert(
-      DatabasePageReader::kMaxPageId <= std::numeric_limits<int>::max(),
-      "Converting the page ID to int may overflow");
-  return std::make_optional(static_cast<int>(root_page));
-}
-
-// Returns (SQLite status, a SQLite database's page size).
-std::pair<int, int> GetDatabasePageSize(sqlite3_file* sqlite_file) {
-  // The SQLite header is documented at:
-  //   https://www.sqlite.org/fileformat.html#the_database_header
-  //
-  // Read the entire header.
-  static constexpr int kHeaderOffset = 0;
-  static constexpr int kHeaderSize = 100;
-  uint8_t header_bytes[kHeaderSize];
-  int sqlite_status = DatabasePageReader::RawRead(sqlite_file, kHeaderSize,
-                                                  kHeaderOffset, header_bytes);
-  if (sqlite_status != SQLITE_OK)
-    return {sqlite_status, 0};
-
-  // This computation uses the alternate interpretation that the page size
-  // header field is a little-endian number encoding the page size divided by
-  // 256.
-  static constexpr int kPageSizeHeaderOffset = 16;
-  const int page_size =
-      LoadBigEndianUint16(header_bytes + kPageSizeHeaderOffset);
-
-  if (!DatabasePageReader::IsValidPageSize(page_size)) {
-    // Invalid page numbers are considered irrecoverable corruption.
-    return {SQLITE_CORRUPT, 0};
-  }
-
-  // TODO(pwnall): This method needs a better name. It also checks the database
-  //               header for unsupported edge cases.
-
-  static constexpr int kReservedSizeHeaderOffset = 20;
-  const uint8_t page_reserved_size = header_bytes[kReservedSizeHeaderOffset];
-  if (page_reserved_size != 0) {
-    // Chrome does not use any extension that requires reserved page space.
-    return {SQLITE_CORRUPT, 0};
-  }
-
-  // The text encoding is stored at offset 56, as a 4-byte big-endian integer.
-  // However, the range of values is 1-3, so reading the last byte is
-  // sufficient.
-  static_assert(SQLITE_UTF8 <= std::numeric_limits<uint8_t>::max(),
-                "Text encoding field reading shortcut is invalid.");
-  static constexpr int kTextEncodingHeaderOffset = 59;
-  const uint8_t text_encoding = header_bytes[kTextEncodingHeaderOffset];
-  if (text_encoding != SQLITE_UTF8) {
-    // This extension only supports databases that use UTF-8 encoding.
-    return {SQLITE_CORRUPT, 0};
-  }
-
-  return {SQLITE_OK, page_size};
-}
-
-// static
-std::pair<int, std::unique_ptr<VirtualTable>> VirtualTable::Create(
-    sqlite3* sqlite_db,
-    TargetTableSpec backing_table_spec,
-    std::vector<RecoveredColumnSpec> column_specs) {
-  DCHECK(backing_table_spec.IsValid());
-
-  std::optional<int64_t> backing_table_root_page_id =
-      GetTableRootPageId(sqlite_db, backing_table_spec);
-  if (!backing_table_root_page_id.has_value()) {
-    // Either the backing table specification is incorrect, or the database
-    // metadata is corrupted beyond hope.
-    //
-    // This virtual table is intended to be used by Chrome features, whose code
-    // is covered by tests. Therefore, the most likely cause is metadata
-    // corruption.
-    return {SQLITE_CORRUPT, nullptr};
-  }
-
-  sqlite3_file* sqlite_file;
-  int sqlite_status =
-      sqlite3_file_control(sqlite_db, backing_table_spec.db_name.c_str(),
-                           SQLITE_FCNTL_FILE_POINTER, &sqlite_file);
-  if (sqlite_status != SQLITE_OK) {
-    // Failed to get the backing store's file. GetTableRootPage() succeeded, so
-    // the attachment point name must be correct. So, this is definitely a
-    // SQLite error, not a virtual table use error. Report the error code as-is,
-    // so it can be captured in histograms.
-    return {sqlite_status, nullptr};
-  }
-
-  int page_size;
-  std::tie(sqlite_status, page_size) = GetDatabasePageSize(sqlite_file);
-  if (sqlite_status != SQLITE_OK) {
-    // By the same reasoning as above, report the error code as-is.
-    return {sqlite_status, nullptr};
-  }
-
-  return {SQLITE_OK,
-          std::make_unique<VirtualTable>(sqlite_db, sqlite_file,
-                                         backing_table_root_page_id.value(),
-                                         page_size, std::move(column_specs))};
-}
-
-VirtualTable::VirtualTable(sqlite3* sqlite_db,
-                           sqlite3_file* sqlite_file,
-                           int root_page_id,
-                           int page_size,
-                           std::vector<RecoveredColumnSpec> column_specs)
-    : sqlite_file_(sqlite_file),
-      root_page_id_(root_page_id),
-      page_size_(page_size),
-      column_specs_(std::move(column_specs)) {
-  DCHECK(sqlite_db != nullptr);
-  DCHECK(sqlite_file != nullptr);
-  DCHECK_GT(root_page_id_, 0);
-  DCHECK(DatabasePageReader::IsValidPageSize(page_size));
-  DCHECK(!column_specs_.empty());
-}
-
-VirtualTable::~VirtualTable() {
-#if DCHECK_IS_ON()
-  DCHECK_EQ(0, open_cursor_count_.load(std::memory_order_relaxed))
-      << "SQLite forgot to xClose() an xOpen()ed cursor";
-#endif  // DCHECK_IS_ON()
-}
-
-std::string VirtualTable::ToCreateTableSql() const {
-  std::vector<std::string> column_sqls;
-  column_sqls.reserve(column_specs_.size());
-  for (const RecoveredColumnSpec& column_spec : column_specs_)
-    column_sqls.push_back(column_spec.ToCreateTableSql());
-
-  static constexpr base::StringPiece kCreateTableSqlStart("CREATE TABLE t(");
-  static constexpr base::StringPiece kCreateTableSqlEnd(")");
-  static constexpr base::StringPiece kColumnSqlSeparator(",");
-  return base::StrCat({kCreateTableSqlStart,
-                       base::JoinString(column_sqls, kColumnSqlSeparator),
-                       kCreateTableSqlEnd});
-}
-
-VirtualCursor* VirtualTable::CreateCursor() {
-#if DCHECK_IS_ON()
-  open_cursor_count_.fetch_add(1, std::memory_order_relaxed);
-#endif  // DCHECK_IS_ON()
-
-  VirtualCursor* result = new VirtualCursor(this);
-  return result;
-}
-
-void VirtualTable::WillDeleteCursor(VirtualCursor* cursor) {
-#if DCHECK_IS_ON()
-  DCHECK_GT(open_cursor_count_.load(std::memory_order_relaxed), 0);
-  open_cursor_count_.fetch_sub(1, std::memory_order_relaxed);
-#endif  // DCHECK_IS_ON()
-}
-
-}  // namespace recover
-}  // namespace sql
diff --git a/sql/recover_module/table.h b/sql/recover_module/table.h
deleted file mode 100644
index f0d729b..0000000
--- a/sql/recover_module/table.h
+++ /dev/null
@@ -1,121 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SQL_RECOVER_MODULE_TABLE_H_
-#define SQL_RECOVER_MODULE_TABLE_H_
-
-#include <atomic>
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/check_op.h"
-#include "base/memory/raw_ptr.h"
-#include "sql/recover_module/parsing.h"
-#include "third_party/sqlite/sqlite3.h"
-
-namespace sql {
-namespace recover {
-
-class VirtualCursor;
-
-// Represents a virtual table created by CREATE VIRTUAL TABLE recover(...).
-//
-// Instances are allocated on the heap using the C++ new operator, and passed to
-// SQLite via pointers to the sqlite_vtab members. SQLite is responsible for
-// managing the instances' lifetimes. SQLite will call xDisconnect() for every
-// successful xConnect(), and xDestroy() for every successful xCreate().
-//
-// Instances are thread-safe.
-class VirtualTable {
- public:
-  // Returns a SQLite status and a VirtualTable instance.
-  //
-  // The VirtualTable is non-null iff the status is SQLITE_OK.
-  //
-  // SQLite is trusted to keep |sqlite_db| alive for as long as this instance
-  // lives.
-  static std::pair<int, std::unique_ptr<VirtualTable>> Create(
-      sqlite3* sqlite_db,
-      TargetTableSpec backing_table_spec,
-      std::vector<RecoveredColumnSpec> column_specs);
-
-  // Use Create() instead of calling the constructor directly.
-  explicit VirtualTable(sqlite3* sqlite_db,
-                        sqlite3_file* sqlite_file,
-                        int root_page,
-                        int page_size,
-                        std::vector<RecoveredColumnSpec> column_specs);
-  ~VirtualTable();
-
-  VirtualTable(const VirtualTable&) = delete;
-  VirtualTable& operator=(const VirtualTable&) = delete;
-
-  // Returns the embedded SQLite virtual table.
-  //
-  // This getter is not const because SQLite wants a non-const pointer to the
-  // structure.
-  sqlite3_vtab* SqliteTable() { return &sqlite_table_; }
-
-  // Returns SQLite VFS file used to access the backing table's database.
-  //
-  // This getter is not const because it must return a non-const pointer.
-  sqlite3_file* SqliteFile() { return sqlite_file_; }
-
-  // Returns the page number of the root page for the table's B-tree.
-  int root_page_id() const { return root_page_id_; }
-
-  // Returns the page size used by the backing table's database.
-  int page_size() const { return page_size_; }
-
-  // Returns the schema of the corrupted table being recovered.
-  const std::vector<RecoveredColumnSpec> column_specs() const {
-    return column_specs_;
-  }
-
-  // Returns a SQL statement describing the virtual table's schema.
-  //
-  // The return value is suitable to be passed to sqlite3_declare_vtab().
-  std::string ToCreateTableSql() const;
-
-  // The VirtualTable instance that embeds a given SQLite virtual table.
-  //
-  // |sqlite_table| must have been returned by VirtualTable::SqliteTable().
-  static inline VirtualTable* FromSqliteTable(sqlite3_vtab* sqlite_table) {
-    static_assert(std::is_standard_layout<VirtualTable>::value,
-                  "needed for the reinterpret_cast below");
-    static_assert(offsetof(VirtualTable, sqlite_table_) == 0,
-                  "sqlite_table_ must be the first member of the class");
-    VirtualTable* const result = reinterpret_cast<VirtualTable*>(sqlite_table);
-    DCHECK_EQ(sqlite_table, &result->sqlite_table_);
-    return result;
-  }
-
-  // Creates a new cursor for iterating over this table.
-  VirtualCursor* CreateCursor();
-
-  // Called right before a cursor belonging to this table will be destroyed.
-  void WillDeleteCursor(VirtualCursor* cursor);
-
- private:
-  // SQLite handle for this table. The struct is populated and used by SQLite.
-  sqlite3_vtab sqlite_table_;
-
-  // See the corresponding getters for these members' purpose.
-  const raw_ptr<sqlite3_file> sqlite_file_;
-  const int root_page_id_;
-  const int page_size_;
-  const std::vector<RecoveredColumnSpec> column_specs_;
-
-#if DCHECK_IS_ON()
-  // Number of cursors that are still opened.
-  std::atomic<int> open_cursor_count_{0};
-#endif  // DCHECK_IS_ON()
-};
-
-}  // namespace recover
-}  // namespace sql
-
-#endif  // SQL_RECOVER_MODULE_TABLE_H_
diff --git a/sql/recovery.cc b/sql/recovery.cc
index dde1b58..2e622e8 100644
--- a/sql/recovery.cc
+++ b/sql/recovery.cc
@@ -14,9 +14,6 @@
 
 #include "base/check_op.h"
 #include "base/containers/contains.h"
-#include "base/dcheck_is_on.h"
-#include "base/feature_list.h"
-#include "base/files/file_path.h"
 #include "base/format_macros.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -24,19 +21,12 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
 #include "base/strings/strcat.h"
-#include "base/strings/string_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/types/pass_key.h"
 #include "build/build_config.h"
 #include "sql/database.h"
 #include "sql/error_delegate_util.h"
 #include "sql/internal_api_token.h"
 #include "sql/meta_table.h"
-#include "sql/recover_module/module.h"
-#include "sql/sql_features.h"
 #include "sql/sqlite_result_code.h"
-#include "sql/sqlite_result_code_values.h"
-#include "sql/statement.h"
 #include "third_party/sqlite/sqlite3.h"
 
 namespace sql {
@@ -48,14 +38,9 @@
 }  // namespace
 
 // static
-bool BuiltInRecovery::IsSupported() {
-  return base::FeatureList::IsEnabled(features::kUseBuiltInRecoveryIfSupported);
-}
-
-// static
 bool BuiltInRecovery::ShouldAttemptRecovery(Database* database,
                                             int extended_error) {
-  return BuiltInRecovery::IsSupported() && database && database->is_open() &&
+  return database && database->is_open() &&
          !database->DbPath(InternalApiToken()).empty() &&
 #if BUILDFLAG(IS_FUCHSIA)
          // Recovering WAL databases is not supported on Fuchsia.
@@ -67,33 +52,15 @@
 // static
 SqliteResultCode BuiltInRecovery::RecoverDatabase(Database* database,
                                                   Strategy strategy) {
-  if (!BuiltInRecovery::IsSupported()) {
-    return SqliteResultCode::kAbort;
-  }
-
   auto recovery = BuiltInRecovery(database, strategy);
   return recovery.RecoverAndReplaceDatabase();
 }
 
 // static
-bool BuiltInRecovery::RecoverIfPossible(
-    Database* database,
-    int extended_error,
-    Strategy strategy,
-    const base::Feature* const use_builtin_recovery_if_supported_flag) {
-  // If `BuiltInRecovery` is supported at all, check the flag for this specific
-  // database, provided by the feature team.
-  bool use_builtin_recovery =
-      BuiltInRecovery::IsSupported() &&
-      (!use_builtin_recovery_if_supported_flag ||
-       base::FeatureList::IsEnabled(*use_builtin_recovery_if_supported_flag));
-
-  if (use_builtin_recovery
-          ? !BuiltInRecovery::ShouldAttemptRecovery(database, extended_error)
-          : !database || !database->is_open() ||
-                database->DbPath(InternalApiToken()).empty() ||
-                database->UseWALMode() ||
-                !Recovery::ShouldRecover(extended_error)) {
+bool BuiltInRecovery::RecoverIfPossible(Database* database,
+                                        int extended_error,
+                                        Strategy strategy) {
+  if (!ShouldAttemptRecovery(database, extended_error)) {
     return false;
   }
 
@@ -102,23 +69,9 @@
   // re-entry.
   database->reset_error_callback();
 
-  if (use_builtin_recovery) {
-    CHECK(BuiltInRecovery::IsSupported());
-    auto result = BuiltInRecovery::RecoverDatabase(database, strategy);
-    if (!IsSqliteSuccessCode(result)) {
-      DLOG(ERROR) << "Database recovery failed with result code: " << result;
-    }
-  } else {
-    switch (strategy) {
-      case BuiltInRecovery::Strategy::kRecoverOrRaze:
-        Recovery::RecoverDatabase(database,
-                                  database->DbPath(InternalApiToken()));
-        break;
-      case BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze:
-        Recovery::RecoverDatabaseWithMetaVersion(
-            database, database->DbPath(InternalApiToken()));
-        break;
-    }
+  auto result = BuiltInRecovery::RecoverDatabase(database, strategy);
+  if (!IsSqliteSuccessCode(result)) {
+    DLOG(ERROR) << "Database recovery failed with result code: " << result;
   }
 
   return true;
@@ -131,7 +84,6 @@
           .page_size = database ? database->page_size() : 0,
           .cache_size = 0,
       }) {
-  CHECK(IsSupported());
   CHECK(db_);
   CHECK(db_->is_open());
   // Recovery is likely to be used in error handling. To prevent re-entry due to
@@ -419,608 +371,4 @@
   return true;
 }
 
-// static
-std::unique_ptr<Recovery> Recovery::Begin(Database* database,
-                                          const base::FilePath& db_path) {
-  // Recovery is likely to be initiated in an error handler. Since recovery
-  // changes the state of the handle, protect against multiple layers attempting
-  // the same recovery.
-  if (!database->is_open()) {
-    // Warn about API mis-use.
-    DCHECK(database->poisoned(InternalApiToken()))
-        << "Illegal to recover with closed Database";
-    return nullptr;
-  }
-
-  // Using `new` to access a non-public constructor
-  std::unique_ptr<Recovery> recovery(new Recovery(database));
-  if (!recovery->Init(db_path)) {
-    // TODO(shess): Should Init() failure result in Raze()?
-    recovery->Shutdown(POISON);
-    return nullptr;
-  }
-
-  return recovery;
-}
-
-// static
-bool Recovery::Recovered(std::unique_ptr<Recovery> r) {
-  return r->Backup();
-}
-
-// static
-void Recovery::Unrecoverable(std::unique_ptr<Recovery> r) {
-  CHECK(r->db_);
-  // ~Recovery() will RAZE_AND_POISON.
-}
-
-// static
-void Recovery::Rollback(std::unique_ptr<Recovery> r) {
-  // TODO(shess): Crash / crash and dump?
-  r->Shutdown(POISON);
-}
-
-Recovery::Recovery(Database* connection)
-    : db_(connection),
-      recover_db_({
-          .exclusive_locking = false,
-          .page_size = db_->page_size(),
-          // The interface to the recovery module is a virtual table.
-          .enable_virtual_tables_discouraged = true,
-      }) {
-  // Files with I/O errors cannot be safely memory-mapped.
-  recover_db_.set_mmap_disabled();
-
-  // TODO(shess): This may not handle cases where the default page
-  // size is used, but the default has changed.  I do not think this
-  // has ever happened.  This could be handled by using "PRAGMA
-  // page_size", at the cost of potential additional failure cases.
-}
-
-Recovery::~Recovery() {
-  Shutdown(RAZE_AND_POISON);
-}
-
-bool Recovery::Init(const base::FilePath& db_path) {
-#if DCHECK_IS_ON()
-  // set_error_callback() will DCHECK if the database already has an error
-  // callback. The recovery process is likely to result in SQLite errors, and
-  // those shouldn't get surfaced to any callback.
-  db_->set_error_callback(base::DoNothing());
-
-  // Undo the set_error_callback() above. We only used it for its DCHECK
-  // behavior.
-  db_->reset_error_callback();
-#endif  // DCHECK_IS_ON()
-
-  // Break any outstanding transactions on the original database to
-  // prevent deadlocks reading through the attached version.
-  // TODO(shess): A client may legitimately wish to recover from
-  // within the transaction context, because it would potentially
-  // preserve any in-flight changes.  Unfortunately, any attach-based
-  // system could not handle that.  A system which manually queried
-  // one database and stored to the other possibly could, but would be
-  // more complicated.
-  db_->RollbackAllTransactions();
-
-  // Disable exclusive locking mode so that the attached database can
-  // access things.  The locking_mode change is not active until the
-  // next database access, so immediately force an access.  Enabling
-  // writable_schema allows processing through certain kinds of
-  // corruption.
-  // TODO(shess): It would be better to just close the handle, but it
-  // is necessary for the final backup which rewrites things.  It
-  // might be reasonable to close then re-open the handle.
-  std::ignore = db_->Execute("PRAGMA writable_schema=1");
-  std::ignore = db_->Execute("PRAGMA locking_mode=NORMAL");
-  std::ignore = db_->Execute("SELECT COUNT(*) FROM sqlite_schema");
-
-  // TODO(shess): If this is a common failure case, it might be
-  // possible to fall back to a memory database.  But it probably
-  // implies that the SQLite tmpdir logic is busted, which could cause
-  // a variety of other random issues in our code.
-  if (!recover_db_.OpenTemporary(base::PassKey<Recovery>()))
-    return false;
-
-  // Enable the recover virtual table for this connection.
-  int rc = EnableRecoveryExtension(&recover_db_, InternalApiToken());
-  if (rc != SQLITE_OK) {
-    LOG(ERROR) << "Failed to initialize recover module: "
-               << recover_db_.GetErrorMessage();
-    return false;
-  }
-
-  // Turn on |SQLITE_RecoveryMode| for the handle, which allows
-  // reading certain broken databases.
-  if (!recover_db_.Execute("PRAGMA writable_schema=1"))
-    return false;
-
-  if (!recover_db_.AttachDatabase(db_path, "corrupt", InternalApiToken()))
-    return false;
-
-  return true;
-}
-
-bool Recovery::Backup() {
-  CHECK(db_);
-  CHECK(recover_db_.is_open());
-
-  // TODO(shess): Some of the failure cases here may need further
-  // exploration.  Just as elsewhere, persistent problems probably
-  // need to be razed, while anything which might succeed on a future
-  // run probably should be allowed to try.  But since Raze() uses the
-  // same approach, even that wouldn't work when this code fails.
-  //
-  // The documentation for the backup system indicate a relatively
-  // small number of errors are expected:
-  // SQLITE_BUSY - cannot lock the destination database.  This should
-  //               only happen if someone has another handle to the
-  //               database, Chromium generally doesn't do that.
-  // SQLITE_LOCKED - someone locked the source database.  Should be
-  //                 impossible (perhaps anti-virus could?).
-  // SQLITE_READONLY - destination is read-only.
-  // SQLITE_IOERR - since source database is temporary, probably
-  //                indicates that the destination contains blocks
-  //                throwing errors, or gross filesystem errors.
-  // SQLITE_NOMEM - out of memory, should be transient.
-  //
-  // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered
-  // transient, with SQLITE_LOCKED being unclear.
-  //
-  // SQLITE_READONLY and SQLITE_IOERR are probably persistent, with a
-  // strong chance that Raze() would not resolve them.  If Delete()
-  // deletes the database file, the code could then re-open the file
-  // and attempt the backup again.
-  //
-  // For now, this code attempts a best effort.
-
-  // Backup the original db from the recovered db.
-  sqlite3_backup* backup = sqlite3_backup_init(
-      db_->db(InternalApiToken()), kMainDatabaseName,
-      recover_db_.db(InternalApiToken()), kMainDatabaseName);
-  if (!backup) {
-    // Error code is in the destination database handle.
-    LOG(ERROR) << "sqlite3_backup_init() failed: "
-               << sqlite3_errmsg(db_->db(InternalApiToken()));
-
-    return false;
-  }
-
-  // -1 backs up the entire database.
-  int rc = sqlite3_backup_step(backup, -1);
-  int pages = sqlite3_backup_pagecount(backup);
-  // TODO(shess): sqlite3_backup_finish() appears to allow returning a
-  // different value from sqlite3_backup_step().  Circle back and
-  // figure out if that can usefully inform the decision of whether to
-  // retry or not.
-  sqlite3_backup_finish(backup);
-  DCHECK_GT(pages, 0);
-
-  if (rc != SQLITE_DONE) {
-    LOG(ERROR) << "sqlite3_backup_step() failed: "
-               << sqlite3_errmsg(db_->db(InternalApiToken()));
-  }
-
-  // The destination database was locked.  Give up, but leave the data
-  // in place.  Maybe it won't be locked next time.
-  if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
-    Shutdown(POISON);
-    return false;
-  }
-
-  // Running out of memory should be transient, retry later.
-  if (rc == SQLITE_NOMEM) {
-    Shutdown(POISON);
-    return false;
-  }
-
-  // TODO(shess): For now, leave the original database alone. Some errors should
-  // probably route to RAZE_AND_POISON.
-  if (rc != SQLITE_DONE) {
-    Shutdown(POISON);
-    return false;
-  }
-
-  // Clean up the recovery db, and terminate the main database
-  // connection.
-  Shutdown(POISON);
-  return true;
-}
-
-void Recovery::Shutdown(Recovery::Disposition raze) {
-  if (!db_)
-    return;
-
-  recover_db_.Close();
-  if (raze == RAZE_AND_POISON) {
-    db_->RazeAndPoison();
-  } else if (raze == POISON) {
-    db_->Poison();
-  }
-  db_ = nullptr;
-}
-
-bool Recovery::AutoRecoverTable(const char* table_name,
-                                size_t* rows_recovered) {
-  // Query the info for the recovered table in database [main].
-  std::string query(
-      base::StringPrintf("PRAGMA main.table_info(%s)", table_name));
-  Statement s(db()->GetUniqueStatement(query.c_str()));
-
-  // The columns of the recover virtual table.
-  std::vector<std::string> create_column_decls;
-
-  // The columns to select from the recover virtual table when copying
-  // to the recovered table.
-  std::vector<std::string> insert_columns;
-
-  // If PRIMARY KEY is a single INTEGER column, then it is an alias
-  // for ROWID.  The primary key can be compound, so this can only be
-  // determined after processing all column data and tracking what is
-  // seen.  |pk_column_count| counts the columns in the primary key.
-  // |rowid_decl| stores the ROWID version of the last INTEGER column
-  // seen, which is at |rowid_ofs| in |create_column_decls|.
-  size_t pk_column_count = 0;
-  size_t rowid_ofs = 0;    // Only valid if rowid_decl is set.
-  std::string rowid_decl;  // ROWID version of column |rowid_ofs|.
-
-  while (s.Step()) {
-    const std::string column_name(s.ColumnString(1));
-    const std::string column_type(s.ColumnString(2));
-    const ColumnType default_type = s.GetColumnType(4);
-    const bool default_is_null = (default_type == ColumnType::kNull);
-    const int pk_column = s.ColumnInt(5);
-
-    // http://www.sqlite.org/pragma.html#pragma_table_info documents column 5 as
-    // the 1-based index of the column in the primary key, otherwise 0.
-    if (pk_column > 0)
-      ++pk_column_count;
-
-    // Construct column declaration as "name type [optional constraint]".
-    std::string column_decl = column_name;
-
-    // SQLite's affinity detection is documented at:
-    // http://www.sqlite.org/datatype3.html#affname
-    // The gist of it is that CHAR, TEXT, and INT use substring matches.
-    // TODO(shess): It would be nice to unit test the type handling,
-    // but it is not obvious to me how to write a test which would
-    // fail appropriately when something was broken.  It would have to
-    // somehow use data which would allow detecting the various type
-    // coercions which happen.  If STRICT could be enabled, type
-    // mismatches could be detected by which rows are filtered.
-    if (base::Contains(column_type, "INT")) {
-      if (pk_column == 1) {
-        rowid_ofs = create_column_decls.size();
-        rowid_decl = column_name + " ROWID";
-      }
-      column_decl += " INTEGER";
-    } else if (base::Contains(column_type, "CHAR") ||
-               base::Contains(column_type, "TEXT")) {
-      column_decl += " TEXT";
-    } else if (column_type == "BLOB") {
-      column_decl += " BLOB";
-    } else if (base::Contains(column_type, "DOUB")) {
-      column_decl += " FLOAT";
-    } else {
-      // TODO(shess): AFAICT, there remain:
-      // - contains("CLOB") -> TEXT
-      // - contains("REAL") -> FLOAT
-      // - contains("FLOA") -> FLOAT
-      // - other -> "NUMERIC"
-      // Just code those in as they come up.
-      NOTREACHED() << " Unsupported type " << column_type;
-      return false;
-    }
-
-    create_column_decls.push_back(column_decl);
-
-    // Per the NOTE in the header file, convert NULL values to the
-    // DEFAULT.  All columns could be IFNULL(column_name,default), but
-    // the NULL case would require special handling either way.
-    if (default_is_null) {
-      insert_columns.push_back(column_name);
-    } else {
-      // The default value appears to be pre-quoted, as if it is
-      // literally from the sqlite_schema CREATE statement.
-      std::string default_value = s.ColumnString(4);
-      insert_columns.push_back(base::StringPrintf(
-          "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str()));
-    }
-  }
-
-  // Receiving no column information implies that the table doesn't exist.
-  if (create_column_decls.empty()) {
-    return false;
-  }
-
-  // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID.
-  if (pk_column_count == 1 && !rowid_decl.empty())
-    create_column_decls[rowid_ofs] = rowid_decl;
-
-  std::string recover_create(base::StringPrintf(
-      "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)",
-      table_name, table_name,
-      base::JoinString(create_column_decls, ",").c_str()));
-
-  // INSERT OR IGNORE means that it will drop rows resulting from constraint
-  // violations.  INSERT OR REPLACE only handles UNIQUE constraint violations.
-  std::string recover_insert(base::StringPrintf(
-      "INSERT OR IGNORE INTO main.%s SELECT %s FROM temp.recover_%s",
-      table_name, base::JoinString(insert_columns, ",").c_str(), table_name));
-
-  std::string recover_drop(
-      base::StringPrintf("DROP TABLE temp.recover_%s", table_name));
-
-  if (!db()->Execute(recover_create.c_str()))
-    return false;
-
-  if (!db()->Execute(recover_insert.c_str())) {
-    std::ignore = db()->Execute(recover_drop.c_str());
-    return false;
-  }
-
-  *rows_recovered = db()->GetLastChangeCount();
-
-  // TODO(shess): Is leaving the recover table around a breaker?
-  return db()->Execute(recover_drop.c_str());
-}
-
-bool Recovery::SetupMeta() {
-  // clang-format off
-  static const char kCreateSql[] =
-      "CREATE VIRTUAL TABLE temp.recover_meta USING recover("
-         "corrupt.meta,"
-         "key TEXT NOT NULL,"
-         "value ANY"  // Whatever is stored.
-      ")";
-  // clang-format on
-  return db()->Execute(kCreateSql);
-}
-
-bool Recovery::GetMetaVersionNumber(int* version) {
-  DCHECK(version);
-  // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta"));
-  // Unfortunately, DoesTableExist() queries sqlite_schema, not
-  // sqlite_temp_master.
-
-  static const char kVersionSql[] =
-      "SELECT value FROM temp.recover_meta WHERE key = 'version'";
-  sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql));
-  if (!recovery_version.Step())
-    return false;
-
-  *version = recovery_version.ColumnInt(0);
-  return true;
-}
-
-namespace {
-
-// Collect statements from [corrupt.sqlite_schema.sql] which start with |prefix|
-// (which should be a valid SQL string ending with the space before a table
-// name), then apply the statements to [main].  Skip any table named
-// 'sqlite_sequence', as that table is created on demand by SQLite if any tables
-// use AUTOINCREMENT.
-//
-// Returns |true| if all of the matching items were created in the main
-// database.  Returns |false| if an item fails on creation, or if the corrupt
-// database schema cannot be queried.
-bool SchemaCopyHelper(Database* db, const char* prefix) {
-  const size_t prefix_len = strlen(prefix);
-  DCHECK_EQ(' ', prefix[prefix_len - 1]);
-
-  sql::Statement s(
-      db->GetUniqueStatement("SELECT DISTINCT sql FROM corrupt.sqlite_schema "
-                             "WHERE name<>'sqlite_sequence'"));
-  while (s.Step()) {
-    std::string sql = s.ColumnString(0);
-
-    // Skip statements that don't start with |prefix|.
-    if (sql.compare(0, prefix_len, prefix) != 0)
-      continue;
-
-    sql.insert(prefix_len, "main.");
-    if (!db->Execute(sql.c_str()))
-      return false;
-  }
-  return s.Succeeded();
-}
-
-}  // namespace
-
-// This method is derived from SQLite's vacuum.c.  VACUUM operates very
-// similarily, creating a new database, populating the schema, then copying the
-// data.
-//
-// TODO(shess): This conservatively uses Rollback() rather than Unrecoverable().
-// With Rollback(), it is expected that the database will continue to generate
-// errors. Change the failure cases to Unrecoverable().
-//
-// static
-std::unique_ptr<Recovery> Recovery::BeginRecoverDatabase(
-    Database* db,
-    const base::FilePath& db_path) {
-  std::unique_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
-  if (!recovery) {
-    // Close the underlying sqlite* handle.  Windows does not allow deleting
-    // open files, and all platforms block opening a second sqlite3* handle
-    // against a database when exclusive locking is set.
-    db->Poison();
-
-    // When this code was written, histograms showed that most failures happened
-    // while attaching a corrupt database. In this case, a large proportion of
-    // attachment failures were SQLITE_NOTADB.
-    //
-    // We currently only delete the database in that specific failure case.
-    {
-      Database probe_db;
-      if (!probe_db.OpenInMemory() ||
-          probe_db.AttachDatabase(db_path, "corrupt", InternalApiToken()) ||
-          probe_db.GetErrorCode() != SQLITE_NOTADB) {
-        return nullptr;
-      }
-    }
-
-    // The database has invalid data in the SQLite header, so it is almost
-    // certainly not recoverable without manual intervention (and likely not
-    // recoverable _with_ manual intervention).  Clear away the broken database.
-    if (!sql::Database::Delete(db_path))
-      return nullptr;
-
-    // Windows deletion is complicated by file scanners and malware - sometimes
-    // Delete() appears to succeed, even though the file remains.  The following
-    // attempts to track if this happens often enough to cause concern.
-    {
-      Database probe_db;
-      if (!probe_db.Open(db_path))
-        return nullptr;
-
-      if (!probe_db.Execute("PRAGMA auto_vacuum"))
-        return nullptr;
-    }
-
-    // The rest of the recovery code could be run on the re-opened database, but
-    // the database is empty, so there would be no point.
-    return nullptr;
-  }
-
-#if DCHECK_IS_ON()
-  // This code silently fails to recover fts3 virtual tables.  At this time no
-  // browser database contain fts3 tables.  Just to be safe, complain loudly if
-  // the database contains virtual tables.
-  //
-  // fts3 has an [x_segdir] table containing a column [end_block INTEGER].  But
-  // it actually stores either an integer or a text containing a pair of
-  // integers separated by a space.  AutoRecoverTable() trusts the INTEGER tag
-  // when setting up the recover vtable, so those rows get dropped.  Setting
-  // that column to ANY may work.
-  if (db->is_open()) {
-    sql::Statement s(db->GetUniqueStatement(
-        "SELECT 1 FROM sqlite_schema WHERE sql LIKE 'CREATE VIRTUAL TABLE %'"));
-    DCHECK(!s.Step()) << "Recovery of virtual tables not supported";
-  }
-#endif
-
-  // TODO(shess): vacuum.c turns off checks and foreign keys.
-
-  // TODO(shess): vacuum.c turns synchronous=OFF for the target.  I do not fully
-  // understand this, as the temporary db should not have a journal file at all.
-  // Perhaps it does in case of cache spill?
-
-  // Copy table schema from [corrupt] to [main].
-  if (!SchemaCopyHelper(recovery->db(), "CREATE TABLE ") ||
-      !SchemaCopyHelper(recovery->db(), "CREATE INDEX ") ||
-      !SchemaCopyHelper(recovery->db(), "CREATE UNIQUE INDEX ")) {
-    // No RecordRecoveryEvent() here because SchemaCopyHelper() already did.
-    Recovery::Rollback(std::move(recovery));
-    return nullptr;
-  }
-
-  // Run auto-recover against each table, skipping the sequence table.  This is
-  // necessary because table recovery can create the sequence table as a side
-  // effect, so recovering that table inline could lead to duplicate data.
-  {
-    sql::Statement s(recovery->db()->GetUniqueStatement(
-        "SELECT name FROM sqlite_schema WHERE sql LIKE 'CREATE TABLE %' "
-        "AND name!='sqlite_sequence'"));
-    while (s.Step()) {
-      const std::string name = s.ColumnString(0);
-      size_t rows_recovered;
-      if (!recovery->AutoRecoverTable(name.c_str(), &rows_recovered)) {
-        Recovery::Rollback(std::move(recovery));
-        return nullptr;
-      }
-    }
-    if (!s.Succeeded()) {
-      Recovery::Rollback(std::move(recovery));
-      return nullptr;
-    }
-  }
-
-  // Overwrite any sequences created.
-  if (recovery->db()->DoesTableExist("corrupt.sqlite_sequence")) {
-    std::ignore = recovery->db()->Execute("DELETE FROM main.sqlite_sequence");
-    size_t rows_recovered;
-    if (!recovery->AutoRecoverTable("sqlite_sequence", &rows_recovered)) {
-      Recovery::Rollback(std::move(recovery));
-      return nullptr;
-    }
-  }
-
-  // Copy triggers and views directly to sqlite_schema.  Any tables they refer
-  // to should already exist.
-  static const char kCreateMetaItemsSql[] =
-      "INSERT INTO main.sqlite_schema "
-      "SELECT type, name, tbl_name, rootpage, sql "
-      "FROM corrupt.sqlite_schema WHERE type='view' OR type='trigger'";
-  if (!recovery->db()->Execute(kCreateMetaItemsSql)) {
-    Recovery::Rollback(std::move(recovery));
-    return nullptr;
-  }
-
-  return recovery;
-}
-
-void Recovery::RecoverDatabase(Database* db, const base::FilePath& db_path) {
-  std::unique_ptr<sql::Recovery> recovery = BeginRecoverDatabase(db, db_path);
-
-  if (recovery)
-    std::ignore = Recovery::Recovered(std::move(recovery));
-}
-
-void Recovery::RecoverDatabaseWithMetaVersion(Database* db,
-                                              const base::FilePath& db_path) {
-  std::unique_ptr<sql::Recovery> recovery = BeginRecoverDatabase(db, db_path);
-  if (!recovery)
-    return;
-
-  int version = 0;
-  if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) {
-    sql::Recovery::Unrecoverable(std::move(recovery));
-    return;
-  }
-
-  std::ignore = Recovery::Recovered(std::move(recovery));
-}
-
-// static
-bool Recovery::ShouldRecover(int extended_error) {
-  // Trim extended error codes.
-  int error = extended_error & 0xFF;
-  switch (error) {
-    case SQLITE_NOTADB:
-      // SQLITE_NOTADB happens if the SQLite header is broken.  Some earlier
-      // versions of SQLite return this where other versions return
-      // SQLITE_CORRUPT, which is a recoverable case.  Later versions only
-      // return this error only in unrecoverable cases, in which case recovery
-      // will fail with no changes to the database, so there's no harm in
-      // attempting recovery in this case.
-      return true;
-
-    case SQLITE_CORRUPT:
-      // SQLITE_CORRUPT generally means that the database is readable as a
-      // SQLite database, but some inconsistency has been detected by SQLite.
-      // In many cases the inconsistency is relatively trivial, such as if an
-      // index refers to a row which was deleted, in which case most or even all
-      // of the data can be recovered.  This can also be reported if parts of
-      // the file have been overwritten with garbage data, in which recovery
-      // should be able to recover partial data.
-      return true;
-
-      // TODO(shess): Possible future options for automated fixing:
-      // - SQLITE_CANTOPEN - delete the broken symlink or directory.
-      // - SQLITE_PERM - permissions could be fixed.
-      // - SQLITE_READONLY - permissions could be fixed.
-      // - SQLITE_IOERR - rewrite using new blocks.
-      // - SQLITE_FULL - recover in memory and rewrite subset of data.
-
-    default:
-      return false;
-  }
-}
-
-// static
-int Recovery::EnableRecoveryExtension(Database* db, InternalApiToken) {
-  return sql::recover::RegisterRecoverExtension(db->db(InternalApiToken()));
-}
-
 }  // namespace sql
diff --git a/sql/recovery.h b/sql/recovery.h
index 93fedc7..ea00217 100644
--- a/sql/recovery.h
+++ b/sql/recovery.h
@@ -16,7 +16,6 @@
 #include "sql/sqlite_result_code_values.h"
 
 namespace base {
-struct Feature;
 class FilePath;
 }
 
@@ -92,8 +91,6 @@
     kMaxValue = kFailedBackupRun,
   };
 
-  [[nodiscard]] static bool IsSupported();
-
   // Returns true if `RecoverDatabase()` can plausibly fix `database` given this
   // `extended_error`. This does not guarantee that `RecoverDatabase()` will
   // successfully recover the database.
@@ -132,38 +129,26 @@
   [[nodiscard]] static SqliteResultCode RecoverDatabase(Database* database,
                                                         Strategy strategy);
 
-  // Similar to `RecoverDatabase()` above, but with a few key differences:
-  //   - Uses `BuiltInRecovery` or the legacy `Recovery` to recover the
-  //     database, as appropriate. This method facilitates the migration to the
-  //     newer recovery module with minimal impact on feature teams. The
-  //     expectation is that `Recovery` will eventually be removed entirely.
-  //     See https://crbug.com/1385500.
+  // Similar to `RecoverDatabase()` above, but with a couple key differences:
   //   - Can be called without first checking `ShouldAttemptRecovery()`.
   //   - `database`'s error callback will be reset if recovery is attempted.
   //   - Must only be called from within a database error callback.
-  //   - Includes the option to pass a per-database feature flag indicating
-  //     whether `BuiltInRecovery` should be used to recover this database, if
-  //     it's supported.
   //
   // Recommended usage from within a database error callback:
   //
   //  // Attempt to recover the database, if recovery is possible.
   //  if (sql::BuiltInRecovery::RecoverIfPossible(
   //          &db, extended_error,
-  //          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze,
-  //          &features::kMyFeatureTeamShouldUseBuiltInRecoveryIfSupported)) {
+  //          sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
   //    // Recovery was attempted. The database handle has been poisoned and the
   //    // error callback has been reset.
   //
   //    // ...
   //  }
   //
-  [[nodiscard]] static bool RecoverIfPossible(
-      Database* database,
-      int extended_error,
-      Strategy strategy,
-      const base::Feature* const use_builtin_recovery_if_supported_flag =
-          nullptr);
+  [[nodiscard]] static bool RecoverIfPossible(Database* database,
+                                              int extended_error,
+                                              Strategy strategy);
 
   BuiltInRecovery(const BuiltInRecovery&) = delete;
   BuiltInRecovery& operator=(const BuiltInRecovery&) = delete;
@@ -205,194 +190,6 @@
   base::FilePath recovery_database_path_;
 };
 
-// WARNING: This class is being deprecated. Please use `BuiltInRecovery` for new
-// databases. See https://crbug.com/1385500.
-//
-// Recovery module for sql/.  The basic idea is to create a fresh database and
-// populate it with the recovered contents of the original database.  If
-// recovery is successful, the recovered database is backed up over the original
-// database.  If recovery is not successful, the original database is razed.  In
-// either case, the original handle is poisoned so that operations on the stack
-// do not accidentally disrupt the restored data.
-//
-// RecoverDatabase() automates this, including recoverying the schema of from
-// the suspect database.  If a database requires special handling, such as
-// recovering between different schema, or tables requiring post-processing,
-// then the module can be used manually like:
-//
-// {
-//   std::unique_ptr<sql::Recovery> r =
-//       sql::Recovery::Begin(orig_db, orig_db_path);
-//   if (r) {
-//     // Create the schema to recover to.  On failure, clear the
-//     // database.
-//     if (!r.db()->Execute(kCreateSchemaSql)) {
-//       sql::Recovery::Unrecoverable(std::move(r));
-//       return;
-//     }
-//
-//     // Recover data in "mytable".
-//     size_t rows_recovered = 0;
-//     if (!r.AutoRecoverTable("mytable", 0, &rows_recovered)) {
-//       sql::Recovery::Unrecoverable(std::move(r));
-//       return;
-//     }
-//
-//     // Manually cleanup additional constraints.
-//     if (!r.db()->Execute(kCleanupSql)) {
-//       sql::Recovery::Unrecoverable(std::move(r));
-//       return;
-//     }
-//
-//     // Commit the recovered data to the original database file.
-//     sql::Recovery::Recovered(std::move(r));
-//   }
-// }
-//
-// If Recovered() is not called, then RazeAndPoison() is called on
-// orig_db.
-class COMPONENT_EXPORT(SQL) Recovery {
- public:
-  Recovery(const Recovery&) = delete;
-  Recovery& operator=(const Recovery&) = delete;
-  ~Recovery();
-
-  // Begin the recovery process by opening a temporary database handle
-  // and attach the existing database to it at "corrupt".  To prevent
-  // deadlock, all transactions on |database| are rolled back.
-  //
-  // Returns nullptr in case of failure, with no cleanup done on the
-  // original database (except for breaking the transactions).  The
-  // caller should Raze() or otherwise cleanup as appropriate.
-  //
-  // TODO(shess): Later versions of SQLite allow extracting the path
-  // from the database.
-  // TODO(shess): Allow specifying the connection point?
-  [[nodiscard]] static std::unique_ptr<Recovery> Begin(
-      Database* database,
-      const base::FilePath& db_path);
-
-  // Mark recovery completed by replicating the recovery database over
-  // the original database, then closing the recovery database.  The
-  // original database handle is poisoned, causing future calls
-  // against it to fail.
-  //
-  // If Recovered() is not called, the destructor will call
-  // Unrecoverable().
-  //
-  // TODO(shess): At this time, this function can fail while leaving
-  // the original database intact.  Figure out which failure cases
-  // should go to RazeAndPoison() instead.
-  [[nodiscard]] static bool Recovered(std::unique_ptr<Recovery> r);
-
-  // Indicate that the database is unrecoverable.  The original
-  // database is razed, and the handle poisoned.
-  static void Unrecoverable(std::unique_ptr<Recovery> r);
-
-  // When initially developing recovery code, sometimes the possible
-  // database states are not well-understood without further
-  // diagnostics.  Abandon recovery but do not raze the original
-  // database.
-  // NOTE(shess): Only call this when adding recovery support.  In the
-  // steady state, all databases should progress to recovered or razed.
-  static void Rollback(std::unique_ptr<Recovery> r);
-
-  // Handle to the temporary recovery database.
-  sql::Database* db() { return &recover_db_; }
-
-  // Attempt to recover the named table from the corrupt database into
-  // the recovery database using a temporary recover virtual table.
-  // The virtual table schema is derived from the named table's schema
-  // in database [main].  Data is copied using INSERT OR IGNORE, so
-  // duplicates are dropped.
-  //
-  // If the source table has fewer columns than the target, the target
-  // DEFAULT value will be used for those columns.
-  //
-  // Returns true if all operations succeeded, with the number of rows
-  // recovered in |*rows_recovered|.
-  //
-  // NOTE(shess): Due to a flaw in the recovery virtual table, at this
-  // time this code injects the DEFAULT value of the target table in
-  // locations where the recovery table returns nullptr.  This is not
-  // entirely correct, because it happens both when there is a short
-  // row (correct) but also where there is an actual NULL value
-  // (incorrect).
-  //
-  // TODO(shess): Flag for INSERT OR REPLACE vs IGNORE.
-  // TODO(shess): Handle extended table names.
-  bool AutoRecoverTable(const char* table_name, size_t* rows_recovered);
-
-  // Setup a recover virtual table at temp.recover_meta, reading from
-  // corrupt.meta.  Returns true if created.
-  // TODO(shess): Perhaps integrate into Begin().
-  // TODO(shess): Add helpers to fetch additional items from the meta
-  // table as needed.
-  bool SetupMeta();
-
-  // Fetch the version number from temp.recover_meta.  Returns false
-  // if the query fails, or if there is no version row.  Otherwise
-  // returns true, with the version in |*version_number|.
-  //
-  // Only valid to call after successful SetupMeta().
-  bool GetMetaVersionNumber(int* version_number);
-
-  // Attempt to recover the database by creating a new database with schema from
-  // |db|, then copying over as much data as possible.  If successful, the
-  // recovery handle is returned to allow the caller to make additional changes,
-  // such as validating constraints not expressed in the schema.
-  //
-  // In case of SQLITE_NOTADB, the database is deemed unrecoverable and deleted.
-  [[nodiscard]] static std::unique_ptr<Recovery> BeginRecoverDatabase(
-      Database* db,
-      const base::FilePath& db_path);
-
-  // Call BeginRecoverDatabase() to recover the database, then commit the
-  // changes using Recovered().  After this call, the |db| handle will be
-  // poisoned (though technically remaining open) so that future calls will
-  // return errors until the handle is re-opened.
-  static void RecoverDatabase(Database* db, const base::FilePath& db_path);
-
-  // Variant on RecoverDatabase() which requires that the database have a valid
-  // meta table with a version value.  The meta version value is used by some
-  // clients to make assertions about the database schema.  If this information
-  // cannot be determined, the database is considered unrecoverable.
-  static void RecoverDatabaseWithMetaVersion(Database* db,
-                                             const base::FilePath& db_path);
-
-  // Returns true for SQLite errors which RecoverDatabase() can plausibly fix.
-  // This does not guarantee that RecoverDatabase() will successfully recover
-  // the database.
-  static bool ShouldRecover(int extended_error);
-
-  // Enables the "recover" SQLite extension for a database connection.
-  //
-  // Returns a SQLite error code.
-  static int EnableRecoveryExtension(Database* db, InternalApiToken);
-
- private:
-  explicit Recovery(Database* database);
-
-  // Setup the recovery database handle for Begin().  Returns false in
-  // case anything failed.
-  [[nodiscard]] bool Init(const base::FilePath& db_path);
-
-  // Copy the recovered database over the original database.
-  [[nodiscard]] bool Backup();
-
-  // Close the recovery database, and poison the original handle.
-  // |raze| controls whether the original database is razed or just
-  // poisoned.
-  enum Disposition {
-    RAZE_AND_POISON,
-    POISON,
-  };
-  void Shutdown(Disposition raze);
-
-  raw_ptr<Database> db_;  // Original Database connection.
-  Database recover_db_;  // Recovery Database connection.
-};
-
 }  // namespace sql
 
 #endif  // SQL_RECOVERY_H_
diff --git a/sql/built_in_recovery_fuzzer.cc b/sql/recovery_fuzzer.cc
similarity index 100%
rename from sql/built_in_recovery_fuzzer.cc
rename to sql/recovery_fuzzer.cc
diff --git a/sql/recovery_unittest.cc b/sql/recovery_unittest.cc
index 6ba67a7..f1ec93d 100644
--- a/sql/recovery_unittest.cc
+++ b/sql/recovery_unittest.cc
@@ -57,10 +57,17 @@
   return ExecuteWithResults(db, kSql, "|", "\n");
 }
 
-// Base class for all recovery-related tests. Each subclass must initialize
-// `scoped_feature_list_`, as appropriate.
-class SqlRecoveryTestBase : public testing::Test {
+// Parameterized to test with and without WAL mode enabled.
+class SqlRecoveryTest : public testing::Test,
+                        public testing::WithParamInterface<bool> {
  public:
+  SqlRecoveryTest() : db_(DatabaseOptions{.wal_mode = ShouldEnableWal()}) {
+    scoped_feature_list_.InitWithFeatureStates(
+        {{features::kEnableWALModeByDefault, ShouldEnableWal()}});
+  }
+
+  bool ShouldEnableWal() { return GetParam(); }
+
   void SetUp() override {
     db_.set_histogram_tag("MyFeatureDatabase");
 
@@ -93,9 +100,6 @@
   }
 
  protected:
-  explicit SqlRecoveryTestBase(DatabaseOptions options)
-      : db_(std::move(options)) {}
-
   base::test::ScopedFeatureList scoped_feature_list_;
   base::ScopedTempDir temp_dir_;
   base::FilePath db_path_;
@@ -103,68 +107,14 @@
   base::HistogramTester histogram_tester_;
 };
 
-// Tests both the legacy `sql::Recovery` interface and the newer
-// `sql::BuiltInRecovery` interface, if it's supported.
-//
-// Parameters are as follows:
-//   - Is BuiltInRecovery enabled?
-//   - Is WAL mode enabled?
-class SqlRecoveryTest
-    : public SqlRecoveryTestBase,
-      public testing::WithParamInterface<std::tuple<bool, bool>> {
- public:
-  SqlRecoveryTest()
-      : SqlRecoveryTestBase(DatabaseOptions{.wal_mode = UseWalMode()}) {
-    scoped_feature_list_.InitWithFeatureStates(
-        {{features::kUseBuiltInRecoveryIfSupported, std::get<0>(GetParam())},
-         {features::kEnableWALModeByDefault, UseWalMode()}});
-  }
+#if BUILDFLAG(IS_FUCHSIA)
+// WAL + recovery is not supported on Fuchsia, so only test without WAL mode.
+INSTANTIATE_TEST_SUITE_P(All, SqlRecoveryTest, testing::Values(false));
+#else
+INSTANTIATE_TEST_SUITE_P(All, SqlRecoveryTest, testing::Bool());
+#endif
 
-  bool UseBuiltIn() {
-    return std::get<0>(GetParam()) && BuiltInRecovery::IsSupported();
-  }
-
-  bool UseWalMode() {
-    // The legacy recovery module does not support recovering WAL databases.
-    return std::get<1>(GetParam()) && BuiltInRecovery::IsSupported();
-  }
-};
-
-// Tests specific to the newer `sql::BuiltInRecovery` interface.
-//
-// Creating a new `SqlRecoveryTest` should be preferred, if possible.
-// These tests should include a comment indicating why it is not relevant to
-// the legacy `sql::Recovery` module.
-class SqlBuiltInRecoveryTest : public SqlRecoveryTestBase {
- public:
-  SqlBuiltInRecoveryTest() : SqlRecoveryTestBase(DatabaseOptions{}) {
-    scoped_feature_list_.InitAndEnableFeature(
-        features::kUseBuiltInRecoveryIfSupported);
-  }
-};
-
-// Tests specific to the legacy `sql::Recovery` interface.
-//
-// Creating a new `SqlRecoveryTest` should be preferred, if possible.
-// These tests should include a comment indicating why it is not relevant to
-// the new `sql::BuiltInRecovery` module.
-class SqlLegacyRecoveryTest : public SqlRecoveryTestBase {
- public:
-  SqlLegacyRecoveryTest()
-      // The legacy recovery module does not support recovering WAL databases.
-      : SqlRecoveryTestBase(DatabaseOptions{.wal_mode = false}) {
-    scoped_feature_list_.InitAndDisableFeature(
-        features::kUseBuiltInRecoveryIfSupported);
-
-    // TODO(https://crbug.com/1385500): All databases which use legacy recovery
-    // must either disable WAL mode manually or be migrated to the new recovery
-    // module before WAL mode may be turned on globally. This assertion is added
-    // here as a guard against accidental regression.
-    assert(!base::FeatureList::IsEnabled(features::kEnableWALModeByDefault));
-  }
-};
-
-TEST_F(SqlBuiltInRecoveryTest, ShouldAttemptRecovery) {
+TEST_P(SqlRecoveryTest, ShouldAttemptRecovery) {
   // Attempt to recover from corruption.
   ASSERT_TRUE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_CORRUPT));
 
@@ -190,212 +140,6 @@
   EXPECT_TRUE(BuiltInRecovery::ShouldAttemptRecovery(&db_, SQLITE_CORRUPT));
 }
 
-// Baseline Recovery test covering the different ways to dispose of the scoped
-// pointer received from Recovery::Begin().
-//
-// This tests behavior of the legacy corruption recovery module which is not
-// needed in the new API. Specifically, this tests what happens to the Recovery
-// object when it goes out of scope. The new API does not publicly expose such
-// an object.
-TEST_F(SqlLegacyRecoveryTest, RecoverBasic) {
-  static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
-  static const char kInsertSql[] = "INSERT INTO x VALUES ('This is a test')";
-  static const char kAltInsertSql[] =
-      "INSERT INTO x VALUES ('That was a test')";
-  ASSERT_TRUE(db_.Execute(kCreateSql));
-  ASSERT_TRUE(db_.Execute(kInsertSql));
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
-
-  // If the Recovery handle goes out of scope without being
-  // Recovered(), the database is razed.
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery.get());
-  }
-  EXPECT_FALSE(db_.is_open());
-  ASSERT_TRUE(Reopen());
-  EXPECT_TRUE(db_.is_open());
-  ASSERT_EQ("", GetSchema(&db_));
-
-  // Recreate the database.
-  ASSERT_TRUE(db_.Execute(kCreateSql));
-  ASSERT_TRUE(db_.Execute(kInsertSql));
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
-
-  // Unrecoverable() also razes.
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery.get());
-    Recovery::Unrecoverable(std::move(recovery));
-
-    // TODO(shess): Test that calls to recover.db_ start failing.
-  }
-  EXPECT_FALSE(db_.is_open());
-  ASSERT_TRUE(Reopen());
-  EXPECT_TRUE(db_.is_open());
-  ASSERT_EQ("", GetSchema(&db_));
-
-  // Attempting to recover a previously-recovered handle fails early.
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery.get());
-    recovery.reset();
-
-    recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_FALSE(recovery.get());
-  }
-  ASSERT_TRUE(Reopen());
-
-  // Recreate the database.
-  ASSERT_TRUE(db_.Execute(kCreateSql));
-  ASSERT_TRUE(db_.Execute(kInsertSql));
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
-
-  // Unrecovered table to distinguish from recovered database.
-  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c INTEGER)"));
-  ASSERT_NE("CREATE TABLE x (t TEXT)", GetSchema(&db_));
-
-  // Recovered() replaces the original with the "recovered" version.
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery.get());
-
-    // Create the new version of the table.
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-
-    // Insert different data to distinguish from original database.
-    ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
-
-    // Successfully recovered.
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
-  EXPECT_FALSE(db_.is_open());
-  ASSERT_TRUE(Reopen());
-  EXPECT_TRUE(db_.is_open());
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
-
-  const char* kXSql = "SELECT * FROM x ORDER BY 1";
-  ASSERT_EQ("That was a test", ExecuteWithResult(&db_, kXSql));
-
-  // Reset the database contents.
-  ASSERT_TRUE(db_.Execute("DELETE FROM x"));
-  ASSERT_TRUE(db_.Execute(kInsertSql));
-
-  // Rollback() discards recovery progress and leaves the database as it was.
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery.get());
-
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-    ASSERT_TRUE(recovery->db()->Execute(kAltInsertSql));
-
-    Recovery::Rollback(std::move(recovery));
-  }
-  EXPECT_FALSE(db_.is_open());
-  ASSERT_TRUE(Reopen());
-  EXPECT_TRUE(db_.is_open());
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
-
-  ASSERT_EQ("This is a test", ExecuteWithResult(&db_, kXSql));
-}
-
-// Test operation of the virtual table used by Recovery.
-//
-// This tests behavior of the legacy corruption recovery module which is not
-// needed in the new API. Specifically, this tests what happens to virtual
-// tables added to the recovery database. The new API does not manually
-// create the recovery database. Virtual table support is required to use
-// the built-in module, but many of the other tests in this file would fail
-// if virtual tables were not supported.
-TEST_F(SqlLegacyRecoveryTest, VirtualTable) {
-  static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
-  ASSERT_TRUE(db_.Execute(kCreateSql));
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('This is a test')"));
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES ('That was a test')"));
-
-  // Successfully recover the database.
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-
-    // Tables to recover original DB, now at [corrupt].
-    static const char kRecoveryCreateSql[] =
-        "CREATE VIRTUAL TABLE temp.recover_x using recover("
-        "  corrupt.x,"
-        "  t TEXT STRICT"
-        ")";
-    ASSERT_TRUE(recovery->db()->Execute(kRecoveryCreateSql));
-
-    // Re-create the original schema.
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-
-    // Copy the data from the recovery tables to the new database.
-    static const char kRecoveryCopySql[] =
-        "INSERT INTO x SELECT t FROM recover_x";
-    ASSERT_TRUE(recovery->db()->Execute(kRecoveryCopySql));
-
-    // Successfully recovered.
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
-
-  // Since the database was not corrupt, the entire schema and all
-  // data should be recovered.
-  ASSERT_TRUE(Reopen());
-  ASSERT_EQ("CREATE TABLE x (t TEXT)", GetSchema(&db_));
-
-  static const char* kXSql = "SELECT * FROM x ORDER BY 1";
-  ASSERT_EQ("That was a test\nThis is a test",
-            ExecuteWithResults(&db_, kXSql, "|", "\n"));
-}
-
-// Our corruption handling assumes that a corrupt index doesn't impact
-// SQL statements that only operate on the associated table. This test verifies
-// the assumption.
-//
-// This tests an assumption of the legacy corruption recovery module which is
-// irrelevant to the new API. Specifically, that a corrupt index doesn't
-// impact SQL statements that only operate on the associated table.
-TEST_F(SqlLegacyRecoveryTest, TableIndependentFromCorruptIndex) {
-  static const char kCreateTable[] =
-      "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL)";
-  ASSERT_TRUE(db_.Execute(kCreateTable));
-  ASSERT_TRUE(db_.Execute("CREATE UNIQUE INDEX rows_index ON rows(indexed)"));
-
-  // Populate the table with powers of two. These numbers make it easy to see if
-  // SUM() missed a row.
-  ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(1, 1)"));
-  ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(2, 2)"));
-  ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(4, 4)"));
-  ASSERT_TRUE(db_.Execute("INSERT INTO rows(indexed, unindexed) VALUES(8, 8)"));
-
-  // SQL statement that performs a table scan. SUM(unindexed) heavily nudges
-  // SQLite to use the table instead of the index.
-  static const char kUnindexedCountSql[] = "SELECT SUM(unindexed) FROM rows";
-  EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
-      << "No SQL statement should fail before corruption";
-
-  // SQL statement that performs an index scan.
-  static const char kIndexedCountSql[] =
-      "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
-  EXPECT_EQ("15", ExecuteWithResult(&db_, kIndexedCountSql))
-      << "Table scan should not fail due to corrupt index";
-
-  db_.Close();
-  ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
-  ASSERT_TRUE(Reopen());
-
-  {
-    sql::test::ScopedErrorExpecter expecter;
-    expecter.ExpectError(SQLITE_CORRUPT);
-    EXPECT_FALSE(db_.Execute(kIndexedCountSql))
-        << "Index scan on corrupt index should fail";
-    EXPECT_TRUE(expecter.SawExpectedErrors())
-        << "Index scan on corrupt index should fail";
-  }
-
-  EXPECT_EQ("15", ExecuteWithResult(&db_, kUnindexedCountSql))
-      << "Table scan should not fail due to corrupt index";
-}
-
 TEST_P(SqlRecoveryTest, RecoverCorruptIndex) {
   static const char kCreateTable[] =
       "CREATE TABLE rows(indexed INTEGER NOT NULL, unindexed INTEGER NOT NULL)";
@@ -424,28 +168,15 @@
         // Recovery::Begin() does not support a pre-existing error callback.
         db_.reset_error_callback();
 
-        if (UseBuiltIn()) {
-          EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                        &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-                    SqliteResultCode::kOk);
-          histogram_tester_.ExpectUniqueSample(
-              kRecoveryResultHistogramName, BuiltInRecovery::Result::kSuccess,
-              /*expected_bucket_count=*/1);
-          histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
-                                               SqliteLoggedResultCode::kNoError,
-                                               /*expected_bucket_count=*/1);
-          return;
-        }
-
-        std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-        ASSERT_TRUE(recovery.get());
-
-        ASSERT_TRUE(recovery->db()->Execute(kCreateTable));
-        ASSERT_TRUE(recovery->db()->Execute(kCreateIndex));
-
-        size_t rows = 0;
-        ASSERT_TRUE(recovery->AutoRecoverTable("rows", &rows));
-        ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
+        EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                      &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+                  SqliteResultCode::kOk);
+        histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
+                                             BuiltInRecovery::Result::kSuccess,
+                                             /*expected_bucket_count=*/1);
+        histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
+                                             SqliteLoggedResultCode::kNoError,
+                                             /*expected_bucket_count=*/1);
       }));
 
   // SUM(unindexed) heavily nudges SQLite to use the table instead of the index.
@@ -540,22 +271,9 @@
         // Recovery::Begin() does not support a pre-existing error callback.
         db_.reset_error_callback();
 
-        if (UseBuiltIn()) {
-          EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                        &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-                    SqliteResultCode::kOk);
-          return;
-        }
-
-        std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-        ASSERT_TRUE(recovery.get());
-
-        ASSERT_TRUE(recovery->db()->Execute(kCreateTable));
-        ASSERT_TRUE(recovery->db()->Execute(kCreateIndex));
-
-        size_t rows = 0;
-        ASSERT_TRUE(recovery->AutoRecoverTable("rows", &rows));
-        ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
+        EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                      &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+                  SqliteResultCode::kOk);
       }));
 
   // SUM(unindexed) heavily nudges SQLite to use the table instead of the index.
@@ -587,26 +305,15 @@
   }
 
   // Test expected case where everything works.
-  if (UseBuiltIn()) {
-    EXPECT_EQ(
-        BuiltInRecovery::RecoverDatabase(
-            &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
-        SqliteResultCode::kOk);
-    histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
-                                         BuiltInRecovery::Result::kSuccess,
-                                         /*expected_bucket_count=*/1);
-    histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
-                                         SqliteLoggedResultCode::kNoError,
-                                         /*expected_bucket_count=*/1);
-  } else {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    EXPECT_TRUE(recovery->SetupMeta());
-    int version = 0;
-    EXPECT_TRUE(recovery->GetMetaVersionNumber(&version));
-    EXPECT_EQ(kVersion, version);
-
-    Recovery::Rollback(std::move(recovery));
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
+            SqliteResultCode::kOk);
+  histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
+                                       BuiltInRecovery::Result::kSuccess,
+                                       /*expected_bucket_count=*/1);
+  histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
+                                       SqliteLoggedResultCode::kNoError,
+                                       /*expected_bucket_count=*/1);
 
   ASSERT_TRUE(Reopen());  // Handle was poisoned.
 
@@ -615,56 +322,31 @@
   // Test version row missing.
   EXPECT_TRUE(db_.Execute("DELETE FROM meta WHERE key = 'version'"));
 
-  if (UseBuiltIn()) {
-    EXPECT_EQ(
-        BuiltInRecovery::RecoverDatabase(
-            &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
-        SqliteResultCode::kError);
-    histogram_tester_.ExpectBucketCount(
-        kRecoveryResultHistogramName,
-        BuiltInRecovery::Result::kFailedMetaTableVersionWasInvalid,
-        /*expected_count=*/1);
-    histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
-                                         SqliteLoggedResultCode::kNoError,
-                                         /*expected_bucket_count=*/2);
-  } else {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    EXPECT_TRUE(recovery->SetupMeta());
-    int version = 0;
-    EXPECT_FALSE(recovery->GetMetaVersionNumber(&version));
-    EXPECT_EQ(0, version);
-
-    Recovery::Rollback(std::move(recovery));
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
+            SqliteResultCode::kError);
+  histogram_tester_.ExpectBucketCount(
+      kRecoveryResultHistogramName,
+      BuiltInRecovery::Result::kFailedMetaTableVersionWasInvalid,
+      /*expected_count=*/1);
+  histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
+                                       SqliteLoggedResultCode::kNoError,
+                                       /*expected_bucket_count=*/2);
   ASSERT_TRUE(Reopen());  // Handle was poisoned.
 
   // Test meta table missing.
-  if (UseBuiltIn()) {
-    ASSERT_FALSE(db_.DoesTableExist("meta"));
+  ASSERT_FALSE(db_.DoesTableExist("meta"));
 
-    EXPECT_EQ(
-        BuiltInRecovery::RecoverDatabase(
-            &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
-        SqliteResultCode::kError);
-    histogram_tester_.ExpectBucketCount(
-        kRecoveryResultHistogramName,
-        BuiltInRecovery::Result::kFailedMetaTableDoesNotExist,
-        /*expected_count=*/1);
-    histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
-                                         SqliteLoggedResultCode::kNoError,
-                                         /*expected_bucket_count=*/3);
-  } else {
-    // The table was rolled back after the recovery failure. Manually drop the
-    // table.
-    ASSERT_TRUE(db_.DoesTableExist("meta"));
-    EXPECT_TRUE(db_.Execute("DROP TABLE meta"));
-
-    sql::test::ScopedErrorExpecter expecter;
-    expecter.ExpectError(SQLITE_CORRUPT);  // From virtual table.
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    EXPECT_FALSE(recovery->SetupMeta());
-    ASSERT_TRUE(expecter.SawExpectedErrors());
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
+            SqliteResultCode::kError);
+  histogram_tester_.ExpectBucketCount(
+      kRecoveryResultHistogramName,
+      BuiltInRecovery::Result::kFailedMetaTableDoesNotExist,
+      /*expected_count=*/1);
+  histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
+                                       SqliteLoggedResultCode::kNoError,
+                                       /*expected_bucket_count=*/3);
 }
 
 // Baseline AutoRecoverTable() test.
@@ -681,35 +363,9 @@
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
   const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
 
-  if (UseBuiltIn()) {
-    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-              SqliteResultCode::kOk);
-  } else {
-    // Create a lame-duck table which will not be propagated by recovery to
-    // detect that the recovery code actually ran.
-    ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
-    ASSERT_NE(orig_schema, GetSchema(&db_));
-
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-
-    // Save a copy of the temp db's schema before recovering the table.
-    static const char kTempSchemaSql[] =
-        "SELECT name, sql FROM sqlite_temp_schema";
-    const std::string temp_schema(
-        ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
-
-    size_t rows = 0;
-    EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
-    EXPECT_EQ(2u, rows);
-
-    // Test that any additional temp tables were cleaned up.
-    EXPECT_EQ(temp_schema,
-              ExecuteWithResults(recovery->db(), kTempSchemaSql, "|", "\n"));
-
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+            SqliteResultCode::kOk);
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
@@ -717,22 +373,10 @@
   ASSERT_EQ(orig_schema, GetSchema(&db_));
   ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
 
-  // Recovery fails if the target table doesn't exist.
-  if (UseBuiltIn()) {
-    // ... or it can succeed silently, since there's nothing to do.
-    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-              SqliteResultCode::kOk);
-  } else {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-
-    // TODO(shess): Should this failure implicitly lead to Raze()?
-    size_t rows = 0;
-    EXPECT_FALSE(recovery->AutoRecoverTable("y", &rows));
-
-    Recovery::Unrecoverable(std::move(recovery));
-  }
+  // Recovery succeeds silently, since there's nothing to do.
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+            SqliteResultCode::kOk);
 }
 
 // Test that default values correctly replace nulls.  The recovery
@@ -761,39 +405,9 @@
 
   std::string final_schema(orig_schema);
   std::string final_data(orig_data);
-  if (UseBuiltIn()) {
-    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-              SqliteResultCode::kOk);
-  } else {
-    // Create a lame-duck table which will not be propagated by recovery to
-    // detect that the recovery code actually ran.
-    ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
-    ASSERT_NE(orig_schema, GetSchema(&db_));
-
-    // Mechanically adjust the stored schema and data to allow detecting
-    // where the default value is coming from.  The target table is just
-    // like the original with the default for [t] changed, to signal
-    // defaults coming from the recovery system.  The two %5 rows should
-    // get the target-table default for [t], while the others should get
-    // the source-table default.
-    size_t pos;
-    while ((pos = final_schema.find("'a''a'")) != std::string::npos) {
-      final_schema.replace(pos, 6, "'c''c'");
-    }
-    while ((pos = final_data.find("5|a'a")) != std::string::npos) {
-      final_data.replace(pos, 5, "5|c'c");
-    }
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    // Different default to detect which table provides the default.
-    ASSERT_TRUE(recovery->db()->Execute(final_schema.c_str()));
-
-    size_t rows = 0;
-    EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
-    EXPECT_EQ(4u, rows);
-
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+            SqliteResultCode::kOk);
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
@@ -802,50 +416,6 @@
   ASSERT_EQ(final_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
 }
 
-// Test that rows with NULL in a NOT NULL column are filtered
-// correctly.  In the wild, this would probably happen due to
-// corruption, but here it is simulated by recovering a table which
-// allowed nulls into a table which does not.
-//
-// This tests behavior of the legacy corruption recovery module which is not
-// needed in the new API. Specifically, this tests what happens to tables
-// with NULL values in non-nullable columns. This test is not easily
-// replicated, since SQLite does not support ALTER COLUMN. We'll trust that
-// it's handled properly upstream.
-TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableNullFilter) {
-  static const char kOrigSchema[] = "CREATE TABLE x (id INTEGER, t TEXT)";
-  static const char kFinalSchema[] =
-      "CREATE TABLE x (id INTEGER, t TEXT NOT NULL)";
-
-  ASSERT_TRUE(db_.Execute(kOrigSchema));
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (5, NULL)"));
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (15, 'this is a test')"));
-
-  // Create a lame-duck table which will not be propagated by recovery to
-  // detect that the recovery code actually ran.
-  ASSERT_EQ(kOrigSchema, GetSchema(&db_));
-  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
-  ASSERT_NE(kOrigSchema, GetSchema(&db_));
-
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery->db()->Execute(kFinalSchema));
-
-    size_t rows = 0;
-    EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
-    EXPECT_EQ(1u, rows);
-
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
-
-  // The schema should be the same, but only one row of data should
-  // have been recovered.
-  ASSERT_TRUE(Reopen());
-  ASSERT_EQ(kFinalSchema, GetSchema(&db_));
-  static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
-  ASSERT_EQ("15|this is a test", ExecuteWithResults(&db_, kXSql, "|", "\n"));
-}
-
 // Test AutoRecoverTable with a ROWID alias.
 TEST_P(SqlRecoveryTest, AutoRecoverTableWithRowid) {
   // The rowid alias is almost always the first column, intentionally
@@ -861,25 +431,9 @@
   static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
   const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
 
-  if (UseBuiltIn()) {
-    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-              SqliteResultCode::kOk);
-  } else {
-    // Create a lame-duck table which will not be propagated by recovery to
-    // detect that the recovery code actually ran.
-    ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
-    ASSERT_NE(orig_schema, GetSchema(&db_));
-
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-
-    size_t rows = 0;
-    EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
-    EXPECT_EQ(2u, rows);
-
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+            SqliteResultCode::kOk);
 
   // Since the database was not corrupt, the entire schema and all
   // data should be recovered.
@@ -888,154 +442,6 @@
   ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
 }
 
-// Test that a compound primary key doesn't fire the ROWID code.
-//
-// This tests behavior of the legacy corruption recovery module which is not
-// needed in the new API. Specifically, this tests how ROWID tables are
-// handled.
-TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableWithCompoundKey) {
-  static const char kCreateSql[] =
-      "CREATE TABLE x ("
-      "id INTEGER NOT NULL,"
-      "id2 TEXT NOT NULL,"
-      "t TEXT,"
-      "PRIMARY KEY (id, id2)"
-      ")";
-  ASSERT_TRUE(db_.Execute(kCreateSql));
-
-  // NOTE(shess): Do not accidentally use [id] 1, 2, 3, as those will
-  // be the ROWID values.
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'a', 'This is a test')"));
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'b', 'That was a test')"));
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (2, 'a', 'Another test')"));
-
-  // Save aside a copy of the original schema and data.
-  const std::string orig_schema(GetSchema(&db_));
-  static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
-  const std::string orig_data(ExecuteWithResults(&db_, kXSql, "|", "\n"));
-
-  // Create a lame-duck table which will not be propagated by recovery to
-  // detect that the recovery code actually ran.
-  ASSERT_TRUE(db_.Execute("CREATE TABLE y (c TEXT)"));
-  ASSERT_NE(orig_schema, GetSchema(&db_));
-
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-
-    size_t rows = 0;
-    EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
-    EXPECT_EQ(3u, rows);
-
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
-
-  // Since the database was not corrupt, the entire schema and all
-  // data should be recovered.
-  ASSERT_TRUE(Reopen());
-  ASSERT_EQ(orig_schema, GetSchema(&db_));
-  ASSERT_EQ(orig_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
-}
-
-// Test recovering from a table with fewer columns than the target.
-//
-// This tests behavior of the legacy corruption recovery module which is not
-// needed in the new API. Specifically, this tests how tables with missing
-// columns are handled.
-TEST_F(SqlLegacyRecoveryTest, AutoRecoverTableMissingColumns) {
-  static const char kCreateSql[] =
-      "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
-  static const char kAlterSql[] =
-      "ALTER TABLE x ADD COLUMN t1 TEXT DEFAULT 't'";
-  ASSERT_TRUE(db_.Execute(kCreateSql));
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (1, 'This is')"));
-  ASSERT_TRUE(db_.Execute("INSERT INTO x VALUES (2, 'That was')"));
-
-  // Generate the expected info by faking a table to match what recovery will
-  // create.
-  const std::string orig_schema(GetSchema(&db_));
-  static const char kXSql[] = "SELECT * FROM x ORDER BY 1";
-  std::string expected_schema;
-  std::string expected_data;
-  {
-    ASSERT_TRUE(db_.BeginTransaction());
-    ASSERT_TRUE(db_.Execute(kAlterSql));
-
-    expected_schema = GetSchema(&db_);
-    expected_data = ExecuteWithResults(&db_, kXSql, "|", "\n");
-
-    db_.RollbackTransaction();
-  }
-
-  // Following tests are pointless if the rollback didn't work.
-  ASSERT_EQ(orig_schema, GetSchema(&db_));
-
-  // Recover the previous version of the table into the altered version.
-  {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-    ASSERT_TRUE(recovery->db()->Execute(kAlterSql));
-    size_t rows = 0;
-    EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
-    EXPECT_EQ(2u, rows);
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
-
-  // Since the database was not corrupt, the entire schema and all
-  // data should be recovered.
-  ASSERT_TRUE(Reopen());
-  ASSERT_EQ(expected_schema, GetSchema(&db_));
-  ASSERT_EQ(expected_data, ExecuteWithResults(&db_, kXSql, "|", "\n"));
-}
-
-// Recover a golden file where an interior page has been manually modified so
-// that the number of cells is greater than will fit on a single page.  This
-// case happened in <http://crbug.com/387868>.
-TEST_P(SqlRecoveryTest, Bug387868) {
-  base::FilePath golden_path;
-  ASSERT_TRUE(base::PathService::Get(sql::test::DIR_TEST_DATA, &golden_path));
-  golden_path = golden_path.AppendASCII("recovery_387868");
-  db_.Close();
-  ASSERT_TRUE(base::CopyFile(golden_path, db_path_));
-  ASSERT_TRUE(Reopen());
-
-  if (UseBuiltIn()) {
-    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-              SqliteResultCode::kOk);
-  } else {
-    std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-    ASSERT_TRUE(recovery.get());
-
-    // Create the new version of the table.
-    static const char kCreateSql[] =
-        "CREATE TABLE x (id INTEGER PRIMARY KEY, t0 TEXT)";
-    ASSERT_TRUE(recovery->db()->Execute(kCreateSql));
-
-    size_t rows = 0;
-    EXPECT_TRUE(recovery->AutoRecoverTable("x", &rows));
-    EXPECT_EQ(43u, rows);
-
-    // Successfully recovered.
-    EXPECT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
-}
-
-// Memory-mapped I/O interacts poorly with I/O errors.  Make sure the recovery
-// database doesn't accidentally enable it.
-//
-// This tests behavior of the legacy corruption recovery module which is not
-// needed in the new API. Specifically, that MMAPing is disabled.
-TEST_F(SqlLegacyRecoveryTest, NoMmap) {
-  std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-  ASSERT_TRUE(recovery.get());
-
-  // In the current implementation, the PRAGMA successfully runs with no result
-  // rows.  Running with a single result of |0| is also acceptable.
-  Statement s(recovery->db()->GetUniqueStatement("PRAGMA mmap_size"));
-  EXPECT_TRUE(!s.Step() || !s.ColumnInt64(0));
-}
-
 void TestRecoverDatabase(Database& db,
                          const base::FilePath& db_path,
                          bool with_meta,
@@ -1110,13 +516,9 @@
 
 TEST_P(SqlRecoveryTest, RecoverDatabase) {
   auto run_recovery = base::BindLambdaForTesting([&]() {
-    if (UseBuiltIn()) {
-      EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                    &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-                SqliteResultCode::kOk);
-    } else {
-      Recovery::RecoverDatabase(&db_, db_path_);
-    }
+    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+              SqliteResultCode::kOk);
   });
 
   TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
@@ -1125,14 +527,10 @@
 
 TEST_P(SqlRecoveryTest, RecoverDatabaseMeta) {
   auto run_recovery = base::BindLambdaForTesting([&]() {
-    if (UseBuiltIn()) {
-      EXPECT_EQ(
-          BuiltInRecovery::RecoverDatabase(
-              &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
-          SqliteResultCode::kOk);
-    } else {
-      Recovery::RecoverDatabaseWithMetaVersion(&db_, db_path_);
-    }
+    EXPECT_EQ(
+        BuiltInRecovery::RecoverDatabase(
+            &db_, BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze),
+        SqliteResultCode::kOk);
   });
 
   TestRecoverDatabase(db_, db_path_, /*with_meta=*/true,
@@ -1207,31 +605,28 @@
 TEST_P(SqlRecoveryTest, RecoverIfPossibleWithPerDatabaseUma) {
   auto run_recovery = base::BindLambdaForTesting([&]() {
     EXPECT_TRUE(BuiltInRecovery::RecoverIfPossible(
-        &db_, SQLITE_CORRUPT, BuiltInRecovery::Strategy::kRecoverOrRaze,
-        &features::kUseBuiltInRecoveryIfSupported));
+        &db_, SQLITE_CORRUPT, BuiltInRecovery::Strategy::kRecoverOrRaze));
   });
 
   TestRecoverDatabase(db_, db_path_, /*with_meta=*/false,
                       std::move(run_recovery));
 
-  if (UseBuiltIn()) {
-    // Log to the overall histograms.
-    histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
-                                         BuiltInRecovery::Result::kSuccess,
-                                         /*expected_bucket_count=*/1);
-    histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
-                                         SqliteLoggedResultCode::kNoError,
-                                         /*expected_bucket_count=*/1);
-    // And the histograms for this specific feature.
-    histogram_tester_.ExpectUniqueSample(
-        base::StrCat({kRecoveryResultHistogramName, ".MyFeatureDatabase"}),
-        BuiltInRecovery::Result::kSuccess,
-        /*expected_bucket_count=*/1);
-    histogram_tester_.ExpectUniqueSample(
-        base::StrCat({kRecoveryResultCodeHistogramName, ".MyFeatureDatabase"}),
-        SqliteLoggedResultCode::kNoError,
-        /*expected_bucket_count=*/1);
-  }
+  // Log to the overall histograms.
+  histogram_tester_.ExpectUniqueSample(kRecoveryResultHistogramName,
+                                       BuiltInRecovery::Result::kSuccess,
+                                       /*expected_bucket_count=*/1);
+  histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
+                                       SqliteLoggedResultCode::kNoError,
+                                       /*expected_bucket_count=*/1);
+  // And the histograms for this specific feature.
+  histogram_tester_.ExpectUniqueSample(
+      base::StrCat({kRecoveryResultHistogramName, ".MyFeatureDatabase"}),
+      BuiltInRecovery::Result::kSuccess,
+      /*expected_bucket_count=*/1);
+  histogram_tester_.ExpectUniqueSample(
+      base::StrCat({kRecoveryResultCodeHistogramName, ".MyFeatureDatabase"}),
+      SqliteLoggedResultCode::kNoError,
+      /*expected_bucket_count=*/1);
 }
 
 TEST_P(SqlRecoveryTest, RecoverDatabaseWithView) {
@@ -1270,13 +665,9 @@
   // Database handle is valid before recovery, poisoned after.
   static constexpr char kTrivialSql[] = "SELECT COUNT(*) FROM sqlite_schema";
   EXPECT_TRUE(db.IsSQLValid(kTrivialSql));
-  if (UseBuiltIn()) {
-    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                  &db, BuiltInRecovery::Strategy::kRecoverOrRaze),
-              SqliteResultCode::kOk);
-  } else {
-    Recovery::RecoverDatabase(&db, db_path_);
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db, BuiltInRecovery::Strategy::kRecoverOrRaze),
+            SqliteResultCode::kOk);
   EXPECT_FALSE(db.IsSQLValid(kTrivialSql));
 
   // Since the database was not corrupt, the entire schema and all data should
@@ -1303,21 +694,16 @@
     ASSERT_FALSE(Reopen());
 
     // This should "recover" the database by making it valid, but empty.
-    if (UseBuiltIn()) {
-      EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                    &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-                SqliteResultCode::kNotADatabase);
-      histogram_tester_.ExpectUniqueSample(
-          kRecoveryResultHistogramName,
-          BuiltInRecovery::Result::kFailedRecoveryRun,
-          /*expected_bucket_count=*/1);
-      histogram_tester_.ExpectUniqueSample(
-          kRecoveryResultCodeHistogramName,
-          SqliteLoggedResultCode::kNotADatabase,
-          /*expected_bucket_count=*/1);
-    } else {
-      Recovery::RecoverDatabase(&db_, db_path_);
-    }
+    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+              SqliteResultCode::kNotADatabase);
+    histogram_tester_.ExpectUniqueSample(
+        kRecoveryResultHistogramName,
+        BuiltInRecovery::Result::kFailedRecoveryRun,
+        /*expected_bucket_count=*/1);
+    histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
+                                         SqliteLoggedResultCode::kNotADatabase,
+                                         /*expected_bucket_count=*/1);
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
 
@@ -1347,16 +733,6 @@
   ASSERT_TRUE(sql::test::CorruptIndexRootPage(db_path_, "rows_index"));
   ASSERT_TRUE(Reopen());
 
-  if (!UseBuiltIn()) {
-    // Run recovery code, then rollback.  Database remains the same.
-    std::unique_ptr<Recovery> recovery =
-        Recovery::BeginRecoverDatabase(&db_, db_path_);
-    ASSERT_TRUE(recovery);
-    Recovery::Rollback(std::move(recovery));
-    db_.Close();
-    ASSERT_TRUE(Reopen());
-  }
-
   static const char kIndexedCountSql[] =
       "SELECT SUM(indexed) FROM rows INDEXED BY rows_index";
   {
@@ -1369,16 +745,9 @@
   }
 
   // Run recovery code, then commit.  The index is recovered.
-  if (UseBuiltIn()) {
-    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-              SqliteResultCode::kOk);
-  } else {
-    std::unique_ptr<Recovery> recovery =
-        Recovery::BeginRecoverDatabase(&db_, db_path_);
-    ASSERT_TRUE(recovery);
-    ASSERT_TRUE(Recovery::Recovered(std::move(recovery)));
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+            SqliteResultCode::kOk);
   db_.Close();
   ASSERT_TRUE(Reopen());
 
@@ -1402,22 +771,16 @@
     ASSERT_FALSE(Reopen());
 
     // Begin() should fail.
-    if (UseBuiltIn()) {
-      EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                    &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
-                SqliteResultCode::kNotADatabase);
-      histogram_tester_.ExpectUniqueSample(
-          kRecoveryResultHistogramName,
-          BuiltInRecovery::Result::kFailedRecoveryRun,
-          /*expected_bucket_count=*/1);
-      histogram_tester_.ExpectUniqueSample(
-          kRecoveryResultCodeHistogramName,
-          SqliteLoggedResultCode::kNotADatabase,
-          /*expected_bucket_count=*/1);
-    } else {
-      std::unique_ptr<Recovery> recovery = Recovery::Begin(&db_, db_path_);
-      EXPECT_FALSE(recovery.get());
-    }
+    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                  &db_, BuiltInRecovery::Strategy::kRecoverOrRaze),
+              SqliteResultCode::kNotADatabase);
+    histogram_tester_.ExpectUniqueSample(
+        kRecoveryResultHistogramName,
+        BuiltInRecovery::Result::kFailedRecoveryRun,
+        /*expected_bucket_count=*/1);
+    histogram_tester_.ExpectUniqueSample(kRecoveryResultCodeHistogramName,
+                                         SqliteLoggedResultCode::kNotADatabase,
+                                         /*expected_bucket_count=*/1);
     ASSERT_TRUE(expecter.SawExpectedErrors());
   }
 }
@@ -1430,8 +793,7 @@
                   int initial_page_size,
                   const std::string& expected_initial_page_size,
                   int final_page_size,
-                  const std::string& expected_final_page_size,
-                  bool use_built_in) {
+                  const std::string& expected_final_page_size) {
   static const char kCreateSql[] = "CREATE TABLE x (t TEXT)";
   static const char kInsertSql1[] = "INSERT INTO x VALUES ('This is a test')";
   static const char kInsertSql2[] = "INSERT INTO x VALUES ('That was a test')";
@@ -1454,13 +816,9 @@
   ASSERT_TRUE(recover_db.Open(db_path));
   // Recovery will use the page size set in the database object, which may not
   // match the file's page size.
-  if (use_built_in) {
-    EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
-                  &recover_db, BuiltInRecovery::Strategy::kRecoverOrRaze),
-              SqliteResultCode::kOk);
-  } else {
-    Recovery::RecoverDatabase(&recover_db, db_path);
-  }
+  EXPECT_EQ(BuiltInRecovery::RecoverDatabase(
+                &recover_db, BuiltInRecovery::Strategy::kRecoverOrRaze),
+            SqliteResultCode::kOk);
 
   // Recovery poisoned the handle, must re-open.
   recover_db.Close();
@@ -1484,63 +842,44 @@
   // Check the default page size first.
   EXPECT_NO_FATAL_FAILURE(TestPageSize(
       db_path_, DatabaseOptions::kDefaultPageSize, default_page_size,
-      DatabaseOptions::kDefaultPageSize, default_page_size, UseBuiltIn()));
+      DatabaseOptions::kDefaultPageSize, default_page_size));
 
   // Sync uses 32k pages.
   EXPECT_NO_FATAL_FAILURE(
-      TestPageSize(db_path_, 32768, "32768", 32768, "32768", UseBuiltIn()));
+      TestPageSize(db_path_, 32768, "32768", 32768, "32768"));
 
   // Many clients use 4k pages.  This is the SQLite default after 3.12.0.
-  EXPECT_NO_FATAL_FAILURE(
-      TestPageSize(db_path_, 4096, "4096", 4096, "4096", UseBuiltIn()));
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 4096, "4096", 4096, "4096"));
 
   // 1k is the default page size before 3.12.0.
-  EXPECT_NO_FATAL_FAILURE(
-      TestPageSize(db_path_, 1024, "1024", 1024, "1024", UseBuiltIn()));
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 1024, "1024", 1024, "1024"));
 
   ASSERT_NE("2048", default_page_size);
-  if (UseBuiltIn()) {
-    // Databases with no page size specified should recover to the page size of
-    // the source database.
-    EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048",
-                                         DatabaseOptions::kDefaultPageSize,
-                                         "2048", UseBuiltIn()));
-  } else {
-    // Databases with no page size specified should recover with the new default
-    // page size.  2k has never been the default page size.
-    EXPECT_NO_FATAL_FAILURE(TestPageSize(db_path_, 2048, "2048",
-                                         DatabaseOptions::kDefaultPageSize,
-                                         default_page_size, UseBuiltIn()));
-  }
+  // Databases with no page size specified should recover to the page size of
+  // the source database.
+  EXPECT_NO_FATAL_FAILURE(TestPageSize(
+      db_path_, 2048, "2048", DatabaseOptions::kDefaultPageSize, "2048"));
 }
 
 TEST_P(SqlRecoveryTest, CannotRecoverClosedDb) {
   db_.Close();
 
-  if (UseBuiltIn()) {
-    EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
-                           &db_, BuiltInRecovery::Strategy::kRecoverOrRaze));
-  } else {
-    EXPECT_DCHECK_DEATH(Recovery::RecoverDatabase(&db_, db_path_));
-  }
+  EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
+                         &db_, BuiltInRecovery::Strategy::kRecoverOrRaze));
 }
 
 TEST_P(SqlRecoveryTest, CannotRecoverDbWithErrorCallback) {
   db_.set_error_callback(base::DoNothing());
 
-  if (UseBuiltIn()) {
-    EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
-                           &db_, BuiltInRecovery::Strategy::kRecoverOrRaze));
-  } else {
-    EXPECT_DCHECK_DEATH(Recovery::RecoverDatabase(&db_, db_path_));
-  }
+  EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
+                         &db_, BuiltInRecovery::Strategy::kRecoverOrRaze));
 }
 
 // TODO(https://crbug.com/1255316): Ideally this would be a
 // `SqlRecoveryTest`, but `Recovery::RecoverDatabase()` does not DCHECK
 // that it is passed a non-null database pointer and will instead likely result
 // in unexpected behavior or crashes.
-TEST_F(SqlBuiltInRecoveryTest, CannotRecoverNullDb) {
+TEST_P(SqlRecoveryTest, CannotRecoverNullDb) {
   EXPECT_CHECK_DEATH(std::ignore = BuiltInRecovery::RecoverDatabase(
                          nullptr, BuiltInRecovery::Strategy::kRecoverOrRaze));
 }
@@ -1549,7 +888,7 @@
 // `SqlRecoveryTest`, but `Recovery::RecoverDatabase()` does not DCHECK
 // whether the database is in-memory and will instead likely result in
 // unexpected behavior or crashes.
-TEST_F(SqlBuiltInRecoveryTest, CannotRecoverInMemoryDb) {
+TEST_P(SqlRecoveryTest, CannotRecoverInMemoryDb) {
   Database in_memory_db;
   ASSERT_TRUE(in_memory_db.OpenInMemory());
 
@@ -1558,19 +897,11 @@
           &in_memory_db, BuiltInRecovery::Strategy::kRecoverOrRaze));
 }
 
-TEST_P(SqlRecoveryTest, BuiltInRecoveryNotAttempedIfNotEnabled) {
-  // `BuiltInRecovery` will return early if the kill switch is disabled.
-  EXPECT_EQ(
-      base::FeatureList::IsEnabled(features::kUseBuiltInRecoveryIfSupported),
-      IsSqliteSuccessCode(BuiltInRecovery::RecoverDatabase(
-          &db_, BuiltInRecovery::Strategy::kRecoverOrRaze)));
-}
-
 // This test mimics the case where a database that was using WAL mode crashed,
 // then next Chrome launch the database is not opened in WAL mode. This may
 // occur when e.g. WAL mode if configured via Finch and the user not in the
 // experiment group on the second launch of Chrome.
-TEST_F(SqlBuiltInRecoveryTest, PRE_RecoverFormerlyWalDbAfterCrash) {
+TEST_P(SqlRecoveryTest, PRE_RecoverFormerlyWalDbAfterCrash) {
   base::FilePath wal_db_path =
       temp_dir_.GetPath().AppendASCII("recovery_wal_test.sqlite");
 
@@ -1586,7 +917,7 @@
   EXPECT_DCHECK_DEATH(wal_db.set_error_callback(base::DoNothing()));
 }
 
-TEST_F(SqlBuiltInRecoveryTest, RecoverFormerlyWalDbAfterCrash) {
+TEST_P(SqlRecoveryTest, RecoverFormerlyWalDbAfterCrash) {
   base::FilePath wal_db_path =
       temp_dir_.GetPath().AppendASCII("recovery_wal_test.sqlite");
 
@@ -1604,20 +935,6 @@
                       std::move(run_recovery));
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    SqlRecoveryTest,
-    testing::Values(
-        std::tuple(true, false),  // BuiltInRecovery with non-WAL databases.
-        std::tuple(false, false)  // Legacy recovery with non-WAL databases.
-// Recovering WAL databases is not supported on Fuchsia.
-#if !BUILDFLAG(IS_FUCHSIA)
-        ,
-        std::tuple(true, true)  // BuiltInRecovery with WAL databases.
-#endif
-        // The legacy recovery module does not support recovering WAL databases.
-        ));
-
 }  // namespace
 
 }  // namespace sql
diff --git a/sql/sql_features.cc b/sql/sql_features.cc
index 3569f14..b6b98b30 100644
--- a/sql/sql_features.cc
+++ b/sql/sql_features.cc
@@ -11,13 +11,4 @@
              "EnableWALModeByDefault",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// When enabled, `sql::BuiltInRecovery` can be used if it's supported. See
-// https://crbug.com/1385500.
-//
-// This is an overarching kill switch which overrides any database-specific
-// flag. See `sql::BuiltInRecovery::RecoverIfPossible()` for more context.
-BASE_FEATURE(kUseBuiltInRecoveryIfSupported,
-             "UseBuiltInRecoveryIfSupported",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 }  // namespace sql::features
diff --git a/sql/sql_features.h b/sql/sql_features.h
index f807d14..fefcbe6 100644
--- a/sql/sql_features.h
+++ b/sql/sql_features.h
@@ -15,7 +15,6 @@
 
 // Alphabetical:
 COMPONENT_EXPORT(SQL) BASE_DECLARE_FEATURE(kEnableWALModeByDefault);
-COMPONENT_EXPORT(SQL) BASE_DECLARE_FEATURE(kUseBuiltInRecoveryIfSupported);
 
 }  // namespace sql::features
 
diff --git a/sql/test/database_test_peer.cc b/sql/test/database_test_peer.cc
index 5bc68363..5c5d1838 100644
--- a/sql/test/database_test_peer.cc
+++ b/sql/test/database_test_peer.cc
@@ -26,9 +26,4 @@
   return db->DetachDatabase(attachment_point, InternalApiToken());
 }
 
-// static
-bool DatabaseTestPeer::EnableRecoveryExtension(Database* db) {
-  return Recovery::EnableRecoveryExtension(db, InternalApiToken()) == SQLITE_OK;
-}
-
 }  // namespace sql
diff --git a/sql/test/database_test_peer.h b/sql/test/database_test_peer.h
index d0a9a93..ff788f2 100644
--- a/sql/test/database_test_peer.h
+++ b/sql/test/database_test_peer.h
@@ -19,8 +19,6 @@
                              const base::FilePath& other_db_path,
                              const char* attachment_point);
   static bool DetachDatabase(Database* db, const char* attachment_point);
-
-  static bool EnableRecoveryExtension(Database* db);
 };
 
 }  // namespace sql
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
index 4338a5e..1b510c5 100644
--- a/storage/browser/quota/quota_database.cc
+++ b/storage/browser/quota/quota_database.cc
@@ -809,7 +809,7 @@
 
   std::ignore = sql::BuiltInRecovery::RecoverIfPossible(
       db_.get(), error_code,
-      sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze, nullptr);
+      sql::BuiltInRecovery::Strategy::kRecoverWithMetaVersionOrRaze);
 
   db_.reset();
   EnsureOpened();
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 99c51edc..f7e6e8f8 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5282,9 +5282,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5294,8 +5294,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -5438,9 +5438,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5450,8 +5450,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index dd852f1..2b8bc25 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -20313,9 +20313,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20325,8 +20325,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -20463,9 +20463,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20475,8 +20475,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 24833f3..a1f8a8c 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -7401,7 +7401,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7449,7 +7449,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7497,7 +7497,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7545,7 +7545,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7593,7 +7593,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7641,7 +7641,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7689,7 +7689,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7737,7 +7737,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7785,7 +7785,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7833,7 +7833,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7881,7 +7881,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7929,7 +7929,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -7977,7 +7977,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8025,7 +8025,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8073,7 +8073,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8121,7 +8121,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8169,7 +8169,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8217,7 +8217,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8265,7 +8265,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8313,7 +8313,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8361,7 +8361,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8409,7 +8409,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8457,7 +8457,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8505,7 +8505,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8553,7 +8553,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8601,7 +8601,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8649,7 +8649,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8697,7 +8697,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8745,7 +8745,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8793,7 +8793,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8841,7 +8841,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8889,7 +8889,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8937,7 +8937,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -8985,7 +8985,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9033,7 +9033,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9081,7 +9081,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9132,7 +9132,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9183,7 +9183,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9236,7 +9236,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9290,7 +9290,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9344,7 +9344,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9398,7 +9398,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9452,7 +9452,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9506,7 +9506,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9560,7 +9560,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9614,7 +9614,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9668,7 +9668,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9722,7 +9722,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9774,7 +9774,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9826,7 +9826,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9878,7 +9878,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9929,7 +9929,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -9982,7 +9982,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10036,7 +10036,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10085,7 +10085,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10133,7 +10133,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10181,7 +10181,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10229,7 +10229,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10277,7 +10277,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10325,7 +10325,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10373,7 +10373,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10421,7 +10421,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10472,7 +10472,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10524,7 +10524,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10573,7 +10573,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10621,7 +10621,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10669,7 +10669,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10718,7 +10718,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10767,7 +10767,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10815,7 +10815,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10863,7 +10863,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10911,7 +10911,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -10959,7 +10959,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11007,7 +11007,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11055,7 +11055,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11103,7 +11103,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11151,7 +11151,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11199,7 +11199,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11247,7 +11247,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11295,7 +11295,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11346,7 +11346,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11397,7 +11397,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11448,7 +11448,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11499,7 +11499,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11550,7 +11550,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11601,7 +11601,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11652,7 +11652,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11703,7 +11703,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11751,7 +11751,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11799,7 +11799,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11847,7 +11847,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11895,7 +11895,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11943,7 +11943,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -11991,7 +11991,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12039,7 +12039,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12087,7 +12087,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12135,7 +12135,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12183,7 +12183,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12231,7 +12231,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12279,7 +12279,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12327,7 +12327,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12375,7 +12375,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12423,7 +12423,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12471,7 +12471,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12519,7 +12519,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12567,7 +12567,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12615,7 +12615,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12663,7 +12663,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12711,7 +12711,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12759,7 +12759,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12807,7 +12807,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12855,7 +12855,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12903,7 +12903,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12951,7 +12951,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -12999,7 +12999,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13047,7 +13047,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13095,7 +13095,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13143,7 +13143,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13191,7 +13191,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13239,7 +13239,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13287,7 +13287,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13335,7 +13335,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13383,7 +13383,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13431,7 +13431,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13479,7 +13479,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13527,7 +13527,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13575,7 +13575,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13623,7 +13623,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13671,7 +13671,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13719,7 +13719,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13767,7 +13767,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13815,7 +13815,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13863,7 +13863,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13911,7 +13911,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -13959,7 +13959,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -14007,7 +14007,7 @@
           ],
           "dimensions": {
             "cpu": "arm64",
-            "os": "Mac-13"
+            "os": "Mac-14"
           },
           "named_caches": [
             {
@@ -40600,6 +40600,1590 @@
       }
     ]
   },
+  "linux-chromeos-dbg-oslogin": {
+    "gtest_tests": [
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "absl_hardening_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "absl_hardening_tests",
+        "test_id_prefix": "ninja://third_party/abseil-cpp:absl_hardening_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "accessibility_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "accessibility_unittests",
+        "test_id_prefix": "ninja://ui/accessibility:accessibility_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "angle_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "angle_unittests",
+        "test_id_prefix": "ninja://third_party/angle/src/tests:angle_unittests/",
+        "use_isolated_scripts_api": true
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "app_shell_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "app_shell_unittests",
+        "test_id_prefix": "ninja://extensions/shell:app_shell_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ash_components_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_components_unittests",
+        "test_id_prefix": "ninja://ash/components:ash_components_unittests/"
+      },
+      {
+        "ci_only": true,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ash_crosapi_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_crosapi_tests",
+        "test_id_prefix": "ninja://chrome/test:ash_crosapi_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ash_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 5
+        },
+        "test": "ash_unittests",
+        "test_id_prefix": "ninja://ash:ash_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ash_webui_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ash_webui_unittests",
+        "test_id_prefix": "ninja://ash/webui:ash_webui_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "aura_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "aura_unittests",
+        "test_id_prefix": "ninja://ui/aura:aura_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "base_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "base_unittests",
+        "test_id_prefix": "ninja://base:base_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "blink_common_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_common_unittests",
+        "test_id_prefix": "ninja://third_party/blink/common:blink_common_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "blink_fuzzer_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_fuzzer_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_fuzzer_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "blink_heap_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_heap_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
+      },
+      {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_platform_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "boringssl_crypto_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_crypto_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_crypto_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "boringssl_ssl_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "boringssl_ssl_tests",
+        "test_id_prefix": "ninja://third_party/boringssl:boringssl_ssl_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "browser_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 10
+        },
+        "test": "browser_tests",
+        "test_id_prefix": "ninja://chrome/test:browser_tests/"
+      },
+      {
+        "args": [
+          "--gtest_filter=-*UsingRealWebcam*"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "capture_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "capture_unittests",
+        "test_id_prefix": "ninja://media/capture:capture_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "cast_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cast_unittests",
+        "test_id_prefix": "ninja://media/cast:cast_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "cc_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "cc_unittests",
+        "test_id_prefix": "ninja://cc:cc_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "chrome_app_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chrome_app_unittests",
+        "test_id_prefix": "ninja://chrome/test:chrome_app_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "chromedriver_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chromedriver_unittests",
+        "test_id_prefix": "ninja://chrome/test/chromedriver:chromedriver_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "chromeos_components_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chromeos_components_unittests",
+        "test_id_prefix": "ninja://chromeos/components:chromeos_components_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "chromeos_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chromeos_unittests",
+        "test_id_prefix": "ninja://chromeos:chromeos_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "components_browsertests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_browsertests",
+        "test_id_prefix": "ninja://components:components_browsertests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "components_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "components_unittests",
+        "test_id_prefix": "ninja://components:components_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "compositor_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "compositor_unittests",
+        "test_id_prefix": "ninja://ui/compositor:compositor_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "content_browsertests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 8
+        },
+        "test": "content_browsertests",
+        "test_id_prefix": "ninja://content/test:content_browsertests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "content_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "content_unittests",
+        "test_id_prefix": "ninja://content/test:content_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "crashpad_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crashpad_tests",
+        "test_id_prefix": "ninja://third_party/crashpad/crashpad:crashpad_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "crypto_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "crypto_unittests",
+        "test_id_prefix": "ninja://crypto:crypto_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "dbus_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "dbus_unittests",
+        "test_id_prefix": "ninja://dbus:dbus_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "device_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "device_unittests",
+        "test_id_prefix": "ninja://device:device_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "display_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "display_unittests",
+        "test_id_prefix": "ninja://ui/display:display_unittests/"
+      },
+      {
+        "ci_only": true,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "env_chromium_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "env_chromium_unittests",
+        "test_id_prefix": "ninja://third_party/leveldatabase:env_chromium_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "events_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "events_unittests",
+        "test_id_prefix": "ninja://ui/events:events_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "exo_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "exo_unittests",
+        "test_id_prefix": "ninja://components/exo:exo_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "extensions_browsertests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "extensions_browsertests",
+        "test_id_prefix": "ninja://extensions:extensions_browsertests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "extensions_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "extensions_unittests",
+        "test_id_prefix": "ninja://extensions:extensions_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "filesystem_service_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "filesystem_service_unittests",
+        "test_id_prefix": "ninja://components/services/filesystem:filesystem_service_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gcm_unit_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gcm_unit_tests",
+        "test_id_prefix": "ninja://google_apis/gcm:gcm_unit_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gfx_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gfx_unittests",
+        "test_id_prefix": "ninja://ui/gfx:gfx_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gin_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gin_unittests",
+        "test_id_prefix": "ninja://gin:gin_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gl_unittests_ozone",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gl_unittests_ozone",
+        "test_id_prefix": "ninja://ui/gl:gl_unittests_ozone/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "google_apis_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "google_apis_unittests",
+        "test_id_prefix": "ninja://google_apis:google_apis_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gpu_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gpu_unittests",
+        "test_id_prefix": "ninja://gpu:gpu_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "gwp_asan_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "gwp_asan_unittests",
+        "test_id_prefix": "ninja://components/gwp_asan:gwp_asan_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "interactive_ui_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "interactive_ui_tests",
+        "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ipc_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ipc_tests",
+        "test_id_prefix": "ninja://ipc:ipc_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "keyboard_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "keyboard_unittests",
+        "test_id_prefix": "ninja://ash/keyboard/ui:keyboard_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "latency_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "latency_unittests",
+        "test_id_prefix": "ninja://ui/latency:latency_unittests/"
+      },
+      {
+        "ci_only": true,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "leveldb_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "leveldb_unittests",
+        "test_id_prefix": "ninja://third_party/leveldatabase:leveldb_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "libjingle_xmpp_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "libjingle_xmpp_unittests",
+        "test_id_prefix": "ninja://third_party/libjingle_xmpp:libjingle_xmpp_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "liburlpattern_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "liburlpattern_unittests",
+        "test_id_prefix": "ninja://third_party/liburlpattern:liburlpattern_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "media_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "media_unittests",
+        "test_id_prefix": "ninja://media:media_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "message_center_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "message_center_unittests",
+        "test_id_prefix": "ninja://ui/message_center:message_center_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "midi_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "midi_unittests",
+        "test_id_prefix": "ninja://media/midi:midi_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "mojo_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "mojo_unittests",
+        "test_id_prefix": "ninja://mojo:mojo_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "nacl_loader_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "nacl_loader_unittests",
+        "test_id_prefix": "ninja://components/nacl/loader:nacl_loader_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "native_theme_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "native_theme_unittests",
+        "test_id_prefix": "ninja://ui/native_theme:native_theme_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "net_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "net_unittests",
+        "test_id_prefix": "ninja://net:net_unittests/"
+      },
+      {
+        "args": [
+          "--ozone-platform=headless"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ozone_gl_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ozone_gl_unittests",
+        "test_id_prefix": "ninja://ui/ozone/gl:ozone_gl_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ozone_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ozone_unittests",
+        "test_id_prefix": "ninja://ui/ozone:ozone_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ozone_x11_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ozone_x11_unittests",
+        "test_id_prefix": "ninja://ui/ozone:ozone_x11_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "pdf_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "pdf_unittests",
+        "test_id_prefix": "ninja://pdf:pdf_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "perfetto_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "perfetto_unittests",
+        "test_id_prefix": "ninja://third_party/perfetto:perfetto_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ppapi_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ppapi_unittests",
+        "test_id_prefix": "ninja://ppapi:ppapi_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "printing_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "printing_unittests",
+        "test_id_prefix": "ninja://printing:printing_unittests/"
+      },
+      {
+        "ci_only": true,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "pthreadpool_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "pthreadpool_unittests",
+        "test_id_prefix": "ninja://third_party/pthreadpool:pthreadpool_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "remoting_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "remoting_unittests",
+        "test_id_prefix": "ninja://remoting:remoting_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "sandbox_linux_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sandbox_linux_unittests",
+        "test_id_prefix": "ninja://sandbox/linux:sandbox_linux_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "services_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "services_unittests",
+        "test_id_prefix": "ninja://services:services_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "shell_dialogs_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_dialogs_unittests",
+        "test_id_prefix": "ninja://ui/shell_dialogs:shell_dialogs_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "shell_encryption_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "shell_encryption_unittests",
+        "test_id_prefix": "ninja://third_party/shell-encryption:shell_encryption_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "skia_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "skia_unittests",
+        "test_id_prefix": "ninja://skia:skia_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "snapshot_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "snapshot_unittests",
+        "test_id_prefix": "ninja://ui/snapshot:snapshot_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "sql_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "sql_unittests",
+        "test_id_prefix": "ninja://sql:sql_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "storage_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "storage_unittests",
+        "test_id_prefix": "ninja://storage:storage_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "sync_integration_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 3
+        },
+        "test": "sync_integration_tests",
+        "test_id_prefix": "ninja://chrome/test:sync_integration_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ui_base_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_base_unittests",
+        "test_id_prefix": "ninja://ui/base:ui_base_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ui_chromeos_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_chromeos_unittests",
+        "test_id_prefix": "ninja://ui/chromeos:ui_chromeos_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ui_touch_selection_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_touch_selection_unittests",
+        "test_id_prefix": "ninja://ui/touch_selection:ui_touch_selection_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "ui_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "ui_unittests",
+        "test_id_prefix": "ninja://ui/tests:ui_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "unit_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "unit_tests",
+        "test_id_prefix": "ninja://chrome/test:unit_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "url_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "url_unittests",
+        "test_id_prefix": "ninja://url:url_unittests/"
+      },
+      {
+        "experiment_percentage": 100,
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "usage_time_limit_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "usage_time_limit_unittests",
+        "test_id_prefix": "ninja://chrome/test:usage_time_limit_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "views_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "views_unittests",
+        "test_id_prefix": "ninja://ui/views:views_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "viz_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "viz_unittests",
+        "test_id_prefix": "ninja://components/viz:viz_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "wayland_client_perftests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wayland_client_perftests",
+        "test_id_prefix": "ninja://components/exo/wayland:wayland_client_perftests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "wayland_client_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wayland_client_tests",
+        "test_id_prefix": "ninja://components/exo/wayland:wayland_client_tests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "wm_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wm_unittests",
+        "test_id_prefix": "ninja://ui/wm:wm_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "wtf_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "wtf_unittests",
+        "test_id_prefix": "ninja://third_party/blink/renderer/platform/wtf:wtf_unittests/"
+      },
+      {
+        "merge": {
+          "script": "//testing/merge_scripts/standard_gtest_merge.py"
+        },
+        "name": "zlib_unittests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "zlib_unittests",
+        "test_id_prefix": "ninja://third_party/zlib:zlib_unittests/"
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "merge": {
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "chrome_sizes",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "chrome_sizes",
+        "test_id_prefix": "ninja://chrome/test:chrome_sizes/"
+      },
+      {
+        "args": [
+          "--num-retries=3",
+          "--test-list=../../third_party/blink/web_tests/TestLists/ppapi",
+          "--write-run-histories-to=${ISOLATED_OUTDIR}/run_histories.json"
+        ],
+        "merge": {
+          "args": [
+            "--verbose"
+          ],
+          "script": "//third_party/blink/tools/merge_web_test_results.py"
+        },
+        "name": "ppapi_blink_web_tests",
+        "resultdb": {
+          "enable": true,
+          "has_native_resultdb_integration": true
+        },
+        "results_handler": "layout tests",
+        "swarming": {
+          "dimensions": {
+            "cpu": "x86-64",
+            "os": "Ubuntu-22.04",
+            "pool": "chromium.tests.oslogin"
+          },
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+        },
+        "test": "blink_web_tests",
+        "test_id_prefix": "ninja://:blink_web_tests/"
+      }
+    ]
+  },
   "linux-fieldtrial-rel": {
     "gtest_tests": [
       {
@@ -41369,9 +42953,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41380,8 +42964,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -41519,9 +43103,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -41530,8 +43114,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -42868,9 +44452,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -42880,8 +44464,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -43024,9 +44608,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -43036,8 +44620,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -44349,9 +45933,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44360,8 +45944,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -44499,9 +46083,9 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44510,8 +46094,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index b0393a3..2a7b34a3 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -16343,12 +16343,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter;../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16358,8 +16358,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -16519,12 +16519,12 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.filter;../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 124.0.6325.0",
+        "description": "Run with ash-chrome version 124.0.6326.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16534,8 +16534,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6325.0",
-              "revision": "version:124.0.6325.0"
+              "location": "lacros_version_skew_tests_v124.0.6326.0",
+              "revision": "version:124.0.6326.0"
             }
           ],
           "dimensions": {
@@ -19881,7 +19881,8 @@
           "dimensions": {
             "os": "Windows-10-19045"
           },
-          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
+          "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
+          "shards": 6
         },
         "test": "blink_unittests",
         "test_id_prefix": "ninja://third_party/blink/renderer/controller:blink_unittests/"
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 1f5c782..7f8052e4 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -399,6 +399,13 @@
       'service_account': 'chromium-tester@chops-service-accounts.iam.gserviceaccount.com',
     },
   },
+  'chromium-tests-oslogin': {
+    'swarming': {
+      'dimensions': {
+        'pool': 'chromium.tests.oslogin',
+      },
+    },
+  },
   'ci_only': {
     'ci_only': True,
   },
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index c72b969..81df0ba9 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -5293,6 +5293,11 @@
           'shards': 2,
         }
       },
+      'win-asan': {
+        'swarming': {
+          'shards': 6,
+        },
+      },
       'win10-code-coverage': {
         'swarming': {
           'shards': 4,
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 7ac9198..99bda77 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -307,16 +307,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 124.0.6325.0',
+    'description': 'Run with ash-chrome version 124.0.6326.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6325.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6326.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v124.0.6325.0',
-          'revision': 'version:124.0.6325.0',
+          'location': 'lacros_version_skew_tests_v124.0.6326.0',
+          'revision': 'version:124.0.6326.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index d34488b..89d9674 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3411,7 +3411,7 @@
         ],
         'mixins': [
           'has_native_resultdb_integration',
-          'mac_13_arm64',
+          'mac_14_arm64',
           'mac_toolchain',
           'out_dir_arg',
           'xcode_15_main',
@@ -3572,6 +3572,17 @@
           'scripts': 'test_traffic_annotation_auditor_script'
         },
       },
+      'linux-chromeos-dbg-oslogin': {
+        'mixins': [
+          'x86-64',
+          'linux-jammy',
+          'chromium-tests-oslogin',
+        ],
+        'test_suites': {
+          'gtest_tests': 'linux_chromeos_gtests',
+          'isolated_scripts': 'linux_chromeos_isolated_scripts',
+        },
+      },
       'linux-fieldtrial-rel': {
         'mixins': [
           'linux-jammy',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 7d187e2..553dd63 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8846,6 +8846,21 @@
             ]
         }
     ],
+    "IOSDefaultBrowserVideoInSettings": [
+        {
+            "platforms": [
+                "ios"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "DefaultBrowserVideoInSettings"
+                    ]
+                }
+            ]
+        }
+    ],
     "IOSDiscoverFeedSportCard": [
         {
             "platforms": [
@@ -9515,29 +9530,6 @@
             ]
         }
     ],
-    "ImageServiceObserveSyncDownloadStatus": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "ImageServiceObserveSyncDownloadStatus"
-                    ],
-                    "disable_features": [
-                        "SyncPollImmediatelyOnEveryStartup2"
-                    ]
-                }
-            ]
-        }
-    ],
     "ImprovedDownloadPageWarnings": [
         {
             "platforms": [
@@ -11771,6 +11763,30 @@
             ]
         }
     ],
+    "OmniboxLogURLScoringSignals": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "ios",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "enable_scoring_signals_annotators": "true"
+                    },
+                    "enable_features": [
+                        "LogUrlScoringSignals"
+                    ]
+                }
+            ]
+        }
+    ],
     "OmniboxMatchToolbarAndStatusBarColor": [
         {
             "platforms": [
@@ -14212,26 +14228,6 @@
             ]
         }
     ],
-    "ProtectedAudiencesEnableUpdatingUserBiddingSignals": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "EnableUpdatingUserBiddingSignals"
-                    ]
-                }
-            ]
-        }
-    ],
     "ProtectedAudiencesHeaderDirectFromSellerSignalsStudy": [
         {
             "platforms": [
@@ -14315,6 +14311,26 @@
             ]
         }
     ],
+    "ProtectedAudiencesSplitTrustedSignalsFetchingURL": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "FledgeSplitTrustedSignalsFetchingURL"
+                    ]
+                }
+            ]
+        }
+    ],
     "ProtectedAudiencesTrustedBiddingSignalsSlotSize": [
         {
             "platforms": [
@@ -15270,6 +15286,14 @@
                         "SafeBrowsingNewGmsApiForBrowseUrlDatabaseCheck",
                         "SafeBrowsingNewGmsApiForSubresourceFilterCheck"
                     ]
+                },
+                {
+                    "name": "EnabledWithCallOnStartup",
+                    "enable_features": [
+                        "SafeBrowsingCallNewGmsApiOnStartup",
+                        "SafeBrowsingNewGmsApiForBrowseUrlDatabaseCheck",
+                        "SafeBrowsingNewGmsApiForSubresourceFilterCheck"
+                    ]
                 }
             ]
         }
diff --git a/third_party/blink/common/media/watch_time_reporter.cc b/third_party/blink/common/media/watch_time_reporter.cc
index 7323ee5..1a2b40ca 100644
--- a/third_party/blink/common/media/watch_time_reporter.cc
+++ b/third_party/blink/common/media/watch_time_reporter.cc
@@ -5,8 +5,8 @@
 #include "third_party/blink/public/common/media/watch_time_reporter.h"
 
 #include <numeric>
+#include <vector>
 
-#include "base/containers/cxx20_erase.h"
 #include "base/functional/bind.h"
 #include "base/power_monitor/power_monitor.h"
 #include "base/task/sequenced_task_runner.h"
@@ -494,7 +494,7 @@
       }
     }
 
-    base::EraseIf(pending_underflow_events_, [](const UnderflowEvent& ufe) {
+    std::erase_if(pending_underflow_events_, [](const UnderflowEvent& ufe) {
       return ufe.reported && ufe.duration != media::kNoTimestamp;
     });
 
diff --git a/third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc b/third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc
index ddb7f4d..d0cf9a4 100644
--- a/third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc
+++ b/third_party/blink/renderer/bindings/core/v8/callback_invoke_helper.cc
@@ -13,7 +13,7 @@
 #include "third_party/blink/renderer/platform/bindings/callback_interface_base.h"
 #include "third_party/blink/renderer/platform/bindings/script_forbidden_scope.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 
 namespace blink {
 
@@ -105,8 +105,7 @@
       callback_this_ =
           callback_this.V8Value(callback_->CallbackRelevantScriptState());
     }
-    if (auto* tracker =
-            ThreadScheduler::Current()->GetTaskAttributionTracker()) {
+    if (auto* tracker = scheduler::TaskAttributionTracker::From(isolate)) {
       // There are 3 possible callbacks here:
       // a) Callbacks which track their registering task as their parent
       // b) Callbacks which don't do the above, split into two groups:
@@ -119,7 +118,7 @@
                         CallbackFunctionWithTaskAttributionBase>::value) {
         parent_task = callback_->GetParentTask();
       }
-      if (parent_task || !tracker->RunningTask(isolate)) {
+      if (parent_task || !tracker->RunningTask()) {
         task_attribution_scope_ = tracker->CreateTaskScope(
             callback_->CallbackRelevantScriptState(), parent_task,
             scheduler::TaskAttributionTracker::TaskScopeType::kCallback);
diff --git a/third_party/blink/renderer/core/css/css_length_resolver.h b/third_party/blink/renderer/core/css/css_length_resolver.h
index 3611e56..0c397f3 100644
--- a/third_party/blink/renderer/core/css/css_length_resolver.h
+++ b/third_party/blink/renderer/core/css/css_length_resolver.h
@@ -56,6 +56,11 @@
   // https://drafts.csswg.org/css-scoping-1/#css-tree-scoped-reference
   virtual void ReferenceTreeScope() const = 0;
 
+  // Called when anchor() or anchor-size() functions are evaluated.
+  //
+  // https://drafts.csswg.org/css-anchor-position-1/
+  virtual void ReferenceAnchor() const = 0;
+
   // The AnchorEvaluator used to evaluate anchor()/anchor-size() queries,
   // when the runtime flag CSSAnchorPositioningComputeAnchor is enabled.
   virtual Length::AnchorEvaluator* AnchorEvaluator() const { return nullptr; }
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.cc b/third_party/blink/renderer/core/css/css_math_expression_node.cc
index 179b54b..09c7a0c2 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.cc
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.cc
@@ -2504,6 +2504,7 @@
 std::optional<LayoutUnit> CSSMathExpressionAnchorQuery::EvaluateQuery(
     const CalculationExpressionNode& query,
     const CSSLengthResolver& length_resolver) const {
+  length_resolver.ReferenceAnchor();
   if (Length::AnchorEvaluator* anchor_evaluator =
           length_resolver.AnchorEvaluator()) {
     return anchor_evaluator->Evaluate(query);
diff --git a/third_party/blink/renderer/core/css/css_to_length_conversion_data.cc b/third_party/blink/renderer/core/css/css_to_length_conversion_data.cc
index 6e5e9f7..1a92209 100644
--- a/third_party/blink/renderer/core/css/css_to_length_conversion_data.cc
+++ b/third_party/blink/renderer/core/css/css_to_length_conversion_data.cc
@@ -464,4 +464,8 @@
   SetFlag(Flag::kTreeScopedReference);
 }
 
+void CSSToLengthConversionData::ReferenceAnchor() const {
+  SetFlag(Flag::kAnchorRelative);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_to_length_conversion_data.h b/third_party/blink/renderer/core/css/css_to_length_conversion_data.h
index 7350eaa..8ee5c5a 100644
--- a/third_party/blink/renderer/core/css/css_to_length_conversion_data.h
+++ b/third_party/blink/renderer/core/css/css_to_length_conversion_data.h
@@ -277,6 +277,9 @@
     kTreeScopedReference = 1u << 7,
     // vi, vb, cqi, cqb, etc
     kLogicalDirectionRelative = 1u << 8,
+    // anchor(), anchor-size()
+    // https://drafts.csswg.org/css-anchor-position-1
+    kAnchorRelative = 1u << 9,
     // Adjust the Flags type above if adding more bits below.
   };
 
@@ -340,6 +343,8 @@
     line_height_size_ = line_height_size;
   }
 
+  void ReferenceAnchor() const override;
+
   Length::AnchorEvaluator* AnchorEvaluator() const override {
     return anchor_data_.GetEvaluator();
   }
diff --git a/third_party/blink/renderer/core/css/css_to_length_conversion_data_test.cc b/third_party/blink/renderer/core/css/css_to_length_conversion_data_test.cc
index c20c94e..baca648 100644
--- a/third_party/blink/renderer/core/css/css_to_length_conversion_data_test.cc
+++ b/third_party/blink/renderer/core/css/css_to_length_conversion_data_test.cc
@@ -31,7 +31,7 @@
       : result_(result) {}
 
   std::optional<LayoutUnit> Evaluate(
-      const CalculationExpressionNode&) const override {
+      const CalculationExpressionNode&) override {
     return result_;
   }
 
diff --git a/third_party/blink/renderer/core/css/media_values.h b/third_party/blink/renderer/core/css/media_values.h
index 92061122..e66c6a5 100644
--- a/third_party/blink/renderer/core/css/media_values.h
+++ b/third_party/blink/renderer/core/css/media_values.h
@@ -139,6 +139,7 @@
 
   // CSSLengthResolver override.
   void ReferenceTreeScope() const override {}
+  void ReferenceAnchor() const override {}
 
  protected:
   virtual ContainerSnappedFlags SnappedFlags() const {
diff --git a/third_party/blink/renderer/core/css/post_style_update_scope.h b/third_party/blink/renderer/core/css/post_style_update_scope.h
index 984975b..111ef284 100644
--- a/third_party/blink/renderer/core/css/post_style_update_scope.h
+++ b/third_party/blink/renderer/core/css/post_style_update_scope.h
@@ -63,6 +63,7 @@
    private:
     friend class PostStyleUpdateScope;
     friend class ContainerQueryTest;
+    friend class StyleResolverTest;
 
     HeapHashSet<Member<Element>> elements_with_pending_updates_;
     HeapHashMap<Member<const Element>, Member<const ComputedStyle>> old_styles_;
diff --git a/third_party/blink/renderer/core/css/properties/css_property_test.cc b/third_party/blink/renderer/core/css/properties/css_property_test.cc
index 25865c26..f71d53d 100644
--- a/third_party/blink/renderer/core/css/properties/css_property_test.cc
+++ b/third_party/blink/renderer/core/css/properties/css_property_test.cc
@@ -38,7 +38,7 @@
       : required_mode_(required_mode) {}
 
   std::optional<LayoutUnit> Evaluate(
-      const CalculationExpressionNode&) const override {
+      const CalculationExpressionNode&) override {
     return (required_mode_ == GetMode()) ? std::optional<LayoutUnit>(1)
                                          : std::optional<LayoutUnit>();
   }
diff --git a/third_party/blink/renderer/core/css/resolver/matched_properties_cache.cc b/third_party/blink/renderer/core/css/resolver/matched_properties_cache.cc
index 8b195f12..3b066144 100644
--- a/third_party/blink/renderer/core/css/resolver/matched_properties_cache.cc
+++ b/third_party/blink/renderer/core/css/resolver/matched_properties_cache.cc
@@ -276,6 +276,11 @@
   if (builder.HasContainerRelativeUnits()) {
     return false;
   }
+  if (builder.HasAnchorFunctions()) {
+    // The result of anchor() and anchor-size() functions can depend on
+    // the 'anchor' attribute on the element.
+    return false;
+  }
   // Avoiding cache for ::highlight styles, and the originating styles they are
   // associated with, because the style depends on the highlight names involved
   // and they're not cached.
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver.cc b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
index dd6e63c..ea520af 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver.cc
@@ -135,12 +135,13 @@
   // position-fallback, we can fall back to the default behavior (in
   // CSSAnimations) of using the current style on Element as the old style.
   //
-  // TODO(crbug.com/1502666): We also need to check whether we are a descendant
+  // TODO(crbug.com/40943044): We also need to check whether we are a descendant
   // of an element with position-fallback to cover the case where the descendant
   // explicitly inherits insets or other valid @try properties from the element
-  // with position-fallback.
+  // with position-fallback. This applies to descendants of elements with
+  // anchor queries as well.
   return (style_recalc_context.container ||
-          style_recalc_context.is_interleaved_oof ||
+          state.StyleBuilder().HasAnchorFunctions() ||
           (RuntimeEnabledFeatures::
                CSSAnchorPositioningCascadeFallbackEnabled() &&
            state.StyleBuilder().PositionFallback())) &&
@@ -393,6 +394,9 @@
   if (flags & static_cast<Flags>(Flag::kTreeScopedReference)) {
     state.SetHasTreeScopedReference();
   }
+  if (flags & static_cast<Flags>(Flag::kAnchorRelative)) {
+    builder.SetHasAnchorFunctions();
+  }
   if (flags & static_cast<Flags>(Flag::kLogicalDirectionRelative)) {
     builder.SetHasLogicalDirectionRelativeUnits();
   }
@@ -2368,10 +2372,19 @@
     return false;
   }
 
+  // TODO(crbug.com/40943044): If we need to disable the optimization for
+  // elements with position-fallback/anchor(), we probably need to disable
+  // for descendants of such elements as well.
   if (RuntimeEnabledFeatures::CSSAnchorPositioningCascadeFallbackEnabled() &&
       base_data->GetBaseComputedStyle()->PositionFallback()) {
     return false;
   }
+  if (RuntimeEnabledFeatures::CSSAnchorPositioningComputeAnchorEnabled() &&
+      base_data->GetBaseComputedStyle()->HasAnchorFunctions()) {
+    // TODO(crbug.com/41483417): Enable this optimization for styles with
+    // anchor queries.
+    return false;
+  }
 
   return true;
 }
diff --git a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
index f07e20a4..34277b8 100644
--- a/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_resolver_test.cc
@@ -19,6 +19,7 @@
 #include "third_party/blink/renderer/core/css/css_value_list.h"
 #include "third_party/blink/renderer/core/css/out_of_flow_data.h"
 #include "third_party/blink/renderer/core/css/parser/css_parser_local_context.h"
+#include "third_party/blink/renderer/core/css/post_style_update_scope.h"
 #include "third_party/blink/renderer/core/css/properties/computed_style_utils.h"
 #include "third_party/blink/renderer/core/css/properties/css_property_ref.h"
 #include "third_party/blink/renderer/core/css/properties/longhands.h"
@@ -117,9 +118,14 @@
         GetStyleEngine().GetPositionFallbackRule(*name);
     if (rule) {
       const CSSPropertyValueSet* set = rule->TryPropertyValueSetAt(index);
-      GetStyleEngine().UpdateStyleForOutOfFlow(element, set);
+      GetStyleEngine().UpdateStyleForOutOfFlow(element, set,
+                                               /* anchor_evaluator */ nullptr);
     }
   }
+
+  size_t GetCurrentOldStylesCount() {
+    return PostStyleUpdateScope::CurrentAnimationData()->old_styles_.size();
+  }
 };
 
 // Variant of `StyleResolverTest` that runs with and without CSSMPCImprovements
@@ -1658,6 +1664,117 @@
   EXPECT_FALSE(e->ComputedStyleRef().DependsOnSizeContainerQueries());
 }
 
+TEST_P(ParameterizedStyleResolverTest, AnchorQueriesMPC) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      .anchor {
+        position: absolute;
+        width: 100px;
+        height: 100px;
+      }
+      #anchor1 { left: 100px; }
+      #anchor2 { left: 150px; }
+      .anchored {
+        position: absolute;
+        left: anchor(left);
+      }
+    </style>
+    <div class=anchor id=anchor1>X</div>
+    <div class=anchor id=anchor2>Y</div>
+    <div class=anchored id=a anchor=anchor1>A</div>
+    <div class=anchored id=b anchor=anchor2>B</div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+
+  // #a and #b have identical styles, but the implicit anchor makes
+  // the anchor() queries give two different answers.
+
+  auto* a = GetDocument().getElementById(AtomicString("a"));
+  auto* b = GetDocument().getElementById(AtomicString("b"));
+
+  ASSERT_TRUE(a);
+  ASSERT_TRUE(b);
+
+  EXPECT_EQ("100px", ComputedValue("left", a->ComputedStyleRef()));
+  EXPECT_EQ("150px", ComputedValue("left", b->ComputedStyleRef()));
+}
+
+TEST_P(ParameterizedStyleResolverTest, AnchorQueryNoOldStyle) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  // This captures any calls to StoreOldStyleIfNeeded made during
+  // StyleResolver::ResolveStyle.
+  PostStyleUpdateScope post_style_update_scope(GetDocument());
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      #anchored {
+        position: absolute;
+        left: anchor(--a left, 42px);
+      }
+    </style>
+    <div id=anchored>A</div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(0u, GetCurrentOldStylesCount());
+}
+
+TEST_P(ParameterizedStyleResolverTest, AnchorQueryStoreOldStyle) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  // This captures any calls to StoreOldStyleIfNeeded made during
+  // StyleResolver::ResolveStyle.
+  PostStyleUpdateScope post_style_update_scope(GetDocument());
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      #anchored {
+        position: absolute;
+        left: anchor(--a left, 42px);
+        transition: left 1s;
+      }
+    </style>
+    <div id=anchored>A</div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ(1u, GetCurrentOldStylesCount());
+}
+
+TEST_P(ParameterizedStyleResolverTest, AnchorQueryBaseComputedStyle) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      #div {
+        position: absolute;
+        left: anchor(--a left, 42px);
+      }
+    </style>
+    <div id=div>A</div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+  Element* div = GetDocument().getElementById(AtomicString("div"));
+
+  // Create a situation where the base computed style optimization
+  // would normally be used.
+  auto* effect = CreateSimpleKeyframeEffectForTest(div, CSSPropertyID::kWidth,
+                                                   "50px", "100px");
+  GetDocument().Timeline().Play(effect);
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_EQ("50px", ComputedValue("width", *StyleForId("div")));
+  div->SetNeedsAnimationStyleRecalc();
+
+  // TODO(crbug.com/41483417): Enable this optimization for styles with
+  // anchor queries.
+  StyleResolverState state(GetDocument(), *div);
+  EXPECT_FALSE(StyleResolver::CanReuseBaseComputedStyle(state));
+}
+
 TEST_P(ParameterizedStyleResolverTest, NoCascadeLayers) {
   GetDocument().documentElement()->setInnerHTML(R"HTML(
     <style>
@@ -3647,6 +3764,101 @@
                                 GetMaxHeight(max_height->ComputedStyleRef())));
 }
 
+TEST_P(ParameterizedStyleResolverTest, NoAnchorFunction) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      div {
+        left: 10px;
+      }
+    </style>
+    <div id=div></div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* div = GetDocument().getElementById(AtomicString("div"));
+  ASSERT_TRUE(div);
+  EXPECT_FALSE(div->ComputedStyleRef().HasAnchorFunctions());
+}
+
+TEST_P(ParameterizedStyleResolverTest, HasAnchorFunction) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      div {
+        left: anchor(--a left);
+      }
+    </style>
+    <div id=div></div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* div = GetDocument().getElementById(AtomicString("div"));
+  ASSERT_TRUE(div);
+  EXPECT_TRUE(div->ComputedStyleRef().HasAnchorFunctions());
+}
+
+TEST_P(ParameterizedStyleResolverTest, HasAnchorFunctionImplicit) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      div {
+        left: anchor(left);
+      }
+    </style>
+    <div id=div></div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* div = GetDocument().getElementById(AtomicString("div"));
+  ASSERT_TRUE(div);
+  EXPECT_TRUE(div->ComputedStyleRef().HasAnchorFunctions());
+}
+
+TEST_P(ParameterizedStyleResolverTest, HasAnchorSizeFunction) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      div {
+        width: anchor-size(--a width);
+      }
+    </style>
+    <div id=div></div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* div = GetDocument().getElementById(AtomicString("div"));
+  ASSERT_TRUE(div);
+  EXPECT_TRUE(div->ComputedStyleRef().HasAnchorFunctions());
+}
+
+TEST_P(ParameterizedStyleResolverTest, HasAnchorSizeFunctionImplicit) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      div {
+        width: anchor-size(width);
+      }
+    </style>
+    <div id=div></div>
+  )HTML");
+
+  UpdateAllLifecyclePhasesForTest();
+
+  auto* div = GetDocument().getElementById(AtomicString("div"));
+  ASSERT_TRUE(div);
+  EXPECT_TRUE(div->ComputedStyleRef().HasAnchorFunctions());
+}
+
 TEST_P(StyleResolverTestCQ, CanAffectAnimationsMPC) {
   GetDocument().documentElement()->setInnerHTML(R"HTML(
     <style>
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index f09b6b5..fb440508 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -3466,8 +3466,10 @@
       container.GetLayoutObject());
 }
 
-void StyleEngine::UpdateStyleForOutOfFlow(Element& element,
-                                          const CSSPropertyValueSet* try_set) {
+void StyleEngine::UpdateStyleForOutOfFlow(
+    Element& element,
+    const CSSPropertyValueSet* try_set,
+    Length::AnchorEvaluator* anchor_evaluator) {
   // Note that we enter this function for any OOF element, not just those that
   // use position-fallback. Therefore, it's important to return immediately
   // without doing any work when `try_set` and `existing_try_set` both are
@@ -3476,8 +3478,21 @@
   OutOfFlowData* out_of_flow_data = element.GetOutOfFlowData();
   const CSSPropertyValueSet* existing_try_set =
       out_of_flow_data ? out_of_flow_data->GetTryPropertyValueSet() : nullptr;
-  if (existing_try_set == try_set) {
-    // No need to update style, the try set is the one we already used.
+
+  bool needs_update = false;
+
+  if (existing_try_set != try_set) {
+    element.EnsureOutOfFlowData().SetTryPropertyValueSet(try_set);
+    needs_update = true;
+  }
+  if (element.ComputedStyleRef().HasAnchorFunctions()) {
+    CHECK(RuntimeEnabledFeatures::CSSAnchorPositioningComputeAnchorEnabled());
+    // TODO(crbug.com/41483417): Store results on OutOfFlowData, and check
+    // if we actually need an update.
+    needs_update = true;
+  }
+
+  if (!needs_update) {
     return;
   }
 
@@ -3492,6 +3507,7 @@
   StyleRecalcContext style_recalc_context =
       StyleRecalcContext::FromAncestors(element);
   style_recalc_context.is_interleaved_oof = true;
+  style_recalc_context.anchor_evaluator = anchor_evaluator;
 
   StyleRecalcChange change = StyleRecalcChange().ForceRecalcChildren();
 
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index b4e9a7a..04ba05b3 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -615,7 +615,8 @@
   // Updates the style of `element`, and descendants if needed.
   // The provided `try_set` represents the declaration block from a @try rule.
   void UpdateStyleForOutOfFlow(Element& element,
-                               const CSSPropertyValueSet* try_set);
+                               const CSSPropertyValueSet* try_set,
+                               Length::AnchorEvaluator*);
   StyleRulePositionFallback* GetPositionFallbackRule(const ScopedCSSName&);
   void RecalcStyle();
 
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 9f76b24..7dab2bb5 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -4556,6 +4556,150 @@
   EXPECT_FALSE(GetDocument().NeedsLayoutTreeUpdateForNode(*a));
 }
 
+TEST_F(StyleEngineTest, UpdateStyleAndLayoutTreeWithAnchorQuery) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      #anchored {
+        position: absolute;
+        left: anchor(--a left, 42px);
+      }
+      #anchored.toggle {
+        left: anchor(--a left, 84px);
+      }
+
+      #inner { left: inherit; }
+    </style>
+    <main id=anchored>
+      <div id=inner></div>
+    </main>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(GetDocument().View()->NeedsLayout());
+
+  Element* anchored = GetDocument().getElementById(AtomicString("anchored"));
+  ASSERT_TRUE(anchored);
+  anchored->classList().Add(AtomicString("toggle"));
+
+  GetDocument().UpdateStyleAndLayoutTree();
+  EXPECT_FALSE(GetDocument().View()->NeedsLayout())
+      << "Layout should happen as part of UpdateStyleAndLayoutTree";
+
+  Element* inner = GetDocument().getElementById(AtomicString("inner"));
+  ASSERT_TRUE(inner);
+  EXPECT_EQ("84px", ComputedValue(inner, "left")->CssText());
+}
+
+TEST_F(StyleEngineTest, UpdateStyleAndLayoutTreeForElementWithAnchorQuery) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      #anchored {
+        position: absolute;
+        left: anchor(--a left, 42px);
+      }
+      #anchored.toggle {
+        left: anchor(--a left, 84px);
+      }
+
+      #inner { left: inherit; }
+    </style>
+    <main id=anchored>
+      <div id=inner></div>
+    </main>
+  )HTML");
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(GetDocument().View()->NeedsLayout());
+
+  Element* anchored = GetDocument().getElementById(AtomicString("anchored"));
+  ASSERT_TRUE(anchored);
+  anchored->classList().Add(AtomicString("toggle"));
+
+  Element* inner = GetDocument().getElementById(AtomicString("inner"));
+  ASSERT_TRUE(inner);
+
+  GetDocument().UpdateStyleAndLayoutTreeForElement(inner,
+                                                   DocumentUpdateReason::kTest);
+  EXPECT_FALSE(GetDocument().View()->NeedsLayout())
+      << "Layout should happen as part of UpdateStyleAndLayoutTreeForElement";
+
+  EXPECT_EQ("84px", ComputedValue(inner, "left")->CssText());
+}
+
+TEST_F(StyleEngineTest, AnchorQueryComputed) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      #anchor {
+        anchor-name: --a;
+        position: absolute;
+        width: 100px;
+        height: 100px;
+        left: 200px;
+        top: 300px;
+      }
+      #anchored {
+        position: absolute;
+        width: anchor-size(--a width);
+        height: anchor-size(--unknown height, 42px);
+        left: anchor(--a right);
+        top: anchor(--a bottom);
+      }
+    </style>
+    <div id=anchor>Anchor</div>
+    <div id=anchored>Anchored</div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* anchored = GetDocument().getElementById(AtomicString("anchored"));
+  ASSERT_TRUE(anchored);
+
+  EXPECT_EQ("300px", ComputedValue(anchored, "left")->CssText());
+  EXPECT_EQ("400px", ComputedValue(anchored, "top")->CssText());
+  EXPECT_EQ("100px", ComputedValue(anchored, "width")->CssText());
+  EXPECT_EQ("42px", ComputedValue(anchored, "height")->CssText());
+}
+
+TEST_F(StyleEngineTest, AnchorQueryComputedChild) {
+  ScopedCSSAnchorPositioningComputeAnchorForTest scoped_feature(true);
+
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+      #anchor {
+        anchor-name: --a;
+        position: absolute;
+        width: 100px;
+        height: 100px;
+        left: 200px;
+        top: 300px;
+      }
+      #anchored {
+        position: absolute;
+        width: anchor-size(--a width);
+        height: width: anchor-size(--a height);
+      }
+      #child {
+        width: anchor-size(--a width, 42px);
+        height: inherit;
+      }
+    </style>
+    <div id=anchor>Anchor</div>
+    <div id=anchored>
+      <div id=child>Child</div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhasesForTest();
+
+  Element* child = GetDocument().getElementById(AtomicString("child"));
+  ASSERT_TRUE(child);
+
+  // Non-absolutely positioned child may not evaluate queries.
+  EXPECT_EQ("42px", ComputedValue(child, "width")->CssText());
+}
+
 TEST_F(StyleEngineTest, VideoControlsReject) {
   GetDocument().body()->setInnerHTML(R"HTML(
     <video controls></video>
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index c67c1579..9243df83 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -3354,8 +3354,8 @@
     style = context->AdjustElementStyle(style);
   }
 
-  // TODO(crbug.com/1502666): Descendants can also depend on position-fallback.
-  if (style->DependsOnSizeContainerQueries() || style->PositionFallback()) {
+  if (style->DependsOnSizeContainerQueries() || style->PositionFallback() ||
+      style->HasAnchorFunctions()) {
     GetDocument().GetStyleEngine().SetStyleAffectedByLayout();
   }
 
@@ -3541,6 +3541,11 @@
 
   StyleRecalcContext child_recalc_context = local_style_recalc_context;
   child_recalc_context.is_interleaved_oof = false;
+  // If we're in StyleEngine::UpdateStyleForOutOfFlow, then anchor_evaluator
+  // may be non-nullptr to allow evaluation of anchor() and anchor-size()
+  // queries. Descendants of the current out-of-flow element must not be
+  // allowed to evaluate such queries, however.
+  child_recalc_context.anchor_evaluator = nullptr;
 
   if (const ComputedStyle* style = GetComputedStyle()) {
     if (style->CanMatchSizeContainerQueries(*this)) {
diff --git a/third_party/blink/renderer/core/frame/history.cc b/third_party/blink/renderer/core/frame/history.cc
index a8c1d44..acdca4b 100644
--- a/third_party/blink/renderer/core/frame/history.cc
+++ b/third_party/blink/renderer/core/frame/history.cc
@@ -45,7 +45,7 @@
 #include "third_party/blink/renderer/platform/bindings/v8_private_property.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_view.h"
@@ -226,11 +226,12 @@
                     /*url=*/String(""));
     // Pass the current task ID so it'd be set as the parent task for the future
     // popstate event.
-    auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+    auto* tracker =
+        scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
     scheduler::TaskAttributionInfo* task = nullptr;
     if (tracker && script_state->World().IsMainWorld() &&
         frame->IsOutermostMainFrame()) {
-      task = tracker->RunningTask(script_state->GetIsolate());
+      task = tracker->RunningTask();
       tracker->AddSameDocumentNavigationTask(task);
     }
     DCHECK(frame->Client());
diff --git a/third_party/blink/renderer/core/frame/local_dom_window.cc b/third_party/blink/renderer/core/frame/local_dom_window.cc
index 756b7b0..b4a959a7 100644
--- a/third_party/blink/renderer/core/frame/local_dom_window.cc
+++ b/third_party/blink/renderer/core/frame/local_dom_window.cc
@@ -151,7 +151,7 @@
 #include "third_party/blink/renderer/platform/scheduler/public/dummy_schedulers.h"
 #include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
 #include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/storage/blink_storage_key.h"
 #include "third_party/blink/renderer/platform/timer.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
@@ -176,9 +176,10 @@
 void SetCurrentTaskAsCallbackParent(
     CallbackFunctionWithTaskAttributionBase* callback) {
   ScriptState* script_state = callback->CallbackRelevantScriptState();
-  auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+  auto* tracker =
+      scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
   if (tracker && script_state->World().IsMainWorld()) {
-    callback->SetParentTask(tracker->RunningTask(script_state->GetIsolate()));
+    callback->SetParentTask(tracker->RunningTask());
   }
 }
 
@@ -929,9 +930,8 @@
   // method.
   std::unique_ptr<scheduler::TaskAttributionTracker::TaskScope>
       task_attribution_scope;
-  CHECK(ThreadScheduler::Current());
-  auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
   if (parent_task) {
+    auto* tracker = scheduler::TaskAttributionTracker::From(GetIsolate());
     ScriptState* script_state = ToScriptStateForMainWorld(GetFrame());
     if (script_state && tracker) {
       task_attribution_scope = tracker->CreateTaskScope(
diff --git a/third_party/blink/renderer/core/layout/absolute_utils.cc b/third_party/blink/renderer/core/layout/absolute_utils.cc
index 46da19f..b20c6ad68 100644
--- a/third_party/blink/renderer/core/layout/absolute_utils.cc
+++ b/third_party/blink/renderer/core/layout/absolute_utils.cc
@@ -354,9 +354,9 @@
     return true;
   }
   const auto& style = node.Style();
-  if (style.LogicalHeight().IsContentOrIntrinsic() ||
-      style.LogicalMinHeight().IsContentOrIntrinsic() ||
-      style.LogicalMaxHeight().IsContentOrIntrinsic()) {
+  if (style.LogicalHeight().HasContentOrIntrinsic() ||
+      style.LogicalMinHeight().HasContentOrIntrinsic() ||
+      style.LogicalMaxHeight().HasContentOrIntrinsic()) {
     return false;
   }
   if (style.LogicalHeight().IsAuto()) {
@@ -597,7 +597,7 @@
     const BoxStrut& border_padding,
     const std::optional<LogicalSize>& replaced_size,
     WritingDirectionMode container_writing_direction,
-    const AnchorEvaluatorImpl* anchor_evaluator,
+    AnchorEvaluatorImpl* anchor_evaluator,
     LogicalOofDimensions* dimensions) {
   DCHECK(dimensions);
   DCHECK_GE(imcb.InlineSize(), LayoutUnit());
@@ -740,7 +740,7 @@
     const BoxStrut& border_padding,
     const std::optional<LogicalSize>& replaced_size,
     WritingDirectionMode container_writing_direction,
-    const AnchorEvaluatorImpl* anchor_evaluator,
+    AnchorEvaluatorImpl* anchor_evaluator,
     LogicalOofDimensions* dimensions) {
   DCHECK(dimensions);
   DCHECK_GE(imcb.BlockSize(), LayoutUnit());
@@ -831,10 +831,12 @@
 
     // Manually resolve any intrinsic/content min/max block-sizes.
     // TODO(crbug.com/1135207): |ComputeMinMaxBlockSizes()| should handle this.
-    if (style.LogicalMinHeight().IsContentOrIntrinsic())
+    if (style.LogicalMinHeight().HasContentOrIntrinsic()) {
       min_max_block_sizes.min_size = IntrinsicBlockSizeFunc();
-    if (style.LogicalMaxHeight().IsContentOrIntrinsic())
+    }
+    if (style.LogicalMaxHeight().HasContentOrIntrinsic()) {
       min_max_block_sizes.max_size = IntrinsicBlockSizeFunc();
+    }
     min_max_block_sizes.max_size =
         std::max(min_max_block_sizes.max_size, min_max_block_sizes.min_size);
 
diff --git a/third_party/blink/renderer/core/layout/absolute_utils.h b/third_party/blink/renderer/core/layout/absolute_utils.h
index a5429ea..5316a6e0 100644
--- a/third_party/blink/renderer/core/layout/absolute_utils.h
+++ b/third_party/blink/renderer/core/layout/absolute_utils.h
@@ -174,7 +174,7 @@
     const BoxStrut& border_padding,
     const std::optional<LogicalSize>& replaced_size,
     WritingDirectionMode container_writing_direction,
-    const AnchorEvaluatorImpl* anchor_evaluator,
+    AnchorEvaluatorImpl* anchor_evaluator,
     LogicalOofDimensions* dimensions);
 
 // If layout was performed to determine the position, this will be returned
@@ -188,7 +188,7 @@
     const BoxStrut& border_padding,
     const std::optional<LogicalSize>& replaced_size,
     WritingDirectionMode container_writing_direction,
-    const AnchorEvaluatorImpl* anchor_evaluator,
+    AnchorEvaluatorImpl* anchor_evaluator,
     LogicalOofDimensions* dimensions);
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/anchor_query.cc b/third_party/blink/renderer/core/layout/anchor_query.cc
index f48256e9..6de4ac8 100644
--- a/third_party/blink/renderer/core/layout/anchor_query.cc
+++ b/third_party/blink/renderer/core/layout/anchor_query.cc
@@ -362,7 +362,7 @@
 }
 
 std::optional<LayoutUnit> AnchorEvaluatorImpl::Evaluate(
-    const CalculationExpressionNode& node) const {
+    const CalculationExpressionNode& node) {
   DCHECK(node.IsAnchorQuery());
   const auto& anchor_query = To<CalculationExpressionAnchorQueryNode>(node);
   switch (anchor_query.Type()) {
diff --git a/third_party/blink/renderer/core/layout/anchor_query.h b/third_party/blink/renderer/core/layout/anchor_query.h
index 7c9024e9..b399f428 100644
--- a/third_party/blink/renderer/core/layout/anchor_query.h
+++ b/third_party/blink/renderer/core/layout/anchor_query.h
@@ -315,8 +315,7 @@
 
   // Evaluates the given anchor query. Returns nullopt if the query invalid
   // (e.g., no target or wrong axis).
-  std::optional<LayoutUnit> Evaluate(
-      const CalculationExpressionNode&) const override;
+  std::optional<LayoutUnit> Evaluate(const CalculationExpressionNode&) override;
 
   // Finds the rect of the element referenced by the `position-fallback-bounds`
   // property, or nullopt if there's no such element.
diff --git a/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc b/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc
index c086685..dad10a1 100644
--- a/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/flex/flex_layout_algorithm.cc
@@ -2571,14 +2571,15 @@
 
     // Only allow growth if the item's block-size is auto and either the item
     // can't shrink or its min-height is auto.
-    if (item_style.LogicalHeight().IsAutoOrContentOrIntrinsic() &&
+    if (item_style.LogicalHeight().HasAutoOrContentOrIntrinsic() &&
         (!can_shrink || algorithm_.ShouldApplyMinSizeAutoForChild(
-                            *item.ng_input_node.GetLayoutBox())))
+                            *item.ng_input_node.GetLayoutBox()))) {
       return true;
+    }
   } else {
     // Don't grow if the item's block-size should be the same as its container.
     if (WillChildCrossSizeBeContainerCrossSize(item.ng_input_node) &&
-        !Style().LogicalHeight().IsAutoOrContentOrIntrinsic()) {
+        !Style().LogicalHeight().HasAutoOrContentOrIntrinsic()) {
       return false;
     }
 
diff --git a/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
index 3027f1d..ec73549 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
@@ -3702,8 +3702,9 @@
 
     // Only allow growth on "auto" block-size items, (a fixed block-size item
     // can't grow).
-    if (!item_style.LogicalHeight().IsAutoOrContentOrIntrinsic())
+    if (!item_style.LogicalHeight().HasAutoOrContentOrIntrinsic()) {
       return false;
+    }
 
     // Only allow growth on items which only span a single row.
     if (grid_item.SpanSize(kForRows) > 1)
@@ -3716,7 +3717,7 @@
       return false;
 
     return !grid_item.IsSpanningFixedMinimumTrack(kForRows) ||
-           Style().LogicalHeight().IsAutoOrContentOrIntrinsic();
+           Style().LogicalHeight().HasAutoOrContentOrIntrinsic();
   };
 
   wtf_size_t previous_expansion_row_set_index = kNotFound;
diff --git a/third_party/blink/renderer/core/layout/inline/inline_item.h b/third_party/blink/renderer/core/layout/inline/inline_item.h
index 130bbf0..ab440fb2 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_item.h
+++ b/third_party/blink/renderer/core/layout/inline/inline_item.h
@@ -271,7 +271,8 @@
 
   unsigned start_offset_;
   unsigned end_offset_;
-  Member<const ShapeResult> shape_result_;
+  Member<const ShapeResult> shape_result_{
+      nullptr, Member<const ShapeResult>::AtomicInitializerTag{}};
   Member<LayoutObject> layout_object_;
 
   InlineItemType type_;
diff --git a/third_party/blink/renderer/core/layout/length_utils.cc b/third_party/blink/renderer/core/layout/length_utils.cc
index ac49574a..013ef37b 100644
--- a/third_party/blink/renderer/core/layout/length_utils.cc
+++ b/third_party/blink/renderer/core/layout/length_utils.cc
@@ -40,8 +40,9 @@
   // TODO(https://crbug.com/313072): calc-size() doesn't work correctly
   // when this function returns true.
 
-  if (length.IsPercentOrCalc())
+  if (length.HasPercent()) {
     return constraint_space.PercentageResolutionInlineSize() == kIndefiniteSize;
+  }
 
   if (length.IsFillAvailable())
     return constraint_space.AvailableSize().inline_size == kIndefiniteSize;
@@ -72,7 +73,7 @@
   if (length.IsAuto() || length.IsMinContent() || length.IsMaxContent() ||
       length.IsMinIntrinsic() || length.IsFitContent() || length.IsNone())
     return true;
-  if (length.IsPercentOrCalc()) {
+  if (length.HasPercent()) {
     const LayoutUnit percentage_resolution_size =
         override_percentage_resolution_size
             ? *override_percentage_resolution_size
@@ -93,7 +94,7 @@
     const std::optional<MinMaxSizes>& min_max_sizes,
     const Length& length,
     LayoutUnit override_available_size,
-    const Length::AnchorEvaluator* anchor_evaluator) {
+    Length::AnchorEvaluator* anchor_evaluator) {
   DCHECK_EQ(constraint_space.GetWritingMode(), style.GetWritingMode());
 
   switch (length.GetType()) {
@@ -112,7 +113,8 @@
     case Length::kCalculated: {
       const LayoutUnit percentage_resolution_size =
           constraint_space.PercentageResolutionInlineSize();
-      DCHECK(length.IsFixed() || percentage_resolution_size != kIndefiniteSize)
+      DCHECK(!length.HasPercent() ||
+             percentage_resolution_size != kIndefiniteSize)
           << length.ToString();
       LayoutUnit value = MinimumValueForLength(
           length, percentage_resolution_size,
@@ -169,7 +171,7 @@
     LayoutUnit intrinsic_size,
     LayoutUnit override_available_size,
     const LayoutUnit* override_percentage_resolution_size,
-    const Length::AnchorEvaluator* anchor_evaluator) {
+    Length::AnchorEvaluator* anchor_evaluator) {
   DCHECK_EQ(constraint_space.GetWritingMode(), style.GetWritingMode());
 
   switch (length.GetType()) {
@@ -190,7 +192,8 @@
           override_percentage_resolution_size
               ? *override_percentage_resolution_size
               : constraint_space.PercentageResolutionBlockSize();
-      DCHECK(length.IsFixed() || percentage_resolution_size != kIndefiniteSize);
+      DCHECK(!length.HasPercent() ||
+             percentage_resolution_size != kIndefiniteSize);
       LayoutUnit value = MinimumValueForLength(
           length, percentage_resolution_size,
           {.anchor_evaluator = anchor_evaluator,
@@ -404,12 +407,11 @@
                                               MinMaxSizesFunc);
 }
 
-MinMaxSizes ComputeMinMaxBlockSizes(
-    const ConstraintSpace& space,
-    const ComputedStyle& style,
-    const BoxStrut& border_padding,
-    LayoutUnit override_available_size,
-    const Length::AnchorEvaluator* anchor_evaluator) {
+MinMaxSizes ComputeMinMaxBlockSizes(const ConstraintSpace& space,
+                                    const ComputedStyle& style,
+                                    const BoxStrut& border_padding,
+                                    LayoutUnit override_available_size,
+                                    Length::AnchorEvaluator* anchor_evaluator) {
   if (const std::optional<MinMaxSizes> override_sizes =
           space.OverrideMinMaxBlockSizes()) {
     DCHECK_GE(override_sizes->max_size, override_sizes->min_size);
@@ -684,7 +686,7 @@
     const ConstraintSpace& space,
     const BoxStrut& border_padding,
     ReplacedSizeMode mode,
-    const Length::AnchorEvaluator* anchor_evaluator) {
+    Length::AnchorEvaluator* anchor_evaluator) {
   DCHECK(node.IsReplaced());
 
   const ComputedStyle& style = node.Style();
@@ -721,7 +723,7 @@
     if (space.IsFixedBlockSize()) {
       replaced_block = space.AvailableSize().block_size;
       DCHECK_GE(*replaced_block, 0);
-    } else if (!block_length.IsAutoOrContentOrIntrinsic() ||
+    } else if (!block_length.HasAutoOrContentOrIntrinsic() ||
                (space.IsBlockAutoBehaviorStretch() &&
                 space.AvailableSize().block_size != kIndefiniteSize)) {
       Length block_length_to_resolve = block_length;
@@ -945,12 +947,11 @@
 }  // namespace
 
 // Computes size for a replaced element.
-LogicalSize ComputeReplacedSize(
-    const BlockNode& node,
-    const ConstraintSpace& space,
-    const BoxStrut& border_padding,
-    ReplacedSizeMode mode,
-    const Length::AnchorEvaluator* anchor_evaluator) {
+LogicalSize ComputeReplacedSize(const BlockNode& node,
+                                const ConstraintSpace& space,
+                                const BoxStrut& border_padding,
+                                ReplacedSizeMode mode,
+                                Length::AnchorEvaluator* anchor_evaluator) {
   DCHECK(node.IsReplaced());
 
   const auto* svg_root = DynamicTo<LayoutSVGRoot>(node.GetLayoutBox());
diff --git a/third_party/blink/renderer/core/layout/length_utils.h b/third_party/blink/renderer/core/layout/length_utils.h
index 00f8ac3..2a3d68c 100644
--- a/third_party/blink/renderer/core/layout/length_utils.h
+++ b/third_party/blink/renderer/core/layout/length_utils.h
@@ -28,9 +28,9 @@
 class Length;
 
 inline bool NeedMinMaxSize(const ComputedStyle& style) {
-  return style.LogicalWidth().IsContentOrIntrinsic() ||
-         style.LogicalMinWidth().IsContentOrIntrinsic() ||
-         style.LogicalMaxWidth().IsContentOrIntrinsic();
+  return style.LogicalWidth().HasContentOrIntrinsic() ||
+         style.LogicalMinWidth().HasContentOrIntrinsic() ||
+         style.LogicalMaxWidth().HasContentOrIntrinsic();
 }
 
 CORE_EXPORT LayoutUnit
@@ -71,7 +71,7 @@
     const std::optional<MinMaxSizes>&,
     const Length&,
     LayoutUnit override_available_size = kIndefiniteSize,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr);
+    Length::AnchorEvaluator* anchor_evaluator = nullptr);
 
 // Same as ResolveInlineLengthInternal, except here |intrinsic_size| roughly
 // plays the part of |MinMaxSizes|.
@@ -83,7 +83,7 @@
     LayoutUnit intrinsic_size,
     LayoutUnit override_available_size = kIndefiniteSize,
     const LayoutUnit* override_percentage_resolution_size = nullptr,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr);
+    Length::AnchorEvaluator* anchor_evaluator = nullptr);
 
 // In this file the template parameter MinMaxSizesFunc should have the
 // following form:
@@ -103,13 +103,13 @@
     const MinMaxSizesFunc& min_max_sizes_func,
     const Length& length,
     LayoutUnit override_available_size = kIndefiniteSize,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr) {
+    Length::AnchorEvaluator* anchor_evaluator = nullptr) {
   if (LIKELY(length.IsAuto() ||
              InlineLengthUnresolvable(constraint_space, length)))
     return border_padding.InlineSum();
 
   std::optional<MinMaxSizes> min_max_sizes;
-  if (length.IsContentOrIntrinsic()) {
+  if (length.HasContentOrIntrinsic()) {
     min_max_sizes =
         min_max_sizes_func(length.IsMinIntrinsic() ? MinMaxSizesType::kIntrinsic
                                                    : MinMaxSizesType::kContent)
@@ -130,13 +130,13 @@
     const MinMaxSizesFunc& min_max_sizes_func,
     const Length& length,
     LayoutUnit override_available_size = kIndefiniteSize,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr) {
+    Length::AnchorEvaluator* anchor_evaluator = nullptr) {
   if (LIKELY(length.IsNone() ||
              InlineLengthUnresolvable(constraint_space, length)))
     return LayoutUnit::Max();
 
   std::optional<MinMaxSizes> min_max_sizes;
-  if (length.IsContentOrIntrinsic()) {
+  if (length.HasContentOrIntrinsic()) {
     min_max_sizes =
         min_max_sizes_func(length.IsMinIntrinsic() ? MinMaxSizesType::kIntrinsic
                                                    : MinMaxSizesType::kContent)
@@ -157,10 +157,10 @@
     const MinMaxSizesFunc& min_max_sizes_func,
     const Length& length,
     LayoutUnit override_available_size = kIndefiniteSize,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr) {
+    Length::AnchorEvaluator* anchor_evaluator = nullptr) {
   DCHECK(!length.IsAuto());
   std::optional<MinMaxSizes> min_max_sizes;
-  if (length.IsContentOrIntrinsic()) {
+  if (length.HasContentOrIntrinsic()) {
     min_max_sizes =
         min_max_sizes_func(length.IsMinIntrinsic() ? MinMaxSizesType::kIntrinsic
                                                    : MinMaxSizesType::kContent)
@@ -180,7 +180,7 @@
     const Length& length,
     LayoutUnit override_available_size = kIndefiniteSize,
     const LayoutUnit* override_percentage_resolution_size = nullptr,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr) {
+    Length::AnchorEvaluator* anchor_evaluator = nullptr) {
   if (LIKELY(BlockLengthUnresolvable(constraint_space, length,
                                      override_percentage_resolution_size)))
     return border_padding.BlockSum();
@@ -199,7 +199,7 @@
     const Length& length,
     LayoutUnit override_available_size = kIndefiniteSize,
     const LayoutUnit* override_percentage_resolution_size = nullptr,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr) {
+    Length::AnchorEvaluator* anchor_evaluator = nullptr) {
   if (LIKELY(BlockLengthUnresolvable(constraint_space, length,
                                      override_percentage_resolution_size)))
     return LayoutUnit::Max();
@@ -219,12 +219,13 @@
     LayoutUnit intrinsic_size,
     LayoutUnit override_available_size = kIndefiniteSize,
     const LayoutUnit* override_percentage_resolution_size = nullptr,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr) {
+    Length::AnchorEvaluator* anchor_evaluator = nullptr) {
   DCHECK(!length.IsAuto());
-  if (UNLIKELY((length.IsPercentOrCalc() || length.IsFillAvailable()) &&
+  if (UNLIKELY((length.HasPercent() || length.IsFillAvailable()) &&
                BlockLengthUnresolvable(constraint_space, length,
-                                       override_percentage_resolution_size)))
+                                       override_percentage_resolution_size))) {
     return intrinsic_size;
+  }
 
   return ResolveBlockLengthInternal(
       constraint_space, style, border_padding, length, intrinsic_size,
@@ -240,15 +241,17 @@
     const Length& length,
     const IntrinsicBlockSizeFunc& intrinsic_block_size_func,
     LayoutUnit override_available_size = kIndefiniteSize,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr) {
+    Length::AnchorEvaluator* anchor_evaluator = nullptr) {
   DCHECK(!length.IsAuto());
-  if (UNLIKELY((length.IsPercentOrCalc() || length.IsFillAvailable()) &&
-               BlockLengthUnresolvable(constraint_space, length)))
+  if (UNLIKELY((length.HasPercent() || length.IsFillAvailable()) &&
+               BlockLengthUnresolvable(constraint_space, length))) {
     return intrinsic_block_size_func();
+  }
 
   LayoutUnit intrinsic_block_size = kIndefiniteSize;
-  if (length.IsContentOrIntrinsic())
+  if (length.HasContentOrIntrinsic()) {
     intrinsic_block_size = intrinsic_block_size_func();
+  }
 
   return ResolveBlockLengthInternal(
       constraint_space, style, border_padding, length, intrinsic_block_size,
@@ -262,7 +265,7 @@
     const ComputedStyle&,
     const BoxStrut& border_padding,
     LayoutUnit override_available_size = kIndefiniteSize,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr);
+    Length::AnchorEvaluator* anchor_evaluator = nullptr);
 
 MinMaxSizes ComputeTransferredMinMaxInlineSizes(
     const LogicalSize& ratio,
@@ -292,7 +295,7 @@
     const MinMaxSizesFunc& min_max_sizes_func,
     const Length* opt_min_length = nullptr,
     LayoutUnit override_available_size = kIndefiniteSize,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr) {
+    Length::AnchorEvaluator* anchor_evaluator = nullptr) {
   const ComputedStyle& style = node.Style();
   const Length& min_length =
       opt_min_length ? *opt_min_length : style.LogicalMinWidth();
@@ -479,12 +482,12 @@
 // This will handle both intrinsic, and layout calculations depending on the
 // space provided. (E.g. if the available inline-size is indefinite it will
 // return the intrinsic size).
-CORE_EXPORT LogicalSize ComputeReplacedSize(
-    const BlockNode&,
-    const ConstraintSpace&,
-    const BoxStrut& border_padding,
-    ReplacedSizeMode = ReplacedSizeMode::kNormal,
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr);
+CORE_EXPORT LogicalSize
+ComputeReplacedSize(const BlockNode&,
+                    const ConstraintSpace&,
+                    const BoxStrut& border_padding,
+                    ReplacedSizeMode = ReplacedSizeMode::kNormal,
+                    Length::AnchorEvaluator* anchor_evaluator = nullptr);
 
 // Based on available inline size, CSS computed column-width, CSS computed
 // column-count and CSS used column-gap, return CSS used column-count.
diff --git a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
index 60eb484..7c67ca7 100644
--- a/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
+++ b/third_party/blink/renderer/core/layout/out_of_flow_layout_part.cc
@@ -135,8 +135,11 @@
   STACK_ALLOCATED();
 
  public:
-  explicit OOFCandidateStyleIterator(const LayoutObject& object)
-      : element_(DynamicTo<Element>(object.GetNode())), style_(object.Style()) {
+  explicit OOFCandidateStyleIterator(const LayoutObject& object,
+                                     Length::AnchorEvaluator* anchor_evaluator)
+      : element_(DynamicTo<Element>(object.GetNode())),
+        style_(object.Style()),
+        anchor_evaluator_(anchor_evaluator) {
     Initialize();
   }
 
@@ -249,7 +252,12 @@
     CHECK(element_);
     if (RuntimeEnabledFeatures::CSSAnchorPositioningCascadeFallbackEnabled()) {
       StyleEngine& style_engine = element_->GetDocument().GetStyleEngine();
-      style_engine.UpdateStyleForOutOfFlow(*element_, try_set);
+      Length::AnchorEvaluator* anchor_evaluator =
+          RuntimeEnabledFeatures::CSSAnchorPositioningCascadeFallbackEnabled()
+              ? anchor_evaluator_
+              : nullptr;
+      style_engine.UpdateStyleForOutOfFlow(*element_, try_set,
+                                           anchor_evaluator);
     }
     CHECK(element_->GetLayoutObject());
     // Returns LayoutObject ComputedStyle instead of element style for layout
@@ -264,6 +272,11 @@
   // Otherwise, the base style for generating auto anchor fallbacks.
   const ComputedStyle* style_ = nullptr;
 
+  // When CSSAnchorPositioningComputeAnchor is enabled, this evaluator is
+  // passed to StyleEngine::UpdateStyleForOutOfFlow to evaluate anchor queries
+  // on the computed style.
+  Length::AnchorEvaluator* anchor_evaluator_ = nullptr;
+
   // If the current style is created from an `@try` rule, this holds
   // the parent rule. Otherwise nullptr.
   const StyleRulePositionFallback* position_fallback_rule_ = nullptr;
@@ -1825,7 +1838,8 @@
 
   // If `@position-fallback` exists, let |TryCalculateOffset| check if the
   // result fits the available space.
-  OOFCandidateStyleIterator iter(*node_info.node.GetLayoutBox());
+  OOFCandidateStyleIterator iter(*node_info.node.GetLayoutBox(),
+                                 anchor_evaluator);
   std::optional<OffsetInfo> offset_info;
   while (!offset_info) {
     const bool has_next_fallback_style = iter.HasNextStyle();
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 2a8d1b1e..01a4f9c0 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -164,7 +164,7 @@
 #include "third_party/blink/renderer/platform/scheduler/main_thread/frame_scheduler_impl.h"
 #include "third_party/blink/renderer/platform/scheduler/public/event_loop.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/storage/blink_storage_key.h"
 #include "third_party/blink/renderer/platform/web_test_support.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
@@ -1035,9 +1035,8 @@
     // If `heuristics` exists, it means we're in an outermost main frame; if
     // `soft_navigation_heuristics_task_id` exists, it means the task state
     // being propagated was captured in a main world history API call.
-    CHECK(ThreadScheduler::Current());
-    if (auto* tracker =
-            ThreadScheduler::Current()->GetTaskAttributionTracker()) {
+    if (auto* tracker = scheduler::TaskAttributionTracker::From(
+            frame_->DomWindow()->GetIsolate())) {
       // Get the TaskId from tracker. We're passing that to dispatchEvent
       // further down, but regardless, we want to get it and previous tasks out
       // of the tracker's task queue, to enable them to get garbage collected if
@@ -2616,8 +2615,8 @@
 
   // Previous same-document navigation tasks are not relevant once a
   // cross-document navigation has happened.
-  CHECK(ThreadScheduler::Current());
-  if (auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker()) {
+  if (auto* tracker = scheduler::TaskAttributionTracker::From(
+          frame_->DomWindow()->GetIsolate())) {
     tracker->ResetSameDocumentNavigationTasks();
   }
 
diff --git a/third_party/blink/renderer/core/messaging/message_port.cc b/third_party/blink/renderer/core/messaging/message_port.cc
index 9a12b88..e74064c 100644
--- a/third_party/blink/renderer/core/messaging/message_port.cc
+++ b/third_party/blink/renderer/core/messaging/message_port.cc
@@ -53,7 +53,6 @@
 #include "third_party/blink/renderer/platform/bindings/thread_debugger.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
 
@@ -137,12 +136,11 @@
   // Only pass the parent task ID if we're in the main world, as isolated world
   // task tracking is not yet supported. Also, only pass the parent task if the
   // port is still entangled to its initially entangled port.
-  if (auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+  if (auto* tracker =
+          scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
       initially_entangled_port_ && tracker &&
       script_state->World().IsMainWorld()) {
-    scheduler::TaskAttributionInfo* task =
-        tracker->RunningTask(script_state->GetIsolate());
-    if (task) {
+    if (scheduler::TaskAttributionInfo* task = tracker->RunningTask()) {
       // Since `initially_entangled_port_` is not nullptr, neither should be
       // `post_message_task_container_`.
       CHECK(post_message_task_container_);
@@ -377,9 +375,8 @@
     // v8::Context may still be empty (and hence
     // ExecutionContext::GetCurrentWorld returns null).
     if (ScriptState* script_state = ToScriptStateForMainWorld(context)) {
-      CHECK(ThreadScheduler::Current());
-      if (auto* tracker =
-              ThreadScheduler::Current()->GetTaskAttributionTracker()) {
+      if (auto* tracker = scheduler::TaskAttributionTracker::From(
+              script_state->GetIsolate())) {
         // Since `initially_entangled_port_` is not nullptr, neither should be
         // its `post_message_task_container_`.
         CHECK(entangled_port->post_message_task_container_);
diff --git a/third_party/blink/renderer/core/navigation_api/navigation_api.cc b/third_party/blink/renderer/core/navigation_api/navigation_api.cc
index 1bfbef5..56bd2b0 100644
--- a/third_party/blink/renderer/core/navigation_api/navigation_api.cc
+++ b/third_party/blink/renderer/core/navigation_api/navigation_api.cc
@@ -47,7 +47,7 @@
 #include "third_party/blink/renderer/platform/bindings/exception_context.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 
 namespace blink {
 
@@ -625,9 +625,10 @@
     if (SoftNavigationHeuristics* heuristics =
             SoftNavigationHeuristics::From(*window_)) {
       heuristics->SameDocumentNavigationStarted();
-      auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+      auto* tracker =
+          scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
       if (tracker && script_state->World().IsMainWorld()) {
-        task = tracker->RunningTask(script_state->GetIsolate());
+        task = tracker->RunningTask();
         tracker->AddSameDocumentNavigationTask(task);
       }
     }
@@ -896,7 +897,8 @@
 
 void NavigationApi::InformAboutCanceledNavigation(
     CancelNavigationReason reason) {
-  if (auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+  if (auto* tracker =
+          scheduler::TaskAttributionTracker::From(window_->GetIsolate());
       tracker && reason != CancelNavigationReason::kNavigateEvent) {
     tracker->ResetSameDocumentNavigationTasks();
   }
diff --git a/third_party/blink/renderer/core/script/pending_script.cc b/third_party/blink/renderer/core/script/pending_script.cc
index de07db2..9a100ec8 100644
--- a/third_party/blink/renderer/core/script/pending_script.cc
+++ b/third_party/blink/renderer/core/script/pending_script.cc
@@ -42,7 +42,6 @@
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 
 namespace blink {
 
@@ -167,9 +166,8 @@
   std::unique_ptr<scheduler::TaskAttributionTracker::TaskScope>
       task_attribution_scope;
   if (ScriptState* script_state = ToScriptStateForMainWorld(frame)) {
-    DCHECK(ThreadScheduler::Current());
-    if (auto* tracker =
-            ThreadScheduler::Current()->GetTaskAttributionTracker()) {
+    if (auto* tracker = scheduler::TaskAttributionTracker::From(
+            script_state->GetIsolate())) {
       task_attribution_scope = tracker->CreateTaskScope(
           script_state, parent_task_,
           scheduler::TaskAttributionTracker::TaskScopeType::kScriptExecution);
diff --git a/third_party/blink/renderer/core/script/script_loader.cc b/third_party/blink/renderer/core/script/script_loader.cc
index 123621d..efb964d 100644
--- a/third_party/blink/renderer/core/script/script_loader.cc
+++ b/third_party/blink/renderer/core/script/script_loader.cc
@@ -79,7 +79,7 @@
 #include "third_party/blink/renderer/platform/loader/subresource_integrity.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -87,19 +87,20 @@
 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_view.h"
 
+namespace blink {
+
 namespace {
-blink::scheduler::TaskAttributionInfo* GetRunningTask(
-    blink::ScriptState* script_state) {
+
+scheduler::TaskAttributionInfo* GetRunningTask(ScriptState* script_state) {
   auto* tracker =
-      blink::ThreadScheduler::Current()->GetTaskAttributionTracker();
+      scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
   if (!script_state || !script_state->World().IsMainWorld() || !tracker) {
     return nullptr;
   }
-  return tracker->RunningTask(script_state->GetIsolate());
+  return tracker->RunningTask();
 }
 
 }  // namespace
-namespace blink {
 
 ScriptLoader::ScriptLoader(ScriptElementBase* element,
                            const CreateElementFlags flags)
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index 0fb6dea1..4d672cd 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -2686,8 +2686,9 @@
 
 bool ComputedStyle::IsInterleavingRoot(const ComputedStyle* style) {
   const ComputedStyle* unensured = ComputedStyle::NullifyEnsured(style);
-  return unensured && (unensured->IsContainerForSizeContainerQueries() ||
-                       unensured->PositionFallback());
+  return unensured &&
+         (unensured->IsContainerForSizeContainerQueries() ||
+          unensured->PositionFallback() || unensured->HasAnchorFunctions());
 }
 
 bool ComputedStyle::CalculateIsStackingContextWithoutContainment() const {
diff --git a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5 b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
index 30c5de1..5d2bb33a 100644
--- a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
+++ b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
@@ -1199,5 +1199,12 @@
       field_group: "*",
       default_value: "false",
     },
+    {
+      name: "HasAnchorFunctions",
+      field_template: "monotonic_flag",
+      field_group: "surround",
+      default_value: "false",
+      custom_compare: true,
+    },
   ],
 }
diff --git a/third_party/blink/renderer/core/style/grid_track_size.h b/third_party/blink/renderer/core/style/grid_track_size.h
index 27752f6..e8b826c 100644
--- a/third_party/blink/renderer/core/style/grid_track_size.h
+++ b/third_party/blink/renderer/core/style/grid_track_size.h
@@ -112,8 +112,8 @@
   GridTrackSizeType GetType() const { return type_; }
 
   bool IsContentSized() const {
-    return min_track_breadth_.IsAutoOrContentOrIntrinsic() ||
-           max_track_breadth_.IsAutoOrContentOrIntrinsic();
+    return min_track_breadth_.HasAutoOrContentOrIntrinsic() ||
+           max_track_breadth_.HasAutoOrContentOrIntrinsic();
   }
   bool IsFitContent() const { return type_ == kFitContentTrackSizing; }
   bool HasPercentage() const {
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
index d582726..316d703 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.cc
@@ -16,7 +16,6 @@
 #include "third_party/blink/renderer/core/paint/timing/paint_timing.h"
 #include "third_party/blink/renderer/core/paint/timing/paint_timing_detector.h"
 #include "third_party/blink/renderer/core/timing/dom_window_performance.h"
-#include "third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 
@@ -215,12 +214,9 @@
   if (potential_soft_navigation_tasks_.empty()) {
     return std::nullopt;
   }
-  ThreadScheduler* scheduler = ThreadScheduler::Current();
-  DCHECK(scheduler);
-  if (scheduler::TaskAttributionTracker* tracker =
-          scheduler->GetTaskAttributionTracker()) {
-    scheduler::TaskAttributionInfo* task =
-        tracker->RunningTask(GetSupplementable()->GetIsolate());
+  if (auto* tracker = scheduler::TaskAttributionTracker::From(
+          GetSupplementable()->GetIsolate())) {
+    scheduler::TaskAttributionInfo* task = tracker->RunningTask();
     if (!task) {
       return std::nullopt;
     }
@@ -655,7 +651,9 @@
     }
   }
 
-  return SoftNavigationHeuristics::EventScope(this);
+  return SoftNavigationHeuristics::EventScope(
+      this, scheduler::TaskAttributionTracker::From(
+                GetSupplementable()->GetIsolate()));
 }
 
 void SoftNavigationHeuristics::OnSoftNavigationEventScopeDestroyed() {
@@ -675,16 +673,13 @@
 // SoftNavigationHeuristics::EventScope implementation
 // ///////////////////////////////////////////
 SoftNavigationHeuristics::EventScope::EventScope(
-    SoftNavigationHeuristics* heuristics)
+    SoftNavigationHeuristics* heuristics,
+    scheduler::TaskAttributionTracker* tracker)
     : heuristics_(heuristics) {
   CHECK(heuristics_);
-  ThreadScheduler* scheduler = ThreadScheduler::Current();
-  DCHECK(scheduler);
-  auto* tracker = scheduler->GetTaskAttributionTracker();
-  if (!tracker) {
-    return;
+  if (tracker) {
+    observer_scope_ = tracker->RegisterObserver(heuristics_);
   }
-  observer_scope_ = tracker->RegisterObserver(heuristics_);
 }
 
 SoftNavigationHeuristics::EventScope::EventScope(EventScope&& other)
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
index bfcc34a..0c0a609 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics.h
@@ -76,7 +76,7 @@
    private:
     friend class SoftNavigationHeuristics;
 
-    explicit EventScope(SoftNavigationHeuristics*);
+    EventScope(SoftNavigationHeuristics*, scheduler::TaskAttributionTracker*);
 
     SoftNavigationHeuristics* heuristics_;
     std::optional<scheduler::TaskAttributionTracker::ObserverScope>
diff --git a/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc b/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc
index 5e56f2b..d3cff14b 100644
--- a/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc
+++ b/third_party/blink/renderer/core/timing/soft_navigation_heuristics_test.cc
@@ -13,7 +13,6 @@
 #include "third_party/blink/renderer/platform/heap/thread_state.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/testing/task_environment.h"
 
 namespace blink {
@@ -132,10 +131,12 @@
 TEST_F(SoftNavigationHeuristicsTest, ResetHeuristicOnSetBecameEmpty) {
   auto* heuristics = CreateSoftNavigationHeuristicsForTest();
   ASSERT_TRUE(heuristics);
-  auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
-  ASSERT_TRUE(tracker);
 
   auto* script_state = GetScriptStateForTest();
+  auto* tracker =
+      scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
+  ASSERT_TRUE(tracker);
+
   Persistent<scheduler::TaskAttributionInfo> root_task = nullptr;
   // Simulate a click.
   {
@@ -145,7 +146,7 @@
             /*is_new_interaction=*/true));
     std::unique_ptr<TaskScope> task_scope = tracker->CreateTaskScope(
         script_state, /*parent_task=*/nullptr, TaskScopeType::kCallback);
-    root_task = tracker->RunningTask(script_state->GetIsolate());
+    root_task = tracker->RunningTask();
   }
   EXPECT_TRUE(root_task);
   EXPECT_GT(heuristics->GetLastInteractionTaskIdForTest(), 0u);
@@ -155,7 +156,7 @@
   {
     std::unique_ptr<TaskScope> task_scope = tracker->CreateTaskScope(
         script_state, root_task, TaskScopeType::kCallback);
-    descendant_task = tracker->RunningTask(script_state->GetIsolate());
+    descendant_task = tracker->RunningTask();
   }
   EXPECT_TRUE(descendant_task);
 
diff --git a/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc b/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
index 13401594..fdbb68f 100644
--- a/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
+++ b/third_party/blink/renderer/core/view_transition/view_transition_supplement.cc
@@ -20,7 +20,7 @@
 #include "third_party/blink/renderer/core/view_transition/view_transition_utils.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 
 namespace blink {
 namespace {
@@ -93,15 +93,15 @@
     const std::optional<Vector<String>>& types,
     ExceptionState& exception_state) {
   DCHECK(script_state);
-  DCHECK(ThreadScheduler::Current());
   auto* supplement = From(document);
 
   if (callback) {
-    auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+    auto* tracker =
+        scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
     // Set the parent task ID if we're not in an extension task (as extensions
     // are not currently supported in TaskAttributionTracker).
     if (tracker && script_state->World().IsMainWorld()) {
-      callback->SetParentTask(tracker->RunningTask(script_state->GetIsolate()));
+      callback->SetParentTask(tracker->RunningTask());
     }
   }
   return supplement->StartTransition(document, callback, types,
diff --git a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
index e8984d6c..2ceec6a 100644
--- a/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
+++ b/third_party/blink/renderer/core/xmlhttprequest/xml_http_request.cc
@@ -98,7 +98,6 @@
 #include "third_party/blink/renderer/platform/network/parsed_content_type.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
@@ -1063,9 +1062,9 @@
   if (async_) {
     CHECK(!execution_context.IsContextDestroyed());
     if (world_ && world_->IsMainWorld()) {
-      if (auto* tracker =
-              ThreadScheduler::Current()->GetTaskAttributionTracker()) {
-        parent_task_ = tracker->RunningTask(execution_context.GetIsolate());
+      if (auto* tracker = scheduler::TaskAttributionTracker::From(
+              execution_context.GetIsolate())) {
+        parent_task_ = tracker->RunningTask();
       }
     }
     async_task_context_.Schedule(&execution_context, "XMLHttpRequest.send");
@@ -2133,20 +2132,20 @@
       GetExecutionContext()->IsContextDestroyed()) {
     return nullptr;
   }
-  auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
-  if (!tracker) {
-    return nullptr;
-  }
-  // `parent_task_` being non-null implies this object is associated with
-  // the main world.
+  // `parent_task_` being non-null implies that task tracking is enabled and
+  // this object is associated with the main world.
   auto* script_state = ToScriptStateForMainWorld(GetExecutionContext());
   CHECK(script_state);
+  auto* tracker =
+      scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
+  CHECK(tracker);
+
   // Don't create a new (nested) task scope if we're still in the parent task,
   // otherwise we risk clobbering other propagated task state.
   //
   // TODO(crbug.com/1439971): Make this safe to do or move the logic into the
   // task attribution implementation.
-  if (tracker->RunningTask(script_state->GetIsolate()) == parent_task_.Get()) {
+  if (tracker->RunningTask() == parent_task_.Get()) {
     return nullptr;
   }
   return tracker->CreateTaskScope(
diff --git a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
index a96ed050..b67f0f1c 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_relation_cache.cc
@@ -553,7 +553,7 @@
         // - AccessibilityEventsSubtreeReparentedViaAriaOwns2/linux
         // TODO(crbug.com/1299031) Find out why this is necessary.
         object_cache_->MarkAXObjectDirtyWithCleanLayout(
-            original_parent->ParentObject());
+            original_parent->CachedParentObject());
       }
       // Now that we're replacing the parent, we need to update cached values
       // for the added child's subtree, because some cached values are inherited
diff --git a/third_party/blink/renderer/modules/modules_initializer.cc b/third_party/blink/renderer/modules/modules_initializer.cc
index 8f0484f3..56f2c2f 100644
--- a/third_party/blink/renderer/modules/modules_initializer.cc
+++ b/third_party/blink/renderer/modules/modules_initializer.cc
@@ -236,8 +236,8 @@
   OffscreenCanvas::RegisterRenderingContextFactory(
       std::make_unique<GPUCanvasContext::Factory>());
 
-  ThreadScheduler::Current()->InitializeTaskAttributionTracker(
-      std::make_unique<scheduler::TaskAttributionTrackerImpl>());
+  V8PerIsolateData::SetTaskAttributionTrackerFactory(
+      &scheduler::TaskAttributionTrackerImpl::Create);
 }
 
 void ModulesInitializer::InitLocalFrame(LocalFrame& frame) const {
diff --git a/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc b/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
index 66a49d1..65966cf2 100644
--- a/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
+++ b/third_party/blink/renderer/modules/scheduler/dom_scheduler.cc
@@ -23,7 +23,6 @@
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/frame_or_worker_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h"
 #include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_queue_type.h"
 #include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_task_queue.h"
@@ -168,16 +167,13 @@
 
 scheduler::TaskAttributionIdType DOMScheduler::taskId(
     ScriptState* script_state) {
-  ThreadScheduler* scheduler = ThreadScheduler::Current();
-  DCHECK(scheduler);
-  auto* tracker = scheduler->GetTaskAttributionTracker();
+  auto* tracker =
+      scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
   if (!tracker) {
     // Can happen when a feature flag disables TaskAttribution.
     return 0;
   }
-  scheduler::TaskAttributionInfo* task =
-      scheduler->GetTaskAttributionTracker()->RunningTask(
-          script_state->GetIsolate());
+  scheduler::TaskAttributionInfo* task = tracker->RunningTask();
   // task cannot be nullptr here, as a task has presumably already ran in order
   // for this API call to be called.
   DCHECK(task);
@@ -187,15 +183,13 @@
 AtomicString DOMScheduler::isAncestor(
     ScriptState* script_state,
     scheduler::TaskAttributionIdType parent_id) {
-  ThreadScheduler* scheduler = ThreadScheduler::Current();
-  DCHECK(scheduler);
-  auto* tracker = scheduler->GetTaskAttributionTracker();
+  auto* tracker =
+      scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
   if (!tracker) {
     // Can happen when a feature flag disables TaskAttribution.
     return AtomicString("unknown");
   }
-  const scheduler::TaskAttributionInfo* current_task =
-      tracker->RunningTask(script_state->GetIsolate());
+  const scheduler::TaskAttributionInfo* current_task = tracker->RunningTask();
   return current_task &&
                  tracker->IsAncestor(*current_task,
                                      scheduler::TaskAttributionId(parent_id))
diff --git a/third_party/blink/renderer/modules/scheduler/dom_task.cc b/third_party/blink/renderer/modules/scheduler/dom_task.cc
index b64af493..846eb32 100644
--- a/third_party/blink/renderer/modules/scheduler/dom_task.cc
+++ b/third_party/blink/renderer/modules/scheduler/dom_task.cc
@@ -26,7 +26,6 @@
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h"
 #include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_task_queue.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
@@ -110,9 +109,9 @@
   DCHECK(script_state && script_state->ContextIsValid());
 
   if (script_state->World().IsMainWorld()) {
-    if (auto* tracker =
-            ThreadScheduler::Current()->GetTaskAttributionTracker()) {
-      parent_task_ = tracker->RunningTask(script_state->GetIsolate());
+    if (auto* tracker = scheduler::TaskAttributionTracker::From(
+            script_state->GetIsolate())) {
+      parent_task_ = tracker->RunningTask();
     }
   }
 
@@ -190,7 +189,8 @@
   // For the main thread (tracker exists), create the task scope with the signal
   // to set up propagation. On workers, set the current context here since there
   // is no tracker.
-  if (auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker()) {
+  if (auto* tracker =
+          scheduler::TaskAttributionTracker::From(script_state->GetIsolate())) {
     task_attribution_scope = tracker->CreateTaskScope(
         script_state, parent_task_,
         scheduler::TaskAttributionTracker::TaskScopeType::kSchedulerPostTask,
diff --git a/third_party/blink/renderer/modules/scheduler/scheduled_action.cc b/third_party/blink/renderer/modules/scheduler/scheduled_action.cc
index 7573bea2..3955cd6f 100644
--- a/third_party/blink/renderer/modules/scheduler/scheduled_action.cc
+++ b/third_party/blink/renderer/modules/scheduler/scheduled_action.cc
@@ -45,7 +45,7 @@
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
@@ -62,10 +62,10 @@
           To<LocalDOMWindow>(&target))) {
     function_ = handler;
     arguments_ = arguments;
-    auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+    auto* tracker =
+        scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
     if (tracker && script_state->World().IsMainWorld()) {
-      function_->SetParentTask(
-          tracker->RunningTask(script_state->GetIsolate()));
+      function_->SetParentTask(tracker->RunningTask());
     }
   } else {
     UseCounter::Count(target, WebFeature::kScheduledActionIgnored);
@@ -82,9 +82,10 @@
           EnteredDOMWindow(script_state->GetIsolate()),
           To<LocalDOMWindow>(&target))) {
     code_ = handler;
-    auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+    auto* tracker =
+        scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
     if (tracker && script_state->World().IsMainWorld()) {
-      code_parent_task_ = tracker->RunningTask(script_state->GetIsolate());
+      code_parent_task_ = tracker->RunningTask();
     }
   } else {
     UseCounter::Count(target, WebFeature::kScheduledActionIgnored);
@@ -157,7 +158,8 @@
   // APIs properly track their ancestor as the registering task.
   std::unique_ptr<scheduler::TaskAttributionTracker::TaskScope>
       task_attribution_scope;
-  auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+  auto* tracker =
+      scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
   if (tracker && script_state->World().IsMainWorld()) {
     task_attribution_scope = tracker->CreateTaskScope(
         script_state, code_parent_task_,
diff --git a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
index 2598263..94fccad 100644
--- a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
+++ b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "base/memory/ptr_util.h"
 #include "base/trace_event/trace_event.h"
 #include "base/trace_event/typed_macros.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -50,12 +51,20 @@
 
 }  // namespace
 
-TaskAttributionTrackerImpl::TaskAttributionTrackerImpl() : next_task_id_(0) {}
+// static
+std::unique_ptr<TaskAttributionTracker> TaskAttributionTrackerImpl::Create(
+    v8::Isolate* isolate) {
+  return base::WrapUnique(new TaskAttributionTrackerImpl(isolate));
+}
 
-TaskAttributionInfo* TaskAttributionTrackerImpl::RunningTask(
-    v8::Isolate* isolate) const {
+TaskAttributionTrackerImpl::TaskAttributionTrackerImpl(v8::Isolate* isolate)
+    : next_task_id_(0), isolate_(isolate) {
+  CHECK(isolate_);
+}
+
+TaskAttributionInfo* TaskAttributionTrackerImpl::RunningTask() const {
   ScriptWrappableTaskState* task_state =
-      ScriptWrappableTaskState::GetCurrent(isolate);
+      ScriptWrappableTaskState::GetCurrent(isolate_);
 
   // V8 embedder state may have no value in the case of a JSPromise that wasn't
   // yet resolved.
@@ -103,9 +112,11 @@
                                             TaskScopeType type,
                                             AbortSignal* abort_source,
                                             DOMTaskSignal* priority_source) {
+  CHECK(script_state);
+  CHECK_EQ(script_state->GetIsolate(), isolate_);
   TaskAttributionInfo* running_task_to_be_restored = running_task_;
   ScriptWrappableTaskState* continuation_task_state_to_be_restored =
-      ScriptWrappableTaskState::GetCurrent(script_state->GetIsolate());
+      ScriptWrappableTaskState::GetCurrent(isolate_);
 
   // This compresses the task graph when encountering long task chains.
   // TODO(crbug.com/1501999): Consider compressing the task graph further.
diff --git a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
index 23d8756b..a5c7845 100644
--- a/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
+++ b/third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h
@@ -36,9 +36,9 @@
 class MODULES_EXPORT TaskAttributionTrackerImpl
     : public TaskAttributionTracker {
  public:
-  TaskAttributionTrackerImpl();
+  static std::unique_ptr<TaskAttributionTracker> Create(v8::Isolate*);
 
-  TaskAttributionInfo* RunningTask(v8::Isolate*) const override;
+  TaskAttributionInfo* RunningTask() const override;
 
   bool IsAncestor(const TaskAttributionInfo& task,
                   TaskAttributionId ancestor_id) override;
@@ -113,6 +113,8 @@
     Persistent<ScriptState> script_state_;
   };
 
+  explicit TaskAttributionTrackerImpl(v8::Isolate*);
+
   void TaskScopeCompleted(const TaskScopeImpl&);
   void OnObserverScopeDestroyed(const ObserverScope&) override;
 
@@ -125,6 +127,9 @@
   // here to ensure the relevant object remains alive (and hence properly
   // tracked through task attribution).
   WTF::Deque<Persistent<TaskAttributionInfo>> same_document_navigation_tasks_;
+
+  // The lifetime of this class is tied to the `isolate_`.
+  raw_ptr<v8::Isolate> isolate_;
 };
 
 }  // namespace blink::scheduler
diff --git a/third_party/blink/renderer/modules/scheduler/window_idle_tasks.cc b/third_party/blink/renderer/modules/scheduler/window_idle_tasks.cc
index 856b9a8..3695271 100644
--- a/third_party/blink/renderer/modules/scheduler/window_idle_tasks.cc
+++ b/third_party/blink/renderer/modules/scheduler/window_idle_tasks.cc
@@ -19,7 +19,6 @@
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_info.h"
 #include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
-#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h"
 
 namespace blink {
@@ -36,9 +35,10 @@
 
   explicit V8IdleTask(V8IdleRequestCallback* callback) : callback_(callback) {
     ScriptState* script_state = callback_->CallbackRelevantScriptState();
-    auto* tracker = ThreadScheduler::Current()->GetTaskAttributionTracker();
+    auto* tracker =
+        scheduler::TaskAttributionTracker::From(script_state->GetIsolate());
     if (tracker && script_state->World().IsMainWorld()) {
-      parent_task_ = tracker->RunningTask(script_state->GetIsolate());
+      parent_task_ = tracker->RunningTask();
     }
   }
 
@@ -48,8 +48,8 @@
     ScriptState* script_state = callback_->CallbackRelevantScriptState();
     std::unique_ptr<scheduler::TaskAttributionTracker::TaskScope>
         task_attribution_scope;
-    if (auto* tracker =
-            ThreadScheduler::Current()->GetTaskAttributionTracker()) {
+    if (auto* tracker = scheduler::TaskAttributionTracker::From(
+            script_state->GetIsolate())) {
       DOMTaskSignal* signal = nullptr;
       if (RuntimeEnabledFeatures::SchedulerYieldEnabled(
               ExecutionContext::From(script_state))) {
diff --git a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
index f4faf012..e4ce5d6 100644
--- a/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
+++ b/third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.cc
@@ -5,9 +5,9 @@
 #include "third_party/blink/renderer/modules/webrtc/webrtc_audio_renderer.h"
 
 #include <utility>
+#include <vector>
 
 #include "base/containers/contains.h"
-#include "base/containers/cxx20_erase.h"
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
@@ -788,7 +788,7 @@
        it != source_playing_states_.end();) {
     PlayingStates& states = it->second;
     // We cannot use RemovePlayingState as it might invalidate |it|.
-    base::Erase(states, state);
+    std::erase(states, state);
     if (states.empty())
       it = source_playing_states_.erase(it);
     else
diff --git a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
index bbf76f9..5384c97 100644
--- a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
+++ b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.cc
@@ -30,6 +30,7 @@
 
 #include "base/allocator/partition_allocator/src/partition_alloc/oom.h"
 #include "base/debug/crash_logging.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/ranges/algorithm.h"
 #include "base/task/single_thread_task_runner.h"
@@ -50,12 +51,18 @@
 #include "third_party/blink/renderer/platform/bindings/v8_value_cache.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/heap/thread_state_scopes.h"
+#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 #include "third_party/blink/renderer/platform/wtf/leak_annotations.h"
 
 namespace blink {
 
+BASE_FEATURE(kTaskAttributionInfrastructureDisabledForTesting,
+             "TaskAttributionInfrastructureDisabledForTesting",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 namespace {
+
 void AddCrashKey(v8::CrashKeyId id, const std::string& value) {
   using base::debug::AllocateCrashKeyString;
   using base::debug::CrashKeySize;
@@ -93,6 +100,10 @@
       break;
   }
 }
+
+V8PerIsolateData::TaskAttributionTrackerFactoryPtr
+    task_attribution_tracker_factory = nullptr;
+
 }  // namespace
 
 static void BeforeCallEnteredCallback(v8::Isolate* isolate) {
@@ -146,6 +157,12 @@
     main_world_ =
         DOMWrapperWorld::Create(GetIsolate(), DOMWrapperWorld::WorldType::kMain,
                                 /*is_default_world_of_isolate=*/true);
+    if (!base::FeatureList::IsEnabled(
+            kTaskAttributionInfrastructureDisabledForTesting)) {
+      CHECK(task_attribution_tracker_factory);
+      task_attribution_tracker_ =
+          task_attribution_tracker_factory(GetIsolate());
+    }
   }
 }
 
@@ -406,6 +423,13 @@
   return password_regexp_;
 }
 
+void V8PerIsolateData::SetTaskAttributionTrackerFactory(
+    TaskAttributionTrackerFactoryPtr factory) {
+  CHECK(!task_attribution_tracker_factory);
+  CHECK(IsMainThread());
+  task_attribution_tracker_factory = factory;
+}
+
 void* CreateHistogram(const char* name, int min, int max, size_t buckets) {
   // Each histogram has an implicit '0' bucket (for underflow), so we can always
   // bump the minimum to 1.
diff --git a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h
index 512cd71f..cefd4c68 100644
--- a/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h
+++ b/third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h
@@ -50,6 +50,10 @@
 class SingleThreadTaskRunner;
 }  // namespace base
 
+namespace blink::scheduler {
+class TaskAttributionTracker;
+}  // namespace blink::scheduler
+
 namespace blink {
 
 class DOMWrapperWorld;
@@ -218,6 +222,21 @@
 
   void LeaveGC() { gc_callback_depth_--; }
 
+  // Set the factory function used to initialize task attribution for the
+  // isolate upon creating main thread `V8PerIsolateData`. This should be set
+  // once per process before creating any isolates.
+  using TaskAttributionTrackerFactoryPtr =
+      std::unique_ptr<scheduler::TaskAttributionTracker> (*)(v8::Isolate*);
+  static void SetTaskAttributionTrackerFactory(
+      TaskAttributionTrackerFactoryPtr factory);
+
+  // Returns the `scheduler::TaskAttributionTracker` associated with the
+  // associated `v8::Isolate`. Returns null if the
+  // TaskAttributionInfrastructureDisabledForTesting feature is enabled.
+  scheduler::TaskAttributionTracker* GetTaskAttributionTracker() {
+    return task_attribution_tracker_.get();
+  }
+
  private:
   V8PerIsolateData(scoped_refptr<base::SingleThreadTaskRunner>,
                    scoped_refptr<base::SingleThreadTaskRunner>,
@@ -292,6 +311,8 @@
   size_t gc_callback_depth_ = 0;
 
   Persistent<DOMWrapperWorld> main_world_;
+
+  std::unique_ptr<scheduler::TaskAttributionTracker> task_attribution_tracker_;
 };
 
 // Creates a histogram for V8. The returned value is a base::Histogram, but
diff --git a/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc b/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
index e171b7b..fd7e475e 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
+++ b/third_party/blink/renderer/platform/geometry/calculation_expression_node.cc
@@ -367,9 +367,22 @@
   result_type_ = ResolvedResultType();
   DCHECK_NE(result_type_, ResultType::kInvalid);
 #endif
-  for (const auto& child : children_) {
-    if (child->HasContentOrIntrinsicSize()) {
-      has_content_or_intrinsic_ = true;
+  if (op == CalculationOperator::kCalcSize) {
+    // "A calc-size() is treated, in all respects, as if it were its
+    // calc-size basis."  This is particularly relevant for ignoring the
+    // presence of percentages in the calculation.
+    DCHECK_EQ(children_.size(), 2u);
+    const auto& basis = children_[0];
+    has_content_or_intrinsic_ = basis->HasContentOrIntrinsicSize();
+    has_percent_ = basis->HasPercent();
+  } else {
+    for (const auto& child : children_) {
+      if (child->HasContentOrIntrinsicSize()) {
+        has_content_or_intrinsic_ = true;
+      }
+      if (child->HasPercent()) {
+        has_percent_ = true;
+      }
     }
   }
 }
diff --git a/third_party/blink/renderer/platform/geometry/calculation_expression_node.h b/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
index c61996c..33488be 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
+++ b/third_party/blink/renderer/platform/geometry/calculation_expression_node.h
@@ -51,6 +51,11 @@
   }
 
   bool HasContentOrIntrinsicSize() const { return has_content_or_intrinsic_; }
+  // HasPercent returns whether this node's value expression should be
+  // treated as having a percent.  Note that this means that percentages
+  // inside of the calculation part of a calc-size() do not make the
+  // calc-size() act as though it has a percent.
+  bool HasPercent() const { return has_percent_; }
 
   virtual bool IsNumber() const { return false; }
   virtual bool IsIdentifier() const { return false; }
@@ -77,6 +82,7 @@
   virtual bool Equals(const CalculationExpressionNode& other) const = 0;
 
   bool has_content_or_intrinsic_ = false;
+  bool has_percent_ = false;
 };
 
 class PLATFORM_EXPORT CalculationExpressionNumberNode final
@@ -218,6 +224,9 @@
 #if DCHECK_IS_ON()
     result_type_ = ResultType::kPixelsAndPercent;
 #endif
+    if (value.has_explicit_percent) {
+      has_percent_ = true;
+    }
   }
 
   float Pixels() const { return value_.pixels; }
diff --git a/third_party/blink/renderer/platform/geometry/calculation_value.cc b/third_party/blink/renderer/platform/geometry/calculation_value.cc
index 5214265..c37c0d80 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_value.cc
+++ b/third_party/blink/renderer/platform/geometry/calculation_value.cc
@@ -144,4 +144,11 @@
   return IsExpression() && data_.expression->HasContentOrIntrinsicSize();
 }
 
+bool CalculationValue::HasPercent() const {
+  if (!IsExpression()) {
+    return HasExplicitPercent();
+  }
+  return data_.expression->HasPercent();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/geometry/calculation_value.h b/third_party/blink/renderer/platform/geometry/calculation_value.h
index bcb51cbc..4884411a 100644
--- a/third_party/blink/renderer/platform/geometry/calculation_value.h
+++ b/third_party/blink/renderer/platform/geometry/calculation_value.h
@@ -68,6 +68,7 @@
                             : Length::ValueRange::kAll;
   }
   bool HasContentOrIntrinsicSize() const;
+  bool HasPercent() const;
 
   float Pixels() const {
     DCHECK(!IsExpression());
diff --git a/third_party/blink/renderer/platform/geometry/length.cc b/third_party/blink/renderer/platform/geometry/length.cc
index 44a33af..f67d3a5 100644
--- a/third_party/blink/renderer/platform/geometry/length.cc
+++ b/third_party/blink/renderer/platform/geometry/length.cc
@@ -203,7 +203,7 @@
   return result;
 }
 
-bool Length::IsContentOrIntrinsic() const {
+bool Length::HasContentOrIntrinsic() const {
   if (GetType() == kCalculated) {
     return GetCalculationValue().HasContentOrIntrinsicSize();
   }
@@ -212,6 +212,13 @@
          GetType() == kContent;
 }
 
+bool Length::HasPercent() const {
+  if (GetType() == kCalculated) {
+    return GetCalculationValue().HasPercent();
+  }
+  return GetType() == kPercent;
+}
+
 bool Length::IsCalculatedEqual(const Length& o) const {
   return IsCalculated() &&
          (&GetCalculationValue() == &o.GetCalculationValue() ||
diff --git a/third_party/blink/renderer/platform/geometry/length.h b/third_party/blink/renderer/platform/geometry/length.h
index 54cc6fd..2f9b344 100644
--- a/third_party/blink/renderer/platform/geometry/length.h
+++ b/third_party/blink/renderer/platform/geometry/length.h
@@ -261,19 +261,20 @@
   }
 
   // For the layout purposes, if this |Length| is a block-axis size, see
-  // |IsAutoOrContentOrIntrinsic()|, it is usually a better choice.
+  // |HasAutoOrContentOrIntrinsic()|, it is usually a better choice.
   bool IsAuto() const { return GetType() == kAuto; }
   bool IsFixed() const { return GetType() == kFixed; }
 
   // For the block axis, intrinsic sizes such as `min-content` behave the same
   // as `auto`. https://www.w3.org/TR/css-sizing-3/#valdef-width-min-content
   // This includes content-based sizes in calc-size().
-  bool IsContentOrIntrinsic() const;
-  bool IsAutoOrContentOrIntrinsic() const {
+  bool HasContentOrIntrinsic() const;
+  bool HasAutoOrContentOrIntrinsic() const {
     // TODO(https://crbug.com/313072): Add support for 'auto' in 'calc-size()'
     // here.
-    return GetType() == kAuto || IsContentOrIntrinsic();
+    return GetType() == kAuto || HasContentOrIntrinsic();
   }
+  bool HasPercent() const;
 
   bool IsSpecified() const {
     return GetType() == kFixed || GetType() == kPercent ||
@@ -290,9 +291,14 @@
   bool IsFitContent() const { return GetType() == kFitContent; }
   bool IsPercent() const { return GetType() == kPercent; }
   bool IsPercentOrCalc() const {
+    // TODO(https://crbug.com/313072): Not all calc()s have percentages;
+    // many callers may want HasPercent, above.
     return GetType() == kPercent || GetType() == kCalculated;
   }
   bool IsPercentOrCalcOrStretch() const {
+    // TODO(https://crbug.com/313072): Not all calc()s have percentages;
+    // many callers may want a function like HasPercent, above (but that
+    // doesn't exist yet).
     return GetType() == kPercent || GetType() == kCalculated ||
            GetType() == kFillAvailable;
   }
@@ -374,7 +380,7 @@
     // (e.g., no targets or wrong axis.), in which case the fallback should
     // be used.
     virtual std::optional<LayoutUnit> Evaluate(
-        const CalculationExpressionNode&) const = 0;
+        const CalculationExpressionNode&) = 0;
 
    protected:
     Mode GetMode() const { return mode_; }
@@ -420,7 +426,7 @@
     STACK_ALLOCATED();
 
    public:
-    const Length::AnchorEvaluator* anchor_evaluator = nullptr;
+    Length::AnchorEvaluator* anchor_evaluator = nullptr;
     std::optional<float> size_keyword_basis = std::nullopt;
     std::optional<IntrinsicLengthEvaluator> intrinsic_evaluator = std::nullopt;
   };
diff --git a/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.cc b/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.cc
index d46f010..73f52c7 100644
--- a/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.cc
+++ b/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.cc
@@ -4,13 +4,27 @@
 
 #include "third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h"
 
+#include "base/memory/ptr_util.h"
+
 namespace blink {
 
 MemoryManagedPaintCanvas::MemoryManagedPaintCanvas(const gfx::Size& size)
     : cc::InspectableRecordPaintCanvas(size) {}
 
+MemoryManagedPaintCanvas::MemoryManagedPaintCanvas(
+    CreateChildCanvasTag,
+    const MemoryManagedPaintCanvas& parent)
+    : cc::InspectableRecordPaintCanvas(CreateChildCanvasTag(), parent) {}
+
 MemoryManagedPaintCanvas::~MemoryManagedPaintCanvas() = default;
 
+std::unique_ptr<MemoryManagedPaintCanvas>
+MemoryManagedPaintCanvas::CreateChildCanvas() {
+  // Using `new` to access a non-public constructor.
+  return base::WrapUnique(
+      new MemoryManagedPaintCanvas(CreateChildCanvasTag(), *this));
+}
+
 cc::PaintRecord MemoryManagedPaintCanvas::ReleaseAsRecord() {
   cached_image_ids_.clear();
   image_bytes_used_ = 0;
diff --git a/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h b/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h
index 5a8741e..653c49ed 100644
--- a/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h
+++ b/third_party/blink/renderer/platform/graphics/memory_managed_paint_canvas.h
@@ -26,6 +26,8 @@
   explicit MemoryManagedPaintCanvas(const cc::RecordPaintCanvas&) = delete;
   ~MemoryManagedPaintCanvas() override;
 
+  std::unique_ptr<MemoryManagedPaintCanvas> CreateChildCanvas();
+
   cc::PaintRecord ReleaseAsRecord() override;
 
   void drawImage(const cc::PaintImage& image,
@@ -44,6 +46,12 @@
   size_t ImageBytesUsed() const { return image_bytes_used_; }
 
  private:
+  // Creates a child canvas that has the same transform matrix and size as
+  // `parent`. `CreateChildCanvasTag` is used to differentiate this from a copy
+  // constructor.
+  MemoryManagedPaintCanvas(CreateChildCanvasTag,
+                           const MemoryManagedPaintCanvas& parent);
+
   void UpdateMemoryUsage(const cc::PaintImage& image);
 
   HashSet<cc::PaintImage::ContentId,
diff --git a/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.cc b/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.cc
index 5fe7dcd8..d726a8ee0 100644
--- a/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.cc
+++ b/third_party/blink/renderer/platform/graphics/memory_managed_paint_recorder.cc
@@ -85,7 +85,7 @@
 void MemoryManagedPaintRecorder::BeginSideRecording() {
   CHECK(!side_canvas_) << "BeginSideRecording() can't be called when side "
                           "recording is already active.";
-  side_canvas_ = std::make_unique<MemoryManagedPaintCanvas>(size_);
+  side_canvas_ = main_canvas_.CreateChildCanvas();
   current_canvas_ = side_canvas_.get();
 }
 
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 6ce6737..70c6b962 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -732,6 +732,7 @@
     {
       // Resolve anchor*() functions computed-value time.
       name: "CSSAnchorPositioningComputeAnchor",
+      depends_on: ["CSSAnchorPositioningCascadeFallback"],
     },
     {
       name: "CSSAnimationComposition",
@@ -3034,7 +3035,7 @@
     },
     {
       name: "ReduceCookieIPCs",
-      status: "stable",
+      status: "test",
     },
     {
       // If enabled, the deviceModel will be reduced to "K" and the
diff --git a/third_party/blink/renderer/platform/scheduler/DEPS b/third_party/blink/renderer/platform/scheduler/DEPS
index a622633..9a82c31 100644
--- a/third_party/blink/renderer/platform/scheduler/DEPS
+++ b/third_party/blink/renderer/platform/scheduler/DEPS
@@ -32,6 +32,7 @@
   "+third_party/blink/renderer/platform/allow_discouraged_type.h",
   "+third_party/blink/renderer/platform/back_forward_cache_utils.h",
   "+third_party/blink/renderer/platform/bindings/parkable_string_manager.h",
+  "+third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h",
   "+third_party/blink/renderer/platform/heap/collection_support/clear_collection_scope.h",
   "+third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h",
   "+third_party/blink/renderer/platform/heap/collection_support/heap_vector.h",
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index a1c8232..a380864 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -41,6 +41,7 @@
 #include "third_party/blink/public/platform/scheduler/web_agent_group_scheduler.h"
 #include "third_party/blink/public/platform/scheduler/web_renderer_process_type.h"
 #include "third_party/blink/public/platform/web_input_event_result.h"
+#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
 #include "third_party/blink/renderer/platform/instrumentation/resource_coordinator/renderer_resource_coordinator.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
@@ -71,10 +72,6 @@
 namespace blink {
 namespace scheduler {
 
-BASE_FEATURE(kTaskAttributionInfrastructureDisabledForTesting,
-             "TaskAttributionInfrastructureDisabledForTesting",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 using base::sequence_manager::TaskQueue;
 using base::sequence_manager::TaskTimeObserver;
 using base::sequence_manager::TimeDomain;
@@ -2852,19 +2849,6 @@
   return true;
 }
 
-TaskAttributionTracker* MainThreadSchedulerImpl::GetTaskAttributionTracker() {
-  return base::FeatureList::IsEnabled(
-             kTaskAttributionInfrastructureDisabledForTesting)
-             ? nullptr
-             : main_thread_only().task_attribution_tracker.get();
-}
-
-void MainThreadSchedulerImpl::InitializeTaskAttributionTracker(
-    std::unique_ptr<TaskAttributionTracker> tracker) {
-  DCHECK(!main_thread_only().task_attribution_tracker);
-  main_thread_only().task_attribution_tracker = std::move(tracker);
-}
-
 // static
 const char* MainThreadSchedulerImpl::UseCaseToString(UseCase use_case) {
   switch (use_case) {
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index 8677be7..c17e65b 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -52,7 +52,6 @@
 #include "third_party/blink/renderer/platform/scheduler/public/frame_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/main_thread_scheduler.h"
 #include "third_party/blink/renderer/platform/scheduler/public/rail_mode_observer.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
@@ -211,9 +210,6 @@
   void AddTaskObserver(base::TaskObserver* task_observer) override;
   void RemoveTaskObserver(base::TaskObserver* task_observer) override;
   void SetV8Isolate(v8::Isolate* isolate) override;
-  TaskAttributionTracker* GetTaskAttributionTracker() override;
-  void InitializeTaskAttributionTracker(
-      std::unique_ptr<TaskAttributionTracker> tracker) override;
   blink::MainThreadScheduler* ToMainThreadScheduler() override;
 
   // ThreadSchedulerBase implementation:
@@ -801,7 +797,6 @@
 
     WTF::Vector<AgentGroupSchedulerScope> agent_group_scheduler_scope_stack;
 
-    std::unique_ptr<TaskAttributionTracker> task_attribution_tracker;
     Persistent<HeapHashSet<WeakMember<AgentGroupSchedulerImpl>>>
         agent_group_schedulers;
     // Task queues that have been detached from their scheduler and may have
diff --git a/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h b/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
index 623bc9f..9ac6c1db 100644
--- a/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
+++ b/third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h
@@ -11,6 +11,7 @@
 #include "base/functional/function_ref.h"
 #include "base/memory/stack_allocated.h"
 #include "third_party/blink/public/common/scheduler/task_attribution_id.h"
+#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
@@ -98,6 +99,10 @@
     Observer* previous_observer_;
   };
 
+  static TaskAttributionTracker* From(v8::Isolate* isolate) {
+    return V8PerIsolateData::From(isolate)->GetTaskAttributionTracker();
+  }
+
   virtual ~TaskAttributionTracker() = default;
 
   // Create a new task scope.
@@ -114,7 +119,7 @@
       DOMTaskSignal* priority_source) = 0;
 
   // Get the `TaskAttributionInfo` for the currently running task.
-  virtual TaskAttributionInfo* RunningTask(v8::Isolate*) const = 0;
+  virtual TaskAttributionInfo* RunningTask() const = 0;
 
   // Returns true iff `task` has an ancestor task with `ancestor_id`.
   virtual bool IsAncestor(const TaskAttributionInfo& task,
diff --git a/third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h b/third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h
index e70ee0c..5f4ce082 100644
--- a/third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h
+++ b/third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h
@@ -5,11 +5,9 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_THREAD_SCHEDULER_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_THREAD_SCHEDULER_H_
 
-#include <memory>
 #include "base/location.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
-#include "third_party/blink/renderer/platform/scheduler/public/task_attribution_tracker.h"
 #include "third_party/blink/renderer/platform/scheduler/public/thread.h"
 
 namespace v8 {
@@ -96,19 +94,8 @@
   // Associates |isolate| to the scheduler.
   virtual void SetV8Isolate(v8::Isolate* isolate) = 0;
 
-  // Returns the scheduler's TaskAttributionTracker is we're running on the main
-  // thread, or a nullptr otherwise.
-  virtual scheduler::TaskAttributionTracker* GetTaskAttributionTracker() {
-    return nullptr;
-  }
-
   // Convert this into a MainThreadScheduler if it is one.
   virtual MainThreadScheduler* ToMainThreadScheduler() { return nullptr; }
-
-  // Test helpers.
-
-  virtual void InitializeTaskAttributionTracker(
-      std::unique_ptr<scheduler::TaskAttributionTracker>) {}
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/testing/task_environment.cc b/third_party/blink/renderer/platform/testing/task_environment.cc
index 3f238cd..c881800 100644
--- a/third_party/blink/renderer/platform/testing/task_environment.cc
+++ b/third_party/blink/renderer/platform/testing/task_environment.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/platform/testing/task_environment.h"
 
 #include "third_party/blink/public/platform/platform.h"
-#include "third_party/blink/renderer/modules/scheduler/task_attribution_tracker_impl.h"
 #include "third_party/blink/renderer/platform/scheduler/public/main_thread_scheduler.h"
 #include "third_party/blink/renderer/platform/wtf/wtf.h"
 
@@ -36,8 +35,6 @@
   main_thread_isolate_.emplace();
 
   main_thread_overrider_.emplace(scheduler_->CreateMainThread());
-  ThreadScheduler::Current()->InitializeTaskAttributionTracker(
-      std::make_unique<scheduler::TaskAttributionTrackerImpl>());
 }
 
 // static
diff --git a/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py b/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
index 4d7c68eae..982a5b1 100644
--- a/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
+++ b/third_party/blink/tools/blinkpy/common/system/filesystem_mock.py
@@ -263,12 +263,15 @@
                         directories.append(directory)
                 else:
                     files.append(remaining)
-        file_system_tuples = [(top[:-1], directories, files)]
+        # The real `os.walk(...)` [0] gives the caller a chance to modify which
+        # subdirectories to traverse by mutating the `directories` list, so we
+        # should yield here instead of returning a precomputed list.
+        #
+        # [0]: https://docs.python.org/3/library/os.html#os.walk
+        yield (top[:-1], directories, files)
         for directory in directories:
             directory = top + directory
-            tuples_from_subdirs = self.walk(directory)
-            file_system_tuples += tuples_from_subdirs
-        return file_system_tuples
+            yield from self.walk(directory)
 
     def mtime(self, path):
         if self.exists(path):
diff --git a/third_party/blink/tools/blinkpy/common/system/filesystem_mock_unittest.py b/third_party/blink/tools/blinkpy/common/system/filesystem_mock_unittest.py
index 346cfed..7409014 100644
--- a/third_party/blink/tools/blinkpy/common/system/filesystem_mock_unittest.py
+++ b/third_party/blink/tools/blinkpy/common/system/filesystem_mock_unittest.py
@@ -113,9 +113,10 @@
         mock_files = {'foo/bar/baz': '', 'foo/a': '', 'foo/b': '', 'foo/c': ''}
         host = MockHost()
         host.filesystem = MockFileSystem(files=mock_files)
-        self.assertEquals(
-            host.filesystem.walk(mock_dir), [('foo', ['bar'], ['a', 'b', 'c']),
-                                             ('foo/bar', [], ['baz'])])
+        self.assertEquals(list(host.filesystem.walk(mock_dir)), [
+            ('foo', ['bar'], ['a', 'b', 'c']),
+            ('foo/bar', [], ['baz']),
+        ])
 
     def test_filesystem_walk_deeply_nested(self):
         mock_dir = 'foo'
@@ -131,11 +132,12 @@
         mock_files_ordered = OrderedDict(sorted(mock_files.items()))
         host = MockHost()
         host.filesystem = MockFileSystem(files=mock_files_ordered)
-        self.assertEquals(host.filesystem.walk(mock_dir),
-                          [('foo', ['a', 'bar'], ['b', 'c']),
-                           ('foo/a', ['z'], ['x', 'y']),
-                           ('foo/a/z', [], ['lyrics']),
-                           ('foo/bar', [], ['baz', 'quux'])])
+        self.assertEquals(list(host.filesystem.walk(mock_dir)), [
+            ('foo', ['a', 'bar'], ['b', 'c']),
+            ('foo/a', ['z'], ['x', 'y']),
+            ('foo/a/z', [], ['lyrics']),
+            ('foo/bar', [], ['baz', 'quux']),
+        ])
 
     def test_relpath_win32(self):
         # This unit test inherits tests from GenericFileSystemTests, but
diff --git a/third_party/blink/tools/blinkpy/w3c/test_copier.py b/third_party/blink/tools/blinkpy/w3c/test_copier.py
index 718f753..1562a862 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_copier.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_copier.py
@@ -30,7 +30,9 @@
 a local W3C repository source directory into a destination directory.
 """
 
+import collections
 import logging
+from typing import Mapping, Sequence, Set, Tuple, TypedDict
 
 from blinkpy.w3c.common import is_basename_skipped
 from blinkpy.common import path_finder
@@ -43,7 +45,12 @@
 DEST_DIR_NAME = 'external'
 
 
-class TestCopier(object):
+class Copy(TypedDict):
+    src: str
+    dest: str
+
+
+class TestCopier:
     def __init__(self, host, source_repo_path):
         """Initializes variables to prepare for copying and converting files.
 
@@ -67,8 +74,6 @@
             self.source_repo_path == self.destination_directory)
         self.dir_above_repo = self.filesystem.dirname(self.source_repo_path)
 
-        self.import_list = []
-
         # This is just a FYI list of CSS properties that still need to be prefixed,
         # which may be output after importing.
         self._prefixed_properties = {}
@@ -76,17 +81,22 @@
     def do_import(self):
         _log.info('Importing %s into %s', self.source_repo_path,
                   self.destination_directory)
-        self.find_importable_tests()
-        self.import_tests()
+        copies_by_dir = self.find_importable_tests()
+        self.import_tests(copies_by_dir)
 
-    def find_importable_tests(self):
-        """Walks through the source directory to find what tests should be imported.
+    def find_importable_tests(self) -> Mapping[str, Sequence[Copy]]:
+        """Walks through the source directory to find what tests should be imported."""
+        paths_to_skip, paths_to_import = self._read_import_filter()
+        copies_by_dir = collections.defaultdict(list)
 
-        This function sets self.import_list, which contains information about how many
-        tests are being imported, and their source and destination paths.
-        """
-        paths_to_skip = self.find_paths_to_skip()
-
+        # TODO(crbug.com/326646909):
+        # * Path construction here is much more complicated than it needs to
+        #   be, and likely only works on Unix.
+        # * Use the simple test list format for import inclusions/exclusions
+        #   (https://bit.ly/chromium-test-list-format) instead of the tagged
+        #   test list.
+        # * Consider handling exclusions/inclusions in one loop going
+        #   file-by-file.
         for root, dirs, files in self.filesystem.walk(self.source_repo_path):
             cur_dir = root.replace(self.dir_above_repo + '/', '') + '/'
             _log.debug('Scanning %s...', cur_dir)
@@ -108,8 +118,6 @@
                         if self.import_in_place:
                             self.filesystem.rmtree(path_full)
 
-            copy_list = []
-
             for filename in files:
                 path_full = self.filesystem.join(root, filename)
                 path_base = path_full.replace(self.source_repo_path + '/', '')
@@ -131,17 +139,31 @@
                     )
                     continue
 
-                copy_list.append({'src': path_full, 'dest': filename})
-
-            if copy_list:
-                # Only add this directory to the list if there's something to import
-                self.import_list.append({
-                    'dirname': root,
-                    'copy_list': copy_list
+                copies_by_dir[root].append({
+                    'src': path_full,
+                    'dest': filename,
                 })
 
-    def find_paths_to_skip(self):
-        paths_to_skip = set()
+        for path in paths_to_import:
+            path_in_chromium = self.filesystem.join(self.web_tests_dir, path)
+            path_from_wpt = path_in_chromium.replace(
+                self.destination_directory + '/', '')
+            src = self.filesystem.join(self.source_repo_path, path_from_wpt)
+            if not self.filesystem.isfile(src):
+                _log.warning(
+                    'Only regular files can be explicitly allowlisted '
+                    f'currently. {src!r} is not; skipping.')
+                continue
+            copies_by_dir[self.filesystem.dirname(src)].append({
+                'src':
+                src,
+                'dest':
+                self.filesystem.basename(src)
+            })
+        return copies_by_dir
+
+    def _read_import_filter(self) -> Tuple[Set[str], Set[str]]:
+        paths_to_skip, paths_to_import = set(), set()
         port = self.host.port_factory.get()
         w3c_import_expectations_path = self.path_finder.path_from_web_tests(
             'W3CImportExpectations')
@@ -150,40 +172,37 @@
         expectations = TestExpectations(
             port, {w3c_import_expectations_path: w3c_import_expectations})
 
-        # get test names that should be skipped
         for line in expectations.get_updated_lines(
                 w3c_import_expectations_path):
+            if not line.test:  # Comment lines
+                continue
             if line.is_glob:
                 _log.warning(
                     'W3CImportExpectations:%d Globs are not allowed in this file.',
                     line.lineno)
                 continue
+            if line.tags:
+                _log.warning(
+                    'W3CImportExpectations:%d should not have any specifiers',
+                    line.lineno)
             if ResultType.Skip in line.results:
-                if line.tags:
-                    _log.warning(
-                        'W3CImportExpectations:%d should not have any specifiers',
-                        line.lineno)
                 paths_to_skip.add(line.test)
+            elif ResultType.Pass in line.results:
+                paths_to_import.add(line.test)
 
-        return paths_to_skip
+        return paths_to_skip, paths_to_import
 
-    def import_tests(self):
-        """Reads |self.import_list|, and converts and copies files to their destination."""
-        for dir_to_copy in self.import_list:
-            if not dir_to_copy['copy_list']:
-                continue
-
-            orig_path = dir_to_copy['dirname']
-
-            relative_dir = self.filesystem.relpath(orig_path,
+    def import_tests(self, copy_by_dir: Mapping[str, Sequence[Copy]]):
+        """Converts and copies files to their destination."""
+        for src_dir, copy_list in copy_by_dir.items():
+            assert copy_list, src_dir
+            relative_dir = self.filesystem.relpath(src_dir,
                                                    self.source_repo_path)
             dest_dir = self.filesystem.join(self.destination_directory,
                                             relative_dir)
-
             if not self.filesystem.exists(dest_dir):
                 self.filesystem.maybe_make_directory(dest_dir)
-
-            for file_to_copy in dir_to_copy['copy_list']:
+            for file_to_copy in copy_list:
                 self.copy_file(file_to_copy, dest_dir)
 
         _log.info('')
diff --git a/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py b/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
index 137f245..a31a63e4 100644
--- a/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
+++ b/third_party/blink/tools/blinkpy/w3c/test_copier_unittest.py
@@ -25,6 +25,8 @@
 # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 # SUCH DAMAGE.
 
+import textwrap
+
 from blinkpy.common.host_mock import MockHost
 from blinkpy.common.path_finder import RELATIVE_WEB_TESTS
 from blinkpy.common.system.executive_mock import MockExecutive, ScriptError
@@ -34,7 +36,7 @@
 
 MOCK_WEB_TESTS = '/mock-checkout/' + RELATIVE_WEB_TESTS
 
-FAKE_SOURCE_REPO_DIR = '/blink'
+FAKE_SOURCE_REPO_DIR = '/blink/w3c'
 
 FAKE_FILES = {
     MOCK_WEB_TESTS + 'external/OWNERS': b'',
@@ -61,41 +63,39 @@
         host = MockHost()
         host.filesystem = MockFileSystem(files=FAKE_FILES)
         copier = TestCopier(host, FAKE_SOURCE_REPO_DIR)
-        copier.find_importable_tests()
-        self.assertEqual(copier.import_list, [{
-            'copy_list': [{
-                'dest': 'run.bat',
-                'src': '/blink/w3c/dir/run.bat'
-            }, {
-                'dest': 'has_shebang.txt',
-                'src': '/blink/w3c/dir/has_shebang.txt'
-            }, {
-                'dest': 'README.txt',
-                'src': '/blink/w3c/dir/README.txt'
-            }],
-            'dirname':
-            '/blink/w3c/dir',
-        }])
+        copy_by_dir = copier.find_importable_tests()
+        self.assertEqual(
+            copy_by_dir, {
+                '/blink/w3c/dir': [{
+                    'dest': 'run.bat',
+                    'src': '/blink/w3c/dir/run.bat'
+                }, {
+                    'dest': 'has_shebang.txt',
+                    'src': '/blink/w3c/dir/has_shebang.txt'
+                }, {
+                    'dest': 'README.txt',
+                    'src': '/blink/w3c/dir/README.txt'
+                }],
+            })
 
     def test_does_not_import_reftestlist_file(self):
         host = MockHost()
         host.filesystem = MockFileSystem(files=FAKE_FILES)
         copier = TestCopier(host, FAKE_SOURCE_REPO_DIR)
-        copier.find_importable_tests()
-        self.assertEqual(copier.import_list, [{
-            'copy_list': [{
-                'dest': 'run.bat',
-                'src': '/blink/w3c/dir/run.bat'
-            }, {
-                'dest': 'has_shebang.txt',
-                'src': '/blink/w3c/dir/has_shebang.txt'
-            }, {
-                'dest': 'README.txt',
-                'src': '/blink/w3c/dir/README.txt'
-            }],
-            'dirname':
-            '/blink/w3c/dir',
-        }])
+        copy_by_dir = copier.find_importable_tests()
+        self.assertEqual(
+            copy_by_dir, {
+                '/blink/w3c/dir': [{
+                    'dest': 'run.bat',
+                    'src': '/blink/w3c/dir/run.bat'
+                }, {
+                    'dest': 'has_shebang.txt',
+                    'src': '/blink/w3c/dir/has_shebang.txt'
+                }, {
+                    'dest': 'README.txt',
+                    'src': '/blink/w3c/dir/README.txt'
+                }],
+            })
 
     def test_executable_files(self):
         # Files with shebangs or .bat files need to be made executable.
@@ -105,8 +105,8 @@
         copier.do_import()
         self.assertEqual(
             host.filesystem.executable_files, {
-                MOCK_WEB_TESTS + 'external/blink/w3c/dir/run.bat',
-                MOCK_WEB_TESTS + 'external/blink/w3c/dir/has_shebang.txt'
+                MOCK_WEB_TESTS + 'external/w3c/dir/run.bat',
+                MOCK_WEB_TESTS + 'external/w3c/dir/has_shebang.txt'
             })
 
     def test_ref_test_with_ref_is_copied(self):
@@ -121,11 +121,11 @@
                 b'',
             })
         copier = TestCopier(host, FAKE_SOURCE_REPO_DIR)
-        copier.find_importable_tests()
-        self.assertEqual(len(copier.import_list), 1)
+        copy_by_dir = copier.find_importable_tests()
+        self.assertEqual(len(copy_by_dir), 1)
         # The order of copy_list depends on the implementation of
         # filesystem.walk, so don't check the order
-        self.assertCountEqual(copier.import_list[0]['copy_list'],
+        self.assertCountEqual(copy_by_dir['/blink/w3c/dir1'],
                               [{
                                   'src': '/blink/w3c/dir1/ref-file.html',
                                   'dest': 'ref-file.html'
@@ -133,5 +133,23 @@
                                   'src': '/blink/w3c/dir1/my-ref-test.html',
                                   'dest': 'my-ref-test.html'
                               }])
-        self.assertEqual(copier.import_list[0]['dirname'],
-                         '/blink/w3c/dir1')
+
+    def test_skip_dir_but_copy_child_file(self):
+        host = MockHost()
+        host.filesystem = MockFileSystem(files=FAKE_FILES)
+        host.filesystem.write_text_file(
+            MOCK_WEB_TESTS + 'W3CImportExpectations',
+            textwrap.dedent("""\
+                # results: [ Pass Skip ]
+                external/w3c/dir [ Skip ]
+                external/w3c/dir/run.bat [ Pass ]
+                """))
+        copier = TestCopier(host, FAKE_SOURCE_REPO_DIR)
+        copy_by_dir = copier.find_importable_tests()
+        self.assertEqual(
+            copy_by_dir, {
+                '/blink/w3c/dir': [{
+                    'dest': 'run.bat',
+                    'src': '/blink/w3c/dir/run.bat',
+                }],
+            })
diff --git a/third_party/blink/tools/blinkpy/web_tests/merge_results_unittest.py b/third_party/blink/tools/blinkpy/web_tests/merge_results_unittest.py
index f97eca0..81a64fb 100644
--- a/third_party/blink/tools/blinkpy/web_tests/merge_results_unittest.py
+++ b/third_party/blink/tools/blinkpy/web_tests/merge_results_unittest.py
@@ -1342,58 +1342,6 @@
 """
 
     web_test_output_filesystem = {
-        '/out/layout-test-results/access_log.txt':
-        output_access_log,
-        '/out/layout-test-results/error_log.txt':
-        output_error_log,
-        '/out/layout-test-results/failing_results.json':
-        "ADD_RESULTS(" + output_output_json + ");",
-        '/out/layout-test-results/full_results.json':
-        output_output_json,
-        '/out/layout-test-results/stats.json':
-        output_stats_json,
-        '/out/layout-test-results/testdir1/test1-actual.png':
-        '1ap',
-        '/out/layout-test-results/testdir1/test1-diff.png':
-        '1dp',
-        '/out/layout-test-results/testdir1/test1-diffs.html':
-        '1dh',
-        '/out/layout-test-results/testdir1/test1-expected-stderr.txt':
-        '1est',
-        '/out/layout-test-results/testdir1/test1-expected.png':
-        '1ep',
-        '/out/layout-test-results/testdir2/testdir2.1/test3-actual.png':
-        '3ap',
-        '/out/layout-test-results/testdir2/testdir2.1/test3-diff.png':
-        '3dp',
-        '/out/layout-test-results/testdir2/testdir2.1/test3-diffs.html':
-        '3dh',
-        '/out/layout-test-results/testdir2/testdir2.1/test3-expected-stderr.txt':
-        '3est',
-        '/out/layout-test-results/testdir2/testdir2.1/test3-expected.png':
-        '3ep',
-        '/out/layout-test-results/testdir2/testdir2.1/test4-actual.png':
-        '4ap',
-        '/out/layout-test-results/testdir2/testdir2.1/test4-diff.png':
-        '4dp',
-        '/out/layout-test-results/testdir2/testdir2.1/test4-diffs.html':
-        '4dh',
-        '/out/layout-test-results/testdir2/testdir2.1/test4-expected-stderr.txt':
-        '4est',
-        '/out/layout-test-results/testdir2/testdir2.1/test4-expected.png':
-        '4ep',
-        '/out/layout-test-results/testdir3/test5-actual.png':
-        '5ap',
-        '/out/layout-test-results/testdir3/test5-diff.png':
-        '5dp',
-        '/out/layout-test-results/testdir3/test5-diffs.html':
-        '5dh',
-        '/out/layout-test-results/testdir3/test5-expected-stderr.txt':
-        '5est',
-        '/out/layout-test-results/testdir3/test5-expected.png':
-        '5ep',
-        '/out/layout-test-results/times_ms.json':
-        output_times_ms_json,
         '/out/output.json':
         output_output_json,
     }
@@ -1407,7 +1355,8 @@
 
         for fname, expected_contents in self.web_test_output_filesystem.items(
         ):
-            self.assertTrue(fs.isfile(fname))
+            self.assertTrue(fs.isfile(fname),
+                            f'{fname} should be a regular file')
             if fname.endswith(".json"):
                 actual_json_str = fs.read_text_file(fname)
                 expected_json_str = expected_contents
diff --git a/third_party/blink/web_tests/W3CImportExpectations b/third_party/blink/web_tests/W3CImportExpectations
index 8a547e3..ec49e76 100644
--- a/third_party/blink/web_tests/W3CImportExpectations
+++ b/third_party/blink/web_tests/W3CImportExpectations
@@ -1,9 +1,11 @@
-# results: [ Skip ]
+# results: [ Pass Skip ]
 
 # This file controls which subdirectories of the w3c test repos we import.
 #
-# This file acts as a list of exclusions; directories and files not listed here
-# will automatically be found and imported.
+# Most lines in this file are exclusions marked with the `Skip` status.
+# Directories and files not listed here will automatically be found and
+# imported. It's also possible to import specific files in an excluded directory
+# by marking them as `Pass`.
 #
 # Tests that are imported but not run are listed in NeverFixTests. This can be
 # used to make it easier to work on getting them running and passing.
@@ -376,3 +378,7 @@
 external/wpt/css/css-page/page-size-010.xht [ Skip ]
 external/wpt/css/css-page/page-size-011.xht [ Skip ]
 external/wpt/css/css-page/page-size-012.xht [ Skip ]
+
+# This is a reference file for a non-denylisted test, so it should be included
+# despite a general exclusion of `css/CSS2/syntax/`.
+external/wpt/css/CSS2/syntax/character-encoding-041-ref.xht [ Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-button-mode-basics.https.html b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-button-mode-basics.https.html
index d7b063f..abf46ee7 100644
--- a/third_party/blink/web_tests/external/wpt/credential-management/fedcm-button-mode-basics.https.html
+++ b/third_party/blink/web_tests/external/wpt/credential-management/fedcm-button-mode-basics.https.html
@@ -30,4 +30,63 @@
   });
 }, "Test that the button mode requires user activation.");
 
+ fedcm_test(async t => {
+   let widget_test_options = request_options_with_mediation_required("manifest_with_rp_mode.json");
+   let button_test_options = request_options_with_mediation_required("manifest_with_rp_mode.json");
+   button_test_options.identity.mode = "button";
+
+   let first_cred = navigator.credentials.get(widget_test_options);
+   let rej = promise_rejects_dom(t, 'NetworkError', first_cred);
+
+   return test_driver.bless('initiate FedCM request', async function() {
+       let second_cred = await fedcm_get_and_select_first_account(t, button_test_options);
+       assert_equals(second_cred.token, "mode=button");
+       await rej;
+   });
+ }, "Test that the button mode can replace widget mode.");
+
+fedcm_test(async t => {
+  let widget_test_options = request_options_with_mediation_required();
+  let button_test_options = request_options_with_mediation_required("manifest_with_rp_mode.json");
+  button_test_options.identity.mode = "button";
+
+  return test_driver.bless('initiate FedCM request', async function() {
+      let first_cred = await fedcm_get_and_select_first_account(t, button_test_options);
+      assert_equals(first_cred.token, "mode=button");
+      let second_cred = await fedcm_get_and_select_first_account(t, widget_test_options);
+      assert_equals(second_cred.token, "token");
+  });
+}, "Test that the widget mode can succeed after the button mode.");
+
+fedcm_test(async t => {
+  let button_test_options = request_options_with_mediation_required("manifest_with_rp_mode.json");
+  button_test_options.identity.mode = "button";
+
+  return test_driver.bless('initiate FedCM request', async function() {
+      let first_cred = fedcm_get_and_select_first_account(t, button_test_options);
+      let second_cred = navigator.credentials.get(button_test_options);
+      let rej = promise_rejects_dom(t, 'NotAllowedError', second_cred);
+
+      let cred = await first_cred;
+      assert_equals(cred.token, "mode=button");
+      await rej;
+  });
+}, "Test that the button mode cannot replace button mode.");
+
+fedcm_test(async t => {
+  let widget_test_options = request_options_with_mediation_required("manifest_with_rp_mode.json");
+  let button_test_options = request_options_with_mediation_required("manifest_with_rp_mode.json");
+  button_test_options.identity.mode = "button";
+
+  return test_driver.bless('initiate FedCM request', async function() {
+      let first_cred = fedcm_get_and_select_first_account(t, button_test_options);
+      let second_cred = navigator.credentials.get(widget_test_options);
+      let rej = promise_rejects_dom(t, 'NotAllowedError', second_cred);
+
+      let cred = await first_cred;
+      assert_equals(cred.token, "mode=button");
+      await rej;
+  });
+}, "Test that the widget mode cannot replace button mode.");
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-height-interpolation.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-height-interpolation.tentative-expected.txt
new file mode 100644
index 0000000..324756dc
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-height-interpolation.tentative-expected.txt
@@ -0,0 +1,132 @@
+This is a testharness.js-based test.
+Found 64 FAIL, 0 TIMEOUT, 0 NOTRUN.
+[FAIL] CSS Transitions: property <height> from neutral to [calc-size(auto, size * 2)] at (-0.25) should be [75px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions: property <height> from neutral to [calc-size(auto, size * 2)] at (0) should be [100px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions: property <height> from neutral to [calc-size(auto, size * 2)] at (0.25) should be [125px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions: property <height> from neutral to [calc-size(auto, size * 2)] at (0.5) should be [150px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions: property <height> from neutral to [calc-size(auto, size * 2)] at (0.75) should be [175px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions: property <height> from neutral to [calc-size(auto, size * 2)] at (1) should be [200px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions: property <height> from neutral to [calc-size(auto, size * 2)] at (1.25) should be [225px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions with transition: all: property <height> from neutral to [calc-size(auto, size * 2)] at (-0.25) should be [75px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions with transition: all: property <height> from neutral to [calc-size(auto, size * 2)] at (0) should be [100px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions with transition: all: property <height> from neutral to [calc-size(auto, size * 2)] at (0.25) should be [125px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions with transition: all: property <height> from neutral to [calc-size(auto, size * 2)] at (0.5) should be [150px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions with transition: all: property <height> from neutral to [calc-size(auto, size * 2)] at (0.75) should be [175px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions with transition: all: property <height> from neutral to [calc-size(auto, size * 2)] at (1) should be [200px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions with transition: all: property <height> from neutral to [calc-size(auto, size * 2)] at (1.25) should be [225px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (-0.25) should be [75px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (0) should be [100px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (0.25) should be [125px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (0.5) should be [150px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (0.75) should be [175px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (1) should be [200px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (1.25) should be [225px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] Web Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (-0.25) should be [75px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] Web Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (0) should be [100px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] Web Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (0.25) should be [125px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] Web Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (0.5) should be [150px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] Web Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (0.75) should be [175px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] Web Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (1) should be [200px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] Web Animations: property <height> from neutral to [calc-size(auto, size * 2)] at (1.25) should be [225px]
+  assert_true: 'to' value should be supported expected true got false
+[FAIL] CSS Transitions: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (-0.25) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (0) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (0.25) should be [25px]
+  assert_equals: expected "25px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (0.5) should be [50px]
+  assert_equals: expected "50px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (0.75) should be [75px]
+  assert_equals: expected "75px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (1.25) should be [125px]
+  assert_equals: expected "125px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (-0.25) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (0) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (0.25) should be [25px]
+  assert_equals: expected "25px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (0.5) should be [50px]
+  assert_equals: expected "50px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (0.75) should be [75px]
+  assert_equals: expected "75px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [calc-size(min-content, 0px)] to [calc-size(min-content, size)] at (1.25) should be [125px]
+  assert_equals: expected "125px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [0] to [calc-size(max-content, size)] at (-0.25) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [0] to [calc-size(max-content, size)] at (0) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [0] to [calc-size(max-content, size)] at (0.25) should be [25px]
+  assert_equals: expected "25px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [0] to [calc-size(max-content, size)] at (0.5) should be [50px]
+  assert_equals: expected "50px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [0] to [calc-size(max-content, size)] at (0.75) should be [75px]
+  assert_equals: expected "75px " but got "100px "
+[FAIL] CSS Transitions: property <height> from [0] to [calc-size(max-content, size)] at (1.25) should be [125px]
+  assert_equals: expected "125px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [0] to [calc-size(max-content, size)] at (-0.25) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [0] to [calc-size(max-content, size)] at (0) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [0] to [calc-size(max-content, size)] at (0.25) should be [25px]
+  assert_equals: expected "25px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [0] to [calc-size(max-content, size)] at (0.5) should be [50px]
+  assert_equals: expected "50px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [0] to [calc-size(max-content, size)] at (0.75) should be [75px]
+  assert_equals: expected "75px " but got "100px "
+[FAIL] CSS Transitions with transition: all: property <height> from [0] to [calc-size(max-content, size)] at (1.25) should be [125px]
+  assert_equals: expected "125px " but got "100px "
+[FAIL] CSS Animations: property <height> from [0] to [calc-size(max-content, size)] at (-0.25) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Animations: property <height> from [0] to [calc-size(max-content, size)] at (0) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] CSS Animations: property <height> from [0] to [calc-size(max-content, size)] at (0.25) should be [25px]
+  assert_equals: expected "25px " but got "100px "
+[FAIL] CSS Animations: property <height> from [0] to [calc-size(max-content, size)] at (0.5) should be [50px]
+  assert_equals: expected "50px " but got "100px "
+[FAIL] CSS Animations: property <height> from [0] to [calc-size(max-content, size)] at (0.75) should be [75px]
+  assert_equals: expected "75px " but got "100px "
+[FAIL] CSS Animations: property <height> from [0] to [calc-size(max-content, size)] at (1.25) should be [125px]
+  assert_equals: expected "125px " but got "100px "
+[FAIL] Web Animations: property <height> from [0] to [calc-size(max-content, size)] at (-0.25) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] Web Animations: property <height> from [0] to [calc-size(max-content, size)] at (0) should be [0]
+  assert_equals: expected "0px " but got "100px "
+[FAIL] Web Animations: property <height> from [0] to [calc-size(max-content, size)] at (0.25) should be [25px]
+  assert_equals: expected "25px " but got "100px "
+[FAIL] Web Animations: property <height> from [0] to [calc-size(max-content, size)] at (0.5) should be [50px]
+  assert_equals: expected "50px " but got "100px "
+[FAIL] Web Animations: property <height> from [0] to [calc-size(max-content, size)] at (0.75) should be [75px]
+  assert_equals: expected "75px " but got "100px "
+[FAIL] Web Animations: property <height> from [0] to [calc-size(max-content, size)] at (1.25) should be [125px]
+  assert_equals: expected "125px " but got "100px "
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-height-interpolation.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-height-interpolation.tentative.html
new file mode 100644
index 0000000..525c388
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-height-interpolation.tentative.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta charset="UTF-8">
+<title>height: calc-size() animations</title>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#calc-size">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../../../support/interpolation-testcommon.js"></script>
+
+<style>
+.target {
+  display: block;
+}
+.target::before {
+  display: block;
+  content: "";
+  width: 23px;
+  height: 100px;
+}
+</style>
+
+<body>
+
+<script>
+  test_interpolation({
+    property: 'height',
+    from: neutralKeyframe,
+    to: 'calc-size(auto, size * 2)',
+  }, [
+    { at: -0.25, expect: '75px' },
+    { at: 0, expect: '100px' },
+    { at: 0.25, expect: '125px' },
+    { at: 0.5, expect: '150px' },
+    { at: 0.75, expect: '175px' },
+    { at: 1, expect: '200px' },
+    { at: 1.25, expect: '225px' },
+  ]);
+
+  test_interpolation({
+    property: 'height',
+    from: 'calc-size(min-content, 0 * size)',
+    to: 'calc-size(min-content, size)',
+  }, [
+    { at: -0.25, expect: '0' },
+    { at: 0, expect: '0' },
+    { at: 0.25, expect: '25px' },
+    { at: 0.5, expect: '50px' },
+    { at: 0.75, expect: '75px' },
+    { at: 1, expect: '100px' },
+    { at: 1.25, expect: '125px' },
+  ]);
+
+  test_interpolation({
+    property: 'height',
+    from: 'calc-size(min-content, 0px)',
+    to: 'calc-size(min-content, size)',
+  }, [
+    { at: -0.25, expect: '0' },
+    { at: 0, expect: '0' },
+    { at: 0.25, expect: '25px' },
+    { at: 0.5, expect: '50px' },
+    { at: 0.75, expect: '75px' },
+    { at: 1, expect: '100px' },
+    { at: 1.25, expect: '125px' },
+  ]);
+
+  test_interpolation({
+    property: 'height',
+    from: '0',
+    to: 'calc-size(max-content, size)',
+  }, [
+    { at: -0.25, expect: '0' },
+    { at: 0, expect: '0' },
+    { at: 0.25, expect: '25px' },
+    { at: 0.5, expect: '50px' },
+    { at: 0.75, expect: '75px' },
+    { at: 1, expect: '100px' },
+    { at: 1.25, expect: '125px' },
+  ]);
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-width-interpolation.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-width-interpolation.tentative.html
index 7254951..ff72ba6a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-width-interpolation.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/animation/calc-size-width-interpolation.tentative.html
@@ -1,8 +1,7 @@
 <!DOCTYPE html>
 <meta charset="UTF-8">
 <title>width: calc-size() animations</title>
-<link rel="help" href="https://drafts.csswg.org/css-values-5/">
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/626#issuecomment-1800254442">
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#calc-size">
 
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-height.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-height.tentative-expected.txt
new file mode 100644
index 0000000..0931cad
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-height.tentative-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+[FAIL] resolved height for height in auto height container: calc-size(any, 31%)
+  assert_equals: expected "0px" but got "10px"
+[FAIL] resolved height for height in auto height container: calc-size(max-content, 31%)
+  assert_equals: expected "0px" but got "10px"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-height.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-height.tentative.html
new file mode 100644
index 0000000..6f93cc0
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-height.tentative.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>calc-size() on height</title>
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#calc-size">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #container {
+    font-size: 20px;
+  }
+  #child {
+    width: 123px;
+    height: 10px;
+  }
+</style>
+
+<div id="container">
+  <div id="target">
+    <div id="child"></div>
+  </div>
+</div>
+
+<script>
+
+let basic_tests = [
+  { value: "calc-size(any, 357px)", expected: "357px" },
+  { value: "calc-size(any, 31%)", expected_auto: "0px", expected_definite: "31px" },
+  { value: "calc-size(31%, size)", expected_auto: "10px", expected_definite: "31px" },
+  { value: "calc-size(max-content, 31%)", expected_auto: "0px", expected_definite: "31px" },
+  { value: "calc-size(fit-content, 72px)", expected: "72px" },
+  { value: "calc-size(37px, 93px)", expected: "93px" },
+  { value: "calc-size(83px, size * 3)", expected: "249px" },
+  { value: "calc-size(min-content, size / 2)", expected: "5px" },
+  { value: "calc-size(max-content, size * 1.2)", expected: "12px" },
+  { value: "calc-size(fit-content, size / 2 + 30px)", expected: "35px" },
+  { value: "calc-size(30px, 15em)", expected: "300px" },
+  { value: "calc-size(calc-size(any, 30px), 15em)", expected: "300px" },
+  { value: "calc-size(calc-size(2in, 30px), 15em)", expected: "300px" },
+  { value: "calc-size(calc-size(min-content, 30px), 15em)", expected: "300px" },
+  { value: "calc-size(calc-size(min-content, size), size)", expected: "10px" },
+];
+const container = document.getElementById("container");
+const target = document.getElementById("target");
+const target_cs = getComputedStyle(target);
+for (const obj of basic_tests) {
+  test((t) => {
+    target.style.removeProperty("height");
+    target.style.height = obj.value;
+    container.style.height = "auto";
+    let expected = "expected" in obj ? obj.expected : obj.expected_auto;
+    assert_equals(target_cs.height, expected);
+  }, `resolved height for height in auto height container: ${obj.value}`);
+  test((t) => {
+    target.style.removeProperty("height");
+    target.style.height = obj.value;
+    container.style.height = "100px";
+    let expected = "expected" in obj ? obj.expected : obj.expected_definite;
+    assert_equals(target_cs.height, expected);
+  }, `resolved height for height in definite height container: ${obj.value}`);
+}
+
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-parsing.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-parsing.tentative.html
index fec24aa..bc0048f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-parsing.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-parsing.tentative.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <title>calc-size() expressions</title>
-<link rel="help" href="https://drafts.csswg.org/css-values-5/">
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/626#issuecomment-1800254442">
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#calc-size">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="../../support/parsing-testcommon.js"></script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-width.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-width.tentative.html
index 228f526..c8000f34 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-width.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/calc-size/calc-size-width.tentative.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <title>calc-size() on width</title>
-<link rel="help" href="https://drafts.csswg.org/css-values-5/">
-<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/626#issuecomment-1800254442">
+<link rel="help" href="https://drafts.csswg.org/css-values-5/#calc-size">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <link rel="stylesheet" href="/fonts/ahem.css">
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/additional-bids.https.window.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/additional-bids.https.window.js
new file mode 100644
index 0000000..0e1d22c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/additional-bids.https.window.js
@@ -0,0 +1,146 @@
+// META: script=/resources/testdriver.js
+// META: script=/common/utils.js
+// META: script=resources/fledge-util.sub.js
+// META: script=/common/subset-tests.js
+// META: timeout=long
+// META: variant=?1-last
+
+"use strict;"
+
+// This file contains tests for additional bids and negative targeting.
+//
+// TODO:
+// - test that an negatively targeted additional bid is suppressed.
+// - test that an incorrectly signed additional bid is not negative targeted.
+// - test that an missing-signature additional bid is not negative targeted.
+// - test that an additional bid with some correct signatures can be negative.
+//       negative targeted for those negative interest groups whose signatures
+//       match.
+// - test an additional bid with multiple negative interest groups.
+// - test that multiple negative interest groups with mismatched joining origins
+//       is not negative targeted.
+// - test that additional bids can be fetched using an iframe navigation.
+// - test that additional bids are not fetched using an iframe navigation for
+//      which the `adAuctionHeaders=true` attribute is not specified.
+// - test that additional bids are not fetched using a Fetch request for which
+//      `adAuctionHeaders: true` is not specified.
+// - test that an additional bid with an incorrect auction nonce is not used
+//       included in an auction. Same for seller and top-level seller.
+// - test that correctly formatted additional bids are included in an auction
+//       when fetched alongside malformed additional bid headers by a Fetch
+//       request.
+// - test that correctly formatted additional bids are included in an auction
+//       when fetched alongside malformed additional bid headers by an iframe
+//       navigation.
+// - test that reportWin is not used for reporting an additional bid win.
+// - test that additional bids can *not* be fetched from iframe subresource
+//       requests.
+// - test that an auction nonce can only be used once, and a second auction
+//       trying to reuse an auction immediately fails.
+// - test that an auction nonce must be created in the same window/tab as the
+//       call to runAdAuction.
+// - test reportAdditionalBidWin with each of no metadata, null metadata, and
+//       an object metadata.
+// - test that an auction running in one tab can't see an additional bid loaded
+//       in a new tab.
+// - test that two auctions running with different nonces only get the
+//       additional bids fetched with their auction nonce.
+// - test that two auctions running with different nonces only get the
+//       additional bids fetched with their auction nonce, when both additional
+//       bids are retrieved with one fetch.
+// - test that a multiseller auction with two component auctions can direct
+//       additional bids to the correct component auctions.
+// - test that two auctions running with different nonces only get the
+//       additional bids fetched with their auction nonce.
+// - test that two auctions running with different nonces only get the
+//       additional bids fetched with their auction nonce, when both additional
+//       bids are retrieved with one fetch.
+// - test that an additional bid can compete against an interest group bid and
+//       lose.
+// - test that an additional bid can compete against an interest group bid and
+//       win.
+// - test that a malformed additional bid causes that one additional bid to be
+//       ignored, but the rest of the auction (and other additional bids, even
+//       from the same fetch) continue on.
+// - test (in join-leave-ad-interest-group.https.window.js) that an IG that
+//       provides `additionalBidKey` fails if the key fails to decode, or if
+//       that IG also provides `ads`, or if it provides `updateURL`.
+// - test that an IG update cannot cause a regular interest group (one that
+//       does not provide `additionalBidKey`) to become a negative interest
+//       group (one that does provide `additionalBidKey`).
+// - test (in auction-config-passed-to-worklets.https.window.js) that a
+//       multi-seller auction fails if the top-level auction provides
+//       a value for `additionalBids`.
+// - test (in auction-config-passed-to-worklets.https.window.js) that an auction
+//       fails if it provides `additionalBids` but not `auctionNonce`, or if it
+//       provides `additionalBids` but not `interestGroupBuyers`.
+
+// The auction is run with the seller being the same as the document origin.
+// The request to fetch additional bids must be issued to the seller's origin
+// for ad auction headers interception to associate it with this auction.
+const SINGLE_SELLER_AUCTION_SELLER = window.location.origin;
+
+// Single-seller auction with a single buyer who places a single additional
+// bid. As the only bid, this wins.
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
+  const auctionNonce = await navigator.createAuctionNonce();
+  const seller = SINGLE_SELLER_AUCTION_SELLER;
+
+  const buyer = OTHER_ORIGIN1;
+  const additionalBid = createAdditionalBid(
+      uuid, auctionNonce, seller, buyer, 'horses', 1.99);
+
+  await runAdditionalBidTest(
+      test, uuid, [buyer], auctionNonce,
+      fetchAdditionalBids(seller, [additionalBid]),
+      /*highestScoringOtherBid=*/0,
+      /*winningAdditionalBidId=*/'horses');
+}, 'single valid additional bid');
+
+// Single-seller auction with a two buyers competing with additional bids.
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
+  const auctionNonce = await navigator.createAuctionNonce();
+  const seller = SINGLE_SELLER_AUCTION_SELLER;
+
+  const buyer1 = OTHER_ORIGIN1;
+  const additionalBid1 = createAdditionalBid(
+      uuid, auctionNonce, seller, buyer1, 'horses', 1.99);
+
+  const buyer2 = OTHER_ORIGIN2;
+  const additionalBid2 = createAdditionalBid(
+      uuid, auctionNonce, seller, buyer2, 'planes', 2.99);
+
+  await runAdditionalBidTest(
+      test, uuid, [buyer1, buyer2], auctionNonce,
+      fetchAdditionalBids(seller, [additionalBid1, additionalBid2]),
+      /*highestScoringOtherBid=*/1.99,
+      /*winningAdditionalBidId=*/'planes');
+}, 'two valid additional bids');
+
+// Same as the test above, except that this uses two Fetch requests instead of
+// one to retrieve the additional bids.
+subsetTest(promise_test, async test => {
+  const uuid = generateUuid(test);
+  const auctionNonce = await navigator.createAuctionNonce();
+  const seller = SINGLE_SELLER_AUCTION_SELLER;
+
+  const buyer1 = OTHER_ORIGIN1;
+  const additionalBid1 = createAdditionalBid(
+      uuid, auctionNonce, seller, buyer1, 'horses', 1.99);
+
+  const buyer2 = OTHER_ORIGIN2;
+  const additionalBid2 = createAdditionalBid(
+      uuid, auctionNonce, seller, buyer2, 'planes', 2.99);
+
+
+  await runAdditionalBidTest(
+    test, uuid, [buyer1, buyer2], auctionNonce,
+    Promise.all([
+        fetchAdditionalBids(seller, [additionalBid1]),
+        fetchAdditionalBids(seller, [additionalBid2])
+    ]),
+    /*highestScoringOtherBid=*/1.99,
+    /*winningAdditionalBidId=*/'planes');
+}, 'two valid additional bids from two distinct Fetch requests');
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/additional-bids.py b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/additional-bids.py
new file mode 100644
index 0000000..060606b4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/additional-bids.py
@@ -0,0 +1,59 @@
+"""Endpoint to return additional bids in the appropriate response header.
+
+Additional bids are returned using the "Ad-Auction-Additional-Bid" response
+header, as described at
+https://github.com/WICG/turtledove/blob/main/FLEDGE.md#63-http-response-headers.
+
+This script generates one of "Ad-Auction-Additional-Bid" response header for
+each additional bid provided in a url-encoded `additionalBids` query parameter.
+
+All requests to this endpoint requires a "Sec-Ad-Auction-Fetch" request header
+with a value of b"?1"; this entrypoint otherwise returns a 400 response.
+"""
+import json
+import base64
+
+import fledge.tentative.resources.fledge_http_server_util as fledge_http_server_util
+
+
+class BadRequestError(Exception):
+  pass
+
+
+def main(request, response):
+  try:
+    if fledge_http_server_util.handle_cors_headers_and_preflight(request, response):
+      return
+
+    # Verify that Sec-Ad-Auction-Fetch is present
+    if (request.headers.get("Sec-Ad-Auction-Fetch", default=b"").decode("utf-8") != "?1"):
+      raise BadRequestError("Sec-Ad-Auction-Fetch missing or unexpected value; expected '?1'")
+
+    # Return each signed additional bid in its own header
+    additional_bids = request.GET.get(b"additionalBids", default=b"").decode("utf-8")
+    if not additional_bids:
+      raise BadRequestError("Missing 'additionalBids' parameter")
+    for additional_bid in json.loads(additional_bids):
+      additional_bid_string = json.dumps(additional_bid)
+      auction_nonce = additional_bid.get("auctionNonce", None)
+      if not auction_nonce:
+        raise BadRequestError("Additional bid missing required 'auctionNonce' field")
+      signed_additional_bid = json.dumps({
+        "bid": additional_bid_string,
+        "signatures": []
+      })
+      additional_bid_header_value = (auction_nonce.encode("utf-8") + b":" +
+                                     base64.b64encode(signed_additional_bid.encode("utf-8")))
+      response.headers.append(b"Ad-Auction-Additional-Bid", additional_bid_header_value)
+
+    response.status = (200, b"OK")
+    response.headers.set(b"Content-Type", b"text/plain")
+
+  except BadRequestError as error:
+    response.status = (400, b"Bad Request")
+    response.headers.set(b"Content-Type", b"text/plain")
+    response.content = str(error)
+
+  except Exception as exception:
+    response.status = (500, b"Internal Server Error")
+    response.content = str(exception)
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/bidding-logic.sub.py b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/bidding-logic.sub.py
index 707e37f..e17f2c2c 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/bidding-logic.sub.py
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/bidding-logic.sub.py
@@ -1,71 +1,83 @@
 from pathlib import Path
 
+from fledge.tentative.resources import fledge_http_server_util
+
 # General bidding logic script. Depending on query parameters, it can
-# simulate a variety of network errors, and its generateBid() and
-# reportWin() functions can have arbitrary Javascript code injected
-# in them. generateBid() will by default return a bid of 9 for the
+# simulate a variety of network errors, and its generateBid(), reportWin(),
+# and reportAdditionalBidWin() functions can have arbitrary Javascript code
+# injected in them. generateBid() will by default return a bid of 9 for the
 # first ad.
 def main(request, response):
-    error = request.GET.first(b"error", None)
+  if fledge_http_server_util.handle_cors_headers_and_preflight(request, response):
+    return
 
-    if error == b"close-connection":
-        # Close connection without writing anything, to simulate a network
-        # error. The write call is needed to avoid writing the default headers.
-        response.writer.write("")
-        response.close_connection = True
-        return
+  error = request.GET.first(b"error", None)
 
-    if error == b"http-error":
-        response.status = (404, b"OK")
-    else:
-        response.status = (200, b"OK")
+  if error == b"close-connection":
+      # Close connection without writing anything, to simulate a network
+      # error. The write call is needed to avoid writing the default headers.
+      response.writer.write("")
+      response.close_connection = True
+      return
 
-    if error == b"wrong-content-type":
-        response.headers.set(b"Content-Type", b"application/json")
-    elif error != b"no-content-type":
-        response.headers.set(b"Content-Type", b"application/javascript")
+  if error == b"http-error":
+      response.status = (404, b"OK")
+  else:
+      response.status = (200, b"OK")
 
-    if error == b"bad-allow-fledge":
-        response.headers.set(b"Ad-Auction-Allowed", b"sometimes")
-    elif error == b"fledge-not-allowed":
-        response.headers.set(b"Ad-Auction-Allowed", b"false")
-    elif error != b"no-allow-fledge":
-        response.headers.set(b"Ad-Auction-Allowed", b"true")
+  if error == b"wrong-content-type":
+      response.headers.set(b"Content-Type", b"application/json")
+  elif error != b"no-content-type":
+      response.headers.set(b"Content-Type", b"application/javascript")
 
-    if error == b"no-body":
-        return b''
+  if error == b"bad-allow-fledge":
+      response.headers.set(b"Ad-Auction-Allowed", b"sometimes")
+  elif error == b"fledge-not-allowed":
+      response.headers.set(b"Ad-Auction-Allowed", b"false")
+  elif error != b"no-allow-fledge":
+      response.headers.set(b"Ad-Auction-Allowed", b"true")
 
-    body = (Path(__file__).parent.resolve() / 'worklet-helpers.js').read_text().encode("ASCII")
-    if error != b"no-generateBid":
-        # Use bid query param if present. Otherwise, use a bid of 9.
-        bid = (request.GET.first(b"bid", None) or b"9").decode("ASCII")
+  if error == b"no-body":
+      return b''
 
-        bidCurrency = ""
-        bidCurrencyParam = request.GET.first(b"bidCurrency", None)
-        if bidCurrencyParam != None:
-            bidCurrency = "bidCurrency: '" + bidCurrencyParam.decode("ASCII") + "',"
+  body = (Path(__file__).parent.resolve() / 'worklet-helpers.js').read_text().encode("ASCII")
+  if error != b"no-generateBid":
+      # Use bid query param if present. Otherwise, use a bid of 9.
+      bid = (request.GET.first(b"bid", None) or b"9").decode("ASCII")
 
-        allowComponentAuction = ""
-        allowComponentAuctionParam = request.GET.first(b"allowComponentAuction", None)
-        if allowComponentAuctionParam != None:
-            allowComponentAuction = f"allowComponentAuction: {allowComponentAuctionParam.decode('ASCII')},"
+      bidCurrency = ""
+      bidCurrencyParam = request.GET.first(b"bidCurrency", None)
+      if bidCurrencyParam != None:
+          bidCurrency = "bidCurrency: '" + bidCurrencyParam.decode("ASCII") + "',"
 
-        body += f"""
-            function generateBid(interestGroup, auctionSignals, perBuyerSignals,
-                                trustedBiddingSignals, browserSignals,
-                                directFromSellerSignals) {{
-              {{{{GET[generateBid]}}}};
-              return {{
-                bid: {bid},
-                {bidCurrency}
-                {allowComponentAuction}
-                render: interestGroup.ads[0].renderURL
-              }};
-            }}""".encode()
-    if error != b"no-reportWin":
-        body += b"""
-            function reportWin(auctionSignals, perBuyerSignals, sellerSignals,
-                              browserSignals, directFromSellerSignals) {
-              {{GET[reportWin]}};
-            }"""
-    return body
+      allowComponentAuction = ""
+      allowComponentAuctionParam = request.GET.first(b"allowComponentAuction", None)
+      if allowComponentAuctionParam != None:
+          allowComponentAuction = f"allowComponentAuction: {allowComponentAuctionParam.decode('ASCII')},"
+
+      body += f"""
+          function generateBid(interestGroup, auctionSignals, perBuyerSignals,
+                              trustedBiddingSignals, browserSignals,
+                              directFromSellerSignals) {{
+            {{{{GET[generateBid]}}}};
+            return {{
+              bid: {bid},
+              {bidCurrency}
+              {allowComponentAuction}
+              render: interestGroup.ads[0].renderURL
+            }};
+          }}""".encode()
+  if error != b"no-reportWin":
+      body += b"""
+          function reportWin(auctionSignals, perBuyerSignals, sellerSignals,
+                            browserSignals, directFromSellerSignals) {
+            {{GET[reportWin]}};
+          }"""
+  if error != b"no-reportAdditionalBidWin":
+    body += b"""
+        function reportAdditionalBidWin(auctionSignals, perBuyerSignals,
+                                        sellerSignals, browserSignals,
+                                        directFromSellerSignals) {
+          {{GET[reportAdditionalBidWin]}};
+        }"""
+  return body
diff --git a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.sub.js b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.sub.js
index c78827f4..5819357 100644
--- a/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.sub.js
+++ b/third_party/blink/web_tests/external/wpt/fledge/tentative/resources/fledge-util.sub.js
@@ -33,7 +33,7 @@
 //     on behavior of the script; it only serves to make the URL unique.
 // `id` will always be the last query parameter.
 function createTrackerURL(origin, uuid, dispatch, id = null) {
-  let url = new URL(`${origin}${BASE_PATH}resources/request-tracker.py`);
+  let url = new URL(`${origin}${RESOURCE_PATH}request-tracker.py`);
   let search = `uuid=${uuid}&dispatch=${dispatch}`;
   if (id)
     search += `&id=${id}`;
@@ -59,6 +59,10 @@
   return createTrackerURL(origin, uuid, `track_get`, `seller_report_${id}`);
 }
 
+function createHighestScoringOtherBidReportURL(uuid, highestScoringOtherBid) {
+  return createSellerReportURL(uuid) + '&highestScoringOtherBid=' + Math.round(highestScoringOtherBid);
+}
+
 // Much like above ReportURL methods, except designed for beacons, which
 // are expected to be POSTs.
 function createBidderBeaconURL(uuid, id = '1', origin = window.location.origin) {
@@ -69,7 +73,7 @@
 }
 
 function createDirectFromSellerSignalsURL(origin = window.location.origin) {
-  let url = new URL(`${origin}${BASE_PATH}resources/direct-from-seller-signals.py`);
+  let url = new URL(`${origin}${RESOURCE_PATH}direct-from-seller-signals.py`);
   return url.toString();
 }
 
@@ -80,7 +84,7 @@
   let uuid = token();
   test.add_cleanup(async () => {
     let response = await fetch(createCleanupURL(uuid),
-                               {credentials: 'omit', mode: 'cors'});
+                               { credentials: 'omit', mode: 'cors' });
     assert_equals(await response.text(), 'cleanup complete',
                   `Sever state cleanup failed`);
   });
@@ -94,7 +98,7 @@
   let trackedRequestsURL = createTrackerURL(window.location.origin, uuid,
                                             'tracked_data');
   let response = await fetch(trackedRequestsURL,
-                             {credentials: 'omit', mode: 'cors'});
+                             { credentials: 'omit', mode: 'cors' });
   let trackedData = await response.json();
 
   // Fail on fetch error.
@@ -179,6 +183,8 @@
     url.searchParams.append('generateBid', params.generateBid);
   if (params.reportWin != null)
     url.searchParams.append('reportWin', params.reportWin);
+  if (params.reportAdditionalBidWin != null)
+    url.searchParams.append('reportAdditionalBidWin', params.reportAdditionalBidWin);
   if (params.error != null)
     url.searchParams.append('error', params.error);
   if (params.bid != null)
@@ -270,7 +276,7 @@
 
   await navigator.joinAdInterestGroup(interestGroup, durationSeconds);
   test.add_cleanup(
-      async () => {await navigator.leaveAdInterestGroup(interestGroup)});
+    async () => { await navigator.leaveAdInterestGroup(interestGroup) });
 }
 
 // Similar to joinInterestGroup, but leaves the interest group instead.
@@ -481,6 +487,24 @@
   await waitForObservedRequests(uuid, expectedReportURLs);
 }
 
+async function runAdditionalBidTest(test, uuid, buyers, auctionNonce,
+                                    additionalBidsPromise,
+                                    highestScoringOtherBid,
+                                    winningAdditionalBidId) {
+  await runBasicFledgeAuctionAndNavigate(
+      test, uuid,
+      { interestGroupBuyers: buyers,
+        auctionNonce: auctionNonce,
+        additionalBids: additionalBidsPromise,
+        decisionLogicURL: createDecisionScriptURL(
+            uuid,
+            { reportResult: `sendReportTo("${createSellerReportURL(uuid)}&highestScoringOtherBid=" + Math.round(browserSignals.highestScoringOtherBid));` })});
+
+  await waitForObservedRequests(
+      uuid, [createHighestScoringOtherBidReportURL(uuid, highestScoringOtherBid),
+             createBidderReportURL(uuid, winningAdditionalBidId)]);
+}
+
 // Runs "script" in "child_window" via an eval call. The "child_window" must
 // have been created by calling "createFrame()" below. "param" is passed to the
 // context "script" is run in, so can be used to pass objects that
@@ -671,3 +695,46 @@
       `sendReportTo("${createBidderReportURL(uuid)}");`,
   };
 }
+
+// Creates an additional bid with the given parameters. This additional bid
+// specifies a biddingLogicURL that provides an implementation of
+// reportAdditionalBidWin that triggers a sendReportTo() to the bidder report
+// URL of the winning additional bid. Additional bids are described in more
+// detail at
+// https://github.com/WICG/turtledove/blob/main/FLEDGE.md#6-additional-bids.
+function createAdditionalBid(uuid, auctionNonce, seller, buyer, interestGroupName, bidAmount,
+                             additionalBidOverrides = {}) {
+  return {
+    interestGroup: {
+      name: interestGroupName,
+      biddingLogicURL: createBiddingScriptURL(
+        {
+          origin: buyer,
+          reportAdditionalBidWin: `sendReportTo("${createBidderReportURL(uuid, interestGroupName)}");`
+        }),
+      owner: buyer
+    },
+    bid: {
+      ad: ['metadata'],
+      bid: bidAmount,
+      render: createRenderURL(uuid)
+    },
+    auctionNonce: auctionNonce,
+    seller: seller,
+    ...additionalBidOverrides
+  }
+}
+
+// Fetch some number of additional bid from a seller and verify that the
+// 'Ad-Auction-Additional-Bid' header is not visible in this JavaScript context.
+// The `additionalBids` parameter is a list of additional bids.
+async function fetchAdditionalBids(seller, additionalBids) {
+  const url = new URL(`${seller}${RESOURCE_PATH}additional-bids.py`);
+  url.searchParams.append('additionalBids', JSON.stringify(additionalBids));
+  const response = await fetch(url.href, {adAuctionHeaders: true});
+
+  assert_equals(response.status, 200, 'Failed to fetch additional bid: ' + await response.text());
+  assert_false(
+      response.headers.has('Ad-Auction-Additional-Bid'),
+      'Header "Ad-Auction-Additional-Bid" should not be available in JavaScript context.');
+}
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/layers/2d.layer.ctm.shadow-in-transformed-layer-expected.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/layers/2d.layer.ctm.shadow-in-transformed-layer-expected.html
new file mode 100644
index 0000000..312ca19b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/layers/2d.layer.ctm.shadow-in-transformed-layer-expected.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.layer.ctm.shadow-in-transformed-layer</title>
+<h1>2d.layer.ctm.shadow-in-transformed-layer</h1>
+<p class="desc">Check shadows inside of a transformed layer.</p>
+<canvas id="canvas" width="200" height="200">
+  <p class="fallback">FAIL (fallback content)</p>
+</canvas>
+<script>
+  const canvas = document.getElementById("canvas");
+  const ctx = canvas.getContext('2d');
+
+  ctx.translate(80, 90);
+  ctx.scale(2, 2);
+  ctx.rotate(Math.PI / 2);
+
+  ctx.shadowOffsetX = 10;
+  ctx.shadowOffsetY = 10;
+  ctx.shadowColor = 'grey';
+  ctx.fillRect(-30, -5, 60, 10);
+
+  const canvas2 = new OffscreenCanvas(100, 100);
+  const ctx2 = canvas2.getContext('2d');
+  ctx2.fillStyle = 'blue';
+  ctx2.fillRect(0, 0, 40, 10);
+  ctx.drawImage(canvas2, -30, -30);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/layers/2d.layer.ctm.shadow-in-transformed-layer.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/layers/2d.layer.ctm.shadow-in-transformed-layer.html
new file mode 100644
index 0000000..f047bd3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/layers/2d.layer.ctm.shadow-in-transformed-layer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<link rel="match" href="2d.layer.ctm.shadow-in-transformed-layer-expected.html">
+<title>Canvas test: 2d.layer.ctm.shadow-in-transformed-layer</title>
+<h1>2d.layer.ctm.shadow-in-transformed-layer</h1>
+<p class="desc">Check shadows inside of a transformed layer.</p>
+<canvas id="canvas" width="200" height="200">
+  <p class="fallback">FAIL (fallback content)</p>
+</canvas>
+<script>
+  const canvas = document.getElementById("canvas");
+  const ctx = canvas.getContext('2d');
+
+  ctx.translate(80, 90);
+  ctx.scale(2, 2);
+  ctx.rotate(Math.PI / 2);
+
+  ctx.beginLayer();
+  ctx.shadowOffsetX = 10;
+  ctx.shadowOffsetY = 10;
+  ctx.shadowColor = 'grey';
+  ctx.fillRect(-30, -5, 60, 10);
+
+  const canvas2 = new OffscreenCanvas(100, 100);
+  const ctx2 = canvas2.getContext('2d');
+  ctx2.fillStyle = 'blue';
+  ctx2.fillRect(0, 0, 40, 10);
+  ctx.drawImage(canvas2, -30, -30);
+
+  ctx.endLayer();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer-expected.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer-expected.html
new file mode 100644
index 0000000..312ca19b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer-expected.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<title>Canvas test: 2d.layer.ctm.shadow-in-transformed-layer</title>
+<h1>2d.layer.ctm.shadow-in-transformed-layer</h1>
+<p class="desc">Check shadows inside of a transformed layer.</p>
+<canvas id="canvas" width="200" height="200">
+  <p class="fallback">FAIL (fallback content)</p>
+</canvas>
+<script>
+  const canvas = document.getElementById("canvas");
+  const ctx = canvas.getContext('2d');
+
+  ctx.translate(80, 90);
+  ctx.scale(2, 2);
+  ctx.rotate(Math.PI / 2);
+
+  ctx.shadowOffsetX = 10;
+  ctx.shadowOffsetY = 10;
+  ctx.shadowColor = 'grey';
+  ctx.fillRect(-30, -5, 60, 10);
+
+  const canvas2 = new OffscreenCanvas(100, 100);
+  const ctx2 = canvas2.getContext('2d');
+  ctx2.fillStyle = 'blue';
+  ctx2.fillRect(0, 0, 40, 10);
+  ctx.drawImage(canvas2, -30, -30);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer.html
new file mode 100644
index 0000000..59305076
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<link rel="match" href="2d.layer.ctm.shadow-in-transformed-layer-expected.html">
+<title>Canvas test: 2d.layer.ctm.shadow-in-transformed-layer</title>
+<h1>2d.layer.ctm.shadow-in-transformed-layer</h1>
+<p class="desc">Check shadows inside of a transformed layer.</p>
+<canvas id="canvas" width="200" height="200">
+  <p class="fallback">FAIL (fallback content)</p>
+</canvas>
+<script>
+  const canvas = new OffscreenCanvas(200, 200);
+  const ctx = canvas.getContext('2d');
+
+  ctx.translate(80, 90);
+  ctx.scale(2, 2);
+  ctx.rotate(Math.PI / 2);
+
+  ctx.beginLayer();
+  ctx.shadowOffsetX = 10;
+  ctx.shadowOffsetY = 10;
+  ctx.shadowColor = 'grey';
+  ctx.fillRect(-30, -5, 60, 10);
+
+  const canvas2 = new OffscreenCanvas(100, 100);
+  const ctx2 = canvas2.getContext('2d');
+  ctx2.fillStyle = 'blue';
+  ctx2.fillRect(0, 0, 40, 10);
+  ctx.drawImage(canvas2, -30, -30);
+
+  ctx.endLayer();
+
+  const outputCanvas = document.getElementById("canvas");
+  outputCanvas.getContext('2d').drawImage(canvas, 0, 0);
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer.w.html b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer.w.html
new file mode 100644
index 0000000..486a028
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/offscreen/layers/2d.layer.ctm.shadow-in-transformed-layer.w.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
+<html class="reftest-wait">
+<link rel="match" href="2d.layer.ctm.shadow-in-transformed-layer-expected.html">
+<title>Canvas test: 2d.layer.ctm.shadow-in-transformed-layer</title>
+<h1>2d.layer.ctm.shadow-in-transformed-layer</h1>
+<p class="desc">Check shadows inside of a transformed layer.</p>
+<canvas id="canvas" width="200" height="200">
+  <p class="fallback">FAIL (fallback content)</p>
+</canvas>
+<script id='myWorker' type='text/worker'>
+  self.onmessage = function(e) {
+    const canvas = new OffscreenCanvas(200, 200);
+    const ctx = canvas.getContext('2d');
+
+    ctx.translate(80, 90);
+    ctx.scale(2, 2);
+    ctx.rotate(Math.PI / 2);
+
+    ctx.beginLayer();
+    ctx.shadowOffsetX = 10;
+    ctx.shadowOffsetY = 10;
+    ctx.shadowColor = 'grey';
+    ctx.fillRect(-30, -5, 60, 10);
+
+    const canvas2 = new OffscreenCanvas(100, 100);
+    const ctx2 = canvas2.getContext('2d');
+    ctx2.fillStyle = 'blue';
+    ctx2.fillRect(0, 0, 40, 10);
+    ctx.drawImage(canvas2, -30, -30);
+
+    ctx.endLayer();
+
+    const bitmap = canvas.transferToImageBitmap();
+    self.postMessage(bitmap, bitmap);
+  };
+</script>
+<script>
+  const blob = new Blob([document.getElementById('myWorker').textContent]);
+  const worker = new Worker(URL.createObjectURL(blob));
+  worker.addEventListener('message', msg => {
+    const outputCtx = document.getElementById("canvas").getContext('2d');
+    outputCtx.drawImage(msg.data, 0, 0);
+    document.documentElement.classList.remove("reftest-wait");
+  });
+  worker.postMessage(null);
+</script>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/layers.yaml b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/layers.yaml
index 070f6506..938cc74 100644
--- a/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/layers.yaml
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/tools/yaml-new/layers.yaml
@@ -387,6 +387,43 @@
       </g>
     </svg>
 
+- name: 2d.layer.ctm.shadow-in-transformed-layer
+  desc: Check shadows inside of a transformed layer.
+  size: [200, 200]
+  code: |
+    ctx.translate(80, 90);
+    ctx.scale(2, 2);
+    ctx.rotate(Math.PI / 2);
+
+    ctx.beginLayer();
+    ctx.shadowOffsetX = 10;
+    ctx.shadowOffsetY = 10;
+    ctx.shadowColor = 'grey';
+    ctx.fillRect(-30, -5, 60, 10);
+
+    const canvas2 = new OffscreenCanvas(100, 100);
+    const ctx2 = canvas2.getContext('2d');
+    ctx2.fillStyle = 'blue';
+    ctx2.fillRect(0, 0, 40, 10);
+    ctx.drawImage(canvas2, -30, -30);
+
+    ctx.endLayer();
+  reference: |
+    ctx.translate(80, 90);
+    ctx.scale(2, 2);
+    ctx.rotate(Math.PI / 2);
+
+    ctx.shadowOffsetX = 10;
+    ctx.shadowOffsetY = 10;
+    ctx.shadowColor = 'grey';
+    ctx.fillRect(-30, -5, 60, 10);
+
+    const canvas2 = new OffscreenCanvas(100, 100);
+    const ctx2 = canvas2.getContext('2d');
+    ctx2.fillStyle = 'blue';
+    ctx2.fillRect(0, 0, 40, 10);
+    ctx.drawImage(canvas2, -30, -30);
+
 - name: 2d.layer.ctm.getTransform
   desc: Tests getTransform inside layers.
   code: |
diff --git a/third_party/blink/web_tests/fast/canvas/canvas-toBlob-jpeg-medium-quality.html b/third_party/blink/web_tests/fast/canvas/canvas-toBlob-jpeg-medium-quality.html
index d4825aa..20bc7b0 100644
--- a/third_party/blink/web_tests/fast/canvas/canvas-toBlob-jpeg-medium-quality.html
+++ b/third_party/blink/web_tests/fast/canvas/canvas-toBlob-jpeg-medium-quality.html
@@ -1,4 +1,5 @@
 <canvas id="mycanvas"></canvas>
+<meta name=fuzzy content="maxDifference=0-1; totalPixels=0-20000">
 <img id="result" onload="testDone()">
 <script type = 'text/javascript'>
 if (window.testRunner)
diff --git a/third_party/catapult b/third_party/catapult
index 6f4a0d6..7fccada 160000
--- a/third_party/catapult
+++ b/third_party/catapult
@@ -1 +1 @@
-Subproject commit 6f4a0d6c8731078f74f3de4fc92748c125da43d6
+Subproject commit 7fccadad2a22e004112191dcd082108eb8297b14
diff --git a/third_party/chromite b/third_party/chromite
index 21c56f5..7efba5e 160000
--- a/third_party/chromite
+++ b/third_party/chromite
@@ -1 +1 @@
-Subproject commit 21c56f5c330bab014098f49c6d5b9f2f9cba3473
+Subproject commit 7efba5ebf4460a8038d1d8155528d960f8df82ab
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 1fae85c..d1a16c4 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 1fae85ca70bdf203941f8ac90392e7d471247147
+Subproject commit d1a16c439233c3c7199fcc3f0507b1d012611ab9
diff --git a/third_party/dawn b/third_party/dawn
index 996ab52..7b482f4 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 996ab52aeac453060d7677672ce28e0504ff75f3
+Subproject commit 7b482f48e63500c46952160a5b71b5bb91c746f0
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 4df6114..d972b83 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 4df61147ba67316806617f74347f408d2e4ff2f1
+Subproject commit d972b831c3ca2be979c4009a1fb66baca9dc0b77
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index 2173cbd..f162658 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit 2173cbdf60dac9a327293104c19c93e63a3e46ee
+Subproject commit f1626580921976b4de940622ede2cdd7a6911c91
diff --git a/third_party/jni_zero/codegen/placeholder_java_type.py b/third_party/jni_zero/codegen/placeholder_java_type.py
new file mode 100644
index 0000000..5d0b766
--- /dev/null
+++ b/third_party/jni_zero/codegen/placeholder_java_type.py
@@ -0,0 +1,27 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+def Generate(java_class, nested_classes, script_name):
+  """Generate an empty placeholder java class with a given name and package.
+
+  The placeholder files allow compiling FooJni.java without requiring access to
+  Foo.java or any of its deps/imports.
+  """
+  sb = []
+  sb.append(f"""\
+//
+// This file is an empty placeholder. It was generated by {script_name}
+//
+
+package {java_class.package_with_dots};
+
+public class {java_class.name} {{
+""")
+  if nested_classes:
+    for clazz in nested_classes:
+      sb.append(f'public class {clazz.nested_name} {{}}\n')
+
+  sb.append('}\n')
+  return ''.join(sb)
diff --git a/third_party/jni_zero/jni_generator.py b/third_party/jni_zero/jni_generator.py
index 02797bc..c3097fc 100644
--- a/third_party/jni_zero/jni_generator.py
+++ b/third_party/jni_zero/jni_generator.py
@@ -26,6 +26,7 @@
 sys.path.insert(1, _BUILD_ANDROID_GYP)
 
 from codegen import placeholder_gen_jni_java
+from codegen import placeholder_java_type
 from codegen import proxy_impl_java
 import common
 import java_types
@@ -1141,11 +1142,42 @@
         zip_path = f'{jni_obj.java_class.class_without_prefix.full_name_with_slashes}Jni.java'
         common.add_to_zip_hermetic(srcjar, zip_path, data=content)
 
+      if gen_jni_class is not None:
+        content = placeholder_gen_jni_java.Generate(jni_objs,
+                                                    gen_jni_class=gen_jni_class,
+                                                    script_name=script_name)
+        zip_path = f'{gen_jni_class.full_name_with_slashes}.java'
+        common.add_to_zip_hermetic(srcjar, zip_path, data=content)
+
+
+def _CreatePlaceholderSrcJar(srcjar_path, gen_jni_class, jni_objs, *,
+                             script_name):
+  with common.atomic_output(srcjar_path) as f:
+    with zipfile.ZipFile(f, 'w') as srcjar:
       content = placeholder_gen_jni_java.Generate(jni_objs,
                                                   gen_jni_class=gen_jni_class,
                                                   script_name=script_name)
       zip_path = f'{gen_jni_class.full_name_with_slashes}.java'
       common.add_to_zip_hermetic(srcjar, zip_path, data=content)
+      for jni_obj in jni_objs:
+        if not jni_obj.proxy_natives:
+          continue
+        main_class = jni_obj.type_resolver.java_class
+        zip_path = main_class.class_without_prefix.full_name_with_slashes + '.java'
+        content = placeholder_java_type.Generate(
+            main_class,
+            jni_obj.type_resolver.nested_classes,
+            script_name=script_name)
+        common.add_to_zip_hermetic(srcjar, zip_path, data=content)
+        for java_class in jni_obj.type_resolver.imports:
+          # java.** are not separate deps and can be assumed to be available in
+          # any compile and thus we do not need to create placeholders for them.
+          if java_class.full_name_with_slashes.startswith('java/'):
+            continue
+          zip_path = java_class.class_without_prefix.full_name_with_slashes + '.java'
+          content = placeholder_java_type.Generate(java_class, [],
+                                                   script_name=script_name)
+          common.add_to_zip_hermetic(srcjar, zip_path, data=content)
 
 
 def _WriteHeaders(jni_objs, output_names, output_dir):
@@ -1180,23 +1212,33 @@
 
   _WriteHeaders(jni_objs, args.output_names, args.output_dir)
 
+  jni_objs_with_proxy_natives = [x for x in jni_objs if x.proxy_natives]
+  if jni_objs_with_proxy_natives:
+    # module_name is set only for proxy_natives.
+    gen_jni_class = proxy.get_gen_jni_class(
+        short=False,
+        name_prefix=jni_objs_with_proxy_natives[0].module_name,
+        package_prefix=args.package_prefix)
   # Write .srcjar
   if args.srcjar_path:
-    # module_name is set only for proxy_natives.
-    jni_objs = [x for x in jni_objs if x.proxy_natives]
-    if jni_objs:
-      gen_jni_class = proxy.get_gen_jni_class(
-          short=False,
-          name_prefix=jni_objs[0].module_name,
-          package_prefix=args.package_prefix)
+    if jni_objs_with_proxy_natives:
       _CreateSrcJar(args.srcjar_path,
-                    gen_jni_class,
-                    jni_objs,
+                    (None if args.placeholder_srcjar_path else gen_jni_class),
+                    jni_objs_with_proxy_natives,
                     script_name=GetScriptName())
     else:
       # Only @CalledByNatives.
       zipfile.ZipFile(args.srcjar_path, 'w').close()
 
+  if args.placeholder_srcjar_path:
+    if jni_objs_with_proxy_natives:
+      _CreatePlaceholderSrcJar(args.placeholder_srcjar_path,
+                               gen_jni_class,
+                               jni_objs_with_proxy_natives,
+                               script_name=GetScriptName())
+    else:
+      zipfile.ZipFile(args.placeholder_srcjar_path, 'w').close()
+
 
 def GenerateFromJar(parser, args):
   if not args.javap:
diff --git a/third_party/jni_zero/jni_zero.py b/third_party/jni_zero/jni_zero.py
index 43bf7b5..ffe309f 100755
--- a/third_party/jni_zero/jni_zero.py
+++ b/third_party/jni_zero/jni_zero.py
@@ -53,6 +53,9 @@
                        help='Output directory. '
                        'Existing .h files in this directory will be assumed '
                        'stale and removed.')
+    group.add_argument('--placeholder-srcjar-path',
+                       help='Path to output srcjar with placeholders for '
+                       'all referenced classes in |input_files|')
 
   group.add_argument('--header-path', help='Path to output header file.')
 
diff --git a/third_party/jni_zero/parse.py b/third_party/jni_zero/parse.py
index 040d1a4..33203b98 100644
--- a/third_party/jni_zero/parse.py
+++ b/third_party/jni_zero/parse.py
@@ -135,7 +135,7 @@
   nested_classes = []
   for m in _CLASSES_REGEX.finditer(contents):
     preamble, class_name = m.groups()
-    # Ignore annoations like @Foo("contains the words class Bar")
+    # Ignore annotations like @Foo("contains the words class Bar")
     if preamble.count('"') % 2 != 0:
       continue
     if outer_class is None:
diff --git a/third_party/jni_zero/sample/BUILD.gn b/third_party/jni_zero/sample/BUILD.gn
index a671cb7..56aba3a3 100644
--- a/third_party/jni_zero/sample/BUILD.gn
+++ b/third_party/jni_zero/sample/BUILD.gn
@@ -6,16 +6,14 @@
 import("//third_party/jni_zero/jni_zero.gni")
 
 generate_jni("sample_header") {
-  sources = [
-    "java/src/org/jni_zero/sample/Sample.java",
-  ]
+  sources = [ "java/src/org/jni_zero/sample/Sample.java" ]
 }
 
 android_library("sample_java") {
   srcjar_deps = [ ":sample_header" ]
   sources = [
-    "java/src/org/jni_zero/sample/SampleActivity.java",
     "java/src/org/jni_zero/sample/Sample.java",
+    "java/src/org/jni_zero/sample/SampleActivity.java",
   ]
 
   deps = [
@@ -29,9 +27,7 @@
     ":sample_header",
     "//base",
   ]
-  sources = [
-    "sample.cc",
-  ]
+  sources = [ "sample.cc" ]
 }
 
 shared_library_with_jni("sample_lib") {
@@ -79,5 +75,8 @@
 
 # Serves to test that generated bindings compile properly.
 group("jni_generator_tests") {
-  deps = [ ":sample_jni_apk" ]
+  deps = [
+    ":sample_jni_apk",
+    "//third_party/jni_zero/test:compile_only_test_jni_apk",
+  ]
 }
diff --git a/third_party/jni_zero/test/BUILD.gn b/third_party/jni_zero/test/BUILD.gn
index e4e995a8..ef60bff9 100644
--- a/third_party/jni_zero/test/BUILD.gn
+++ b/third_party/jni_zero/test/BUILD.gn
@@ -4,6 +4,9 @@
 import("//build/config/android/rules.gni")
 import("//third_party/jni_zero/jni_zero.gni")
 
+# All targets in this file are meant as compile tests only to make sure the
+# generated code compiles for all edgecases.
+
 generate_jni("test_jni") {
   sources = [
     "java/src/org/jni_zero/SampleForAnnotationProcessor.java",
@@ -11,6 +14,13 @@
   ]
 }
 
+android_library("dummy_java") {
+  sources = [
+    "java/src/org/dummy/MyClass.java",
+    "java/src/org/dummy/MyInterface.java",
+  ]
+}
+
 android_library("test_java") {
   srcjar_deps = [ ":test_jni" ]
   sources = [
@@ -18,7 +28,10 @@
     "java/src/org/jni_zero/SampleForTests.java",
   ]
 
-  deps = [ "//third_party/jni_zero:jni_zero_java" ]
+  deps = [
+    ":dummy_java",
+    "//third_party/jni_zero:jni_zero_java",
+  ]
 }
 
 source_set("test_native_side") {
@@ -39,11 +52,15 @@
     ":test_native_side",
     "//third_party/jni_zero",
   ]
-  java_targets = [ ":test_jni_apk" ]
+  java_targets = [ ":compile_only_test_jni_apk" ]
   remove_uncalled_jni = true
 }
 
-android_apk("test_jni_apk") {
+# This apk is meant to be a compile only test to make sure the generated code,
+# both java and native, compiles for all the edgecases. APK itself is not meant
+# to be run or installed on a device since a lot of internal implementations are
+# empty stubs and are just meant for the compiler or linker to see.
+android_apk("compile_only_test_jni_apk") {
   apk_name = "TestJni"
   android_manifest = "../sample/AndroidManifest.xml"
   deps = [ ":test_java" ]
diff --git a/third_party/jni_zero/test/golden/testEndToEndProxyHashed-placeholder.srcjar.golden b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-placeholder.srcjar.golden
new file mode 100644
index 0000000..0c7f5d01
--- /dev/null
+++ b/third_party/jni_zero/test/golden/testEndToEndProxyHashed-placeholder.srcjar.golden
@@ -0,0 +1,99 @@
+This is the concatenated contents of all files inside the placeholder srcjar.
+
+
+## Contents of org/jni_zero/GEN_JNI.java:
+//
+// This file is a placeholder. It was generated by third_party/jni_zero/jni_generator.py
+//
+
+package org.jni_zero;
+
+public class GEN_JNI {
+  public static boolean TESTING_ENABLED;
+  public static boolean REQUIRE_MOCK;
+
+  public static native Object org_jni_1zero_SampleForAnnotationProcessor_bar(Object sample);
+  public static native void org_jni_1zero_SampleForAnnotationProcessor_foo();
+  public static native boolean org_jni_1zero_SampleForAnnotationProcessor_hasPhalange();
+  public static native Class org_jni_1zero_SampleForAnnotationProcessor_returnClass();
+  public static native Class[] org_jni_1zero_SampleForAnnotationProcessor_returnClasses();
+  public static native String org_jni_1zero_SampleForAnnotationProcessor_returnConvertedString();
+  public static native String[] org_jni_1zero_SampleForAnnotationProcessor_returnConvertedStrings();
+  public static native Object org_jni_1zero_SampleForAnnotationProcessor_returnObject();
+  public static native Object[] org_jni_1zero_SampleForAnnotationProcessor_returnObjects();
+  public static native String org_jni_1zero_SampleForAnnotationProcessor_returnString();
+  public static native String[] org_jni_1zero_SampleForAnnotationProcessor_returnStrings();
+  public static native Object org_jni_1zero_SampleForAnnotationProcessor_returnStruct();
+  public static native Object[] org_jni_1zero_SampleForAnnotationProcessor_returnStructs();
+  public static native Throwable org_jni_1zero_SampleForAnnotationProcessor_returnThrowable();
+  public static native Throwable[] org_jni_1zero_SampleForAnnotationProcessor_returnThrowables();
+  public static native String org_jni_1zero_SampleForAnnotationProcessor_revString(String stringToReverse);
+  public static native Object[] org_jni_1zero_SampleForAnnotationProcessor_sendSamplesToNative(Object[] strs);
+  public static native String[] org_jni_1zero_SampleForAnnotationProcessor_sendToNative(String[] strs);
+  public static native int[] org_jni_1zero_SampleForAnnotationProcessor_testAllPrimitives(int zint, int[] ints, long zlong, long[] longs, short zshort, short[] shorts, char zchar, char[] chars, byte zbyte, byte[] bytes, double zdouble, double[] doubles, float zfloat, float[] floats, boolean zbool, boolean[] bools);
+  public static native void org_jni_1zero_SampleForAnnotationProcessor_testSpecialTypes(Class clazz, Class[] classes, Throwable throwable, Throwable[] throwables, String string, String[] strings, String convertedString, String[] convertedStrings, String optionalString, Object tStruct, Object[] structs, Object obj, Object convertedObj, Object[] objects, Object[] convertedObjects);
+}
+
+
+
+## Contents of org/jni_zero/SampleForAnnotationProcessor.java:
+//
+// This file is an empty placeholder. It was generated by third_party/jni_zero/jni_generator.py
+//
+
+package org.jni_zero;
+
+public class SampleForAnnotationProcessor {
+public class TestStruct {}
+public class Natives {}
+}
+
+
+
+## Contents of android/content/Context.java:
+//
+// This file is an empty placeholder. It was generated by third_party/jni_zero/jni_generator.py
+//
+
+package android.content;
+
+public class Context {
+}
+
+
+
+## Contents of android/view/View.java:
+//
+// This file is an empty placeholder. It was generated by third_party/jni_zero/jni_generator.py
+//
+
+package android.view;
+
+public class View {
+}
+
+
+
+## Contents of org/dummy/MyClass.java:
+//
+// This file is an empty placeholder. It was generated by third_party/jni_zero/jni_generator.py
+//
+
+package org.dummy;
+
+public class MyClass {
+}
+
+
+
+## Contents of org/dummy/MyInterface.java:
+//
+// This file is an empty placeholder. It was generated by third_party/jni_zero/jni_generator.py
+//
+
+package org.dummy;
+
+public class MyInterface {
+}
+
+
diff --git a/third_party/jni_zero/test/integration_tests.py b/third_party/jni_zero/test/integration_tests.py
index 5ebd216..d30a654 100755
--- a/third_party/jni_zero/test/integration_tests.py
+++ b/third_party/jni_zero/test/integration_tests.py
@@ -121,7 +121,24 @@
         contents = srcjar.read(name).decode('utf-8')
         self.AssertGoldenTextEquals(contents, name_to_goldens[name])
 
-  def _TestEndToEndGeneration(self, input_file, *, srcjar=False, **kwargs):
+  def _CheckPlaceholderSrcjarGolden(self, srcjar_path, golden_path):
+    expected_contents = [
+        'This is the concatenated contents of all files '
+        'inside the placeholder srcjar.\n\n'
+    ]
+    with zipfile.ZipFile(srcjar_path, 'r') as srcjar:
+      for name in srcjar.namelist():
+        file_contents = srcjar.read(name).decode('utf-8')
+        expected_contents += [f'## Contents of {name}:', file_contents, '\n']
+
+    self.AssertGoldenTextEquals('\n'.join(expected_contents), golden_path)
+
+  def _TestEndToEndGeneration(self,
+                              input_file,
+                              *,
+                              srcjar=False,
+                              generate_placeholders=False,
+                              **kwargs):
     is_javap = input_file.endswith('.class')
     golden_name = self._testMethodName
     options = CliOptions(is_javap=is_javap, **kwargs)
@@ -152,6 +169,9 @@
       if srcjar:
         srcjar_path = os.path.join(tdir, 'srcjar.jar')
         cmd += ['--srcjar-path', srcjar_path]
+      if generate_placeholders:
+        placeholder_srcjar_path = os.path.join(tdir, 'placeholders.srcjar')
+        cmd += ['--placeholder-srcjar-path', placeholder_srcjar_path]
 
       logging.info('Running: %s', shlex.join(cmd))
       subprocess.check_call(cmd)
@@ -163,6 +183,10 @@
 
       if srcjar:
         self._CheckSrcjarGoldens(srcjar_path, name_to_goldens)
+      if generate_placeholders:
+        placeholder_srcjar_golden = f'{golden_name}-placeholder.srcjar.golden'
+        self._CheckPlaceholderSrcjarGolden(placeholder_srcjar_path,
+                                           placeholder_srcjar_golden)
 
   def _TestEndToEndRegistration(self,
                                 input_files,
@@ -310,7 +334,8 @@
     self._TestEndToEndGeneration('SampleUniqueAnnotations.java', srcjar=True)
 
   def testEndToEndProxyHashed(self):
-    self._TestEndToEndGeneration('SampleForAnnotationProcessor.java')
+    self._TestEndToEndGeneration('SampleForAnnotationProcessor.java',
+                                 generate_placeholders=True)
     self._TestEndToEndRegistration(['SampleForAnnotationProcessor.java'],
                                    use_proxy_hash=True)
 
diff --git a/third_party/jni_zero/test/java/src/org/dummy/MyClass.java b/third_party/jni_zero/test/java/src/org/dummy/MyClass.java
new file mode 100644
index 0000000..7ffb5a5a
--- /dev/null
+++ b/third_party/jni_zero/test/java/src/org/dummy/MyClass.java
@@ -0,0 +1,7 @@
+// Copyright 2024 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.dummy;
+
+public class MyClass<T> {}
diff --git a/third_party/jni_zero/test/java/src/org/dummy/MyInterface.java b/third_party/jni_zero/test/java/src/org/dummy/MyInterface.java
new file mode 100644
index 0000000..ca9a5e12
--- /dev/null
+++ b/third_party/jni_zero/test/java/src/org/dummy/MyInterface.java
@@ -0,0 +1,7 @@
+// Copyright 2024 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.dummy;
+
+public interface MyInterface {}
diff --git a/third_party/jni_zero/test/java/src/org/jni_zero/SampleForAnnotationProcessor.java b/third_party/jni_zero/test/java/src/org/jni_zero/SampleForAnnotationProcessor.java
index 255c77d6..b610a6d 100644
--- a/third_party/jni_zero/test/java/src/org/jni_zero/SampleForAnnotationProcessor.java
+++ b/third_party/jni_zero/test/java/src/org/jni_zero/SampleForAnnotationProcessor.java
@@ -1,9 +1,15 @@
 // Copyright 2018 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.jni_zero;
 
+import android.content.Context;
+import android.view.View;
+
+import org.dummy.MyClass;
+import org.dummy.MyInterface;
+
+import java.util.ArrayList;
 
 /**
  * Sample class that uses the JNI annotation processor for static methods.
@@ -87,6 +93,13 @@
     }
 
     public static void test() {
+        // Using the imports so they are not removed.
+        View view = null;
+        Context context = null;
+        MyClass myclass = null;
+        MyInterface myinterface = null;
+        ArrayList myList = new ArrayList<String>();
+
         int[] x = new int[] {1, 2, 3, 4, 5};
         String[] strs = new String[] {"the", "quick", "brown", "fox"};
         strs = SampleForAnnotationProcessorJni.get().sendToNative(strs);
diff --git a/third_party/libc++abi/src b/third_party/libc++abi/src
index 5b35c9f..204deaa 160000
--- a/third_party/libc++abi/src
+++ b/third_party/libc++abi/src
@@ -1 +1 @@
-Subproject commit 5b35c9f06c3f8f17b43bd3da9527d6fecdf916c2
+Subproject commit 204deaa9c53f76d6f23e0d119fc0110adc3ea6f2
diff --git a/third_party/perfetto b/third_party/perfetto
index 6427f36..cd05247e 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 6427f365ba48d17a474a837b2596ba683655386e
+Subproject commit cd05247e5cb7251177910169dfe482f586f6a5bd
diff --git a/third_party/skia b/third_party/skia
index 734953a..170fbb0 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 734953afbc19e81884e465829685d2574fe40fc9
+Subproject commit 170fbb029e61a116431fd96a35eeeca8ee22c2b2
diff --git a/third_party/webrtc b/third_party/webrtc
index 524a06b..a8c4727 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 524a06bc5423ef2289bc6102ee5a2b9e747137cc
+Subproject commit a8c47276cb6d3188db8b5a4ba77abd07797c0071
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 2046775e..1141c5d 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -9512,6 +9512,15 @@
   </description>
 </action>
 
+<action name="DownloadNotificationV2.Button_CopyToClipboard">
+  <owner>andrewxu@chromium.org</owner>
+  <owner>cros-system-ui-productivity-eng@google.com</owner>
+  <description>
+    User clicks &quot;Copy to clipboard&quot; button on a download notification
+    with the downloads integration V2 feature enabled.
+  </description>
+</action>
+
 <action name="DownloadNotificationV2.Button_Pause">
   <owner>andrewxu@chromium.org</owner>
   <owner>cros-system-ui-productivity-eng@google.com</owner>
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index e619bae..c925b7a 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -11676,6 +11676,7 @@
   <int value="36" label="OOBE or Login screen"/>
   <int value="37" label="AI"/>
   <int value="38" label="Focus Mode"/>
+  <int value="39" label="Overview Grid"/>
 </enum>
 
 <!-- Keep elements in sync with components/feed/core/feed_logging_metrics.cc and
@@ -32337,6 +32338,7 @@
   <int value="59" label="UMA_CREATOR_UNFOLLOW_FAILURE"/>
   <int value="60" label="UMA_QUICK_DELETE"/>
   <int value="61" label="UMA_AUTO_TRANSLATE"/>
+  <int value="63" label="UMA_CLEAR_BROWSING_DATA"/>
 </enum>
 
 <enum name="SnapshotItemId">
diff --git a/tools/metrics/histograms/metadata/apps/histograms.xml b/tools/metrics/histograms/metadata/apps/histograms.xml
index 24f3791c..ee4a44e6 100644
--- a/tools/metrics/histograms/metadata/apps/histograms.xml
+++ b/tools/metrics/histograms/metadata/apps/histograms.xml
@@ -439,19 +439,6 @@
   </summary>
 </histogram>
 
-<histogram name="Apps.AppList.AppLaunched{TabletOrClamshell}"
-    enum="AppListLaunchedFrom" expires_after="2023-06-19">
-  <owner>gzadina@google.com</owner>
-  <owner>tbarzic@chromium.org</owner>
-  <summary>
-    The number of apps launched from different parts of the launcher (apps grid,
-    search results UI, recent apps), and the shelf. Values are incremented each
-    time the user launches an app. Each bucket represents where in the launcher
-    or shelf the app was launched from. Recorded for {TabletOrClamshell}.
-  </summary>
-  <token key="TabletOrClamshell" variants="TabletOrClamshellMode"/>
-</histogram>
-
 <histogram name="Apps.AppList.AppListSortDiscoveryDurationAfterActivation"
     units="ms" expires_after="2023-06-25">
   <owner>andrewxu@chromium.org</owner>
@@ -1410,46 +1397,6 @@
   <token key="TabletOrClamshell" variants="TabletOrClamshellMode"/>
 </histogram>
 
-<histogram name="Apps.AppList.UserEvent.LaunchIndex{LauncherUISurface}"
-    units="index" expires_after="2023-07-31">
-  <owner>tby@chromium.org</owner>
-  <owner>thanhdng@chromium.org</owner>
-  <summary>
-    Emitted on a usage of the launcher, and records overall impressions,
-    launches, and abandons for a launcher UI view. The bucket proportion is not
-    meaningful for this metric, because impressions are a superset of launches
-    and abandons. Instead, the ratio between buckets can be used to calculate
-    accurate overall CTR. {LauncherUISurface}
-
-    This metric stopped collecting data in November 2021, but has not yet been
-    marked obsolete because the historical data is still being used for
-    analysis.
-  </summary>
-  <token key="LauncherUISurface" variants="LauncherUISurface">
-    <variant name=""/>
-  </token>
-</histogram>
-
-<histogram name="Apps.AppList.UserEvent.Overall{LauncherUISurface}"
-    enum="AppListUserEvent" expires_after="2023-07-31">
-  <owner>tby@chromium.org</owner>
-  <owner>thanhdng@chromium.org</owner>
-  <summary>
-    Emitted on a usage of the launcher, and records overall impressions,
-    launches, and abandons for a launcher UI view. The bucket proportion is not
-    meaningful for this metric, because impressions are a superset of launches
-    and abandons. Instead, the ratio between buckets can be used to calculate
-    accurate overall CTR. {LauncherUISurface}
-
-    This metric stopped collecting data in November 2021, but has not yet been
-    marked obsolete because the historical data is still being used for
-    analysis.
-  </summary>
-  <token key="LauncherUISurface" variants="LauncherUISurface">
-    <variant name=""/>
-  </token>
-</histogram>
-
 <histogram name="Apps.AppList.UserEvent.Query" enum="Boolean"
     expires_after="2023-07-31">
   <owner>tby@chromium.org</owner>
@@ -1925,24 +1872,6 @@
   </summary>
 </histogram>
 
-<histogram name="Apps.AppListUsageByNewUsers{TabletOrClamshell}"
-    enum="AppListUsageStateByNewUsers" expires_after="2023-06-19">
-  <owner>andrewxu@chromium.org</owner>
-  <owner>tbarzic@chromium.org</owner>
-  <summary>
-    Records the launcher usage state during the session started by a new user
-    (i.e. the session completing the OOBE flow). This metric is recorded in the
-    following scenarios: (1) the launcher shows and the current user is new (2)
-    the launcher has never shown before launcher is destructed. Destruction can
-    be triggered by loging out accounts, shuting down the device or system
-    crashes and meanwhile the current user is new (3) the launcher has never
-    shown when the active user has changed and the previous active user was a
-    new user. Split depending on whether the device was in tablet or clamshell
-    mode when the metric was recorded.
-  </summary>
-  <token key="TabletOrClamshell" variants="TabletOrClamshellMode"/>
-</histogram>
-
 <histogram name="Apps.AppsCount.{AppType}" units="Apps"
     expires_after="2024-08-04">
   <owner>nancylingwang@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 45cf0cd..4d20321 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -8273,7 +8273,7 @@
 </histogram>
 
 <histogram name="Ash.WindowCycleView.AnimationSmoothness.Container" units="%"
-    expires_after="2024-02-22">
+    expires_after="2025-02-27">
   <owner>yichenz@chromium.org</owner>
   <owner>chromeos-wmp@google.com</owner>
   <summary>
@@ -8288,7 +8288,7 @@
 </histogram>
 
 <histogram name="Ash.WindowCycleView.AnimationSmoothness.Show" units="%"
-    expires_after="2024-02-22">
+    expires_after="2025-02-27">
   <owner>yichenz@chromium.org</owner>
   <owner>chromeos-wmp@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/enterprise/enums.xml b/tools/metrics/histograms/metadata/enterprise/enums.xml
index c2d3748..db7b9ca 100644
--- a/tools/metrics/histograms/metadata/enterprise/enums.xml
+++ b/tools/metrics/histograms/metadata/enterprise/enums.xml
@@ -2064,6 +2064,7 @@
   <int value="1230" label="DefaultDirectSocketsSetting"/>
   <int value="1231" label="DirectSocketsAllowedForUrls"/>
   <int value="1232" label="DirectSocketsBlockedForUrls"/>
+  <int value="1233" label="ProductSpecificationsEnabled"/>
 </enum>
 
 <enum name="EnterprisePoliciesSources">
diff --git a/tools/metrics/structured/sync/structured_chromiumos.xml b/tools/metrics/structured/sync/structured_chromiumos.xml
index 7e52165f..a1ff3cc 100644
--- a/tools/metrics/structured/sync/structured_chromiumos.xml
+++ b/tools/metrics/structured/sync/structured_chromiumos.xml
@@ -584,6 +584,205 @@
   </event>
 </project>
 
+<project name="CrashReporting">
+  <owner>chromeos-data-eng@google.com</owner>
+  <id>none</id>
+  <summary>
+    Project for the crash reporting system to provide metrics on how well
+    crash reporting is working and whether or not we are losing crashes.
+  </summary>
+  <event name="CrashpadDetect">
+    <summary>
+      Chrome's crashpad has detected a crash.
+    </summary>
+    <metric name="Dummy" type="int">
+      <summary>
+        Dummy metric to work around b/326452906 (events must have at least one
+        metric).
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashReporterStart">
+    <summary>
+      A crash reporter collector has started Collect()ing a crash. Usually
+      recorded once per invocation, other than --init invocations; may be
+      recorded twice for "crash reporter failure" results.
+    </summary>
+    <metric name="Collector" type="int">
+      <summary>
+        Value from the CrashReporterCollector enum, indicating the collector
+        that is running.
+      </summary>
+    </metric>
+    <metric name="IsCrashLoop" type="int">
+      <summary>
+        Boolean: 1 if using crash-loop mode.
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashReporterStatus">
+    <summary>
+      A crash reporter collector has finished Collect()ing a crash, successfully
+      or otherwise. Usually recorded once per invocation, other than --init
+      invocations; may be recorded twice for "crash reporter failure" results.
+    </summary>
+    <metric name="Status" type="int">
+      <summary>
+        Value from CrashReporterStatus enum, indicating the final result of the
+        collection attempt.
+      </summary>
+    </metric>
+    <metric name="Collector" type="int">
+      <summary>
+        Value from the CrashReporterCollector enum, indicating the collector
+        that is running.
+      </summary>
+    </metric>
+    <metric name="IsCrashLoop" type="int">
+      <summary>
+        Boolean: 1 if using crash-loop mode.
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashReporterInitializationStart">
+    <summary>
+      crash_reporter --init started. Recorded twice per boot (once early and
+      once late).
+    </summary>
+    <metric name="IsEarly" type="int">
+      <summary>
+        Boolean: 1 for early init, 0 for late init
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashReporterInitializationStatus">
+    <summary>
+      Result of crash_reporter --init. Recorded twice per boot (once early and
+      once late).
+    </summary>
+    <metric name="IsEarly" type="int">
+      <summary>
+        Boolean: 1 for early init, 0 for late init
+      </summary>
+    </metric>
+    <metric name="Status" type="int">
+      <summary>
+        Value from CrashReporterInitStatus enum, indicating the final result of
+        the initialization attempt.
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashSenderStart">
+    <summary>
+      crash_sender has started processing a new crash file. Recorded once per
+      crash send attempt. Not recorded in dry run mode.
+    </summary>
+    <metric name="IsCrashLoop" type="int">
+      <summary>
+        Boolean: 1 if crash_sender was invoked by crash_reporter using
+        crash-loop mode.
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashSenderStartPerCollector">
+    <summary>
+      crash_sender has started processing a new crash file and has parsed the
+      .meta file enough to know what collector the crash came from. Recorded
+      once per crash send attempt, unless we can't parse the .meta file. Not
+      recorded in dry run mode.
+    </summary>
+    <metric name="Collector" type="int">
+      <summary>
+        Value from the CrashReporterCollector enum, indicating the collector
+        that produced the .meta file.
+      </summary>
+    </metric>
+    <metric name="IsCrashLoop" type="int">
+      <summary>
+        Boolean: 1 if crash_sender was invoked by crash_reporter using
+        crash-loop mode.
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashSenderRemovalReason">
+    <summary>
+      If crash_sender removes a crash, records the reason it was removed.
+      Recorded at most once per crash send attempt. Not recorded in dry run
+      mode.
+    </summary>
+    <metric name="Collector" type="int">
+      <summary>
+        Value from the CrashReporterCollector enum, indicating the collector
+        that produced the .meta file. kUnknownCollector if we couldn't parse the
+        .meta file.
+      </summary>
+    </metric>
+    <metric name="Reason" type="int">
+      <summary>
+        Value from CrashRemoveReason enum, indicating the reason crash_sender
+        removed the crash record.
+      </summary>
+    </metric>
+    <metric name="IsCrashLoop" type="int">
+      <summary>
+        Boolean: 1 if crash_sender was invoked by crash_reporter using
+        crash-loop mode.
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashSenderComplete">
+    <summary>
+      crash_sender is finished with a crash send attempt, whether successfully
+      or otherwise. Recorded once per crash send attempt. Not recorded in dry
+      run mode.
+    </summary>
+    <metric name="Collector" type="int">
+      <summary>
+        Value from the CrashReporterCollector enum, indicating the collector
+        that produced the .meta file. kUnknownCollector if we couldn't parse the
+        .meta  file.
+      </summary>
+    </metric>
+    <metric name="Removed" type="int">
+      <summary>
+        Boolean: 1 if the crash record was removed, 0 if the crash record was
+        left for the next time.
+      </summary>
+    </metric>
+    <metric name="IsCrashLoop" type="int">
+      <summary>
+        Boolean: 1 if crash_sender was invoked by crash_reporter using
+        crash-loop mode.
+      </summary>
+    </metric>
+  </event>
+  <event name="CrashSenderOrphanFileRemoved">
+    <summary>
+      crash_sender found an old orphaned crash file (a file in the crash
+      directory that does not correspond to a .meta file and is over 24 hours
+      old) and attempted to remove it. Recorded once per orphan removal attempt.
+    </summary>
+    <metric name="Removed" type="int">
+      <summary>
+        Boolean: 1 if the orphan file was successfully removed, 0 if the orphan
+        file could not be removed.
+      </summary>
+    </metric>
+    <metric name="IsCrashLoop" type="int">
+      <summary>
+        Boolean: 1 if crash_sender was invoked by crash_reporter using
+        crash-loop mode.
+      </summary>
+    </metric>
+    <metric name="SeemingCrashFile" type="int">
+      <summary>
+        Boolean: 1 if the removed file matched crash_reporter's normal naming
+        pattern (execname.date.time.random.pid.extention), 0 otherwise.
+      </summary>
+    </metric>
+  </event>
+</project>
+
 <project name="HardwareVerifier">
   <owner>kevinptt@chromium.org</owner>
   <id>per-project</id>
diff --git a/tools/perf/README.md b/tools/perf/README.md
index dcc5d15..a119fb7 100644
--- a/tools/perf/README.md
+++ b/tools/perf/README.md
@@ -102,19 +102,4 @@
 recordings from live websites, replay those to make sure they work, upload them
 to cloud storage, and finally send a CL to review with the new recordings.
 
-[wpr]: https://github.com/catapult-project/catapult/tree/master/web_page_replay_go
-
-## `soundwave`
-
-Allows to fetch data from the [Chrome Performance Dashboard][chromeperf] and
-stores it locally on a SQLite database for further analysis and processing. It
-also allows defining [studies][], pre-sets of measurements a team is interested
-in tracking, and uploads them to cloud storage to visualize with the help of
-[Looker Studio][]. This currently backs the [v8][v8_dashboard] and
-[health][health_dashboard] dashboards.
-
-[chromeperf]: https://chromeperf.appspot.com/
-[studies]: https://cs.chromium.org/chromium/src/tools/perf/cli_tools/soundwave/studies/
-[Looker Studio]: https://lookerstudio.google.com/
-[v8_dashboard]: https://lookerstudio.google.com/s/iNcXppkP3DI
-[health_dashboard]: https://lookerstudio.google.com/s/jUXfKZXXfT8
+[wpr]: https://github.com/catapult-project/catapult/tree/master/web_page_replay_go
\ No newline at end of file
diff --git a/tools/perf/contrib/shared_storage/README.md b/tools/perf/contrib/shared_storage/README.md
index c310f4b..8904058 100644
--- a/tools/perf/contrib/shared_storage/README.md
+++ b/tools/perf/contrib/shared_storage/README.md
@@ -43,6 +43,8 @@
         given number. Default is 10. Maximum allowed is 10.
 * `--pageset-repeat=PAGESET_REPEAT`
         Number of times to repeat the entire story set. Default is 10.
+* `--xvfb`
+        Runs tests with Xvfb server if possible.
 * `--verbose-cpu-metrics`
         Enables non-UMA CPU metrics.
 * `--verbose-memory-metrics`
@@ -58,5 +60,5 @@
 
 For example, a modified version of the original benchmark command is:
 ```bash
-tools/perf/run_benchmark shared_storage.small --browser=system --story-filter=Append --iterations=5 --pageset-repeat=1 --verbose-cpu-metrics --verbose-memory-metrics --verbose
+tools/perf/run_benchmark shared_storage.small --browser=system --story-filter=Append --iterations=5 --pageset-repeat=1 --xvfb --verbose-cpu-metrics --verbose-memory-metrics --verbose
 ```
diff --git a/tools/perf/contrib/shared_storage/page_set.py b/tools/perf/contrib/shared_storage/page_set.py
index 50bafb9b..7dd5320 100644
--- a/tools/perf/contrib/shared_storage/page_set.py
+++ b/tools/perf/contrib/shared_storage/page_set.py
@@ -254,8 +254,10 @@
                enable_memory_metric,
                user_agent='desktop',
                iterations=_PLACEHOLDER_ITERATIONS,
-               verbosity=0):
+               verbosity=0,
+               xvfb_process=None):
     super(SharedStorageStorySet, self).__init__()
+    self.xvfb_process = xvfb_process
     if user_agent == 'mobile':
       shared_page_state_class = state.SharedStorageSharedMobilePageState
     elif user_agent == 'desktop':
diff --git a/tools/perf/contrib/shared_storage/shared_storage.py b/tools/perf/contrib/shared_storage/shared_storage.py
index 3c8af7f..11f3a9bb 100644
--- a/tools/perf/contrib/shared_storage/shared_storage.py
+++ b/tools/perf/contrib/shared_storage/shared_storage.py
@@ -13,6 +13,8 @@
 from telemetry.timeline import chrome_trace_category_filter
 from telemetry.web_perf import timeline_based_measurement
 
+from py_utils import xvfb
+
 # Shared-storage-related histograms to measure.
 _SHARED_STORAGE_UMA_HISTOGRAMS = [
     "Storage.SharedStorage.Document.Timing.AddModule",
@@ -66,11 +68,16 @@
   verbose_memory_metrics = False
   iterations = _DEFAULT_NUM_ITERATIONS
   verbosity = 0
+  xvfb_process = None
 
   options = {'pageset_repeat': _DEFAULT_NUM_REPEAT}
 
   @classmethod
   def AddBenchmarkCommandLineArgs(cls, parser):
+    parser.add_option('--xvfb',
+                      action='store_true',
+                      default=False,
+                      help='Run with Xvfb server if possible.')
     parser.add_option('--user-agent',
                       action='store',
                       type='string',
@@ -102,16 +109,21 @@
       logging.warning('The maximum allowed number of iterations is 10. ' +
                       'Increase pageset_repeat instead.')
       cls.iterations = _MAX_NUM_ITERATIONS
+    if args.xvfb and xvfb.ShouldStartXvfb():
+      cls.xvfb_process = xvfb.StartXvfb()
 
   def SetExtraBrowserOptions(self, options):
     # `options` is an instance of `browser_options.BrowserOptions`.
     if self.verbose_memory_metrics:
       memory.SetExtraBrowserOptionsForMemoryMeasurement(options)
 
-    options.AppendExtraBrowserArgs([
+    extra_args = [
         '--enable-features=' + ','.join(_ENABLED_FEATURES),
         '--enable-privacy-sandbox-ads-apis'
-    ])
+    ]
+    if self.xvfb_process:
+      extra_args.append('--disable-gpu')
+    options.AppendExtraBrowserArgs(extra_args)
 
     # Increase the default shutdown timeout due to some long-running tests.
     os.environ['CHROME_SHUTDOWN_TIMEOUT'] = str(_SHUTDOWN_TIMEOUT)
@@ -158,7 +170,8 @@
         enable_memory_metric=self.verbose_memory_metrics,
         user_agent=options.user_agent,
         iterations=self.iterations,
-        verbosity=self.verbosity)
+        verbosity=self.verbosity,
+        xvfb_process=self.xvfb_process)
 
 
 @benchmark.Info(emails=['cammie@chromium.org'],
diff --git a/tools/perf/contrib/shared_storage/shared_storage_shared_page_state.py b/tools/perf/contrib/shared_storage/shared_storage_shared_page_state.py
index 2d257ccc..5106004 100644
--- a/tools/perf/contrib/shared_storage/shared_storage_shared_page_state.py
+++ b/tools/perf/contrib/shared_storage/shared_storage_shared_page_state.py
@@ -2,10 +2,25 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import logging
+
+import py_utils
+
 from telemetry.page import shared_page_state
 
 
 class SharedStorageSharedPageState(shared_page_state.SharedPageState):
+  _xvfb_process = None
+
+  def __init__(self, test, finder_options, story_set, possible_browser):
+    super(SharedStorageSharedPageState,
+          self).__init__(test, finder_options, story_set, possible_browser)
+
+    if 'xvfb_process' not in story_set.__dict__:
+      msg = "story_set is of type %s but expected type " % type(story_set)
+      msg += "<class 'contrib.shared_storage.page_set.SharedStorageStorySet'>"
+      raise TypeError(msg)
+    self._xvfb_process = story_set.xvfb_process
 
   def RunStory(self, results):
     # We use a print statement instead of logging here so that we can display
@@ -16,6 +31,34 @@
     print('[ RUN #%3d ]' % results.current_story_run.index)
     super(SharedStorageSharedPageState, self).RunStory(results)
 
+  def TearDownState(self):
+    super(SharedStorageSharedPageState, self).TearDownState()
+    self._TerminateOrKillXvfbIfNeeded()
+
+  def _TerminateOrKillXvfbIfNeeded(self):
+    if not self._xvfb_process:
+      return
+
+    done = False
+    repr_proc = repr(self._xvfb_process)
+    try:
+      self._xvfb_process.terminate()
+      py_utils.WaitFor(lambda: self._xvfb_process.poll() is not None, 10)
+      done = True
+    except py_utils.TimeoutException:
+      try:
+        self._xvfb_process.kill()
+        py_utils.WaitFor(lambda: self._xvfb_process.poll() is not None, 10)
+        done = True
+      except py_utils.TimeoutException:
+        pass
+    if done:
+      self._xvfb_process = None
+      logging.info('Shut down xvfb process: %s' % repr_proc)
+    else:
+      msg = 'Failed to terminate/kill the xvfb process %s' % repr_proc
+      msg += ' after 20 seconds.'
+      logging.warning(msg)
 
 class SharedStorageSharedMobilePageState(SharedStorageSharedPageState):
   _device_type = 'mobile'
diff --git a/tools/perf/core/find_dependencies.py b/tools/perf/core/find_dependencies.py
index 989a6c3..fc9e0b5 100644
--- a/tools/perf/core/find_dependencies.py
+++ b/tools/perf/core/find_dependencies.py
@@ -214,25 +214,31 @@
       zip_file.writestr(link_info, link_script)
 
 
-class FindDependenciesCommand(command_line.OptparseCommand):
+class FindDependenciesCommand(command_line.Command):
   """Prints all dependencies"""
 
   @classmethod
-  def AddCommandLineArgs(cls, parser, _):
-    parser.add_option(
-        '-v', '--verbose', action='count', dest='verbosity', default=0,
-        help='Increase verbosity level (repeat as needed).')
+  def AddCommandLineArgs(cls, parser):
+    parser.add_argument('-v',
+                        '--verbose',
+                        action='count',
+                        dest='verbosity',
+                        default=0,
+                        help='Increase verbosity level (repeat as needed).')
 
-    parser.add_option(
-        '-e', '--exclude', action='append', default=[],
+    parser.add_argument(
+        '-e',
+        '--exclude',
+        action='append',
+        default=[],
         help='Exclude paths matching EXCLUDE. Can be used multiple times.')
 
-    parser.add_option(
-        '-z', '--zip',
-        help='Store files in a zip archive at ZIP.')
+    parser.add_argument('-z',
+                        '--zip',
+                        help='Store files in a zip archive at ZIP.')
 
   @classmethod
-  def ProcessCommandLineArgs(cls, parser, args, _):
+  def ProcessCommandLineArgs(cls, parser, args):
     if args.verbosity >= 2:
       logging.getLogger().setLevel(logging.DEBUG)
     elif args.verbosity:
diff --git a/tools/perf/experimental/story_clustering/OWNERS b/tools/perf/experimental/story_clustering/OWNERS
deleted file mode 100644
index 29a8320..0000000
--- a/tools/perf/experimental/story_clustering/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-behdadb@chromium.org
diff --git a/tools/perf/experimental/story_clustering/README.md b/tools/perf/experimental/story_clustering/README.md
deleted file mode 100644
index 419e939..0000000
--- a/tools/perf/experimental/story_clustering/README.md
+++ /dev/null
@@ -1,56 +0,0 @@
-# Clustering Benchmark Stories
-
-The code in this directory provides support for clustering and choosing
-representatives for benchmarks.
-
-Input needed for the clustering methods are:
-1. Benchmark name
-2. List of metrics to use.
-    Clustering will be done once for each metric.
-3. List of platforms to gather data from.
-4. List of test-cases/story-names in the benchmark which should be clustered.
-    If some stories are recognized as outliers, they can be removed from this
-    list. The testcases can be provided as a story per line text file.
-5. Maximum number of clusters.
-    The actual number of clusters may be less than this number, as clusters
-    with only one member are not presented as a cluster.
-6. How many days of data history to be used for clustering
-
-Examples of creating clusters:
-```shell
-python ./tools/perf/experimental/story_clustering/gather_historical_records_and_cluster_stories.py \
-rendering.desktop \
---metrics frame_times thread_total_all_cpu_time_per_frame \
---platforms ChromiumPerf:mac-10_13_laptop_high_end-perf ChromiumPerf:mac-10_12_laptop_low_end-perf \
---testcases-path //tmp/story_clustering/rendering.desktop/test_cases.txt \
---days=100 \
---normalize \
---processes 20
-```
-
-```shell
-python ./tools/perf/experimental/story_clustering/gather_historical_records_and_cluster_stories.py \
-rendering.desktop \
---metrics frame_times thread_total_all_cpu_time_per_frame \
---platforms 'ChromiumPerf:Win 7 Nvidia GPU Perf' 'ChromiumPerf:Win 7 Perf' ChromiumPerf:win-10-perf \
---testcases-path //tmp/story_clustering/rendering.desktop/test_cases.txt \
---days=100 \
---normalize
-```
-
-```shell
-python ./tools/perf/experimental/story_clustering/gather_historical_records_and_cluster_stories.py \
-rendering.mobile \
---metrics frame_times thread_total_all_cpu_time_per_frame \
---platforms 'ChromiumPerf:Android Nexus5 Perf' 'ChromiumPerf:Android Nexus5X WebView Perf' \
-'ChromiumPerf:Android Nexus6 WebView Perf' \
---testcases-path //tmp/story_clustering/rendering.mobile/test_cases.txt \
---days=100 \
---normalize
-```
-
-Results of the clustering will be written in `clusters.json` file, located in the output directory given to the script
-
-If the script fails due to a "HTTP Error 429: Rate exceeded" error, try a smaller number for the `--processes` argument (defaulted to 20).
-
-[Method explanation](https://goto.google.com/chrome-benchmark-clustering)
\ No newline at end of file
diff --git a/tools/perf/experimental/story_clustering/__init__.py b/tools/perf/experimental/story_clustering/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tools/perf/experimental/story_clustering/__init__.py
+++ /dev/null
diff --git a/tools/perf/experimental/story_clustering/cluster_stories.py b/tools/perf/experimental/story_clustering/cluster_stories.py
deleted file mode 100644
index 3ff47ed..0000000
--- a/tools/perf/experimental/story_clustering/cluster_stories.py
+++ /dev/null
@@ -1,156 +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.
-
-from __future__ import division
-
-import heapq
-
-
-class Cluster(object):
-  def __init__(self, members):
-    """Initializes the cluster instance.
-
-    Args:
-      members: Set of story names which belong to this cluster.
-    """
-    self._members = frozenset(members)
-    self._representative = None
-
-  def __len__(self):
-    return len(self._members)
-
-  def GetDistanceFrom(self, other_cluster, distance_matrix):
-    """Calculates the distance of two clusters.
-
-    The maximum distance between any story of first cluster to any story of
-    the second cluster is used as the distance between clusters._members
-
-    Args:
-      other_cluster: Cluster object to calculate distance from.
-      distance_matrix: A dataframe containing the distances between any
-      two stories.
-
-    Returns:
-      A float number representing the distacne between two clusters.
-    """
-    matrix_slice = distance_matrix.loc[self.members, other_cluster.members]
-    return matrix_slice.max().max()
-
-  @property
-  def members(self):
-    return self._members
-
-  def GetRepresentative(self, distance_matrix=None):
-    """Finds and sets the representative of cluster.
-
-    The story which its max distance to all other members is minimum is
-    used as the representative.
-
-    Args:
-      distance_matrix: A dataframe containing the distances between any
-      two stories.
-
-    Returns:
-      A story which is the representative of cluster
-    """
-    if self._representative:
-      return self._representative
-
-    if distance_matrix is None:
-      raise Exception('Distance matrix is not set.')
-
-    self._representative = distance_matrix.loc[
-      self._members, self._members].sum().idxmin()
-    return self._representative
-
-  def Merge(self, other_cluster):
-    """Merges two clusters.
-
-    Returns:
-      A new cluster object which is a result of merging two clusters.
-    """
-    return Cluster(self.members | other_cluster.members)
-
-  def AsDict(self):
-    """Creates a dictionary which describes cluster object.
-
-    Returns:
-      A dictionary containing the members of the cluster and its
-      representative. The representative will not be listed in members
-      list.
-    """
-    representative = self.GetRepresentative()
-    members_list = list(self.members.difference([representative]))
-    return {
-      'members': members_list,
-      'representative': self.GetRepresentative()
-    }
-
-
-def RunHierarchicalClustering(
-  distance_matrix,
-  max_cluster_count,
-  min_cluster_size):
-  """Clusters stories.
-
-  Runs a hierarchical clustering algorithm based on the similarity measures.
-
-  Args:
-    distance_matrix: A dataframe containing distance matrix of stories.
-    max_cluster_count: number representing the maximum number of clusters
-      needed per metric.
-    min_cluster_size: number representing the least number of members needed
-      to make the cluster valid.
-
-  Returns:
-    A tuple containing:
-    clusters: A list of cluster objects
-    coverage: Ratio(float) of stories covered using this clustering
-  """
-  stories = distance_matrix.index.values
-  remaining_clusters = set([])
-  for story in stories:
-    remaining_clusters.add(Cluster([story]))
-
-  # The hierarchical clustering relies on a sorted list of possible
-  # cluster merges ordered by the distance between them.
-  heap = []
-
-  # Initially each story is a cluster on it's own. And story pairs are
-  # added all possible merges.
-  for cluster1 in remaining_clusters:
-    for cluster2 in remaining_clusters:
-      if cluster1 == cluster2:
-        break
-      heapq.heappush(heap,
-        (cluster1.GetDistanceFrom(cluster2, distance_matrix),
-         cluster1, cluster2))
-
-  # At each step the two clusters will be merged together.
-  while (len(remaining_clusters) > max_cluster_count and len(heap) > 0):
-    _, cluster1, cluster2 = heapq.heappop(heap)
-    if (cluster1 not in remaining_clusters or
-      cluster2 not in remaining_clusters):
-      continue
-    new_cluster = cluster1.Merge(cluster2)
-    remaining_clusters.discard(cluster1)
-    remaining_clusters.discard(cluster2)
-
-    # Adding all possible merges to the heap
-    for cluster in remaining_clusters:
-      distance = new_cluster.GetDistanceFrom(cluster, distance_matrix)
-      heapq.heappush(heap, (distance, new_cluster, cluster))
-
-    remaining_clusters.add(new_cluster)
-
-  final_clusters = []
-  number_of_stories_covered = 0
-  for cluster in remaining_clusters:
-    cluster.GetRepresentative(distance_matrix)
-    if len(cluster) >= min_cluster_size:
-      final_clusters.append(cluster)
-      number_of_stories_covered += len(cluster)
-  coverage = number_of_stories_covered / len(stories)
-
-  return final_clusters, coverage
diff --git a/tools/perf/experimental/story_clustering/create_soundwave_input.py b/tools/perf/experimental/story_clustering/create_soundwave_input.py
deleted file mode 100644
index 7567e492..0000000
--- a/tools/perf/experimental/story_clustering/create_soundwave_input.py
+++ /dev/null
@@ -1,53 +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.
-
-import argparse
-import json
-import sys
-
-
-def CreateInput(test_suite, platforms, metrics, test_cases_path, output_dir):
-  with open(test_cases_path, 'r') as test_case_file:
-    test_cases = [line.strip() for line in test_case_file]
-  json_data = []
-
-  for platform in platforms:
-    for metric in metrics:
-      for test_case in test_cases:
-        json_data.append({
-          'test_suite': test_suite,
-          'bot': platform,
-          'measurement': metric,
-          'test_case': test_case
-        })
-
-  with open(output_dir, 'w') as output:
-    json.dump(json_data, output)
-
-
-def Main(argv):
-  parser = argparse.ArgumentParser(
-    description=('Creates the Json needed for the soundwave'))
-  parser.add_argument('test_suite', help=('Name of test_suite (example: "'
-            'rendering.desktop")'))
-  parser.add_argument('--platforms', help='Name of platform (example: '
-            '"ChromiumPerf:Win 7 Nvidia GPU Perf")', nargs='*')
-  parser.add_argument('--metrics', help='Name of measurement (example: '
-            '"frame_times")', nargs='*')
-  parser.add_argument('--test-cases-path', type=str,
-            help='Path for the file having test_cases')
-  parser.add_argument('--output-dir', type=str,
-            help='Path for the output file')
-
-  args = parser.parse_args(argv[1:])
-  return CreateInput(
-    args.test_suite,
-    args.platforms,
-    args.metrics,
-    args.test_cases_path,
-    args.output_dir)
-
-
-if __name__ == '__main__':
-  sys.exit(Main(sys.argv))
diff --git a/tools/perf/experimental/story_clustering/gather_historical_records_and_cluster_stories.py b/tools/perf/experimental/story_clustering/gather_historical_records_and_cluster_stories.py
deleted file mode 100644
index 0c13e3fd..0000000
--- a/tools/perf/experimental/story_clustering/gather_historical_records_and_cluster_stories.py
+++ /dev/null
@@ -1,160 +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.
-
-from __future__ import print_function
-
-import argparse
-import json
-import logging
-import os
-import shutil
-import subprocess
-import sys
-import tempfile
-
-TOOLS_PERF_PATH = os.path.abspath(os.path.join(
-  os.path.dirname(__file__), '..', '..'))
-sys.path.insert(1, TOOLS_PERF_PATH)
-
-from experimental.story_clustering import similarity_calculator
-from experimental.story_clustering import cluster_stories
-from experimental.story_clustering import create_soundwave_input
-from core.external_modules import pandas
-
-
-def CalculateDistances(
-  all_bots_dataframe,
-  bots,
-  rolling_window,
-  metric_name,
-  normalize = False):
-  timeseries = []
-
-  for bot_name, bot_group in all_bots_dataframe.groupby(bots):
-    temp_dataframe = bot_group.pivot(index='test_case',
-      columns='commit_pos', values='value')
-    temp_dataframe_with_solling_avg = temp_dataframe.rolling(
-      rolling_window,
-      min_periods=1,
-      axis=1
-    ).mean().stack().rename('value').reset_index()
-
-    temp_dataframe_with_solling_avg['bot'] = bot_name
-    timeseries.append(temp_dataframe_with_solling_avg)
-
-  all_bots = pandas.concat(timeseries)
-  distance_matrix = similarity_calculator.CalculateDistances(
-    all_bots,
-    metric=metric_name,
-    normalize=normalize,
-  )
-  print('Similarities are calculated for', metric_name)
-
-  return distance_matrix
-
-
-def Main(argv):
-  parser = argparse.ArgumentParser(
-    description=('Gathers the values of each metric and platfrom pair in a'
-    ' csv file to be used in clustering of stories.'))
-  parser.add_argument('benchmark', type=str, help='benchmark to be used')
-  parser.add_argument('--metrics', type=str, nargs='*',
-    help='List of metrics to use')
-  parser.add_argument('--platforms', type=str, nargs='*',
-    help='List of platforms to use')
-  parser.add_argument('--testcases-path', type=str, help=('Path to the file '
-    'containing a list of all test_cases in the benchmark that needs to '
-    'be clustered'))
-  parser.add_argument('--days', default=180, help=('Number of days to gather'
-    ' data about'))
-  parser.add_argument('--output-path', type=str, help='Output file',
-    default='//tmp/story_clustering/clusters.json')
-  parser.add_argument('--max-cluster-count', default='10',
-    help='Number of not valid clusters needed')
-  parser.add_argument('--min-cluster-size', default='2', help=('Least number '
-            'of members in cluster, to make cluster valied'))
-  parser.add_argument('--rolling-window', default='1', help=('Number of '
-    'samples to take average from while calculating the moving average'))
-  parser.add_argument('--normalize', default=False,
-    help='Normalize timeseries to calculate similarity', action='store_true')
-  parser.add_argument('--processes', default='20', help=('Number of '
-    'concurrent workers used by soundwave.'))
-  args = parser.parse_args(argv[1:])
-
-  temp_dir = tempfile.mkdtemp('telemetry')
-  startup_timeseries = os.path.join(temp_dir, 'startup_timeseries.json')
-  soundwave_output_path = os.path.join(temp_dir, 'data.csv')
-  soundwave_path = os.path.join(TOOLS_PERF_PATH, 'soundwave')
-
-  try:
-    output_dir = os.path.dirname(args.output_path)
-    clusters_json = {}
-
-    if not os.path.isdir(output_dir):
-      os.makedirs(output_dir)
-
-    # creating the json file needed for soundwave
-    create_soundwave_input.CreateInput(
-      test_suite=args.benchmark,
-      platforms=args.platforms,
-      metrics=args.metrics,
-      test_cases_path=args.testcases_path,
-      output_dir=startup_timeseries)
-
-    subprocess.call([
-      soundwave_path,
-      '-d', args.days,
-      '--processes', args.processes,
-      'timeseries',
-      '-i', startup_timeseries,
-      '--output-csv', soundwave_output_path
-    ])
-
-    # Processing the data.
-    dataframe = pandas.read_csv(soundwave_output_path)
-    dataframe_per_metric = dataframe.groupby(dataframe['measurement'])
-    for metric_name, all_bots in list(dataframe_per_metric):
-      clusters_json[metric_name] = []
-
-      distance_matrix = CalculateDistances(
-        all_bots_dataframe=all_bots,
-        bots=dataframe['bot'],
-        rolling_window=int(args.rolling_window),
-        metric_name=metric_name,
-        normalize=args.normalize)
-
-      clusters, coverage = cluster_stories.RunHierarchicalClustering(
-        distance_matrix,
-        max_cluster_count=int(args.max_cluster_count),
-        min_cluster_size=int(args.min_cluster_size),
-      )
-      print()
-      print(metric_name, ':')
-      print(format(coverage * 100.0, '.1f'), 'percent coverage.')
-      print('Stories are grouped into', len(clusters), 'clusters.')
-      print('representatives:')
-      for cluster in clusters:
-        print (cluster.GetRepresentative())
-      print()
-
-      for cluster in clusters:
-        clusters_json[metric_name].append(cluster.AsDict())
-
-    with open(args.output_path, 'w') as outfile:
-      json.dump(
-        clusters_json,
-        outfile,
-        separators=(',',': '),
-        indent=4,
-        sort_keys=True
-      )
-
-  except Exception:
-    logging.exception('The following exception may have prevented the code'
-      ' from clustering stories.')
-  finally:
-    shutil.rmtree(temp_dir, ignore_errors=True)
-
-if __name__ == '__main__':
-  sys.exit(Main(sys.argv))
diff --git a/tools/perf/experimental/story_clustering/similarity_calculator.py b/tools/perf/experimental/story_clustering/similarity_calculator.py
deleted file mode 100644
index 4b091fb..0000000
--- a/tools/perf/experimental/story_clustering/similarity_calculator.py
+++ /dev/null
@@ -1,71 +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.
-
-import os
-
-from core.external_modules import pandas
-
-HIGHEST_VALID_NAN_RATIO = 0.5
-
-
-def CalculateDistances(
-  input_dataframe,
-  metric,
-  normalize=False,
-  output_path=None):
-  """Calculates the distances of stories.
-
-  If normalize flag is set the values are first normalized using min-max
-  normalization. Then the similarity measure between every two stories is
-  calculated using pearson correlation.
-
-  Args:
-    input_dataframe: A dataframe containing a list of records
-    having (test_case, commit_pos, bot, value).
-    metric: String containing name of the metric.
-    normalize: A flag to determine if normalization is needed.
-    output_path: Path to write the calculated distances.
-
-  Returns:
-    A dataframe containing the distance matrix of the stories.
-  """
-  input_by_story = input_dataframe.groupby('test_case')['value']
-  total_values_per_story = input_by_story.size()
-  nan_values_per_story = input_by_story.apply(lambda s: s.isna().sum())
-  should_keep = nan_values_per_story < (
-    total_values_per_story * HIGHEST_VALID_NAN_RATIO)
-  valid_stories = total_values_per_story[should_keep].index
-
-  filtered_dataframe = input_dataframe[
-    input_dataframe['test_case'].isin(valid_stories)]
-
-  temp_df = filtered_dataframe.copy()
-
-  if normalize:
-    # Min Max normalization
-    grouped = temp_df.groupby(['bot', 'test_case'])['value']
-    min_value = grouped.transform('min')
-    max_value = grouped.transform('max')
-    temp_df['value'] = temp_df['value'] / (1 + max_value - min_value)
-
-  distances = pandas.DataFrame()
-  grouped_temp = temp_df.groupby(temp_df['bot'])
-  for _, group in grouped_temp:
-    sample_df = group.pivot(index='commit_pos', columns='test_case',
-      values='value')
-
-    if distances.empty:
-      distances = 1 - sample_df.corr(method='pearson')
-    else:
-      distances = distances.add(1 - sample_df.corr(method='pearson'),
-        fill_value=0)
-
-  if output_path is not None:
-    if not os.path.isdir(output_path):
-      os.makedirs(output_path)
-    distances.to_csv(
-      os.path.join(output_path, metric + '_distances.csv')
-    )
-
-  return distances
diff --git a/tools/perf/export_csv b/tools/perf/export_csv
deleted file mode 100755
index 1bbab52..0000000
--- a/tools/perf/export_csv
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env vpython3
-# Copyright 2018 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import argparse
-import contextlib
-import csv
-import os
-import sqlite3
-import sys
-
-
-DEFAULT_DATABASE_PATH = os.path.abspath(os.path.join(
-    os.path.dirname(__file__), '_cached_data', 'soundwave', 'soundwave.db'))
-
-
-@contextlib.contextmanager
-def OutputStream(filename):
-  if filename is None or filename == '-':
-    yield sys.stdout
-  else:
-    with open(filename, 'w') as f:
-      yield f
-
-
-def EncodeUnicode(v):
-  return v.encode('utf-8') if isinstance(v, unicode) else v
-
-
-def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument(
-      'table', help='Name of a table to export')
-  parser.add_argument(
-      '--database-file', default=DEFAULT_DATABASE_PATH,
-      help='File path for database where to store data.')
-  parser.add_argument(
-      '--output', '-o',
-      help='Where to write the csv output, defaults to stdout.')
-  args = parser.parse_args()
-
-  con = sqlite3.connect(args.database_file)
-  try:
-    cur = con.execute('SELECT * FROM %s' % args.table)
-    header = [c[0] for c in cur.description]
-    with OutputStream(args.output) as out:
-      writer = csv.writer(out)
-      writer.writerow(header)
-      for row in cur:
-        writer.writerow([EncodeUnicode(v) for v in row])
-  finally:
-    con.close()
-
-
-if __name__ == '__main__':
-  sys.exit(main())
diff --git a/tools/perf/process_perf_results.pydeps b/tools/perf/process_perf_results.pydeps
index 9136ac6..99ddd4b3 100644
--- a/tools/perf/process_perf_results.pydeps
+++ b/tools/perf/process_perf_results.pydeps
@@ -39,6 +39,7 @@
 ../../third_party/catapult/common/py_utils/py_utils/tempfile_ext.py
 ../../third_party/catapult/common/py_utils/py_utils/ts_proxy_server.py
 ../../third_party/catapult/common/py_utils/py_utils/webpagereplay_go_server.py
+../../third_party/catapult/common/py_utils/py_utils/xvfb.py
 ../../third_party/catapult/common/py_vulcanize/py_vulcanize/__init__.py
 ../../third_party/catapult/common/py_vulcanize/py_vulcanize/generate.py
 ../../third_party/catapult/common/py_vulcanize/py_vulcanize/html_generation_controller.py
diff --git a/tools/traffic_annotation/OWNERS b/tools/traffic_annotation/OWNERS
index 6f7434c..af3a167 100644
--- a/tools/traffic_annotation/OWNERS
+++ b/tools/traffic_annotation/OWNERS
@@ -1,6 +1,3 @@
+crmullins@chromium.org
 nicolaso@chromium.org
 rhalavati@chromium.org  # emeritus
-
-per-file safe_list.txt=crmullins@chromium.org
-per-file safe_list.txt=nicolaso@chromium.org
-per-file safe_list.txt=ramyagopalan@google.com
diff --git a/ui/base/ime/win/input_method_win_base.cc b/ui/base/ime/win/input_method_win_base.cc
index e0aeb0d5..d838ade 100644
--- a/ui/base/ime/win/input_method_win_base.cc
+++ b/ui/base/ime/win/input_method_win_base.cc
@@ -12,6 +12,7 @@
 
 #include "base/auto_reset.h"
 #include "base/command_line.h"
+#include "base/containers/heap_array.h"
 #include "base/functional/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_util.h"
@@ -83,8 +84,8 @@
 
   // Retrieve the keyboard layouts in an array and check if there is an RTL
   // layout in it.
-  std::unique_ptr<HKL[]> layouts(new HKL[size]);
-  ::GetKeyboardLayoutList(size, layouts.get());
+  auto layouts = base::HeapArray<HKL>::Uninit(size);
+  ::GetKeyboardLayoutList(size, layouts.data());
   for (int i = 0; i < size; ++i) {
     if (IsRTLPrimaryLangID(
             PRIMARYLANGID(reinterpret_cast<uintptr_t>(layouts[i])))) {
diff --git a/ui/display/BUILD.gn b/ui/display/BUILD.gn
index fb806ddd..2dbfeb1 100644
--- a/ui/display/BUILD.gn
+++ b/ui/display/BUILD.gn
@@ -231,8 +231,8 @@
       "win/test/scoped_screen_win.h",
       "win/test/screen_util_win.cc",
       "win/test/screen_util_win.h",
-      "win/test/virtual_display_win_util.cc",
-      "win/test/virtual_display_win_util.h",
+      "win/test/virtual_display_util_win.cc",
+      "win/test/virtual_display_util_win.h",
     ]
     deps += [
       "//third_party/win_virtual_display/controller",
@@ -244,8 +244,8 @@
     sources += [
       "mac/test/test_screen_mac.h",
       "mac/test/test_screen_mac.mm",
-      "mac/test/virtual_display_mac_util.h",
-      "mac/test/virtual_display_mac_util.mm",
+      "mac/test/virtual_display_util_mac.h",
+      "mac/test/virtual_display_util_mac.mm",
     ]
   }
 
@@ -373,23 +373,19 @@
   }
 }
 
-# This target is added as a dependency of browser interactive_ui_tests.It must
+# This target is added as a dependency of browser interactive_ui_tests. It must
 # be source_set, otherwise the linker will drop the tests as dead code.
 source_set("display_interactive_ui_tests") {
   testonly = true
   if (is_mac) {
-    sources = [ "mac/test/virtual_display_mac_util_interactive_uitest.mm" ]
-
-    deps = [
-      ":display",
-      ":test_support",
-      "//base/test:test_support",
-      "//testing/gtest",
-    ]
+    sources = [ "mac/test/virtual_display_util_mac_interactive_uitest.mm" ]
   }
-  if (is_win) {
-    sources = [ "win/test/virtual_display_win_util_interactive_uitest.cc" ]
 
+  if (is_win) {
+    sources = [ "win/test/virtual_display_util_win_interactive_uitest.cc" ]
+  }
+
+  if (is_win || is_mac) {
     deps = [
       ":display",
       ":test_support",
diff --git a/ui/display/mac/test/virtual_display_mac_util.h b/ui/display/mac/test/virtual_display_util_mac.h
similarity index 95%
rename from ui/display/mac/test/virtual_display_mac_util.h
rename to ui/display/mac/test/virtual_display_util_mac.h
index d3ad2f2b3..68c4cd6 100644
--- a/ui/display/mac/test/virtual_display_mac_util.h
+++ b/ui/display/mac/test/virtual_display_util_mac.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UI_DISPLAY_MAC_TEST_VIRTUAL_DISPLAY_MAC_UTIL_H_
-#define UI_DISPLAY_MAC_TEST_VIRTUAL_DISPLAY_MAC_UTIL_H_
+#ifndef UI_DISPLAY_MAC_TEST_VIRTUAL_DISPLAY_UTIL_MAC_H_
+#define UI_DISPLAY_MAC_TEST_VIRTUAL_DISPLAY_UTIL_MAC_H_
 
 #import <IOKit/pwr_mgt/IOPMLib.h>
 #include <stdint.h>
@@ -108,4 +108,4 @@
 }  // namespace test
 }  // namespace display
 
-#endif  // UI_DISPLAY_MAC_TEST_VIRTUAL_DISPLAY_MAC_UTIL_H_
+#endif  // UI_DISPLAY_MAC_TEST_VIRTUAL_DISPLAY_UTIL_MAC_H_
diff --git a/ui/display/mac/test/virtual_display_mac_util.mm b/ui/display/mac/test/virtual_display_util_mac.mm
similarity index 99%
rename from ui/display/mac/test/virtual_display_mac_util.mm
rename to ui/display/mac/test/virtual_display_util_mac.mm
index 9b1ee91..720324f 100644
--- a/ui/display/mac/test/virtual_display_mac_util.mm
+++ b/ui/display/mac/test/virtual_display_util_mac.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/display/mac/test/virtual_display_mac_util.h"
+#include "ui/display/mac/test/virtual_display_util_mac.h"
 
 #include <CoreGraphics/CoreGraphics.h>
 #import <Foundation/Foundation.h>
diff --git a/ui/display/mac/test/virtual_display_mac_util_interactive_uitest.mm b/ui/display/mac/test/virtual_display_util_mac_interactive_uitest.mm
similarity index 98%
rename from ui/display/mac/test/virtual_display_mac_util_interactive_uitest.mm
rename to ui/display/mac/test/virtual_display_util_mac_interactive_uitest.mm
index cf7a733..a6f2880a 100644
--- a/ui/display/mac/test/virtual_display_mac_util_interactive_uitest.mm
+++ b/ui/display/mac/test/virtual_display_util_mac_interactive_uitest.mm
@@ -5,7 +5,7 @@
 #include <memory>
 #include "base/test/task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "ui/display/mac/test/virtual_display_mac_util.h"
+#include "ui/display/mac/test/virtual_display_util_mac.h"
 #include "ui/display/screen.h"
 #include "ui/display/test/virtual_display_util.h"
 #include "ui/display/types/display_constants.h"
diff --git a/ui/display/manager/display_configurator.cc b/ui/display/manager/display_configurator.cc
index 5bcf366..210f3a66 100644
--- a/ui/display/manager/display_configurator.cc
+++ b/ui/display/manager/display_configurator.cc
@@ -114,7 +114,7 @@
       MultipleDisplayState new_display_state,
       chromeos::DisplayPowerState new_power_state,
       RefreshRateThrottleState new_throttle_state,
-      bool new_vrr_enabled_state,
+      const base::flat_set<int64_t>& new_vrr_enabled_state,
       std::vector<DisplayConfigureRequest>* requests) const override;
   DisplayStateList GetDisplayStates() const override;
   bool IsMirroring() const override;
@@ -249,7 +249,7 @@
     MultipleDisplayState new_display_state,
     chromeos::DisplayPowerState new_power_state,
     RefreshRateThrottleState new_throttle_state,
-    bool new_vrr_enabled_state,
+    const base::flat_set<int64_t>& new_vrr_enabled_state,
     std::vector<DisplayConfigureRequest>* requests) const {
   std::vector<DisplayState> states = ParseDisplays(displays);
   std::vector<bool> display_power;
@@ -263,9 +263,12 @@
   gfx::Size size;
 
   for (display::DisplaySnapshot* display : displays) {
-    requests->push_back(DisplayConfigureRequest(
-        display, display->current_mode(), gfx::Point(),
-        new_vrr_enabled_state && display->IsVrrCapable()));
+    const bool enable_vrr =
+        display->IsVrrCapable() &&
+        (::features::IsVariableRefreshRateAlwaysOn() ||
+         new_vrr_enabled_state.contains(display->display_id()));
+    requests->emplace_back(display, display->current_mode(), gfx::Point(),
+                           enable_vrr);
   }
 
   switch (new_display_state) {
@@ -598,8 +601,7 @@
           layout_manager_.get(),
           base::BindRepeating(&DisplayConfigurator::configurator_disabled,
                               base::Unretained(this)))),
-      has_unassociated_display_(false),
-      pending_vrr_state_(::features::IsVariableRefreshRateAlwaysOn()) {
+      has_unassociated_display_(false) {
   AddObserver(content_protection_manager_.get());
 }
 
@@ -1097,8 +1099,7 @@
     const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&
         unassociated_displays,
     MultipleDisplayState new_display_state,
-    chromeos::DisplayPowerState new_power_state,
-    bool new_vrr_state_) {
+    chromeos::DisplayPowerState new_power_state) {
   VLOG(1) << "OnConfigured: success=" << success << " new_display_state="
           << MultipleDisplayStateToString(new_display_state)
           << " new_power_state=" << DisplayPowerStateToString(new_power_state);
@@ -1109,7 +1110,6 @@
   if (success) {
     current_display_state_ = new_display_state;
     UpdatePowerState(new_power_state);
-    current_vrr_state_ = new_vrr_state_;
   }
 
   configuration_task_.reset();
@@ -1217,34 +1217,52 @@
   return current_power_state_ != chromeos::DISPLAY_POWER_ALL_OFF;
 }
 
-void DisplayConfigurator::SetVrrEnabled(bool enable_vrr) {
-  if (current_vrr_state_ == enable_vrr) {
-    return;
-  }
-
-  pending_vrr_state_ = enable_vrr;
-
-  if (!configure_timer_.IsRunning()) {
-    RunPendingConfiguration();
-  }
-}
-
-bool DisplayConfigurator::GetRequestedVrrState() const {
-  return pending_vrr_state_.value_or(current_vrr_state_);
-}
-
-bool DisplayConfigurator::ShouldConfigureVrr() const {
+void DisplayConfigurator::SetVrrEnabled(
+    const base::flat_set<int64_t>& display_ids) {
+  // Filter the provided set for VRR-capable displays only, and determine
+  // whether a configuration is required given the current state.
+  base::flat_set<int64_t> filtered_display_ids;
+  bool requires_configuration = false;
   for (const display::DisplaySnapshot* display : cached_displays_) {
     if (!display->IsVrrCapable()) {
       continue;
     }
 
-    if (display->IsVrrEnabled() != GetRequestedVrrState()) {
-      return true;
+    const bool vrr_should_be_enabled =
+        display_ids.contains(display->display_id());
+    if (vrr_should_be_enabled) {
+      filtered_display_ids.emplace(display->display_id());
+    }
+    requires_configuration |= vrr_should_be_enabled != display->IsVrrEnabled();
+  }
+
+  if (requires_configuration) {
+    pending_vrr_state_.emplace(filtered_display_ids);
+
+    if (!configure_timer_.IsRunning()) {
+      RunPendingConfiguration();
+    }
+  }
+}
+
+const base::flat_set<int64_t> DisplayConfigurator::GetRequestedVrrState()
+    const {
+  if (pending_vrr_state_.has_value()) {
+    return pending_vrr_state_.value();
+  }
+
+  base::flat_set<int64_t> requested_vrr_state;
+  for (const display::DisplaySnapshot* display : cached_displays_) {
+    if (display->IsVrrEnabled()) {
+      requested_vrr_state.emplace(display->display_id());
     }
   }
 
-  return false;
+  return requested_vrr_state;
+}
+
+bool DisplayConfigurator::ShouldConfigureVrr() const {
+  return pending_vrr_state_.has_value();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/ui/display/manager/display_configurator.h b/ui/display/manager/display_configurator.h
index 4a8a32c..897d33c 100644
--- a/ui/display/manager/display_configurator.h
+++ b/ui/display/manager/display_configurator.h
@@ -10,6 +10,7 @@
 #include <optional>
 #include <vector>
 
+#include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
@@ -312,9 +313,10 @@
     return requested_power_state_;
   }
 
-  // Requests to enable or disable variable refresh rates across all capable
-  // displays, and schedules a configuration change as needed.
-  void SetVrrEnabled(bool enable_vrr);
+  // Requests to enable variable refresh rates on the specified displays and to
+  // disable variable refresh rates on all other displays, and schedules a
+  // configuration change as needed.
+  void SetVrrEnabled(const base::flat_set<int64_t>& display_ids);
 
  private:
   friend class test::DisplayManagerTestApi;
@@ -358,8 +360,7 @@
       const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&
           unassociated_displays,
       MultipleDisplayState new_display_state,
-      chromeos::DisplayPowerState new_power_state,
-      bool new_vrr_state);
+      chromeos::DisplayPowerState new_power_state);
 
   // Updates the current and pending power state and notifies observers.
   void UpdatePowerState(chromeos::DisplayPowerState new_power_state);
@@ -400,8 +401,9 @@
   void SendRelinquishDisplayControl(DisplayControlCallback callback,
                                     bool success);
 
-  // Returns the requested VRR state, or the current state by default.
-  bool GetRequestedVrrState() const;
+  // Returns the requested VRR state listing the display ids which should have
+  // VRR enabled, defaulting to the current state as needed.
+  const base::flat_set<int64_t> GetRequestedVrrState() const;
 
   // Returns whether a configuration should occur on account of a pending VRR
   // request.
@@ -487,10 +489,10 @@
   // notification will be created to inform user.
   bool has_unassociated_display_;
 
-  // Stores the current variable refresh rate enabled state.
-  bool current_vrr_state_ = false;
-  // Stores the requested variable refresh rate enabled state.
-  std::optional<bool> pending_vrr_state_;
+  // Stores the requested variable refresh rate state as a set of display ids
+  // for which VRR should be enabled. All omitted displays should have VRR
+  // disabled. Absent if there is no pending state.
+  std::optional<base::flat_set<int64_t>> pending_vrr_state_ = std::nullopt;
 
   // This must be the last variable.
   base::WeakPtrFactory<DisplayConfigurator> weak_ptr_factory_{this};
diff --git a/ui/display/manager/display_configurator_unittest.cc b/ui/display/manager/display_configurator_unittest.cc
index 7404ad3..c91a0d8 100644
--- a/ui/display/manager/display_configurator_unittest.cc
+++ b/ui/display/manager/display_configurator_unittest.cc
@@ -2192,7 +2192,7 @@
   test_api_.GetDisplayLayoutManager()->GetDisplayLayout(
       native_display_delegate_->GetOutputs(), MULTIPLE_DISPLAY_STATE_SINGLE,
       chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
-      kRefreshRateThrottleEnabled, false, &requests);
+      kRefreshRateThrottleEnabled, /*new_vrr_enabled_state=*/{}, &requests);
 
   bool has_internal_request = false;
   for (auto& request: requests) {
@@ -2213,14 +2213,15 @@
   observer_.Reset();
 
   // Set VRR noop.
-  configurator_.SetVrrEnabled(false);
+  configurator_.SetVrrEnabled({});
   EXPECT_EQ(0, observer_.num_changes());
   EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
   EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
   EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
 
   // Set VRR enabled.
-  configurator_.SetVrrEnabled(true);
+  configurator_.SetVrrEnabled(
+      {GetOutput(0)->display_id(), GetOutput(1)->display_id()});
   EXPECT_EQ(1, observer_.num_changes());
   EXPECT_TRUE(GetOutput(0)->IsVrrEnabled());
   EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
@@ -2249,7 +2250,7 @@
   observer_.Reset();
 
   // Set VRR disabled.
-  configurator_.SetVrrEnabled(false);
+  configurator_.SetVrrEnabled({});
   EXPECT_EQ(1, observer_.num_changes());
   EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
   EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
@@ -2286,7 +2287,8 @@
   log_->GetActionsAndClear();
   observer_.Reset();
 
-  configurator_.SetVrrEnabled(true);
+  configurator_.SetVrrEnabled(
+      {GetOutput(0)->display_id(), GetOutput(1)->display_id()});
   EXPECT_EQ(0, observer_.num_changes());
   EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
   EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
@@ -2313,7 +2315,7 @@
   state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
   UpdateOutputs(1, true);
   // Enable VRR on internal display.
-  configurator_.SetVrrEnabled(true);
+  configurator_.SetVrrEnabled({GetOutput(0)->display_id()});
   EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
   EXPECT_TRUE(GetOutput(0)->IsVrrEnabled());
   log_->GetActionsAndClear();
@@ -2391,7 +2393,8 @@
   state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
   UpdateOutputs(2, true);
   // Enable VRR when only the external display is VRR-capable.
-  configurator_.SetVrrEnabled(true);
+  configurator_.SetVrrEnabled(
+      {GetOutput(0)->display_id(), GetOutput(1)->display_id()});
   EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
   EXPECT_EQ(60.0f, GetOutput(1)->current_mode()->refresh_rate());
   EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
diff --git a/ui/display/manager/display_layout_manager.h b/ui/display/manager/display_layout_manager.h
index e51782c0..bfbab2b 100644
--- a/ui/display/manager/display_layout_manager.h
+++ b/ui/display/manager/display_layout_manager.h
@@ -41,7 +41,7 @@
       MultipleDisplayState new_display_state,
       chromeos::DisplayPowerState new_power_state,
       RefreshRateThrottleState new_throttle_state,
-      bool new_vrr_enabled_state,
+      const base::flat_set<int64_t>& new_vrr_enabled_state,
       std::vector<DisplayConfigureRequest>* requests) const = 0;
 
   virtual std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>
diff --git a/ui/display/manager/test/test_display_layout_manager.cc b/ui/display/manager/test/test_display_layout_manager.cc
index a1357f7..5296c297 100644
--- a/ui/display/manager/test/test_display_layout_manager.cc
+++ b/ui/display/manager/test/test_display_layout_manager.cc
@@ -40,7 +40,7 @@
     MultipleDisplayState new_display_state,
     chromeos::DisplayPowerState new_power_state,
     RefreshRateThrottleState new_throttle_state,
-    bool new_vrr_state,
+    const base::flat_set<int64_t>& new_vrr_enabled_state,
     std::vector<DisplayConfigureRequest>* requests) const {
   NOTREACHED();
   return false;
diff --git a/ui/display/manager/test/test_display_layout_manager.h b/ui/display/manager/test/test_display_layout_manager.h
index 7625f3b..8907b22 100644
--- a/ui/display/manager/test/test_display_layout_manager.h
+++ b/ui/display/manager/test/test_display_layout_manager.h
@@ -45,7 +45,7 @@
       MultipleDisplayState new_display_state,
       chromeos::DisplayPowerState new_power_state,
       RefreshRateThrottleState new_throttle_state,
-      bool new_vrr_enabled_state,
+      const base::flat_set<int64_t>& new_vrr_enabled_state,
       std::vector<DisplayConfigureRequest>* requests) const override;
   std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>> GetDisplayStates()
       const override;
diff --git a/ui/display/manager/update_display_configuration_task.cc b/ui/display/manager/update_display_configuration_task.cc
index 008e0662..e29a156e 100644
--- a/ui/display/manager/update_display_configuration_task.cc
+++ b/ui/display/manager/update_display_configuration_task.cc
@@ -74,7 +74,7 @@
     chromeos::DisplayPowerState new_power_state,
     int power_flags,
     RefreshRateThrottleState refresh_rate_throttle_state,
-    bool new_vrr_state_,
+    const base::flat_set<int64_t>& new_vrr_state,
     bool force_configure,
     ConfigurationType configuration_type,
     ResponseCallback callback)
@@ -84,7 +84,7 @@
       new_power_state_(new_power_state),
       power_flags_(power_flags),
       refresh_rate_throttle_state_(refresh_rate_throttle_state),
-      new_vrr_state_(new_vrr_state_),
+      new_vrr_state_(new_vrr_state),
       force_configure_(force_configure),
       configuration_type_(configuration_type),
       callback_(std::move(callback)),
@@ -131,7 +131,7 @@
           << " new_power_state=" << DisplayPowerStateToString(new_power_state_)
           << " flags=" << power_flags_ << " refresh_rate_throttle_state_="
           << RefreshRateThrottleStateToString(refresh_rate_throttle_state_)
-          << " new_vrr_state=" << new_vrr_state_
+          << " new_vrr_state=" << VrrStateToString(new_vrr_state_)
           << " force_configure=" << force_configure_
           << " display_count=" << cached_displays_.size();
   if (ShouldConfigure()) {
@@ -214,7 +214,7 @@
 
   std::move(callback_).Run(success, cached_displays_,
                            cached_unassociated_displays_, new_display_state_,
-                           new_power_state_, new_vrr_state_);
+                           new_power_state_);
 }
 
 bool UpdateDisplayConfigurationTask::ShouldForceDpms() const {
@@ -279,7 +279,8 @@
       continue;
     }
 
-    if (display->IsVrrEnabled() != new_vrr_state_) {
+    if (new_vrr_state_.contains(display->display_id()) !=
+        display->IsVrrEnabled()) {
       return true;
     }
   }
diff --git a/ui/display/manager/update_display_configuration_task.h b/ui/display/manager/update_display_configuration_task.h
index 94307c28..98aca83 100644
--- a/ui/display/manager/update_display_configuration_task.h
+++ b/ui/display/manager/update_display_configuration_task.h
@@ -33,8 +33,7 @@
       /*unassociated_displays=*/
       const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&,
       /*new_display_state=*/MultipleDisplayState,
-      /*new_power_state=*/chromeos::DisplayPowerState,
-      /*new_vrr_state=*/bool)>;
+      /*new_power_state=*/chromeos::DisplayPowerState)>;
 
   UpdateDisplayConfigurationTask(
       NativeDisplayDelegate* delegate,
@@ -43,7 +42,7 @@
       chromeos::DisplayPowerState new_power_state,
       int power_flags,
       RefreshRateThrottleState refresh_rate_throttle_state,
-      bool new_vrr_state,
+      const base::flat_set<int64_t>& new_vrr_state,
       bool force_configure,
       ConfigurationType configuration_type,
       ResponseCallback callback);
@@ -114,9 +113,9 @@
   // for the internal display.
   RefreshRateThrottleState refresh_rate_throttle_state_;
 
-  // The requested VRR enabled state which the configuration task should apply
-  // to all capable displays.
-  bool new_vrr_state_;
+  // The requested VRR state which lists the set of display ids that should have
+  // VRR enabled, while all omitted displays should have VRR disabled.
+  const base::flat_set<int64_t> new_vrr_state_;
 
   bool force_configure_;
 
diff --git a/ui/display/manager/update_display_configuration_task_unittest.cc b/ui/display/manager/update_display_configuration_task_unittest.cc
index 3270b01..93755c9 100644
--- a/ui/display/manager/update_display_configuration_task_unittest.cc
+++ b/ui/display/manager/update_display_configuration_task_unittest.cc
@@ -97,7 +97,7 @@
       MultipleDisplayState new_display_state,
       chromeos::DisplayPowerState new_power_state,
       RefreshRateThrottleState new_throttle_state,
-      bool new_vrr_state,
+      const base::flat_set<int64_t>& new_vrr_state,
       std::vector<DisplayConfigureRequest>* requests) const override {
     gfx::Point origin;
     for (DisplaySnapshot* display : displays) {
@@ -110,7 +110,8 @@
 
       const DisplayMode* request_mode =
           new_power_state == chromeos::DISPLAY_POWER_ALL_ON ? mode : nullptr;
-      bool request_vrr_state = new_vrr_state && display->IsVrrCapable();
+      bool request_vrr_state = new_vrr_state.contains(display->display_id()) &&
+                               display->IsVrrCapable();
       requests->push_back(DisplayConfigureRequest(display, request_mode, origin,
                                                   request_vrr_state));
 
@@ -201,14 +202,12 @@
       const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>&
           unassociated_displays,
       MultipleDisplayState new_display_state,
-      chromeos::DisplayPowerState new_power_state,
-      bool new_vrr_state) {
+      chromeos::DisplayPowerState new_power_state) {
     configured_ = true;
     configuration_status_ = success;
     display_states_ = displays;
     display_state_ = new_display_state;
     power_state_ = new_power_state;
-    vrr_state_ = new_vrr_state;
 
     if (success) {
       layout_manager_.set_display_state(display_state_);
@@ -231,7 +230,6 @@
   std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>> display_states_;
   MultipleDisplayState display_state_ = MULTIPLE_DISPLAY_STATE_INVALID;
   chromeos::DisplayPowerState power_state_ = chromeos::DISPLAY_POWER_ALL_ON;
-  bool vrr_state_ = false;
 };
 
 }  // namespace
@@ -243,8 +241,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -266,8 +263,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -298,8 +294,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -339,8 +334,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -378,8 +372,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -400,8 +393,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -464,8 +456,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -493,8 +484,7 @@
         chromeos::DISPLAY_POWER_ALL_OFF,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -527,8 +517,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -542,8 +531,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -569,8 +557,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -584,8 +571,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleEnabled,
-        /*new_vrr_state=*/false, /*force_configure=*/true,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/true, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -626,8 +612,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleDisabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -644,8 +629,9 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay,
         kRefreshRateThrottleDisabled,
-        /*new_vrr_state=*/true, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/
+        {displays_[0]->display_id(), displays_[1]->display_id()},
+        /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -686,8 +672,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerNoFlags,
         kRefreshRateThrottleDisabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
@@ -704,8 +689,7 @@
         chromeos::DISPLAY_POWER_ALL_ON,
         DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay,
         kRefreshRateThrottleDisabled,
-        /*new_vrr_state=*/false, /*force_configure=*/false,
-        kConfigurationTypeFull,
+        /*new_vrr_state=*/{}, /*force_configure=*/false, kConfigurationTypeFull,
         base::BindOnce(&UpdateDisplayConfigurationTaskTest::ResponseCallback,
                        base::Unretained(this)));
     task.Run();
diff --git a/ui/display/manager/util/display_manager_util.cc b/ui/display/manager/util/display_manager_util.cc
index ed3e171f..cfbca0ad 100644
--- a/ui/display/manager/util/display_manager_util.cc
+++ b/ui/display/manager/util/display_manager_util.cc
@@ -17,6 +17,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/notreached.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
 #include "build/chromeos_buildflags.h"
 #include "ui/base/ui_base_switches.h"
 #include "ui/display/display_switches.h"
@@ -60,6 +61,14 @@
          ")";
 }
 
+std::string VrrStateToString(const base::flat_set<int64_t>& state) {
+  std::vector<std::string> entries;
+  for (const int64_t id : state) {
+    entries.push_back(base::NumberToString(id));
+  }
+  return "{" + base::JoinString(entries, ", ") + "}";
+}
+
 int GetDisplayPower(
     const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays,
     chromeos::DisplayPowerState state,
@@ -193,10 +202,10 @@
   const int effective_width = std::round(
       static_cast<float>(std::max(mode.size().width(), mode.size().height())) /
       mode.device_scale_factor());
-  return GetDisplayZoomFactorsByDsiplayWidth(effective_width);
+  return GetDisplayZoomFactorsByDisplayWidth(effective_width);
 }
 
-std::vector<float> GetDisplayZoomFactorsByDsiplayWidth(
+std::vector<float> GetDisplayZoomFactorsByDisplayWidth(
     const int display_width) {
   std::size_t index = kZoomListBuckets.size() - 1;
   while (index > 0 && display_width < kZoomListBuckets[index].first) {
diff --git a/ui/display/manager/util/display_manager_util.h b/ui/display/manager/util/display_manager_util.h
index af3c1f6..64ae0e4 100644
--- a/ui/display/manager/util/display_manager_util.h
+++ b/ui/display/manager/util/display_manager_util.h
@@ -9,6 +9,7 @@
 #include <string>
 #include <vector>
 
+#include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "build/chromeos_buildflags.h"
 #include "ui/display/display.h"
@@ -41,6 +42,9 @@
 // Returns a string describing |state|.
 std::string RefreshRateThrottleStateToString(RefreshRateThrottleState state);
 
+// Returns a string describing |state|.
+std::string VrrStateToString(const base::flat_set<int64_t>& state);
+
 // Returns the number of displays in |displays| that should be turned on, per
 // |state|.  If |display_power| is non-NULL, it is updated to contain the
 // on/off state of each corresponding entry in |displays|.
@@ -76,7 +80,7 @@
     const ManagedDisplayMode& mode);
 
 // Returns a list of display zooms supported by the given |display_width|.
-DISPLAY_MANAGER_EXPORT std::vector<float> GetDisplayZoomFactorsByDsiplayWidth(
+DISPLAY_MANAGER_EXPORT std::vector<float> GetDisplayZoomFactorsByDisplayWidth(
     const int display_width);
 
 // Returns a list of display zooms based on the provided |dsf| of the display.
diff --git a/ui/display/manager/util/display_manager_util_unittest.cc b/ui/display/manager/util/display_manager_util_unittest.cc
index 0ed2944..a9384a5f 100644
--- a/ui/display/manager/util/display_manager_util_unittest.cc
+++ b/ui/display/manager/util/display_manager_util_unittest.cc
@@ -69,7 +69,7 @@
   // equal to the |second| of the pair.
   for (const auto& data : kTestData) {
     const std::vector<float> zoom_values =
-        GetDisplayZoomFactorsByDsiplayWidth(data.first);
+        GetDisplayZoomFactorsByDisplayWidth(data.first);
     for (std::size_t j = 0; j < kNumOfZoomFactors; j++) {
       EXPECT_FLOAT_EQ(zoom_values[j], data.second[j]);
     }
diff --git a/ui/display/win/test/virtual_display_win_util.cc b/ui/display/win/test/virtual_display_util_win.cc
similarity index 98%
rename from ui/display/win/test/virtual_display_win_util.cc
rename to ui/display/win/test/virtual_display_util_win.cc
index b211ae0..8864383 100644
--- a/ui/display/win/test/virtual_display_win_util.cc
+++ b/ui/display/win/test/virtual_display_util_win.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/display/win/test/virtual_display_win_util.h"
+#include "ui/display/win/test/virtual_display_util_win.h"
 
 #include "base/containers/flat_tree.h"
 #include "base/logging.h"
diff --git a/ui/display/win/test/virtual_display_win_util.h b/ui/display/win/test/virtual_display_util_win.h
similarity index 92%
rename from ui/display/win/test/virtual_display_win_util.h
rename to ui/display/win/test/virtual_display_util_win.h
index 6670143..13a1038 100644
--- a/ui/display/win/test/virtual_display_win_util.h
+++ b/ui/display/win/test/virtual_display_util_win.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UI_DISPLAY_WIN_TEST_VIRTUAL_DISPLAY_WIN_UTIL_H_
-#define UI_DISPLAY_WIN_TEST_VIRTUAL_DISPLAY_WIN_UTIL_H_
+#ifndef UI_DISPLAY_WIN_TEST_VIRTUAL_DISPLAY_UTIL_WIN_H_
+#define UI_DISPLAY_WIN_TEST_VIRTUAL_DISPLAY_UTIL_WIN_H_
 
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
@@ -65,4 +65,4 @@
 }  // namespace test
 }  // namespace display
 
-#endif  // UI_DISPLAY_WIN_TEST_VIRTUAL_DISPLAY_WIN_UTIL_H_
+#endif  // UI_DISPLAY_WIN_TEST_VIRTUAL_DISPLAY_UTIL_WIN_H_
diff --git a/ui/display/win/test/virtual_display_win_util_interactive_uitest.cc b/ui/display/win/test/virtual_display_util_win_interactive_uitest.cc
similarity index 98%
rename from ui/display/win/test/virtual_display_win_util_interactive_uitest.cc
rename to ui/display/win/test/virtual_display_util_win_interactive_uitest.cc
index 7c6a8449..aba0f5c 100644
--- a/ui/display/win/test/virtual_display_win_util_interactive_uitest.cc
+++ b/ui/display/win/test/virtual_display_util_win_interactive_uitest.cc
@@ -7,7 +7,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/display/win/screen_win.h"
-#include "ui/display/win/test/virtual_display_win_util.h"
+#include "ui/display/win/test/virtual_display_util_win.h"
 
 // Flag passed to use the custom display driver to make virtual displays.
 static constexpr char kSwitchWindowsVirtualDisplayDriver[] =
diff --git a/ui/gl/gl_surface_egl.cc b/ui/gl/gl_surface_egl.cc
index a9eda1d..a492a9c7 100644
--- a/ui/gl/gl_surface_egl.cc
+++ b/ui/gl/gl_surface_egl.cc
@@ -13,6 +13,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/containers/heap_array.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
@@ -235,10 +236,10 @@
       continue;
     }
 
-    std::unique_ptr<EGLConfig[]> matching_configs(new EGLConfig[num_configs]);
+    auto matching_configs = base::HeapArray<EGLConfig>::Uninit(num_configs);
     if (want_rgb565 || visual_id >= 0) {
       config_size = num_configs;
-      config_data = matching_configs.get();
+      config_data = matching_configs.data();
     }
 
     if (!eglChooseConfig(display, choose_attributes, config_data, config_size,
diff --git a/ui/gl/gl_switches.cc b/ui/gl/gl_switches.cc
index 7f21bafa..0df2852 100644
--- a/ui/gl/gl_switches.cc
+++ b/ui/gl/gl_switches.cc
@@ -258,12 +258,7 @@
 // Default to using ANGLE's Metal backend.
 BASE_FEATURE(kDefaultANGLEMetal,
              "DefaultANGLEMetal",
-#if BUILDFLAG(IS_IOS) || (BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64))
-             base::FEATURE_ENABLED_BY_DEFAULT
-#else
-             base::FEATURE_DISABLED_BY_DEFAULT
-#endif
-);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Default to using ANGLE's Vulkan backend.
 BASE_FEATURE(kDefaultANGLEVulkan,
diff --git a/ui/views/layout/animating_layout_manager.cc b/ui/views/layout/animating_layout_manager.cc
index 02d6ef9e..d6abe107 100644
--- a/ui/views/layout/animating_layout_manager.cc
+++ b/ui/views/layout/animating_layout_manager.cc
@@ -523,7 +523,7 @@
     }
   }
 
-  return LayoutManagerBase::OnViewAdded(host, view);
+  return RecalculateTarget();
 }
 
 void AnimatingLayoutManager::OnLayoutChanged() {
diff --git a/ui/views/layout/layout_manager_base.cc b/ui/views/layout/layout_manager_base.cc
index 013f5e7..21c46ed 100644
--- a/ui/views/layout/layout_manager_base.cc
+++ b/ui/views/layout/layout_manager_base.cc
@@ -156,11 +156,8 @@
   // During callbacks when a child is removed we can get in a state where a view
   // in the child list of the host view is not in |child_infos_|. In that case,
   // the view is being removed and is not part of the layout.
-  // TODO(crbug.com/1524187): This is a symptom of bad removal handling. Fix,
-  // then convert this to a CHECK.
-  if (it == child_infos_.end()) {
+  if (it == child_infos_.end())
     return false;
-  }
 
   return !child->GetProperty(kViewIgnoredByLayoutKey) &&
          !child->GetProperty(kIsDecorativeViewKey) &&
@@ -173,11 +170,8 @@
   // During callbacks when a child is removed we can get in a state where a view
   // in the child list of the host view is not in |child_infos_|. In that case,
   // the view is being removed and is not part of the layout.
-  // TODO(crbug.com/1524187): This is a symptom of bad removal handling. Fix,
-  // then convert this to a CHECK.
-  if (it == child_infos_.end()) {
+  if (it == child_infos_.end())
     return false;
-  }
 
   return it->second.can_be_visible;
 }
@@ -253,16 +247,12 @@
 }
 
 bool LayoutManagerBase::OnViewAdded(View* host, View* view) {
-  if (IsChildIncludedInLayout(view)) {
-    OnLayoutChanged();
-  }
+  OnLayoutChanged();
   return false;
 }
 
 bool LayoutManagerBase::OnViewRemoved(View* host, View* view) {
-  if (IsChildIncludedInLayout(view)) {
-    OnLayoutChanged();
-  }
+  OnLayoutChanged();
   return false;
 }
 
@@ -418,15 +408,14 @@
 }
 
 bool LayoutManagerBase::PropagateViewRemoved(View* host, View* view) {
+  child_infos_.erase(view);
+
   bool result = false;
 
   for (auto& owned_layout : owned_layouts_)
     result |= owned_layout->PropagateViewRemoved(host, view);
 
   result |= OnViewRemoved(host, view);
-
-  child_infos_.erase(view);
-
   return result;
 }
 
diff --git a/ui/webui/resources/cr_components/history_embeddings/BUILD.gn b/ui/webui/resources/cr_components/history_embeddings/BUILD.gn
index c18cefa..3b7e7d3 100644
--- a/ui/webui/resources/cr_components/history_embeddings/BUILD.gn
+++ b/ui/webui/resources/cr_components/history_embeddings/BUILD.gn
@@ -2,18 +2,31 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//mojo/public/tools/bindings/mojom.gni")
 import("//ui/webui/resources/tools/build_webui.gni")
 
 assert(!is_android)
 
+mojom("mojo_bindings") {
+  sources = [ "history_embeddings.mojom" ]
+  webui_module_path = ""
+}
+
 build_webui("build") {
-  grd_prefix = "cr_components_history_clusters"
+  grd_prefix = "cr_components_history_embeddings"
   web_component_files = [ "history_embeddings.ts" ]
+  non_web_component_files = [ "browser_proxy.ts" ]
+
+  mojo_files_deps = [ ":mojo_bindings_ts__generator" ]
+  mojo_files = [ "$target_gen_dir/history_embeddings.mojom-webui.ts" ]
 
   ts_out_dir =
       "$root_gen_dir/ui/webui/resources/tsc/cr_components/history_embeddings"
   ts_composite = true
-  ts_deps = [ "//third_party/polymer/v3_0:library" ]
+  ts_deps = [
+    "//third_party/polymer/v3_0:library",
+    "//ui/webui/resources/mojo:build_ts",
+  ]
   generate_grdp = true
   grd_resource_path_prefix = rebase_path(".", "//ui/webui/resources")
 }
diff --git a/ui/webui/resources/cr_components/history_embeddings/browser_proxy.ts b/ui/webui/resources/cr_components/history_embeddings/browser_proxy.ts
new file mode 100644
index 0000000..8967cb9
--- /dev/null
+++ b/ui/webui/resources/cr_components/history_embeddings/browser_proxy.ts
@@ -0,0 +1,31 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import type {PageHandlerRemote} from './history_embeddings.mojom-webui.js';
+import {PageHandler} from './history_embeddings.mojom-webui.js';
+
+export interface HistoryEmbeddingsBrowserProxy {
+  doSomething(): Promise<boolean>;
+}
+
+export class HistoryEmbeddingsBrowserProxyImpl implements
+    HistoryEmbeddingsBrowserProxy {
+  static instance: HistoryEmbeddingsBrowserProxy|null = null;
+
+  handler: PageHandlerRemote = PageHandler.getRemote();
+
+  static getInstance(): HistoryEmbeddingsBrowserProxy {
+    return HistoryEmbeddingsBrowserProxyImpl.instance ||
+        (HistoryEmbeddingsBrowserProxyImpl.instance =
+             new HistoryEmbeddingsBrowserProxyImpl());
+  }
+
+  static setInstance(newInstance: HistoryEmbeddingsBrowserProxy) {
+    HistoryEmbeddingsBrowserProxyImpl.instance = newInstance;
+  }
+
+  doSomething() {
+    return this.handler.doSomething().then(response => response.success);
+  }
+}
diff --git a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
index 3b18e51..1316062 100644
--- a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
+++ b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.html
@@ -1 +1,2 @@
 hello world
+[[hasLoaded_]]
diff --git a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.mojom b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.mojom
new file mode 100644
index 0000000..45058c0
--- /dev/null
+++ b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.mojom
@@ -0,0 +1,11 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module history_embeddings.mojom;
+
+// Browser-side handler for requests from WebUI page.
+interface PageHandler {
+  // Temporary method to see successful interaction between WebUI and handler.
+  DoSomething() => (bool success);
+};
diff --git a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts
index c36b43d0..27e0ec4 100644
--- a/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts
+++ b/ui/webui/resources/cr_components/history_embeddings/history_embeddings.ts
@@ -4,9 +4,10 @@
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
+import {HistoryEmbeddingsBrowserProxyImpl} from './browser_proxy.js';
 import {getTemplate} from './history_embeddings.html.js';
 
-export class HistoryEmbeddingsElement extends PolymerElement {
+class HistoryEmbeddingsElement extends PolymerElement {
   static get is() {
     return 'cr-history-embeddings';
   }
@@ -14,6 +15,20 @@
   static get template() {
     return getTemplate();
   }
+
+  static get properties() {
+    return {
+      hasLoaded_: Boolean,
+    };
+  }
+
+  private browserProxy_ = HistoryEmbeddingsBrowserProxyImpl.getInstance();
+  private hasLoaded_ = false;
+
+  override ready() {
+    super.ready();
+    this.browserProxy_.doSomething().then(success => this.hasLoaded_ = success);
+  }
 }
 
 declare global {
diff --git a/v8 b/v8
index c3a7867..b53eb02 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit c3a7867744ac5753621f6ff77120217cf165081d
+Subproject commit b53eb026438b102249f055de1af5d8aff5bb0779