diff --git a/DEPS b/DEPS
index ced1a5f..f078e42 100644
--- a/DEPS
+++ b/DEPS
@@ -312,15 +312,15 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'ef2511b0a6f21b4bb43414ac0571d755791ae22a',
+  'skia_revision': '081ba94858f6ee93b518e1a4107f81cbf7a224cb',
   # 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': 'ea012d232c66229b2d4f3f2a268aa999d668972b',
+  'v8_revision': '2c63d7b6b700b0a2d257d4f4f19d2948c41e9b88',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'a971e5b42e1e8e61ccb0d8e4b3f2245aaa4f2d4d',
+  'angle_revision': '19e725e49c7d23f810ebd709b79d91323af921c1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -387,7 +387,7 @@
   # 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': '1abc236bbd98af59cac64b471b3ea6ab900350db',
+  'chromium_variations_revision': '32e2e5c8d0343434cf154f2b928775c2d95ad8c5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -403,7 +403,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '5ccdd75fa57a758fa15db394dd6ad6092963cfed',
+  'devtools_frontend_revision': 'e13f34726b11afcb77bebca0e6a79c08ba3fd2c6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -431,7 +431,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.
-  'quiche_revision': '42dab6be059fbe2a3d98367e1d5ad19d887156d3',
+  'quiche_revision': '6fed96483ed2f0fd9567211f50df8beef43ac140',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -827,12 +827,12 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'ed4b774419588a1496c267e4b9ba422d20ea8552',
+    '306e4ba09278acec4c40f05cc49e5eb5f42275e5',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'b13d20e7483a7a2b3e600b5981693d476d43d19d',
+    'url': Var('chromium_git') + '/website.git' + '@' + '7918c586771aed5e170cb5ce33fdfc38f6203063',
   },
 
   'src/ios/third_party/earl_grey2/src': {
@@ -989,7 +989,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'Qdbpp4CESrciZ3ZF1ZZmOg-NQSUdK-DkNAddEJeZbbgC',
+          'version': 'rTiFKohCdnT81G3SjzFlb536YE6DnBkp_3Ig-Pt7gCUC',
       },
     ],
     'condition': 'checkout_android',
@@ -1205,7 +1205,7 @@
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
 
   'src/third_party/devtools-frontend-internal': {
-      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'b92185a4c9aba77a6cbbc8d8aa65820e8be5dae7',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'acc4bb2e6ba2063e1171c81951abd331aa9cf068',
     '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' + '@' + '609cb8ef0288f4e1667b1034dc53ad319d43ca99',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '1553701a9f0a47be12af882f0d7801df5b674122',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '8ef97ff3b7332e38e61b347a2fbed425a4617151',
@@ -1699,7 +1699,7 @@
   },
 
   'src/third_party/re2/src':
-    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + 'f9550c3f7207f946a45bbccd1814b12b136aae72',
+    Var('chromium_git') + '/external/github.com/google/re2.git' + '@' + '2d866a3d0753f4f4fce93cccc6c59c4b052d7db4',
 
   'src/third_party/r8': {
       'packages': [
@@ -1850,7 +1850,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '98673cc24786be6c10dd8908e0b0b4ed27625c6a',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'c935bb2141e2e616e1f9e1df4568d976ad1828c0',
+    Var('webrtc_git') + '/src.git' + '@' + '16ac10d9f75cde959f00df062f544c49941882da',
 
   # 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.
@@ -2017,7 +2017,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'Y9cLYmmmtcWhsN5F6APjq_pYVro6_TSV6-7MZqVwYIQC',
+        'version': 'DZBWu_gDTcO8mVYLHW38B-bCWgvLZG4RRjsxywx5roEC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/components/arc/mojom/BUILD.gn b/ash/components/arc/mojom/BUILD.gn
index 30177f9..00f88f4 100644
--- a/ash/components/arc/mojom/BUILD.gn
+++ b/ash/components/arc/mojom/BUILD.gn
@@ -86,7 +86,6 @@
     "//services/device/public/mojom:usb",
     "//services/media_session/public/mojom",
     "//services/resource_coordinator/public/mojom",
-    "//ui/accessibility/mojom:ax_assistant_mojom",
     "//ui/gfx/geometry/mojom",
     "//url/mojom:url_mojom_gurl",
   ]
diff --git a/ash/components/arc/mojom/app.mojom b/ash/components/arc/mojom/app.mojom
index 4763d7d..feb41f67 100644
--- a/ash/components/arc/mojom/app.mojom
+++ b/ash/components/arc/mojom/app.mojom
@@ -10,7 +10,6 @@
 import "ash/components/arc/mojom/compatibility_mode.mojom";
 import "ash/components/arc/mojom/gfx.mojom";
 import "ash/components/arc/mojom/scale_factor.mojom";
-import "ui/accessibility/mojom/ax_assistant_structure.mojom";
 
 // Describes installation result.
 struct InstallationResult {
@@ -437,9 +436,9 @@
                                                  bool active);
 };
 
-// TODO(lhchavez): Migrate all request/response messages to Mojo.
 // Next method ID: 44
-// Deprecated method IDs: 0, 1, 2, 3, 4, 6, 9, 12, 13, 15, 17, 18, 19, 22, 31
+// Deprecated method IDs: 0, 1, 2, 3, 4, 6, 9, 12, 13, 15, 17, 18, 19, 22, 29,
+//     31
 interface AppInstance {
   // Establishes full-duplex communication with the host.
   [MinVersion=26] Init@21(pending_remote<AppHost> host_remote) => ();
@@ -573,11 +572,6 @@
           (AppDiscoveryRequestState state@1,
            array<AppDiscoveryResult> results@0);
 
-  // Requests assist structure.
-  [MinVersion=37] RequestAssistStructure@29() =>
-        (ax.mojom.AssistantExtra? assistant_extra,
-         ax.mojom.AssistantTree? assistant_tree);
-
   // Queries whether |package_name| is installable for the current user. This
   // may return false if the user has already installed |package_name|, or if
   // it isn't available in the user's store.
diff --git a/ash/components/arc/mojom/notifications.mojom b/ash/components/arc/mojom/notifications.mojom
index fe9e908..2a5772b 100644
--- a/ash/components/arc/mojom/notifications.mojom
+++ b/ash/components/arc/mojom/notifications.mojom
@@ -307,7 +307,6 @@
 
 // Deprecated method IDs: 0
 // Next Method ID: 15
-// TODO(lhchavez): Migrate all request/response messages to Mojo.
 interface NotificationsInstance {
   // Establishes full-duplex communication with the host.
   [MinVersion=14] Init@5(pending_remote<NotificationsHost> host_remote) => ();
diff --git a/ash/components/arc/test/fake_app_instance.cc b/ash/components/arc/test/fake_app_instance.cc
index f89cde8..bcb9c1eb 100644
--- a/ash/components/arc/test/fake_app_instance.cc
+++ b/ash/components/arc/test/fake_app_instance.cc
@@ -464,11 +464,6 @@
   ++start_fast_app_reinstall_request_count_;
 }
 
-void FakeAppInstance::RequestAssistStructure(
-    RequestAssistStructureCallback callback) {
-  std::move(callback).Run(nullptr, nullptr);
-}
-
 void FakeAppInstance::IsInstallable(const std::string& package_name,
                                     IsInstallableCallback callback) {
   std::move(callback).Run(is_installable_);
diff --git a/ash/components/arc/test/fake_app_instance.h b/ash/components/arc/test/fake_app_instance.h
index f281694..207647b 100644
--- a/ash/components/arc/test/fake_app_instance.h
+++ b/ash/components/arc/test/fake_app_instance.h
@@ -166,7 +166,6 @@
   void StartPaiFlow(StartPaiFlowCallback callback) override;
   void StartFastAppReinstallFlow(
       const std::vector<std::string>& package_names) override;
-  void RequestAssistStructure(RequestAssistStructureCallback callback) override;
   void IsInstallable(const std::string& package_name,
                      IsInstallableCallback callback) override;
   void GetAppCategory(const std::string& package_name,
diff --git a/ash/shelf/login_shelf_button.cc b/ash/shelf/login_shelf_button.cc
index c1f1aca..45a85135 100644
--- a/ash/shelf/login_shelf_button.cc
+++ b/ash/shelf/login_shelf_button.cc
@@ -31,7 +31,8 @@
 namespace {
 
 // The highlight radius of the button.
-constexpr int kButtonHighlightRadiusDp = 16;
+// The large pill buttons height is 36 and the radius should be half of that.
+constexpr int kButtonHighlightRadiusDp = 18;
 
 }  // namespace
 
diff --git a/ash/shelf/login_shelf_view_pixeltest.cc b/ash/shelf/login_shelf_view_pixeltest.cc
index ad1470e7..aebdf12e 100644
--- a/ash/shelf/login_shelf_view_pixeltest.cc
+++ b/ash/shelf/login_shelf_view_pixeltest.cc
@@ -77,28 +77,28 @@
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_login_user_expand_button",
-      /*revision_number=*/8, primary_big_user_view_.get(),
+      /*revision_number=*/10, primary_big_user_view_.get(),
       primary_shelf_window));
 
   // Trigger the tab key. Check that the login shelf shutdown button is focused.
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_shutdown_button",
-      /*revision_number=*/8, primary_big_user_view_.get(),
+      /*revision_number=*/10, primary_big_user_view_.get(),
       primary_shelf_window));
 
   // Trigger the tab key. Check that the browser as guest button is focused.
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_browser_as_guest_button",
-      /*revision_number=*/8, primary_big_user_view_.get(),
+      /*revision_number=*/10, primary_big_user_view_.get(),
       primary_shelf_window));
 
   // Trigger the tab key. Check that the add person button is focused.
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_add_person_button",
-      /*revision_number=*/8, primary_big_user_view_.get(),
+      /*revision_number=*/10, primary_big_user_view_.get(),
       primary_shelf_window));
 }
 
@@ -113,13 +113,13 @@
   aura::Window* primary_shelf_window = GetPrimaryShelf()->GetWindow();
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_calendar_view",
-      /*revision_number=*/4, primary_shelf_window));
+      /*revision_number=*/6, primary_shelf_window));
 
   // Focus on the time view.
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_time_view.rev_0",
-      /*revision_number=*/4, primary_shelf_window));
+      /*revision_number=*/6, primary_shelf_window));
 
   PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
   PressAndReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
@@ -127,7 +127,7 @@
   // Move the focus back to the add person button.
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "refocus_on_login_shelf",
-      /*revision_number=*/4, primary_shelf_window));
+      /*revision_number=*/6, primary_shelf_window));
 }
 
 class LoginShelfWithPolicyWallpaperPixelTestWithRTL
@@ -156,7 +156,7 @@
   FocusOnShutdownButton();
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_shutdown_button",
-      /*revision_number=*/6, primary_big_user_view_.get(),
+      /*revision_number=*/8, primary_big_user_view_.get(),
       GetPrimaryShelf()->GetWindow()));
 }
 
diff --git a/ash/webui/camera_app_ui/resources/js/device/camera_manager.ts b/ash/webui/camera_app_ui/resources/js/device/camera_manager.ts
index 7d57ad34..d36c89b 100644
--- a/ash/webui/camera_app_ui/resources/js/device/camera_manager.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/camera_manager.ts
@@ -115,7 +115,7 @@
   ) {
     this.preview = new Preview(async () => {
       await this.reconfigure();
-    });
+    }, () => this.useSquareResolution());
 
     this.scheduler = new OperationScheduler(
         this,
diff --git a/ash/webui/camera_app_ui/resources/js/device/preview.ts b/ash/webui/camera_app_ui/resources/js/device/preview.ts
index a2323d52..7c07063 100644
--- a/ash/webui/camera_app_ui/resources/js/device/preview.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/preview.ts
@@ -51,7 +51,11 @@
 import * as util from '../util.js';
 import {WaitableEvent} from '../waitable_event.js';
 
-import {MediaStreamPTZController, PTZController} from './ptz_controller.js';
+import {
+  DigitalZoomPTZController,
+  MediaStreamPTZController,
+  PTZController,
+} from './ptz_controller.js';
 import {
   StreamConstraints,
   toMediaStreamConstraints,
@@ -123,6 +127,9 @@
 
   private readonly autoQRFlag = loadTimeData.getChromeFlag(Flag.AUTO_QR);
 
+  private readonly digitalZoomFlag =
+      loadTimeData.getChromeFlag(Flag.DIGITAL_ZOOM);
+
   /**
    * PTZController for the current stream constraint. Null if PTZ is not
    * supported.
@@ -132,7 +139,9 @@
   /**
    * @param onNewStreamNeeded Callback to request new stream.
    */
-  constructor(private readonly onNewStreamNeeded: () => Promise<void>) {
+  constructor(
+      private readonly onNewStreamNeeded: () => Promise<void>,
+      private readonly isSquareResolution: () => boolean) {
     expert.addObserver(
         expert.ExpertOption.SHOW_METADATA,
         queuedAsyncCallback('keepLatest', () => this.updateShowMetadata()));
@@ -205,6 +214,18 @@
   private async updatePTZ() {
     const deviceOperator = DeviceOperator.getInstance();
     const {pan, tilt, zoom} = this.getVideoTrack().getCapabilities();
+    const {deviceId} = getVideoTrackSettings(this.getVideoTrack());
+    const isDigitalZoomSupported = this.digitalZoomFlag &&
+        (await deviceOperator?.isDigitalZoomSupported(deviceId) ?? false);
+
+    if (isDigitalZoomSupported) {
+      this.isSupportPTZInternal = true;
+      const isSquare = this.isSquareResolution();
+      const aspectRatio = isSquare ? 1 : this.getResolution().aspectRatio;
+      this.ptzController =
+          await DigitalZoomPTZController.create(deviceId, aspectRatio);
+      return;
+    }
 
     this.isSupportPTZInternal = (() => {
       if (pan === undefined && tilt === undefined && zoom === undefined) {
@@ -217,6 +238,8 @@
       if (this.facing === Facing.EXTERNAL) {
         return true;
       } else if (expert.isEnabled(expert.ExpertOption.ENABLE_PTZ_FOR_BUILTIN)) {
+        // TODO(b/225112054): Remove the expert option once digital zoom is
+        // enabled by default.
         return true;
       }
 
@@ -228,7 +251,6 @@
       return;
     }
 
-    const {deviceId} = getVideoTrackSettings(this.getVideoTrack());
     const deviceDefaultPTZ = await this.getDeviceDefaultPTZ(deviceId);
     this.ptzController = new MediaStreamPTZController(
         this.getVideoTrack(), deviceDefaultPTZ, this.vidPid);
diff --git a/ash/webui/camera_app_ui/resources/js/device/ptz_controller.ts b/ash/webui/camera_app_ui/resources/js/device/ptz_controller.ts
index 6bf48578..21f2a22 100644
--- a/ash/webui/camera_app_ui/resources/js/device/ptz_controller.ts
+++ b/ash/webui/camera_app_ui/resources/js/device/ptz_controller.ts
@@ -2,9 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {assert, assertExists} from '../assert.js';
+import {DeviceOperator} from '../mojo/device_operator.js';
 import * as state from '../state.js';
+import {CropRegionRect, Resolution} from '../type.js';
 
-type PTZAttr = 'pan'|'tilt'|'zoom';
+enum PTZAttr {
+  PAN = 'pan',
+  TILT = 'tilt',
+  ZOOM = 'zoom',
+}
+
 interface PTZCapabilities {
   pan: MediaSettingsRange;
   tilt: MediaSettingsRange;
@@ -121,15 +129,15 @@
   }
 
   async pan(value: number): Promise<void> {
-    await this.applyPTZ('pan', value);
+    await this.applyPTZ(PTZAttr.PAN, value);
   }
 
   async tilt(value: number): Promise<void> {
-    await this.applyPTZ('tilt', value);
+    await this.applyPTZ(PTZAttr.TILT, value);
   }
 
   async zoom(value: number): Promise<void> {
-    await this.applyPTZ('zoom', value);
+    await this.applyPTZ(PTZAttr.ZOOM, value);
   }
 
   private async applyPTZ(attr: PTZAttr, value: number): Promise<void> {
@@ -139,3 +147,191 @@
     await this.track.applyConstraints({advanced: [{[attr]: value}]});
   }
 }
+
+const DIGITAL_ZOOM_MAX_PAN = 1;
+const DIGITAL_ZOOM_MAX_TILT = 1;
+const DIGITAL_ZOOM_DEFAULT_MAX_ZOOM = 6;
+const DIGITAL_ZOOM_CAPABILITIES: PTZCapabilities = {
+  pan: {min: -DIGITAL_ZOOM_MAX_PAN, max: DIGITAL_ZOOM_MAX_PAN, step: 0.1},
+  tilt: {min: -DIGITAL_ZOOM_MAX_TILT, max: DIGITAL_ZOOM_MAX_TILT, step: 0.1},
+  zoom: {min: 1, max: DIGITAL_ZOOM_DEFAULT_MAX_ZOOM, step: 0.1},
+};
+const DIGITAL_ZOOM_DEFAULT_SETTINGS: PTZSettings = {
+  pan: 0,
+  tilt: 0,
+  zoom: 1,
+};
+
+/**
+ * Calculate the crop region when fully zoomed out for the given aspect ratio.
+ * The crop region is calculated based on camera metadata
+ * ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE. If the target aspect ratio doesn't
+ * match the active array's aspect ratio, the crop region is either cropped
+ * vertically or horizontally, and centered within the active array.
+ */
+function getFullCropRegionForAspectRatio(
+    activeArray: Resolution, targetAspectRatio: number): CropRegionRect {
+  const {width: originalWidth, height: originalHeight} = activeArray;
+  if (activeArray.aspectRatio > targetAspectRatio) {
+    // Crop vertically if the original aspect ratio is wider than the target.
+    const croppedWidth = Math.round(originalHeight * targetAspectRatio);
+    return {
+      x: Math.round((originalWidth - croppedWidth) / 2),
+      y: 0,
+      width: croppedWidth,
+      height: originalHeight,
+    };
+  }
+  // Otherwise, crop horizontally.
+  const croppedHeight = Math.round(originalWidth / targetAspectRatio);
+  return {
+    x: 0,
+    y: Math.round((originalHeight - croppedHeight) / 2),
+    width: originalWidth,
+    height: croppedHeight,
+  };
+}
+
+/**
+ * Calculate a crop region from given PTZ settings. The crop region result is
+ * normalized given full width and full height equal to 1.
+ */
+function calculateNormalizedCropRegion({pan, tilt, zoom}: PTZSettings):
+    CropRegionRect {
+  assert(pan !== undefined);
+  assert(tilt !== undefined);
+  assert(zoom !== undefined && zoom > 0, `Zoom value ${zoom} is invalid.`);
+
+  const width = 1 / zoom;
+  const height = 1 / zoom;
+
+  // Top-left coordinate of the crop region before the pan and tilt values are
+  // applied.
+  const startX = (1 - width) / 2;
+  const startY = (1 - height) / 2;
+
+  // Move x, y with pan and tilt values. Pan and tilt values are in the range
+  // [-1, 1], with pan = -1 being leftmost, and tilt = -1 being bottommost.
+  const x = startX + ((pan / DIGITAL_ZOOM_MAX_PAN) * startX);
+  const y = startY - ((tilt / DIGITAL_ZOOM_MAX_TILT) * startY);
+
+  // Verify that the calculated crop region is valid.
+  const lowerBound = -1e-3;
+  const upperBound = 1 + 1e-3;
+  assert(x > lowerBound && x < upperBound && y > lowerBound && y < upperBound);
+  assert((x + width) < upperBound && (y + height) < upperBound);
+
+  return {x, y, width, height};
+}
+
+/**
+ * Calculate a crop region from PTZ settings with respect to |fullCropRegion|.
+ */
+function calculateCropRegion(
+    ptzSettings: PTZSettings, fullCropRegion: CropRegionRect): CropRegionRect {
+  const normCropRegion = calculateNormalizedCropRegion(ptzSettings);
+  const {width: fullWidth, height: fullHeight} = fullCropRegion;
+
+  return {
+    x: Math.round(fullCropRegion.x + (normCropRegion.x * fullWidth)),
+    y: Math.round(fullCropRegion.y + (normCropRegion.y * fullHeight)),
+    width: Math.round(normCropRegion.width * fullWidth),
+    height: Math.round(normCropRegion.height * fullHeight),
+  };
+}
+
+/**
+ * Asserts that pan, tilt, or zoom value is within the range defined in
+ * |DIGITAL_ZOOM_CAPABILITIES|.
+ */
+function assertPTZRange(attr: PTZAttr, value: number) {
+  const {max: maxValue, min: minValue} = DIGITAL_ZOOM_CAPABILITIES[attr];
+  const tolerance = 1e-3;
+  assert(
+      value >= minValue - tolerance && value <= maxValue + tolerance,
+      `${attr} value ${value} is not within the allowed range.`);
+}
+
+export class DigitalZoomPTZController implements PTZController {
+  private ptzSettings: PTZSettings = DIGITAL_ZOOM_DEFAULT_SETTINGS;
+
+  private constructor(
+      private readonly deviceId: string,
+      private readonly fullCropRegion: CropRegionRect) {}
+
+  canPan(): boolean {
+    return true;
+  }
+
+  canTilt(): boolean {
+    return true;
+  }
+
+  canZoom(): boolean {
+    return true;
+  }
+
+  getCapabilities(): PTZCapabilities {
+    return DIGITAL_ZOOM_CAPABILITIES;
+  }
+
+  getSettings(): PTZSettings {
+    return this.ptzSettings;
+  }
+
+  isPanTiltRestricted(): boolean {
+    // When fully zoomed out, calculated crop region equals to |fullCropRegion|,
+    // this means pan and tilt are disabled.
+    return true;
+  }
+
+  async resetPTZ(): Promise<void> {
+    const deviceOperator = assertExists(DeviceOperator.getInstance());
+    await deviceOperator.resetCropRegion(this.deviceId);
+    this.ptzSettings = DIGITAL_ZOOM_DEFAULT_SETTINGS;
+  }
+
+  async pan(value: number): Promise<void> {
+    assertPTZRange(PTZAttr.PAN, value);
+    const newSettings = {...this.ptzSettings, pan: value};
+    await this.applyPTZ(newSettings);
+  }
+
+  async tilt(value: number): Promise<void> {
+    assertPTZRange(PTZAttr.TILT, value);
+    const newSettings = {...this.ptzSettings, tilt: value};
+    await this.applyPTZ(newSettings);
+  }
+
+  async zoom(value: number): Promise<void> {
+    assertPTZRange(PTZAttr.ZOOM, value);
+    const newSettings = {...this.ptzSettings, zoom: value};
+    await this.applyPTZ(newSettings);
+  }
+
+  private async applyPTZ(newSettings: PTZSettings): Promise<void> {
+    if (this.isFullFrame(newSettings)) {
+      return this.resetPTZ();
+    }
+    const deviceOperator = assertExists(DeviceOperator.getInstance());
+    const cropRegion = calculateCropRegion(newSettings, this.fullCropRegion);
+    await deviceOperator.setCropRegion(this.deviceId, cropRegion);
+    this.ptzSettings = newSettings;
+  }
+
+  private isFullFrame({zoom}: PTZSettings): boolean {
+    assert(zoom !== undefined);
+    const minZoom = assertExists(DIGITAL_ZOOM_CAPABILITIES.zoom.min);
+    const zoomStep = assertExists(DIGITAL_ZOOM_CAPABILITIES.zoom.step);
+    return Math.abs(zoom - minZoom) < zoomStep;
+  }
+
+  static async create(deviceId: string, aspectRatio: number):
+      Promise<DigitalZoomPTZController> {
+    const deviceOperator = assertExists(DeviceOperator.getInstance());
+    const activeArray = await deviceOperator.getActiveArraySize(deviceId);
+    const fullCropRegion =
+        getFullCropRegionForAspectRatio(activeArray, aspectRatio);
+    return new DigitalZoomPTZController(deviceId, fullCropRegion);
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/mojo/device_operator.ts b/ash/webui/camera_app_ui/resources/js/mojo/device_operator.ts
index 4130d98..7f11f23 100644
--- a/ash/webui/camera_app_ui/resources/js/mojo/device_operator.ts
+++ b/ash/webui/camera_app_ui/resources/js/mojo/device_operator.ts
@@ -813,6 +813,26 @@
   }
 
   /**
+   * Returns whether digital zoom is supported in the camera.
+   */
+  async isDigitalZoomSupported(deviceId: string): Promise<boolean> {
+    // Checks if the device can do zoom through the stream manipulator.
+    // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
+    const digitalZoomTag = 0x80070000 as CameraMetadataTag;
+    const digitalZoomData =
+        await this.getStaticMetadata(deviceId, digitalZoomTag);
+
+    // Some devices can do zoom given the crop region in their HALs. This
+    // ability can be checked with AVAILABLE_MAX_DIGITAL_ZOOM value being
+    // greater than 1.
+    const maxZoomRatio = await this.getStaticMetadata(
+        deviceId, CameraMetadataTag.ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
+    const hasInternalZoom = maxZoomRatio.length > 0 && maxZoomRatio[0] > 1;
+
+    return digitalZoomData.length > 0 || hasInternalZoom;
+  }
+
+  /**
    * Initializes the singleton instance.
    *
    * This should be called before all invocation of static getInstance() and
diff --git a/ash/webui/common/resources/quick_unlock/fingerprint_progress.ts b/ash/webui/common/resources/quick_unlock/fingerprint_progress.ts
index 4754ffc0..d203a7c 100644
--- a/ash/webui/common/resources/quick_unlock/fingerprint_progress.ts
+++ b/ash/webui/common/resources/quick_unlock/fingerprint_progress.ts
@@ -168,6 +168,7 @@
     this.progressCircleBackgroundColor =
         getComputedStyle(document.body)
             .getPropertyValue('--cros-sys-primary_container');
+    this.$.scanningAnimation.refreshAnimationColors();
   }
 
   override connectedCallback() {
@@ -204,6 +205,7 @@
     this.updateAnimationAsset_();
     this.resizeAndCenterIcon_(scanningAnimation);
     scanningAnimation.hidden = false;
+    this.$.scanningAnimation.refreshAnimationColors();
   }
 
   /**
diff --git a/ash/wm/desks/OWNERS b/ash/wm/desks/OWNERS
index dbaf9f1b8..b534716 100644
--- a/ash/wm/desks/OWNERS
+++ b/ash/wm/desks/OWNERS
@@ -1,2 +1,11 @@
 afakhry@chromium.org
 dandersson@chromium.org
+
+# For desks UIs, desk bars, tests, and utilities.
+per-file *button*=yongshun@chromium.org
+per-file *menu*=yongshun@chromium.org
+per-file *textfield*=yongshun@chromium.org
+per-file *view*=yongshun@chromium.org
+per-file *desk_bar*=yongshun@chromium.org
+per-file *test*=yongshun@chromium.org
+per-file *util*=yongshun@chromium.org
diff --git a/ash/wm/desks/templates/OWNERS b/ash/wm/desks/templates/OWNERS
index e4f3971..2ce2928 100644
--- a/ash/wm/desks/templates/OWNERS
+++ b/ash/wm/desks/templates/OWNERS
@@ -1,3 +1,4 @@
 dandersson@chromium.org
 richui@chromium.org
 sammiequon@chromium.org
+yongshun@chromium.org
diff --git a/ash/wm/toplevel_window_event_handler.cc b/ash/wm/toplevel_window_event_handler.cc
index a104bdda..a244869 100644
--- a/ash/wm/toplevel_window_event_handler.cc
+++ b/ash/wm/toplevel_window_event_handler.cc
@@ -735,6 +735,10 @@
     is_moving_floated_window_ = true;
   }
 
+  // Mark the currently dragged or resized window as excluded for occlusion
+  // purposes.
+  scoped_exclude_.emplace(window);
+
   return true;
 }
 
@@ -908,8 +912,11 @@
 }
 
 bool ToplevelWindowEventHandler::CompleteDrag(DragResult result) {
-  if (!window_resizer_)
+  scoped_exclude_.reset();
+
+  if (!window_resizer_) {
     return false;
+  }
 
   std::unique_ptr<ScopedWindowResizer> resizer(std::move(window_resizer_));
   switch (result) {
diff --git a/ash/wm/toplevel_window_event_handler.h b/ash/wm/toplevel_window_event_handler.h
index 426c5f8f..42077a3 100644
--- a/ash/wm/toplevel_window_event_handler.h
+++ b/ash/wm/toplevel_window_event_handler.h
@@ -14,6 +14,7 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
 #include "ui/aura/window_observer.h"
+#include "ui/aura/window_occlusion_tracker.h"
 #include "ui/display/display_observer.h"
 #include "ui/events/event_handler.h"
 #include "ui/events/gestures/gesture_types.h"
@@ -225,6 +226,13 @@
 
   std::unique_ptr<ScopedWindowResizer> window_resizer_;
 
+  // We exclude dragged or resized windows from occluding things below them to
+  // prevent windows from being marked as occluded temporarily while another
+  // window is being, for example, dragged over them. This is particularly
+  // necessary for lacros where occlusion may cause the content to be evicted
+  // and replaced with a snapshot.
+  std::optional<aura::WindowOcclusionTracker::ScopedExclude> scoped_exclude_;
+
   display::ScopedDisplayObserver display_observer_{this};
 
   EndClosure end_closure_;
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 7317f2b..4fadefb0 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -290,6 +290,8 @@
     "containers/map_util.h",
     "containers/small_map.h",
     "containers/stack.h",
+    "containers/to_value_list.h",
+    "containers/to_vector.h",
     "containers/unique_ptr_adapters.h",
     "containers/util.h",
     "containers/vector_buffer.h",
@@ -3183,6 +3185,8 @@
     "containers/map_util_unittest.cc",
     "containers/small_map_unittest.cc",
     "containers/span_unittest.cc",
+    "containers/to_value_list_unittest.cc",
+    "containers/to_vector_unittest.cc",
     "containers/unique_ptr_adapters_unittest.cc",
     "containers/vector_buffer_unittest.cc",
     "cpu_unittest.cc",
@@ -3455,7 +3459,6 @@
     "test/test_pending_task_unittest.cc",
     "test/test_proto_loader_unittest.cc",
     "test/test_waitable_event_unittest.cc",
-    "test/to_vector_unittest.cc",
     "third_party/dynamic_annotations/dynamic_annotations_compiletest.cc",
     "thread_annotations_unittest.cc",
     "threading/hang_watcher_unittest.cc",
@@ -3533,7 +3536,7 @@
     ]
   }
 
-  if (use_safe_libcxx) {
+  if (use_safe_libcxx || use_safe_libstdcxx) {
     sources += [ "libcpp_hardening_test.cc" ]
   }
 
@@ -4220,6 +4223,8 @@
       "containers/enum_set_nocompile.nc",
       "containers/heap_array_nocompile.nc",
       "containers/span_nocompile.nc",
+      "containers/to_value_list_nocompile.nc",
+      "containers/to_vector_nocompile.nc",
       "debug/crash_logging_nocompile.nc",
       "functional/bind_nocompile.nc",
       "functional/callback_nocompile.nc",
diff --git a/base/containers/to_value_list.h b/base/containers/to_value_list.h
new file mode 100644
index 0000000..0f2f23f
--- /dev/null
+++ b/base/containers/to_value_list.h
@@ -0,0 +1,45 @@
+// 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 BASE_CONTAINERS_TO_VALUE_LIST_H_
+#define BASE_CONTAINERS_TO_VALUE_LIST_H_
+
+#include <concepts>
+#include <functional>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+
+#include "base/ranges/algorithm.h"
+#include "base/ranges/ranges.h"
+#include "base/values.h"
+
+namespace base {
+
+namespace internal {
+template <typename T>
+concept AppendableToValueList =
+    requires(T&& value) { Value::List().Append(std::forward<T>(value)); };
+}  // namespace internal
+
+// Maps a container to a Value::List with respect to the provided projection.
+//
+// Complexity: Exactly `size(range)` applications of `proj`.
+template <typename Range, typename Proj = std::identity>
+  requires requires { typename internal::range_category_t<Range>; } &&
+           std::indirectly_unary_invocable<Proj, ranges::iterator_t<Range>> &&
+           internal::AppendableToValueList<
+               std::indirect_result_t<Proj, ranges::iterator_t<Range>>>
+Value::List ToValueList(Range&& range, Proj proj = {}) {
+  auto container = Value::List::with_capacity(std::size(range));
+  ranges::for_each(
+      std::forward<Range>(range),
+      [&]<typename T>(T&& value) { container.Append(std::forward<T>(value)); },
+      std::move(proj));
+  return container;
+}
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_TO_VALUE_LIST_H_
diff --git a/base/containers/to_value_list_nocompile.nc b/base/containers/to_value_list_nocompile.nc
new file mode 100644
index 0000000..2f27a92
--- /dev/null
+++ b/base/containers/to_value_list_nocompile.nc
@@ -0,0 +1,49 @@
+// 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.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/containers/to_value_list.h"
+
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace base {
+
+// ToValueList() doesn't implicitly deduce initializer lists.
+void InitializerList() {
+  std::ignore = ToValueList({"aaa", "bbb", "ccc"}); // expected-error@*:* {{no matching function for call to 'ToValueList'}}
+}
+
+// Lambdas operating on rvalue ranges of move-only elements expect lvalue
+// references to the element type.
+void MoveOnlyProjections() {
+  struct MoveOnly {
+    MoveOnly() = default;
+
+    MoveOnly(const MoveOnly&) = delete;
+    MoveOnly& operator=(const MoveOnly&) = delete;
+
+    MoveOnly(MoveOnly&&) = default;
+    MoveOnly& operator=(MoveOnly&&) = default;
+  };
+
+  std::vector<MoveOnly> vec;
+  std::ignore = ToValueList(std::move(vec), [](MoveOnly arg) {
+    return arg;
+  }); // expected-error@*:* {{no matching function for call to 'ToValueList'}}
+  std::ignore = ToValueList(std::move(vec), [](MoveOnly&& arg) {
+    return std::move(arg);
+  }); // expected-error@*:* {{no matching function for call to 'ToValueList'}}
+}
+
+// Return type of the projection must be compatible with Value::List::Append().
+void AppendableToList() {
+  std::vector<int> vec;
+  std::ignore = ToValueList(vec, [](int) -> int* { return nullptr; }); // expected-error@*:* {{no matching function for call to 'ToValueList'}}
+}
+
+}  // namespace base
diff --git a/base/containers/to_value_list_unittest.cc b/base/containers/to_value_list_unittest.cc
new file mode 100644
index 0000000..585d520
--- /dev/null
+++ b/base/containers/to_value_list_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/containers/to_value_list.h"
+
+#include <set>
+
+#include "base/containers/flat_set.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+namespace {
+
+// `Value` isn't copyable, so it's not possible to match against Value(x)
+// directly in `testing::ElementsAre`. This is why Value(x) is replaced with
+// IsInt(x).
+auto IsInt(int value) {
+  return testing::Property(&Value::GetInt, testing::Eq(value));
+}
+
+template <class C>
+void IdentityTest() {
+  C c = {1, 2, 3, 4, 5};
+  // See comment in `IsInt()` above.
+  EXPECT_THAT(ToValueList(c), testing::ElementsAre(IsInt(1), IsInt(2), IsInt(3),
+                                                   IsInt(4), IsInt(5)));
+}
+
+template <class C>
+void ProjectionTest() {
+  C c = {1, 2, 3, 4, 5};
+  // See comment in `IsInt()` above.
+  EXPECT_THAT(
+      ToValueList(c, [](int x) { return x + 1; }),
+      testing::ElementsAre(IsInt(2), IsInt(3), IsInt(4), IsInt(5), IsInt(6)));
+}
+
+TEST(ToListTest, Identity) {
+  IdentityTest<std::vector<int>>();
+  IdentityTest<std::set<int>>();
+  IdentityTest<int[]>();
+  IdentityTest<flat_set<int>>();
+}
+
+TEST(ToListTest, Projection) {
+  ProjectionTest<std::vector<int>>();
+  ProjectionTest<std::set<int>>();
+  ProjectionTest<int[]>();
+  ProjectionTest<flat_set<int>>();
+}
+
+// Validates that consuming projections work as intended (every single `Value`
+// inside `Value::List` is a move-only type).
+TEST(ToListTest, MoveOnly) {
+  Value::List list;
+  list.resize(10);
+  Value::List mapped_list = ToValueList(
+      std::move(list), [](Value& value) { return std::move(value); });
+  EXPECT_EQ(mapped_list.size(), 10U);
+}
+
+}  // namespace
+}  // namespace base
diff --git a/base/containers/to_vector.h b/base/containers/to_vector.h
new file mode 100644
index 0000000..6213ec5
--- /dev/null
+++ b/base/containers/to_vector.h
@@ -0,0 +1,43 @@
+// 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 BASE_CONTAINERS_TO_VECTOR_H_
+#define BASE_CONTAINERS_TO_VECTOR_H_
+
+#include <functional>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "base/ranges/algorithm.h"
+#include "base/ranges/ranges.h"
+
+namespace base {
+
+// Maps a container to a std::vector<> with respect to the provided projection.
+// The deduced vector element type is equal to the projection's return type with
+// cv-qualifiers removed.
+//
+// In C++20 this is roughly equal to:
+// auto vec = range | std::views:transform(proj) |
+// std::ranges::to<std::vector>;
+//
+// Complexity: Exactly `size(range)` applications of `proj`.
+template <typename Range, typename Proj = std::identity>
+  requires requires { typename internal::range_category_t<Range>; } &&
+           std::indirectly_unary_invocable<Proj, ranges::iterator_t<Range>>
+auto ToVector(Range&& range, Proj proj = {}) {
+  using ProjectedType =
+      std::projected<ranges::iterator_t<Range>, Proj>::value_type;
+  std::vector<ProjectedType> container;
+  container.reserve(std::size(range));
+  ranges::transform(std::forward<Range>(range), std::back_inserter(container),
+                    std::move(proj));
+  return container;
+}
+
+}  // namespace base
+
+#endif  // BASE_CONTAINERS_TO_VECTOR_H_
diff --git a/base/containers/to_vector_nocompile.nc b/base/containers/to_vector_nocompile.nc
new file mode 100644
index 0000000..e55f791
--- /dev/null
+++ b/base/containers/to_vector_nocompile.nc
@@ -0,0 +1,43 @@
+// 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.
+
+// This is a "No Compile Test" suite.
+// http://dev.chromium.org/developers/testing/no-compile-tests
+
+#include "base/containers/to_vector.h"
+
+#include <tuple>
+#include <utility>
+#include <vector>
+
+namespace base {
+
+// ToVector() doesn't implicitly deduce initializer lists.
+void InitializerList() {
+  std::ignore = ToVector({"aaa", "bbb", "ccc"}); // expected-error@*:* {{no matching function for call to 'ToVector'}}
+}
+
+// Lambdas operating on rvalue ranges of move-only elements expect lvalue
+// references to the element type.
+void MoveOnlyProjections() {
+  struct MoveOnly {
+    MoveOnly() = default;
+
+    MoveOnly(const MoveOnly&) = delete;
+    MoveOnly& operator=(const MoveOnly&) = delete;
+
+    MoveOnly(MoveOnly&&) = default;
+    MoveOnly& operator=(MoveOnly&&) = default;
+  };
+
+  std::vector<MoveOnly> vec;
+  std::ignore = ToVector(std::move(vec), [](MoveOnly arg) {
+    return arg;
+  }); // expected-error@*:* {{no matching function for call to 'ToVector'}}
+  std::ignore = ToVector(std::move(vec), [](MoveOnly&& arg) {
+    return std::move(arg);
+  }); // expected-error@*:* {{no matching function for call to 'ToVector'}}
+}
+
+}  // namespace base
diff --git a/base/test/to_vector_unittest.cc b/base/containers/to_vector_unittest.cc
similarity index 96%
rename from base/test/to_vector_unittest.cc
rename to base/containers/to_vector_unittest.cc
index 36117c4..9657d339 100644
--- a/base/test/to_vector_unittest.cc
+++ b/base/containers/to_vector_unittest.cc
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/test/to_vector.h"
+#include "base/containers/to_vector.h"
 
 #include <set>
 
 #include "base/containers/flat_set.h"
+#include "base/ranges/ranges.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/base/libcpp_hardening_test.cc b/base/libcpp_hardening_test.cc
index 4224447..b1f4d99 100644
--- a/base/libcpp_hardening_test.cc
+++ b/base/libcpp_hardening_test.cc
@@ -12,7 +12,11 @@
 
 // TODO(thakis): Remove _LIBCPP_ENABLE_ASSERTIONS here once
 // pnacl-saigo's libc++ is new enough.
-#if !_LIBCPP_ENABLE_ASSERTIONS && \
+#if defined(__GLIBCXX__)
+#if !defined(_GLIBCXX_ASSERTIONS)
+#error "libstdc++ assertions should be enabled"
+#endif
+#elif !_LIBCPP_ENABLE_ASSERTIONS && \
     _LIBCPP_HARDENING_MODE != _LIBCPP_HARDENING_MODE_EXTENSIVE
 #error "_LIBCPP_HARDENING_MODE not defined"
 #endif
diff --git a/base/test/to_vector.h b/base/test/to_vector.h
index a2aeeff..83dfd021 100644
--- a/base/test/to_vector.h
+++ b/base/test/to_vector.h
@@ -6,33 +6,26 @@
 #define BASE_TEST_TO_VECTOR_H_
 
 #include <functional>
-#include <iterator>
-#include <type_traits>
-#include <utility>
-#include <vector>
 
+#include "base/containers/to_vector.h"
 #include "base/ranges/algorithm.h"
-#include "base/template_util.h"
 
 namespace base::test {
 
-// A handy helper for mapping any container into an std::vector<> with respect
-// to the provided projection. The deduced vector element type is equal to the
-// projection's return type with cv-qualifiers removed.
+// Maps a container to a std::vector<> with respect to the provided projection.
+// The deduced vector element type is equal to the projection's return type with
+// cv-qualifiers removed.
 //
 // In C++20 this is roughly equal to:
-// auto vec = range | views:transform(proj) | ranges::to<std::vector>;
+// auto vec = range | std::views:transform(proj) | std::ranges::to<std::vector>;
 //
 // Complexity: Exactly `size(range)` applications of `proj`.
+//
+// TODO(crbug.com/326392658): Replace existing callsites with base::ToVector<>.
 template <typename Range, typename Proj = std::identity>
+  requires requires { typename base::internal::range_category_t<Range>; }
 auto ToVector(Range&& range, Proj proj = {}) {
-  using ProjectedType =
-      std::invoke_result_t<Proj, decltype(*std::begin(range))>;
-  std::vector<std::remove_cvref_t<ProjectedType>> container;
-  container.reserve(std::size(range));
-  base::ranges::transform(std::forward<Range>(range),
-                          std::back_inserter(container), std::move(proj));
-  return container;
+  return base::ToVector(std::forward<Range>(range), std::move(proj));
 }
 
 }  // namespace base::test
diff --git a/base/win/security_util.cc b/base/win/security_util.cc
index 596da7bc..29634b8 100644
--- a/base/win/security_util.cc
+++ b/base/win/security_util.cc
@@ -10,6 +10,7 @@
 #include <optional>
 
 #include "base/check.h"
+#include "base/containers/to_vector.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/threading/scoped_blocking_call.h"
@@ -87,12 +88,7 @@
 }
 
 std::vector<Sid> CloneSidVector(const std::vector<Sid>& sids) {
-  std::vector<Sid> clone;
-  clone.reserve(sids.size());
-  for (const Sid& sid : sids) {
-    clone.push_back(sid.Clone());
-  }
-  return clone;
+  return base::ToVector(sids, &Sid::Clone);
 }
 
 void AppendSidVector(std::vector<Sid>& base_sids,
diff --git a/build/config/c++/c++.gni b/build/config/c++/c++.gni
index 3c0b549..bd8e711 100644
--- a/build/config/c++/c++.gni
+++ b/build/config/c++/c++.gni
@@ -59,6 +59,10 @@
 # enable libc++ hardening there as well.
 use_safe_libcxx = (use_custom_libcxx && enable_safe_libcxx) || is_nacl_saigo
 
+# libstdc++ has its own hardening assertions that we want to enable by default
+# in Chromium builds.
+use_safe_libstdcxx = is_linux && !use_custom_libcxx && enable_safe_libstdcxx
+
 # libc++abi needs to be exported from executables to be picked up by shared
 # libraries on certain instrumented builds.
 export_libcxxabi_from_executables =
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 84e6c03e..62088a0 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -1069,6 +1069,12 @@
   } else {
     defines += [ "_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_NONE" ]
   }
+
+  # Enable libstdc++ hardening lightweight assertions. Those have a low
+  # performance penalty but are considered a bare minimum for security.
+  if (use_safe_libstdcxx) {
+    defines += [ "_GLIBCXX_ASSERTIONS=1" ]
+  }
 }
 
 # The BUILDCONFIG file sets this config on targets by default, which means when
diff --git a/build_overrides/build.gni b/build_overrides/build.gni
index 4ea4da3..6c36c6c 100644
--- a/build_overrides/build.gni
+++ b/build_overrides/build.gni
@@ -30,6 +30,12 @@
 # `use_custom_libcxx = true`.
 enable_safe_libcxx = true
 
+# Enable assertions on safety checks, also in libstdc++
+#
+# In case the C++ standard library implementation used is libstdc++, then
+# enable its own hardening checks.
+enable_safe_libstdcxx = true
+
 # Features used by //base/trace_event and //services/tracing.
 declare_args() {
   # Tracing support requires //third_party/perfetto, which is not available in
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java
index 4650cd20..955769c 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherTabletTest.java
@@ -42,6 +42,7 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Features.DisableFeatures;
 import org.chromium.base.test.util.Features.EnableFeatures;
 import org.chromium.base.test.util.RequiresRestart;
@@ -157,6 +158,7 @@
     @Test
     @MediumTest
     @RequiresRestart
+    @DisabledTest(message = "https://crbug.com/327457591")
     public void testEnterAndExitTabSwitcher() throws TimeoutException {
         Layout layout = sActivityTestRule.getActivity().getLayoutManager().getOverviewLayout();
         assertNull("StartSurface layout should not be initialized", layout);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java
index bf9561f..8c7225c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/settings/AccountManagementFragment.java
@@ -44,6 +44,8 @@
 import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.GAIAServiceType;
+import org.chromium.components.signin.SigninFeatureMap;
+import org.chromium.components.signin.SigninFeatures;
 import org.chromium.components.signin.Tribool;
 import org.chromium.components.signin.base.AccountInfo;
 import org.chromium.components.signin.base.CoreAccountInfo;
@@ -555,8 +557,11 @@
     }
 
     private boolean isSupervisedUser() {
+        // SEED_ACCOUNTS_REVAMP is needed for using capabilities, otherwise
+        // findExtendedAccountInfoByEmailAddress is not guaranteed to have the needed account
         if (ChromeFeatureList.isEnabled(
-                ChromeFeatureList.MIGRATE_ACCOUNT_MANAGEMENT_SETTINGS_TO_CAPABILITIES)) {
+                        ChromeFeatureList.MIGRATE_ACCOUNT_MANAGEMENT_SETTINGS_TO_CAPABILITIES)
+                && SigninFeatureMap.isEnabled(SigninFeatures.SEED_ACCOUNTS_REVAMP)) {
             assert mSignedInCoreAccountInfo != null;
             AccountInfo accountinfo =
                     IdentityServicesProvider.get()
diff --git a/chrome/browser/ash/crosapi/BUILD.gn b/chrome/browser/ash/crosapi/BUILD.gn
index 9ab6494..002134f 100644
--- a/chrome/browser/ash/crosapi/BUILD.gn
+++ b/chrome/browser/ash/crosapi/BUILD.gn
@@ -183,6 +183,7 @@
     "lacros_data_backward_migration_mode_policy_observer.cc",
     "lacros_data_backward_migration_mode_policy_observer.h",
     "lacros_selection_loader.h",
+    "lacros_selection_loader_factory.h",
     "lacros_shelf_item_tracker.cc",
     "lacros_shelf_item_tracker.h",
     "local_printer_ash.cc",
diff --git a/chrome/browser/ash/crosapi/browser_loader.cc b/chrome/browser/ash/crosapi/browser_loader.cc
index d5c0eab..68c21ea 100644
--- a/chrome/browser/ash/crosapi/browser_loader.cc
+++ b/chrome/browser/ash/crosapi/browser_loader.cc
@@ -22,6 +22,7 @@
 #include "base/values.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/crosapi/lacros_selection_loader.h"
+#include "chrome/browser/ash/crosapi/lacros_selection_loader_factory.h"
 #include "chrome/browser/ash/crosapi/rootfs_lacros_loader.h"
 #include "chrome/browser/ash/crosapi/stateful_lacros_loader.h"
 #include "chrome/browser/browser_process.h"
@@ -33,18 +34,45 @@
 namespace {
 // There are 2 lacros selections, rootfs lacros and stateful lacros.
 constexpr size_t kLacrosSelectionTypes = 2;
+
+class LacrosSelectionLoaderFactoryImpl : public LacrosSelectionLoaderFactory {
+ public:
+  explicit LacrosSelectionLoaderFactoryImpl(
+      scoped_refptr<component_updater::CrOSComponentManager> manager)
+      : component_manager_(manager) {}
+
+  LacrosSelectionLoaderFactoryImpl(const LacrosSelectionLoaderFactoryImpl&) =
+      delete;
+  LacrosSelectionLoaderFactoryImpl& operator=(
+      const LacrosSelectionLoaderFactoryImpl&) = delete;
+
+  ~LacrosSelectionLoaderFactoryImpl() override = default;
+
+  std::unique_ptr<LacrosSelectionLoader> CreateRootfsLacrosLoader() override {
+    return std::make_unique<RootfsLacrosLoader>();
+  }
+
+  std::unique_ptr<LacrosSelectionLoader> CreateStatefulLacrosLoader() override {
+    return std::make_unique<StatefulLacrosLoader>(component_manager_);
+  }
+
+ private:
+  scoped_refptr<component_updater::CrOSComponentManager> component_manager_;
+};
+
+bool IsUnloading(LacrosSelectionLoader* loader) {
+  return loader && loader->IsUnloading();
+}
+
 }  // namespace
 
 BrowserLoader::BrowserLoader(
     scoped_refptr<component_updater::CrOSComponentManager> manager)
-    : BrowserLoader(std::make_unique<RootfsLacrosLoader>(),
-                    std::make_unique<StatefulLacrosLoader>(manager)) {}
+    : factory_(std::make_unique<LacrosSelectionLoaderFactoryImpl>(manager)) {}
 
 BrowserLoader::BrowserLoader(
-    std::unique_ptr<LacrosSelectionLoader> rootfs_lacros_loader,
-    std::unique_ptr<LacrosSelectionLoader> stateful_lacros_loader)
-    : rootfs_lacros_loader_(std::move(rootfs_lacros_loader)),
-      stateful_lacros_loader_(std::move(stateful_lacros_loader)) {}
+    std::unique_ptr<LacrosSelectionLoaderFactory> factory)
+    : factory_(std::move(factory)) {}
 
 BrowserLoader::~BrowserLoader() = default;
 
@@ -78,6 +106,8 @@
 
 void BrowserLoader::SelectRootfsLacros(LoadCompletionCallback callback,
                                        LacrosSelectionSource source) {
+  CHECK(rootfs_lacros_loader_);
+
   LOG(WARNING) << "rootfs lacros is selected by " << source;
 
   rootfs_lacros_loader_->Load(
@@ -88,6 +118,8 @@
 
 void BrowserLoader::SelectStatefulLacros(LoadCompletionCallback callback,
                                          LacrosSelectionSource source) {
+  CHECK(stateful_lacros_loader_);
+
   LOG(WARNING) << "stateful lacros is selected by " << source;
 
   stateful_lacros_loader_->Load(
@@ -98,13 +130,38 @@
   // Unmount the rootfs lacros-chrome when using stateful lacros-chrome.
   // This will keep stateful lacros-chrome only mounted and not hold the rootfs
   // lacros-chrome mount until an `Unload`.
-  rootfs_lacros_loader_->Unload();
+  if (rootfs_lacros_loader_) {
+    rootfs_lacros_loader_->Unload(
+        base::BindOnce(&BrowserLoader::OnUnloadCompleted,
+                       weak_factory_.GetWeakPtr(), LacrosSelection::kRootfs));
+  }
 }
 
 void BrowserLoader::Load(LoadCompletionCallback callback) {
-  // Reset lacros selection loaders before reloading.
-  rootfs_lacros_loader_->Reset();
-  stateful_lacros_loader_->Reset();
+  // Load should NOT be called after Unload is requested to BrowserLoader.
+  CHECK(!is_unload_requested_);
+
+  // If either of rootfs or stateful lacros loader is still unloading, wait
+  // until the unload completion.
+  if (IsUnloading(rootfs_lacros_loader_.get()) ||
+      IsUnloading(stateful_lacros_loader_.get())) {
+    LOG(WARNING) << "Wait load until unload completes";
+    callback_on_unload_completion_ =
+        base::BindOnce(&BrowserLoader::LoadNow, weak_factory_.GetWeakPtr(),
+                       std::move(callback));
+    return;
+  }
+
+  LoadNow(std::move(callback));
+}
+
+void BrowserLoader::LoadNow(LoadCompletionCallback callback) {
+  // Reset lacros selection loaders since it may be already initialized one if
+  // this is reloading.
+  // TODO(elkurin): We should call Unload before reloading if these loaders
+  // exist, then we can remove `reset` here.
+  rootfs_lacros_loader_.reset();
+  stateful_lacros_loader_.reset();
 
   lacros_start_load_time_ = base::TimeTicks::Now();
   // TODO(crbug.com/1078607): Remove non-error logging from this class.
@@ -130,9 +187,11 @@
     // too.
     switch (lacros_selection.value()) {
       case browser_util::LacrosSelection::kRootfs:
+        rootfs_lacros_loader_ = factory_->CreateRootfsLacrosLoader();
         SelectRootfsLacros(std::move(callback), LacrosSelectionSource::kForced);
         return;
       case browser_util::LacrosSelection::kStateful:
+        stateful_lacros_loader_ = factory_->CreateStatefulLacrosLoader();
         SelectStatefulLacros(std::move(callback),
                              LacrosSelectionSource::kForced);
         return;
@@ -145,6 +204,9 @@
     }
   }
 
+  rootfs_lacros_loader_ = factory_->CreateRootfsLacrosLoader();
+  stateful_lacros_loader_ = factory_->CreateStatefulLacrosLoader();
+
   // Proceed to load/mount the stateful lacros-chrome binary.
   // In the case that the stateful lacros-chrome binary wasn't installed, this
   // might take some time.
@@ -174,6 +236,13 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   CHECK_EQ(versions.size(), kLacrosSelectionTypes);
 
+  if (is_unload_requested_) {
+    LOG(WARNING) << "Unload is requested during collecting Lacros version.";
+    std::move(callback).Run(base::FilePath(), LacrosSelection::kStateful,
+                            base::Version());
+    return;
+  }
+
   // Compare the rootfs vs stateful lacros-chrome binary versions.
   // If the rootfs lacros-chrome is greater than lacros-chrome version,
   // prioritize using the rootfs lacros-chrome. If the stateful lacros-chrome is
@@ -236,10 +305,52 @@
 }
 
 void BrowserLoader::Unload() {
+  is_unload_requested_ = true;
+
   // Can be called even if Lacros isn't enabled, to clean up the old install.
-  stateful_lacros_loader_->Unload();
-  // Unmount the rootfs lacros-chrome if it was mounted.
-  rootfs_lacros_loader_->Unload();
+  // Unmount the rootfs/stateful lacros-chrome if it was mounted.
+  if (rootfs_lacros_loader_) {
+    rootfs_lacros_loader_->Unload(
+        base::BindOnce(&BrowserLoader::OnUnloadCompleted,
+                       weak_factory_.GetWeakPtr(), LacrosSelection::kRootfs));
+  }
+
+  if (stateful_lacros_loader_) {
+    stateful_lacros_loader_->Unload(
+        base::BindOnce(&BrowserLoader::OnUnloadCompleted,
+                       weak_factory_.GetWeakPtr(), LacrosSelection::kStateful));
+  }
+}
+
+void BrowserLoader::OnUnloadCompleted(LacrosSelection selection) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  switch (selection) {
+    case LacrosSelection::kRootfs:
+      CHECK(rootfs_lacros_loader_->IsUnloaded());
+      rootfs_lacros_loader_.reset();
+      break;
+    case LacrosSelection::kStateful:
+      CHECK(stateful_lacros_loader_->IsUnloaded());
+      stateful_lacros_loader_.reset();
+      break;
+    case LacrosSelection::kDeployedLocally:
+      NOTREACHED();
+      break;
+  }
+
+  // If either of rootfs or stateful lacros loader is still in the process of
+  // unload, wait running completion callback.
+  if (IsUnloading(rootfs_lacros_loader_.get()) ||
+      IsUnloading(stateful_lacros_loader_.get())) {
+    return;
+  }
+
+  // If both of the rootfs and stateful lacros load completed unloading, run the
+  // stored callback if exists.
+  if (callback_on_unload_completion_) {
+    std::move(callback_on_unload_completion_).Run();
+  }
 }
 
 base::FilePath DetermineLacrosBinaryPath(const base::FilePath& path) {
@@ -261,6 +372,13 @@
                                    const base::FilePath& path) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  if (is_unload_requested_) {
+    LOG(WARNING) << "Unload is requested during loading.";
+    std::move(callback).Run(base::FilePath(), LacrosSelection::kStateful,
+                            base::Version());
+    return;
+  }
+
   // Bail out on empty `path` which implies there was an error on loading
   // lacros.
   if (path.empty()) {
@@ -288,6 +406,13 @@
                                          const base::FilePath& lacros_binary) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
+  if (is_unload_requested_) {
+    LOG(WARNING) << "Unload is requested during determining lacros path.";
+    std::move(callback).Run(base::FilePath(), LacrosSelection::kStateful,
+                            base::Version());
+    return;
+  }
+
   if (lacros_binary.empty()) {
     LOG(ERROR) << "Failed to find binary at " << path;
     std::move(callback).Run(base::FilePath(), selection, base::Version());
diff --git a/chrome/browser/ash/crosapi/browser_loader.h b/chrome/browser/ash/crosapi/browser_loader.h
index c063f86c..cd6ca11 100644
--- a/chrome/browser/ash/crosapi/browser_loader.h
+++ b/chrome/browser/ash/crosapi/browser_loader.h
@@ -26,19 +26,18 @@
 namespace crosapi {
 
 class LacrosSelectionLoader;
+class LacrosSelectionLoaderFactory;
 using browser_util::LacrosSelection;
 
 // Manages download of the lacros-chrome binary.
 // This class is a part of ash-chrome.
 class BrowserLoader {
  public:
-  // Constructor for production.
   explicit BrowserLoader(
       scoped_refptr<component_updater::CrOSComponentManager> manager);
+
   // Constructor for testing.
-  explicit BrowserLoader(
-      std::unique_ptr<LacrosSelectionLoader> rootfs_lacros_loader,
-      std::unique_ptr<LacrosSelectionLoader> stateful_lacros_loader);
+  explicit BrowserLoader(std::unique_ptr<LacrosSelectionLoaderFactory> factory);
 
   BrowserLoader(const BrowserLoader&) = delete;
   BrowserLoader& operator=(const BrowserLoader&) = delete;
@@ -81,15 +80,36 @@
   };
 
   // Starts to load lacros-chrome binary or the rootfs lacros-chrome binary.
-  // |callback| is called on completion with the path to the lacros-chrome on
+  // `callback` is called on completion with the path to the lacros-chrome on
   // success, or an empty filepath on failure, and the loaded lacros selection
   // which is either 'rootfs' or 'stateful'.
+  //
+  // If Load is called during Load, the previous load requests are discarded
+  // and immediately begin loading. LoadCompletionCallback for the previous
+  // request will NOT be called in this scenario.
+  // TODO(elkurin): We should run Unload for previous LacroSelectionLoader.
+  //
+  // Load should NOT be called once Unlaod is requested. Note that per-
+  // LacrosSelectionLoader unload that is requested inside BrowserManager class
+  // may run before or while calling Load.
+  // If Load is called during per-loader unload, Load request will be processed
+  // after all ongoing per-loader unload requests complete that are at most 2.
+  // Currently, this happens only when selecting stateful lacros-chrome and
+  // unloading rootfs.
   using LoadCompletionCallback = base::OnceCallback<
       void(const base::FilePath&, LacrosSelection, base::Version)>;
   virtual void Load(LoadCompletionCallback callback);
 
   // Starts to unload lacros-chrome binary.
   // Note that this triggers to remove the user directory for lacros-chrome.
+  // Once Unload is called, BrowserLoader should NOT accept Load requests.
+  //
+  // If Unload is called during Load, stops loading procedure on main thread and
+  // requests LacrosSelectionLoaders to unload. Each loader will start unloading
+  // after the current task completed.
+  //
+  // If Unload is called during Unload, no need to unload again so the
+  // coming unload request will be ignored.
   virtual void Unload();
 
  private:
@@ -118,6 +138,10 @@
                            OnLoadLacrosBinarySpecifiedBySwitch);
   FRIEND_TEST_ALL_PREFIXES(BrowserLoaderTest,
                            OnLoadLacrosDirectorySpecifiedBySwitch);
+  FRIEND_TEST_ALL_PREFIXES(BrowserLoaderTest, LoadWhileUnloading);
+
+  // Starts loading now/
+  void LoadNow(LoadCompletionCallback callback);
 
   // `source` indicates why rootfs/stateful is selected. `source` is only used
   // for logging.
@@ -150,13 +174,25 @@
                             base::Version version,
                             const base::FilePath& lacros_binary);
 
-  // Loader for rootfs lacros and stateful lacros.
+  // Called on unload completed.
+  void OnUnloadCompleted(LacrosSelection selection);
+
+  // Loader for rootfs lacros and stateful lacros. Loader objects are
+  // constructed on start loading and reset on unload completion or on reload.
   std::unique_ptr<LacrosSelectionLoader> rootfs_lacros_loader_;
   std::unique_ptr<LacrosSelectionLoader> stateful_lacros_loader_;
 
+  std::unique_ptr<LacrosSelectionLoaderFactory> factory_;
+
   // Time when the lacros component was loaded.
   base::TimeTicks lacros_start_load_time_;
 
+  // Called when Unload is completed for both rootfs and stateful lacros.
+  base::OnceClosure callback_on_unload_completion_;
+
+  // Set to true if BrowserLoader::Unload is requested.
+  bool is_unload_requested_ = false;
+
   // Used for DCHECKs to ensure method calls executed in the correct thread.
   SEQUENCE_CHECKER(sequence_checker_);
 
diff --git a/chrome/browser/ash/crosapi/browser_loader_unittest.cc b/chrome/browser/ash/crosapi/browser_loader_unittest.cc
index b2c00a3..e2a8b459 100644
--- a/chrome/browser/ash/crosapi/browser_loader_unittest.cc
+++ b/chrome/browser/ash/crosapi/browser_loader_unittest.cc
@@ -6,17 +6,23 @@
 
 #include "ash/constants/ash_switches.h"
 #include "base/auto_reset.h"
+#include "base/check_op.h"
 #include "base/command_line.h"
+#include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/task/single_thread_task_runner.h"
 #include "base/test/bind.h"
 #include "base/test/scoped_command_line.h"
 #include "base/test/test_future.h"
 #include "base/version.h"
 #include "chrome/browser/ash/crosapi/browser_util.h"
 #include "chrome/browser/ash/crosapi/lacros_selection_loader.h"
+#include "chrome/browser/ash/crosapi/lacros_selection_loader_factory.h"
+#include "chrome/browser/component_updater/cros_component_manager.h"
 #include "chromeos/ash/components/standalone_browser/browser_support.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/policy_constants.h"
@@ -28,6 +34,156 @@
 namespace crosapi {
 namespace {
 
+// Call the registered callback when Unload is started.
+class UnloadObserver {
+ public:
+  UnloadObserver() = default;
+  UnloadObserver(const UnloadObserver&) = delete;
+  UnloadObserver& operator=(const UnloadObserver&) = delete;
+  ~UnloadObserver() = default;
+
+  void OnUnloadStarted() {
+    if (callback_) {
+      std::move(callback_).Run();
+    }
+  }
+
+  void SetCallback(base::OnceClosure cb) { callback_ = std::move(cb); }
+
+ private:
+  base::OnceClosure callback_;
+};
+
+// This fake class is used to test BrowserLoader who is responsible for deciding
+// which lacros selection to use.
+// This class does not load nor get actual version. Such features are tested in
+// RootfsLacrosLoaderTest for rootfs and StatefulLacrosLoaderTest for stateful.
+class FakeLacrosSelectionLoader : public LacrosSelectionLoader {
+ public:
+  FakeLacrosSelectionLoader(
+      const base::Version& version,
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+      : version_(version), task_runner_(task_runner) {
+    // Create dummy chrome binary path.
+    CHECK(temp_dir_.CreateUniqueTempDir());
+    chrome_path_ = temp_dir_.GetPath().Append(kLacrosChromeBinary);
+    base::WriteFile(chrome_path_, "I am chrome binary.");
+  }
+
+  FakeLacrosSelectionLoader(const FakeLacrosSelectionLoader&) = delete;
+  FakeLacrosSelectionLoader& operator=(const FakeLacrosSelectionLoader&) =
+      delete;
+
+  ~FakeLacrosSelectionLoader() override = default;
+
+  void Load(LoadCompletionCallback callback, bool forced) override {
+    // Load should NOT be called when it's unloaded or busy.
+    CHECK(!is_unloading_ && !is_unloaded_);
+
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&FakeLacrosSelectionLoader::OnLoadCompleted,
+                                  base::Unretained(this), std::move(callback)));
+  }
+
+  void Unload(base::OnceClosure callback) override {
+    is_unloading_ = true;
+    unload_observer_.OnUnloadStarted();
+
+    task_runner_->PostTask(
+        FROM_HERE, base::BindOnce(&FakeLacrosSelectionLoader::OnUnloadCompleted,
+                                  base::Unretained(this), std::move(callback)));
+  }
+
+  bool IsUnloading() const override { return is_unloading_; }
+
+  bool IsUnloaded() const override { return is_unloaded_; }
+
+  void GetVersion(
+      base::OnceCallback<void(const base::Version&)> callback) override {
+    task_runner_->PostTask(
+        FROM_HERE,
+        base::BindOnce(&FakeLacrosSelectionLoader::OnGetVersionCompleted,
+                       base::Unretained(this), std::move(callback)));
+  }
+
+  void SetCallbackOnUnload(base::OnceClosure cb) {
+    unload_observer_.SetCallback(std::move(cb));
+  }
+
+ private:
+  void OnLoadCompleted(LoadCompletionCallback callback) {
+    if (!callback) {
+      return;
+    }
+
+    // If version is invalid, returns empty path. Otherwise fill in with some
+    // path. Whether path is empty or not is used as a condition to check
+    // whether error has occurred during loading.
+    const base::FilePath path =
+        version_.IsValid() ? temp_dir_.GetPath() : base::FilePath();
+    std::move(callback).Run(version_, path);
+  }
+
+  void OnUnloadCompleted(base::OnceClosure callback) {
+    is_unloading_ = false;
+    is_unloaded_ = true;
+    if (callback) {
+      std::move(callback).Run();
+    }
+  }
+
+  void OnGetVersionCompleted(
+      base::OnceCallback<void(const base::Version&)> callback) {
+    std::move(callback).Run(version_);
+  }
+
+  const base::Version version_;
+  base::ScopedTempDir temp_dir_;
+  base::FilePath chrome_path_;
+  // `task_runner_` to run Load/Unload/GetVersion task as asynchronous
+  // operations.
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+  UnloadObserver unload_observer_;
+
+  bool is_unloaded_ = false;
+  bool is_unloading_ = false;
+};
+
+class FakeLacrosSelectionLoaderFactory : public LacrosSelectionLoaderFactory {
+ public:
+  FakeLacrosSelectionLoaderFactory(
+      const base::Version& rootfs_version,
+      const base::Version& stateful_version,
+      scoped_refptr<base::SingleThreadTaskRunner> task_runner)
+      : rootfs_version_(rootfs_version),
+        stateful_version_(stateful_version),
+        task_runner_(task_runner) {}
+
+  FakeLacrosSelectionLoaderFactory(const FakeLacrosSelectionLoaderFactory&) =
+      delete;
+  FakeLacrosSelectionLoaderFactory& operator=(
+      const FakeLacrosSelectionLoaderFactory&) = delete;
+
+  ~FakeLacrosSelectionLoaderFactory() override = default;
+
+  std::unique_ptr<LacrosSelectionLoader> CreateRootfsLacrosLoader() override {
+    return std::make_unique<FakeLacrosSelectionLoader>(rootfs_version_,
+                                                       task_runner_);
+  }
+
+  std::unique_ptr<LacrosSelectionLoader> CreateStatefulLacrosLoader() override {
+    return std::make_unique<FakeLacrosSelectionLoader>(stateful_version_,
+                                                       task_runner_);
+  }
+
+ private:
+  // These versions will be set on initializing lacros selection loaders.
+  const base::Version rootfs_version_ = base::Version();
+  const base::Version stateful_version_ = base::Version();
+
+  scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+};
+
 // This implementation of RAII for LacrosSelection is to make it easy reset
 // the state between runs.
 class ScopedLacrosSelectionCache {
@@ -57,150 +213,108 @@
 
 }  // namespace
 
-// This fake class is used to test BrowserLoader who is responsible for deciding
-// which lacros selection to use.
-// This class does not load nor get actual version. Such features are tested in
-// RootfsLacrosLoaderTest for rootfs and StatefulLacrosLoaderTest for stateful.
-class FakeLacrosSelectionLoader : public LacrosSelectionLoader {
- public:
-  FakeLacrosSelectionLoader() {
-    // Create dummy chrome binary path.
-    CHECK(temp_dir_.CreateUniqueTempDir());
-    chrome_path_ = temp_dir_.GetPath().Append(kLacrosChromeBinary);
-    base::WriteFile(chrome_path_, "I am chrome binary.");
-  }
-
-  FakeLacrosSelectionLoader(const FakeLacrosSelectionLoader&) = delete;
-  FakeLacrosSelectionLoader& operator=(const FakeLacrosSelectionLoader&) =
-      delete;
-
-  ~FakeLacrosSelectionLoader() override = default;
-
-  void Load(LoadCompletionCallback callback, bool forced) override {
-    if (!callback) {
-      return;
-    }
-
-    // If version is invalid, returns empty path. Otherwise fill in with some
-    // path. Whether path is empty or not is used as a condition to check
-    // whether error has occurred during loading.
-    const base::FilePath path =
-        version_.IsValid() ? temp_dir_.GetPath() : base::FilePath();
-    std::move(callback).Run(version_, path);
-  }
-  void Unload() override {}
-  void Reset() override {}
-  void GetVersion(
-      base::OnceCallback<void(const base::Version&)> callback) override {
-    std::move(callback).Run(version_);
-  }
-
-  void SetVersionForTesting(const base::Version& version) override {
-    version_ = version;
-  }
-
- private:
-  base::Version version_ = base::Version();
-  base::ScopedTempDir temp_dir_;
-  base::FilePath chrome_path_;
-};
-
 class BrowserLoaderTest : public testing::Test {
  public:
   BrowserLoaderTest() {
-    browser_loader_ = std::make_unique<BrowserLoader>(
-        /*rootfs_lacros_loader=*/std::make_unique<FakeLacrosSelectionLoader>(),
-        /*stateful_lacros_loader=*/std::make_unique<
-            FakeLacrosSelectionLoader>());
     EXPECT_TRUE(BrowserLoader::WillLoadStatefulComponentBuilds());
   }
 
-  // Public because this is test code.
-  content::BrowserTaskEnvironment task_environment_;
-
  protected:
-  std::unique_ptr<BrowserLoader> browser_loader_;
+  BrowserLoader CreateBrowserLoaderWithFakeSelectionLoaders(
+      const base::Version& rootfs_lacros_version,
+      const base::Version& stateful_lacros_version) {
+    return BrowserLoader(std::make_unique<FakeLacrosSelectionLoaderFactory>(
+        rootfs_lacros_version, stateful_lacros_version,
+        task_environment_.GetMainThreadTaskRunner()));
+  }
+
+  void WaitForTaskComplete() { task_environment_.RunUntilIdle(); }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
 };
 
 TEST_F(BrowserLoaderTest, OnLoadVersionSelectionNeitherIsAvailable) {
   // If both stateful and rootfs lacros-chrome version is invalid, the chrome
   // path should be empty.
+  const base::Version rootfs_lacros_version = base::Version();
+  const base::Version stateful_lacros_version = base::Version();
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
+
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
   EXPECT_TRUE(future.Get<0>().empty());
 }
 
 TEST_F(BrowserLoaderTest, OnLoadVersionSelectionStatefulIsUnavailable) {
-  const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
   // Pass invalid `base::Version` to stateful lacros-chrome and set valid
   // version to rootfs lacros-chrome.
+  const base::Version rootfs_lacros_version = base::Version("2.0.0");
+  const base::Version stateful_lacros_version = base::Version();
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
+
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
   EXPECT_EQ(LacrosSelection::kRootfs, future.Get<1>());
   EXPECT_EQ(rootfs_lacros_version, future.Get<2>());
 }
 
 TEST_F(BrowserLoaderTest, OnLoadVersionSelectionRootfsIsUnavailable) {
-  const base::Version stateful_lacros_version = base::Version("1.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
-
   // Pass invalid `base::Version` as a rootfs lacros-chrome version.
+  const base::Version rootfs_lacros_version = base::Version();
+  const base::Version stateful_lacros_version = base::Version("1.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
+
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
   EXPECT_EQ(LacrosSelection::kStateful, future.Get<1>());
   EXPECT_EQ(stateful_lacros_version, future.Get<2>());
 }
 
 TEST_F(BrowserLoaderTest, OnLoadVersionSelectionRootfsIsNewer) {
   // Use rootfs when a stateful lacros-chrome version is older.
-  const base::Version stateful_lacros_version = base::Version("1.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
   const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
+  const base::Version stateful_lacros_version = base::Version("1.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
 
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
   EXPECT_EQ(LacrosSelection::kRootfs, future.Get<1>());
   EXPECT_EQ(rootfs_lacros_version, future.Get<2>());
 }
 
 TEST_F(BrowserLoaderTest, OnLoadVersionSelectionRootfsIsOlder) {
   // Use stateful when a rootfs lacros-chrome version is older.
-  const base::Version stateful_lacros_version = base::Version("3.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
   const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
+  const base::Version stateful_lacros_version = base::Version("3.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
 
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
   EXPECT_EQ(LacrosSelection::kStateful, future.Get<1>());
   EXPECT_EQ(stateful_lacros_version, future.Get<2>());
 }
 
 TEST_F(BrowserLoaderTest, OnLoadVersionSelectionSameVersions) {
   // Use stateful when rootfs and stateful lacros-chrome versions are the same.
-  const base::Version stateful_lacros_version = base::Version("2.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
   const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
+  const base::Version stateful_lacros_version = base::Version("2.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
 
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
   EXPECT_EQ(LacrosSelection::kStateful, future.Get<1>());
   EXPECT_EQ(stateful_lacros_version, future.Get<2>());
 }
@@ -215,20 +329,22 @@
 
   // Set stateful lacros version newer than rootfs to test that the selection
   // policy is prioritized higher.
-  const base::Version stateful_lacros_version = base::Version("3.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
   const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
+  const base::Version stateful_lacros_version = base::Version("3.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
 
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
 
   const LacrosSelection selection = future.Get<1>();
   EXPECT_EQ(selection, LacrosSelection::kRootfs);
   EXPECT_FALSE(BrowserLoader::WillLoadStatefulComponentBuilds());
+
+  // Check stateful lacros loader is not initialized since the selection is
+  // forced by policy.
+  EXPECT_FALSE(browser_loader.stateful_lacros_loader_);
 }
 
 TEST_F(BrowserLoaderTest,
@@ -242,20 +358,22 @@
 
   // Set stateful lacros version newer than rootfs to test that the user choice
   // is prioritized higher.
-  const base::Version stateful_lacros_version = base::Version("3.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
   const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
+  const base::Version stateful_lacros_version = base::Version("3.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
 
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
 
   const LacrosSelection selection = future.Get<1>();
   EXPECT_EQ(selection, LacrosSelection::kRootfs);
   EXPECT_FALSE(BrowserLoader::WillLoadStatefulComponentBuilds());
+
+  // Check stateful lacros loader is not initialized since the selection is
+  // forced by policy.
+  EXPECT_FALSE(browser_loader.stateful_lacros_loader_);
 }
 
 TEST_F(BrowserLoaderTest,
@@ -267,22 +385,24 @@
       browser_util::kLacrosSelectionSwitch,
       browser_util::kLacrosSelectionStateful);
 
-  // Set rootfs lacros version newer than stateful to test that the user choice
+  // Set rootfs lacros version newer than rootfs to test that the user choice
   // is prioritized higher.
-  const base::Version stateful_lacros_version = base::Version("1.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
   const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
+  const base::Version stateful_lacros_version = base::Version("1.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
 
   base::test::TestFuture<const base::FilePath&, LacrosSelection, base::Version>
       future;
-  browser_loader_->Load(future.GetCallback());
+  browser_loader.Load(future.GetCallback());
 
   const LacrosSelection selection = future.Get<1>();
   EXPECT_EQ(selection, LacrosSelection::kStateful);
   EXPECT_TRUE(BrowserLoader::WillLoadStatefulComponentBuilds());
+
+  // Check rootfs lacros loader is not initialized since the selection is forced
+  // by policy.
+  EXPECT_FALSE(browser_loader.rootfs_lacros_loader_);
 }
 
 TEST_F(BrowserLoaderTest, OnLoadLacrosBinarySpecifiedBySwitch) {
@@ -301,21 +421,24 @@
 
   // Set stateful/rootfs lacros-chrome version to check that specified
   // lacros-chrome is prioritized higher.
-  const base::Version stateful_lacros_version = base::Version("3.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
   const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
+  const base::Version stateful_lacros_version = base::Version("3.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
 
   base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
-  browser_loader_->Load(future.GetCallback<const base::FilePath&,
-                                           LacrosSelection, base::Version>());
+  browser_loader.Load(future.GetCallback<const base::FilePath&, LacrosSelection,
+                                         base::Version>());
 
   const base::FilePath path = future.Get<0>();
   const LacrosSelection selection = future.Get<1>();
   EXPECT_EQ(path, lacros_chrome_path);
   EXPECT_EQ(selection, LacrosSelection::kDeployedLocally);
+
+  // Check both rootfs and stateful lacros loader are not initialized since the
+  // selection is forced by switch.
+  EXPECT_FALSE(browser_loader.rootfs_lacros_loader_);
+  EXPECT_FALSE(browser_loader.stateful_lacros_loader_);
 }
 
 TEST_F(BrowserLoaderTest, OnLoadLacrosDirectorySpecifiedBySwitch) {
@@ -330,21 +453,64 @@
 
   // Set stateful/rootfs lacros-chrome version to check that specified
   // lacros-chrome is prioritized higher.
-  const base::Version stateful_lacros_version = base::Version("3.0.0");
-  browser_loader_->stateful_lacros_loader_->SetVersionForTesting(
-      stateful_lacros_version);
   const base::Version rootfs_lacros_version = base::Version("2.0.0");
-  browser_loader_->rootfs_lacros_loader_->SetVersionForTesting(
-      rootfs_lacros_version);
+  const base::Version stateful_lacros_version = base::Version("3.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
 
   base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
-  browser_loader_->Load(future.GetCallback<const base::FilePath&,
-                                           LacrosSelection, base::Version>());
+  browser_loader.Load(future.GetCallback<const base::FilePath&, LacrosSelection,
+                                         base::Version>());
 
   const base::FilePath path = future.Get<0>();
   const LacrosSelection selection = future.Get<1>();
   EXPECT_EQ(path, lacros_chrome_dir.Append("chrome"));
   EXPECT_EQ(selection, LacrosSelection::kDeployedLocally);
+
+  // Check both rootfs and stateful lacros loader are not initialized since the
+  // selection is forced by switch.
+  EXPECT_FALSE(browser_loader.rootfs_lacros_loader_);
+  EXPECT_FALSE(browser_loader.stateful_lacros_loader_);
+}
+
+TEST_F(BrowserLoaderTest, LoadWhileUnloading) {
+  // If stateful is newer, rootfs lacros will be unloaded.
+  const base::Version rootfs_lacros_version = base::Version("2.0.0");
+  const base::Version stateful_lacros_version = base::Version("3.0.0");
+  auto browser_loader = CreateBrowserLoaderWithFakeSelectionLoaders(
+      rootfs_lacros_version, stateful_lacros_version);
+
+  // Load once. This will start asynchronous unload for rootfs lacros loader.
+  base::test::TestFuture<base::FilePath, LacrosSelection, base::Version> future;
+  browser_loader.Load(future.GetCallback<const base::FilePath&, LacrosSelection,
+                                         base::Version>());
+
+  // Wait until rootfs lacros loader starts Unload. GetVersion runs
+  // asynchronously before it start unloading.
+  base::RunLoop run_loop1;
+  FakeLacrosSelectionLoader* rootfs_lacros_loader =
+      static_cast<FakeLacrosSelectionLoader*>(
+          browser_loader.rootfs_lacros_loader_.get());
+  rootfs_lacros_loader->SetCallbackOnUnload(run_loop1.QuitClosure());
+  run_loop1.Run();
+
+  // On requesting Load while Unloading, the load request should be stored to
+  // `callback_on_unload_completion_` and wait for unload to complete to resume
+  // load request.
+  base::RunLoop run_loop2;
+  std::optional<LacrosSelection> selection_result;
+  browser_loader.Load(base::BindOnce(
+      [](base::OnceClosure quit_closure,
+         std::optional<LacrosSelection>* selection_result,
+         const base::FilePath&, LacrosSelection selection, base::Version) {
+        *selection_result = selection;
+        std::move(quit_closure).Run();
+      },
+      run_loop2.QuitClosure(), &selection_result));
+  EXPECT_FALSE(selection_result.has_value());
+
+  run_loop2.Run();
+  EXPECT_EQ(LacrosSelection::kStateful, selection_result);
 }
 
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/lacros_selection_loader.h b/chrome/browser/ash/crosapi/lacros_selection_loader.h
index 0a29028..d6b0b5b 100644
--- a/chrome/browser/ash/crosapi/lacros_selection_loader.h
+++ b/chrome/browser/ash/crosapi/lacros_selection_loader.h
@@ -28,26 +28,25 @@
   using LoadCompletionCallback =
       base::OnceCallback<void(base::Version, const base::FilePath&)>;
 
-  // Loads chrome binary.
+  // Loads chrome binary. This must be called only when LacrosSelectionLoader is
+  // idle and has not yet unloaded.
   // `forced` specifies whether the lacros selection is forced.
   virtual void Load(LoadCompletionCallback callback, bool forced) = 0;
 
-  // Unloads chrome binary.
-  virtual void Unload() = 0;
+  // Unloads chrome binary. Unload can be called anytime, and this request will
+  // stop other tasks. Once Unload is called, LacrosSelectionLoader is no longer
+  // valid.
+  // `callback` will be called on unload completion.
+  virtual void Unload(base::OnceClosure callback) = 0;
 
-  // Resets the state. This is called before reloading lacros.
-  // TODO(elkurin): Instead of resetting the state, throw away and recreate
-  // loader instance.
-  virtual void Reset() = 0;
+  // Returns the current unloading/unloaded state.
+  virtual bool IsUnloading() const = 0;
+  virtual bool IsUnloaded() const = 0;
 
   // Calculates version and send it back via `callback`.
   // It may take time since it requires to load/mount lacros binary.
   virtual void GetVersion(
       base::OnceCallback<void(const base::Version&)> callback) = 0;
-
-  // Sets version.
-  // Only implemented for testing class (FakeLacrosSelectionLoader).
-  virtual void SetVersionForTesting(const base::Version& version) {}
 };
 
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/lacros_selection_loader_factory.h b/chrome/browser/ash/crosapi/lacros_selection_loader_factory.h
new file mode 100644
index 0000000..06010259
--- /dev/null
+++ b/chrome/browser/ash/crosapi/lacros_selection_loader_factory.h
@@ -0,0 +1,26 @@
+// 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_ASH_CROSAPI_LACROS_SELECTION_LOADER_FACTORY_H_
+#define CHROME_BROWSER_ASH_CROSAPI_LACROS_SELECTION_LOADER_FACTORY_H_
+
+#include <memory>
+
+namespace crosapi {
+class LacrosSelectionLoader;
+
+// Abstract interface to create LacrosSelectionLoader.
+class LacrosSelectionLoaderFactory {
+ public:
+  virtual ~LacrosSelectionLoaderFactory() = default;
+
+  // Interface to create LacrosSelectionLoader for rootfs/stateful.
+  virtual std::unique_ptr<LacrosSelectionLoader> CreateRootfsLacrosLoader() = 0;
+  virtual std::unique_ptr<LacrosSelectionLoader>
+  CreateStatefulLacrosLoader() = 0;
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_ASH_CROSAPI_LACROS_SELECTION_LOADER_FACTORY_H_
diff --git a/chrome/browser/ash/crosapi/rootfs_lacros_loader.cc b/chrome/browser/ash/crosapi/rootfs_lacros_loader.cc
index 18cae9c0..4d09962 100644
--- a/chrome/browser/ash/crosapi/rootfs_lacros_loader.cc
+++ b/chrome/browser/ash/crosapi/rootfs_lacros_loader.cc
@@ -51,35 +51,70 @@
 RootfsLacrosLoader::~RootfsLacrosLoader() = default;
 
 void RootfsLacrosLoader::Load(LoadCompletionCallback callback, bool forced) {
+  CHECK(state_ == State::kNotLoaded ||
+        state_ == State::kVersionReadyButNotLoaded)
+      << state_;
   LOG(WARNING) << "Loading rootfs lacros.";
 
-  // Make sure to calculate `version_` before start loading.
-  // It may not be calculated yet in case when lacros selection is defined by
+  if (state_ == State::kVersionReadyButNotLoaded) {
+    OnVersionReadyToLoad(std::move(callback), version_.value());
+    return;
+  }
+
+  // Calculate `version_` before start loading.
+  // It's not calculated yet in case when lacros selection is defined by
   // selection policy or stateful lacros is not installed.
-  GetVersion(base::BindOnce(&RootfsLacrosLoader::OnVersionReadyToLoad,
-                            weak_factory_.GetWeakPtr(), std::move(callback)));
+  state_ = State::kReadingVersion;
+  GetVersionInternal(base::BindOnce(&RootfsLacrosLoader::OnVersionReadyToLoad,
+                                    weak_factory_.GetWeakPtr(),
+                                    std::move(callback)));
 }
 
-void RootfsLacrosLoader::Unload() {
-  upstart_client_->StartJob(kLacrosUnmounterUpstartJob, {},
-                            base::BindOnce([](bool) {}));
-}
+void RootfsLacrosLoader::Unload(base::OnceClosure callback) {
+  switch (state_) {
+    case State::kNotLoaded:
+    case State::kVersionReadyButNotLoaded:
+    case State::kUnloaded:
+      // Nothing to unload if it's not loaded or already unloaded.
+      std::move(callback).Run();
+      break;
+    case State::kReadingVersion:
+    case State::kLoading:
+    case State::kUnloading:
+      // If loader is busy, wait Unload until the current task has finished.
+      pending_unload_ =
+          base::BindOnce(&RootfsLacrosLoader::Unload,
+                         weak_factory_.GetWeakPtr(), std::move(callback));
+      break;
+    case State::kLoaded:
+      state_ = State::kUnloading;
 
-void RootfsLacrosLoader::Reset() {
-  // TODO(crbug.com/1432069): Reset call while loading breaks the behavior. Need
-  // to handle such edge cases.
-  version_ = std::nullopt;
+      upstart_client_->StartJob(
+          kLacrosUnmounterUpstartJob, {},
+          base::BindOnce(&RootfsLacrosLoader::OnUnloadCompleted,
+                         weak_factory_.GetWeakPtr(), std::move(callback)));
+  }
 }
 
 void RootfsLacrosLoader::GetVersion(
     base::OnceCallback<void(const base::Version&)> callback) {
-  // If version is already calculated, immediately return the cached value.
-  // Calculate if not.
-  // Note that version value is reset on reloading.
-  if (version_.has_value()) {
-    std::move(callback).Run(version_.value());
-    return;
-  }
+  CHECK_EQ(state_, State::kNotLoaded) << state_;
+  state_ = State::kReadingVersion;
+  GetVersionInternal(std::move(callback));
+}
+
+bool RootfsLacrosLoader::IsUnloading() const {
+  return state_ == State::kUnloading;
+}
+
+bool RootfsLacrosLoader::IsUnloaded() const {
+  return state_ == State::kUnloaded;
+}
+
+void RootfsLacrosLoader::GetVersionInternal(
+    base::OnceCallback<void(const base::Version&)> callback) {
+  CHECK_EQ(state_, State::kReadingVersion) << state_;
+  CHECK(!version_);
 
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock()},
@@ -93,19 +128,39 @@
     base::OnceCallback<void(const base::Version&)> callback,
     base::Version version) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(state_, State::kReadingVersion) << state_;
 
   version_ = version;
+  state_ = State::kVersionReadyButNotLoaded;
+
+  if (pending_unload_) {
+    LOG(WARNING) << "Unload is requested during getting version of rootfs.";
+    std::move(callback).Run(base::Version());
+    std::move(pending_unload_).Run();
+    return;
+  }
+
   std::move(callback).Run(version_.value());
 }
 
 void RootfsLacrosLoader::OnVersionReadyToLoad(LoadCompletionCallback callback,
                                               const base::Version& version) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(state_, State::kVersionReadyButNotLoaded) << state_;
+
+  if (pending_unload_) {
+    LOG(WARNING) << "Unload is requested during loading rootfs.";
+    std::move(callback).Run(base::Version(), base::FilePath());
+    std::move(pending_unload_).Run();
+    return;
+  }
 
   // `version_` must be already filled by `version`.
-  DCHECK(version_.has_value() &&
-         ((!version_.value().IsValid() && !version.IsValid()) ||
-          (version_.value() == version)));
+  CHECK(version_.has_value() &&
+        ((!version_.value().IsValid() && !version.IsValid()) ||
+         (version_.value() == version)));
+
+  state_ = State::kLoading;
 
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock()},
@@ -119,6 +174,15 @@
 void RootfsLacrosLoader::OnMountCheckToLoad(LoadCompletionCallback callback,
                                             bool already_mounted) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(state_, State::kLoading) << state_;
+
+  if (pending_unload_) {
+    LOG(WARNING) << "Unload is requested during loading rootfs.";
+    state_ = State::kVersionReadyButNotLoaded;
+    std::move(callback).Run(base::Version(), base::FilePath());
+    std::move(pending_unload_).Run();
+    return;
+  }
 
   if (already_mounted) {
     OnUpstartLacrosMounter(std::move(callback), true);
@@ -139,17 +203,20 @@
 void RootfsLacrosLoader::OnUpstartLacrosMounter(LoadCompletionCallback callback,
                                                 bool success) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(state_, State::kLoading) << state_;
+  state_ = State::kLoaded;
 
   LOG_IF(WARNING, !success) << "Upstart failed to mount rootfs lacros.";
 
-  // `version_` must be calculated before coming here.
-  // If `version_` is not filled, it implies Reset() is called, so handling this
-  // case as an error.
-  if (!version_.has_value()) {
+  if (pending_unload_) {
+    LOG(WARNING) << "Unload is requested during loading rootfs.";
     std::move(callback).Run(base::Version(), base::FilePath());
+    std::move(pending_unload_).Run();
     return;
   }
 
+  // `version_` must be calculated before coming here.
+  CHECK(version_.has_value());
   std::move(callback).Run(
       version_.value(),
       // If mounting wasn't successful, return a empty mount point to indicate
@@ -158,4 +225,36 @@
       success ? base::FilePath(kRootfsLacrosMountPoint) : base::FilePath());
 }
 
+void RootfsLacrosLoader::OnUnloadCompleted(base::OnceClosure callback,
+                                           bool success) {
+  // Proceed anyway regardless of unload success.
+  if (!success) {
+    LOG(ERROR) << "Failed to unload rootfs lacros";
+  }
+
+  CHECK_EQ(state_, State::kUnloading) << state_;
+  state_ = State::kUnloaded;
+  std::move(callback).Run();
+}
+
+std::ostream& operator<<(std::ostream& ostream,
+                         RootfsLacrosLoader::State state) {
+  switch (state) {
+    case RootfsLacrosLoader::State::kNotLoaded:
+      return ostream << "NotLoaded";
+    case RootfsLacrosLoader::State::kReadingVersion:
+      return ostream << "ReadingVersion";
+    case RootfsLacrosLoader::State::kVersionReadyButNotLoaded:
+      return ostream << "VersionReadyButNotLoaded";
+    case RootfsLacrosLoader::State::kLoading:
+      return ostream << "Loading";
+    case RootfsLacrosLoader::State::kLoaded:
+      return ostream << "Loaded";
+    case RootfsLacrosLoader::State::kUnloading:
+      return ostream << "Unloading";
+    case RootfsLacrosLoader::State::kUnloaded:
+      return ostream << "Unloaded";
+  }
+}
+
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/rootfs_lacros_loader.h b/chrome/browser/ash/crosapi/rootfs_lacros_loader.h
index ee028dc..b492af5 100644
--- a/chrome/browser/ash/crosapi/rootfs_lacros_loader.h
+++ b/chrome/browser/ash/crosapi/rootfs_lacros_loader.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_ASH_CROSAPI_ROOTFS_LACROS_LOADER_H_
 
 #include <optional>
+#include <ostream>
 
 #include "base/files/file_path.h"
 #include "base/functional/callback.h"
@@ -32,14 +33,45 @@
   RootfsLacrosLoader& operator=(const RootfsLacrosLoader&) = delete;
   ~RootfsLacrosLoader() override;
 
+  // The state representing the loading state.
+  enum class State {
+    // Loader is not running any task.
+    kNotLoaded,
+
+    // Loader is in the process of reading the version from manifest file.
+    kReadingVersion,
+
+    // Loader gets version of lacros-chrome but not loaded it yet.
+    kVersionReadyButNotLoaded,
+
+    // Loader is in the process of loading lacros-chrome.
+    kLoading,
+
+    // Loader has loaded lacros-chrome and `version_` and `path_` is ready.
+    kLoaded,
+
+    // Loader is in the process of unloading lacros-chrome.
+    kUnloading,
+
+    // Loader has unloaded the lacros-chrome. State must NOT change once it
+    // becomes kUnloaded.
+    kUnloaded,
+  };
+
+  State GetState() const { return state_; }
+
   // LacrosSelectionLoader:
   void Load(LoadCompletionCallback callback, bool forced) override;
-  void Unload() override;
-  void Reset() override;
+  void Unload(base::OnceClosure callback) override;
   void GetVersion(
       base::OnceCallback<void(const base::Version&)> callback) override;
+  bool IsUnloading() const override;
+  bool IsUnloaded() const override;
 
  private:
+  void GetVersionInternal(
+      base::OnceCallback<void(const base::Version&)> callback);
+
   // Called after GetVersion.
   void OnGetVersion(base::OnceCallback<void(const base::Version&)> callback,
                     base::Version version);
@@ -56,6 +88,9 @@
   // Callback from upstart mounting lacros-chrome.
   void OnUpstartLacrosMounter(LoadCompletionCallback callback, bool success);
 
+  // Called on unload completed.
+  void OnUnloadCompleted(base::OnceClosure callback, bool success);
+
   // The bundled rootfs lacros-chrome binary version. This is set after the
   // first async call that checks the installed rootfs lacros version number.
   // If `version_` is null, it implies the version is not yet calculated.
@@ -72,12 +107,18 @@
   // testing.
   base::FilePath metadata_path_;
 
+  base::OnceClosure pending_unload_;
+
+  State state_ = State::kNotLoaded;
+
   // Used for DCHECKs to ensure method calls executed in the correct thread.
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<RootfsLacrosLoader> weak_factory_{this};
 };
 
+std::ostream& operator<<(std::ostream&, RootfsLacrosLoader::State);
+
 }  // namespace crosapi
 
 #endif  // CHROME_BROWSER_ASH_CROSAPI_ROOTFS_LACROS_LOADER_H_
diff --git a/chrome/browser/ash/crosapi/rootfs_lacros_loader_unittest.cc b/chrome/browser/ash/crosapi/rootfs_lacros_loader_unittest.cc
index a163e5a..8fcdbde 100644
--- a/chrome/browser/ash/crosapi/rootfs_lacros_loader_unittest.cc
+++ b/chrome/browser/ash/crosapi/rootfs_lacros_loader_unittest.cc
@@ -49,7 +49,7 @@
   std::unique_ptr<RootfsLacrosLoader> rootfs_lacros_loader_;
 };
 
-TEST_F(RootfsLacrosLoaderTest, LoadRootfsLacros) {
+TEST_F(RootfsLacrosLoaderTest, LoadRootfsLacrosSelectedByCompatibilityCheck) {
   bool callback_called = false;
   fake_upstart_client_.set_start_job_cb(base::BindRepeating(
       [](bool* callback_called, const std::string& job,
@@ -60,12 +60,57 @@
       },
       &callback_called));
 
+  EXPECT_EQ(RootfsLacrosLoader::State::kNotLoaded,
+            rootfs_lacros_loader_->GetState());
+
+  // If rootfs is selected by compatibility check, it first calls GetVersion to
+  // read the version, and then Load is requested. Inside GetVersion, Load won't
+  // complete.
+  base::test::TestFuture<const base::Version&> future1;
+  rootfs_lacros_loader_->GetVersion(
+      future1.GetCallback<const base::Version&>());
+  EXPECT_EQ(base::Version(version_str), future1.Get<0>());
+  EXPECT_EQ(RootfsLacrosLoader::State::kVersionReadyButNotLoaded,
+            rootfs_lacros_loader_->GetState());
+  EXPECT_FALSE(callback_called);
+
+  // Load is called after version is calculated.
+  base::test::TestFuture<base::Version, const base::FilePath&> future2;
+  rootfs_lacros_loader_->Load(
+      future2.GetCallback<base::Version, const base::FilePath&>(),
+      /*forced=*/false);
+  EXPECT_EQ(base::Version(version_str), future2.Get<0>());
+  EXPECT_TRUE(callback_called);
+
+  EXPECT_EQ(RootfsLacrosLoader::State::kLoaded,
+            rootfs_lacros_loader_->GetState());
+}
+
+TEST_F(RootfsLacrosLoaderTest, LoadRootfsLacrosSelectedByPolicy) {
+  bool callback_called = false;
+  fake_upstart_client_.set_start_job_cb(base::BindRepeating(
+      [](bool* callback_called, const std::string& job,
+         const std::vector<std::string>& upstart_env) {
+        EXPECT_EQ(job, kLacrosMounterUpstartJob);
+        *callback_called = true;
+        return ash::FakeUpstartClient::StartJobResult(true /* success */);
+      },
+      &callback_called));
+
+  EXPECT_EQ(RootfsLacrosLoader::State::kNotLoaded,
+            rootfs_lacros_loader_->GetState());
+
+  // If rootfs is selected by policy, it does not call GetVersion. Instead, it
+  // calls Load directly and compute read the version inside Load together.
   base::test::TestFuture<base::Version, const base::FilePath&> future;
   rootfs_lacros_loader_->Load(
       future.GetCallback<base::Version, const base::FilePath&>(),
       /*forced=*/false);
   EXPECT_EQ(base::Version(version_str), future.Get<0>());
   EXPECT_TRUE(callback_called);
+
+  EXPECT_EQ(RootfsLacrosLoader::State::kLoaded,
+            rootfs_lacros_loader_->GetState());
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/crosapi/stateful_lacros_loader.cc b/chrome/browser/ash/crosapi/stateful_lacros_loader.cc
index bda31947..cb497b85 100644
--- a/chrome/browser/ash/crosapi/stateful_lacros_loader.cc
+++ b/chrome/browser/ash/crosapi/stateful_lacros_loader.cc
@@ -146,9 +146,11 @@
   DCHECK(component_manager_);
 }
 
+// TODO(elkurin): Maybe we should call Unload or pending_unload at least?
 StatefulLacrosLoader::~StatefulLacrosLoader() = default;
 
 void StatefulLacrosLoader::Load(LoadCompletionCallback callback, bool forced) {
+  CHECK(state_ == State::kNotLoaded || state_ == State::kLoaded) << state_;
   LOG(WARNING) << "Loading stateful lacros.";
 
   // If stateful lacros-chrome is already loaded once, run `callback`
@@ -156,39 +158,49 @@
   // This code path is used in most cases as they are already calculated on
   // getting version except for the case where BrowserLoader is forced to select
   // stateful lacros-chrome by lacros selection policy.
-  if (IsReady()) {
+  if (state_ == State::kLoaded) {
     std::move(callback).Run(version_.value(), path_.value());
     return;
   }
 
+  state_ = State::kLoading;
   LoadInternal(std::move(callback), forced);
 }
 
-void StatefulLacrosLoader::Unload() {
-  base::ThreadPool::PostTaskAndReplyWithResult(
-      FROM_HERE, {base::MayBlock()},
-      base::BindOnce(&CheckInstalledAndMaybeRemoveUserDirectory,
-                     component_manager_, lacros_component_name_),
-      base::BindOnce(&StatefulLacrosLoader::OnCheckInstalledToUnload,
-                     weak_factory_.GetWeakPtr()));
-}
+void StatefulLacrosLoader::Unload(base::OnceClosure callback) {
+  switch (state_) {
+    case State::kNotLoaded:
+    case State::kUnloaded:
+      // Nothing to unload if it's not loaded or already unloaded.
+      std::move(callback).Run();
+      state_ = State::kUnloaded;
+      break;
+    case State::kLoading:
+    case State::kUnloading:
+      // If loader is busy, wait Unload until the current task has finished.
+      pending_unload_ =
+          base::BindOnce(&StatefulLacrosLoader::Unload,
+                         weak_factory_.GetWeakPtr(), std::move(callback));
+      break;
+    case State::kLoaded:
+      // Start unloading if lacros-chrome is loaded.
+      state_ = State::kUnloading;
 
-void StatefulLacrosLoader::Reset() {
-  // TODO(crbug.com/1432069): Reset call while loading breaks the behavior. Need
-  // to handle such edge cases.
-  version_ = std::nullopt;
-  path_ = std::nullopt;
+      base::ThreadPool::PostTaskAndReplyWithResult(
+          FROM_HERE, {base::MayBlock()},
+          base::BindOnce(&CheckInstalledAndMaybeRemoveUserDirectory,
+                         component_manager_, lacros_component_name_),
+          base::BindOnce(&StatefulLacrosLoader::OnCheckInstalledToUnload,
+                         weak_factory_.GetWeakPtr(), std::move(callback)));
+      break;
+  }
 }
 
 void StatefulLacrosLoader::GetVersion(
     base::OnceCallback<void(const base::Version&)> callback) {
-  // If version is already calculated, immediately return the cached value.
-  // Calculate if not.
-  // Note that version value is reset on reloading.
-  if (IsReady()) {
-    std::move(callback).Run(version_.value());
-    return;
-  }
+  CHECK_EQ(state_, State::kNotLoaded) << state_;
+
+  state_ = State::kLoading;
 
   // TODO(crbug.com/1455070): There's KI that the current implementation
   // occasionally wrongly identifies there exists. Fix the logic.
@@ -202,8 +214,18 @@
                      weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
+bool StatefulLacrosLoader::IsUnloading() const {
+  return state_ == State::kUnloading;
+}
+
+bool StatefulLacrosLoader::IsUnloaded() const {
+  return state_ == State::kUnloaded;
+}
+
 void StatefulLacrosLoader::LoadInternal(LoadCompletionCallback callback,
                                         bool forced) {
+  CHECK_EQ(state_, State::kLoading) << state_;
+
   // If a compatible installation exists, use that and download any updates in
   // the background. If not, report just there is no available stateful lacros
   // unless stateful lacros is forced by policy or about:flag entry.
@@ -223,15 +245,22 @@
                      std::move(callback)));
 }
 
-bool StatefulLacrosLoader::IsReady() {
-  return version_.has_value() && path_.has_value();
-}
-
 void StatefulLacrosLoader::OnLoad(
     LoadCompletionCallback callback,
     component_updater::CrOSComponentManager::Error error,
     const base::FilePath& path) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(state_, State::kLoading) << state_;
+  state_ = State::kLoaded;
+
+  if (pending_unload_) {
+    LOG(WARNING) << "Unload is requested during loading stateful.";
+    if (callback) {
+      std::move(callback).Run(base::Version(), base::FilePath());
+    }
+    std::move(pending_unload_).Run();
+    return;
+  }
 
   bool is_stateful_lacros_available =
       error == component_updater::CrOSComponentManager::Error::NONE &&
@@ -262,12 +291,21 @@
     base::OnceCallback<void(const base::Version&)> callback,
     bool is_installed) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(state_, State::kLoading) << state_;
+
+  if (pending_unload_) {
+    LOG(WARNING) << "Unload is requested during getting version of stateful.";
+    state_ = State::kNotLoaded;
+    if (callback) {
+      std::move(callback).Run(base::Version());
+    }
+    std::move(pending_unload_).Run();
+    return;
+  }
 
   if (!is_installed) {
     // Run `callback` immediately with empty version and start loading stateful
     // lacros-chrome in the background.
-    // TODO(crbug.com/1432069): Reconsider `version_` and `path_` values
-    // semantics.
     LoadInternal({}, /*forced=*/false);
     std::move(callback).Run(base::Version());
     return;
@@ -282,10 +320,14 @@
                false);
 }
 
-void StatefulLacrosLoader::OnCheckInstalledToUnload(bool was_installed) {
+void StatefulLacrosLoader::OnCheckInstalledToUnload(base::OnceClosure callback,
+                                                    bool was_installed) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(state_, State::kUnloading) << state_;
 
   if (!was_installed) {
+    state_ = State::kUnloaded;
+    std::move(callback).Run();
     return;
   }
 
@@ -294,15 +336,37 @@
   // assumes that system salt is available. This isn't always true when chrome
   // restarts to apply non-owner flags. It's hard to make MetadataTable async.
   // Ensure salt is available before unloading. https://crbug.com/1122674
-  ash::SystemSaltGetter::Get()->GetSystemSalt(base::BindOnce(
-      &StatefulLacrosLoader::UnloadAfterCleanUp, weak_factory_.GetWeakPtr()));
+  ash::SystemSaltGetter::Get()->GetSystemSalt(
+      base::BindOnce(&StatefulLacrosLoader::UnloadAfterCleanUp,
+                     weak_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-void StatefulLacrosLoader::UnloadAfterCleanUp(const std::string& ignored_salt) {
+void StatefulLacrosLoader::UnloadAfterCleanUp(base::OnceClosure callback,
+                                              const std::string& ignored_salt) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  CHECK_EQ(state_, State::kUnloading) << state_;
 
   CHECK(ash::SystemSaltGetter::Get()->GetRawSalt());
   component_manager_->Unload(lacros_component_name_);
+
+  state_ = State::kUnloaded;
+  std::move(callback).Run();
+}
+
+std::ostream& operator<<(std::ostream& ostream,
+                         StatefulLacrosLoader::State state) {
+  switch (state) {
+    case StatefulLacrosLoader::State::kNotLoaded:
+      return ostream << "NotLoaded";
+    case StatefulLacrosLoader::State::kLoading:
+      return ostream << "Loading";
+    case StatefulLacrosLoader::State::kLoaded:
+      return ostream << "Loaded";
+    case StatefulLacrosLoader::State::kUnloading:
+      return ostream << "Unloading";
+    case StatefulLacrosLoader::State::kUnloaded:
+      return ostream << "Unloaded";
+  }
 }
 
 }  // namespace crosapi
diff --git a/chrome/browser/ash/crosapi/stateful_lacros_loader.h b/chrome/browser/ash/crosapi/stateful_lacros_loader.h
index d23316f..d9737ae 100644
--- a/chrome/browser/ash/crosapi/stateful_lacros_loader.h
+++ b/chrome/browser/ash/crosapi/stateful_lacros_loader.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_ASH_CROSAPI_STATEFUL_LACROS_LOADER_H_
 
 #include <optional>
+#include <ostream>
 #include <string>
 
 #include "base/files/file_path.h"
@@ -38,20 +39,38 @@
   StatefulLacrosLoader& operator=(const StatefulLacrosLoader&) = delete;
   ~StatefulLacrosLoader() override;
 
+  // The state representing the loading state.
+  enum class State {
+    // Loader is not running any task.
+    kNotLoaded,
+
+    // Loader is in the process of loading lacros-chrome.
+    kLoading,
+
+    // Loader has loaded lacros-chrome and `version_` and `path_` is ready.
+    kLoaded,
+
+    // Loader is in the process of unloading lacros-chrome.
+    kUnloading,
+
+    // Loader has unloaded the lacros-chrome. State must NOT change once it
+    // becomes kUnloaded.
+    kUnloaded,
+  };
+
+  State GetState() const { return state_; }
+
   // LacrosSelectionLoader:
   void Load(LoadCompletionCallback callback, bool forced) override;
-  void Unload() override;
-  void Reset() override;
+  void Unload(base::OnceClosure callback) override;
   void GetVersion(
       base::OnceCallback<void(const base::Version&)> callback) override;
+  bool IsUnloading() const override;
+  bool IsUnloaded() const override;
 
  private:
   void LoadInternal(LoadCompletionCallback callback, bool forced);
 
-  // Returns true if the stateful lacros-chrome is already loaded and both
-  // `version_` and `path_` are ready.
-  bool IsReady();
-
   // Called after Load.
   void OnLoad(LoadCompletionCallback callback,
               component_updater::CrOSComponentManager::Error error,
@@ -70,10 +89,11 @@
   // Called in Unload sequence.
   // Unloading hops threads. This is called after we check whether Lacros was
   // installed and maybe clean up the user directory.
-  void OnCheckInstalledToUnload(bool was_installed);
+  void OnCheckInstalledToUnload(base::OnceClosure callback, bool was_installed);
 
   // Unloads the component. Called after system salt is available.
-  void UnloadAfterCleanUp(const std::string& ignored_salt);
+  void UnloadAfterCleanUp(base::OnceClosure callback,
+                          const std::string& ignored_salt);
 
   // If `version_` is null, it implies the version is not yet calculated.
   // For cases where it failed to read the version, invalid `base::Version()` is
@@ -90,12 +110,18 @@
 
   const std::string lacros_component_name_;
 
+  base::OnceClosure pending_unload_;
+
+  State state_ = State::kNotLoaded;
+
   // Used for DCHECKs to ensure method calls executed in the correct thread.
   SEQUENCE_CHECKER(sequence_checker_);
 
   base::WeakPtrFactory<StatefulLacrosLoader> weak_factory_{this};
 };
 
+std::ostream& operator<<(std::ostream&, StatefulLacrosLoader::State);
+
 }  // namespace crosapi
 
 #endif  // CHROME_BROWSER_ASH_CROSAPI_STATEFUL_LACROS_LOADER_H_
diff --git a/chrome/browser/ash/crosapi/stateful_lacros_loader_unittest.cc b/chrome/browser/ash/crosapi/stateful_lacros_loader_unittest.cc
index f523331..a870e25 100644
--- a/chrome/browser/ash/crosapi/stateful_lacros_loader_unittest.cc
+++ b/chrome/browser/ash/crosapi/stateful_lacros_loader_unittest.cc
@@ -10,15 +10,20 @@
 #include "base/auto_reset.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
+#include "base/test/scoped_path_override.h"
 #include "base/test/test_future.h"
 #include "base/version.h"
 #include "chrome/browser/ash/crosapi/browser_loader.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
 #include "chrome/browser/component_updater/fake_cros_component_manager.h"
 #include "chrome/test/base/browser_process_platform_part_test_api_chromeos.h"
 #include "chromeos/ash/components/standalone_browser/browser_support.h"
+#include "components/component_updater/component_updater_paths.h"
 #include "components/component_updater/mock_component_updater_service.h"
 #include "components/update_client/update_client.h"
 #include "content/public/test/browser_task_environment.h"
@@ -49,6 +54,15 @@
         g_browser_process->platform_part());
     browser_part_->InitializeCrosComponentManager(component_manager_);
 
+    // Set up component path.
+    base::FilePath root_dir;
+    CHECK(base::PathService::Get(component_updater::DIR_COMPONENT_USER,
+                                 &root_dir));
+    base::FilePath component_root =
+        root_dir.Append(component_updater::kComponentsRootPath)
+            .Append(kLacrosComponentName);
+    base::CreateDirectory(component_root.Append("sub-dir"));
+
     stateful_lacros_loader_ = std::make_unique<StatefulLacrosLoader>(
         component_manager_, &mock_component_update_service_,
         kLacrosComponentName);
@@ -56,6 +70,8 @@
   }
 
   ~StatefulLacrosLoaderTest() override {
+    stateful_lacros_loader_.reset();
+
     browser_part_->ShutdownCrosComponentManager();
   }
 
@@ -64,26 +80,42 @@
  protected:
   content::BrowserTaskEnvironment task_environment_;
 
+  base::ScopedPathOverride scoped_component_user_dir_{
+      component_updater::DIR_COMPONENT_USER};
   component_updater::MockComponentUpdateService mock_component_update_service_;
   scoped_refptr<component_updater::FakeCrOSComponentManager> component_manager_;
   std::unique_ptr<BrowserProcessPlatformPartTestApi> browser_part_;
   std::unique_ptr<StatefulLacrosLoader> stateful_lacros_loader_;
 };
 
-TEST_F(StatefulLacrosLoaderTest, LoadStatefulLacros) {
+TEST_F(StatefulLacrosLoaderTest,
+       LoadStatefulLacrosSelectedByCompatibilityCheck) {
   std::u16string lacros_component_name =
       base::UTF8ToUTF16(std::string_view(kLacrosComponentName));
   EXPECT_CALL(mock_component_update_service_, GetComponents())
       .WillOnce(Return(std::vector<component_updater::ComponentInfo>{
           {kLacrosComponentId, "", lacros_component_name, version, ""}}));
 
-  // Set stateful lacros-chrome version. Wait until the version calculation is
-  // completed before verifying the version.
-  base::test::TestFuture<base::Version, const base::FilePath&> future;
-  stateful_lacros_loader_->Load(
-      future.GetCallback<base::Version, const base::FilePath&>(),
-      /*forced=*/false);
+  EXPECT_EQ(StatefulLacrosLoader::State::kNotLoaded,
+            stateful_lacros_loader_->GetState());
+
+  // If stateful is selected by compatibility check, it first calls GetVersion
+  // and complete loading, and then Load is called.
+  base::test::TestFuture<const base::Version&> future;
+  stateful_lacros_loader_->GetVersion(
+      future.GetCallback<const base::Version&>());
   EXPECT_EQ(version, future.Get<0>());
+  EXPECT_EQ(StatefulLacrosLoader::State::kLoaded,
+            stateful_lacros_loader_->GetState());
+
+  // Load is already completed in GetVersion, so this call is synchronous.
+  base::Version result_version;
+  stateful_lacros_loader_->Load(
+      base::BindOnce([](base::Version* result_version, base::Version version,
+                        const base::FilePath&) { *result_version = version; },
+                     &result_version),
+      /*forced=*/false);
+  EXPECT_EQ(version, result_version);
 }
 
 TEST_F(StatefulLacrosLoaderTest, LoadStatefulLacrosSelectedByPolicy) {
@@ -93,10 +125,12 @@
       .WillOnce(Return(std::vector<component_updater::ComponentInfo>{
           {kLacrosComponentId, "", lacros_component_name, version, ""}}));
 
-  // Set stateful lacros-chrome version. Wait until the version calculation is
-  // completed before verifying the version.
+  EXPECT_EQ(StatefulLacrosLoader::State::kNotLoaded,
+            stateful_lacros_loader_->GetState());
+
+  // If stateful is forced by policy, it does not call GetVersion. Instead, it
+  // calls Load directly and compute the version inside Load together.
   base::test::TestFuture<base::Version, const base::FilePath&> future;
-  // Load stateful lacros-chrome as enforced by a policy.
   stateful_lacros_loader_->Load(
       future.GetCallback<base::Version, const base::FilePath&>(),
       /*forced=*/true);
diff --git a/chrome/browser/ash/dbus/vm/vm_applications_service_provider.cc b/chrome/browser/ash/dbus/vm/vm_applications_service_provider.cc
index ef5bad5..60d16ec 100644
--- a/chrome/browser/ash/dbus/vm/vm_applications_service_provider.cc
+++ b/chrome/browser/ash/dbus/vm/vm_applications_service_provider.cc
@@ -88,13 +88,8 @@
   // VmApplicationsServiceProvider::SelectFile(). Take back ownership.
   auto data = base::WrapUnique(static_cast<SelectFileData*>(params));
 
-  ui::EndpointType target = ui::EndpointType::kDefault;
-  if (data->signal.vm_name() == crostini::kCrostiniDefaultVmName) {
-    target = ui::EndpointType::kCrostini;
-  }
-
   ShareWithVMAndTranslateToFileUrls(
-      target, ui::SelectedFileInfoListToFilePathList(files),
+      data->signal.vm_name(), ui::SelectedFileInfoListToFilePathList(files),
       base::BindOnce(
           [](std::unique_ptr<SelectFileData> data,
              std::vector<std::string> file_urls) {
@@ -287,14 +282,8 @@
                                         base::FilePath()));
     }
     // Translate to path in host and DLP component type if possible.
-    ui::EndpointType source = ui::EndpointType::kUnknownVm;
-    if (request.vm_name() == crostini::kCrostiniDefaultVmName) {
-      source = ui::EndpointType::kCrostini;
-      owner.dialog_caller =
-          policy::DlpFileDestination(data_controls::Component::kCrostini);
-    }
     std::vector<base::FilePath> paths =
-        TranslateVMPathsToHost(source, file_infos);
+        TranslateVMPathsToHost(request.vm_name(), file_infos);
     default_path =
         !paths.empty() ? std::move(paths[0]) : std::move(file_infos[0].path);
   }
diff --git a/chrome/browser/ash/exo/chrome_security_delegate.cc b/chrome/browser/ash/exo/chrome_security_delegate.cc
index 374b393..0627f08 100644
--- a/chrome/browser/ash/exo/chrome_security_delegate.cc
+++ b/chrome/browser/ash/exo/chrome_security_delegate.cc
@@ -41,6 +41,7 @@
 
 constexpr char kUriListSeparator[] = "\r\n";
 constexpr char kVmFileScheme[] = "vmfile";
+constexpr char kDefaultVmMount[] = "/mnt/shared";
 
 void SendArcUrls(exo::SecurityDelegate::SendDataCallback callback,
                  const std::vector<GURL>& urls) {
@@ -95,23 +96,23 @@
   return false;
 }
 
+// Return VM mount path for specified `vm_name`.
+base::FilePath GetVmMount(const std::string& vm_name) {
+  if (vm_name == crostini::kCrostiniDefaultVmName) {
+    return crostini::ContainerChromeOSBaseDirectory();
+  }
+  if (vm_name == plugin_vm::kPluginVmName) {
+    return plugin_vm::ChromeOSBaseDirectory();
+  }
+  return base::FilePath(std::string(kDefaultVmMount));
+}
+
 // Translate |vm_paths| from |source| VM to host paths.
-std::vector<FileInfo> TranslateVMToHost(ui::EndpointType source,
+std::vector<FileInfo> TranslateVMToHost(const std::string vm_name,
                                         std::vector<ui::FileInfo> vm_paths) {
   std::vector<FileInfo> file_infos;
   Profile* primary_profile = ProfileManager::GetPrimaryUserProfile();
-  bool is_crostini = source == ui::EndpointType::kCrostini;
-  bool is_plugin_vm = source == ui::EndpointType::kPluginVm;
-
-  base::FilePath vm_mount;
-  std::string vm_name;
-  if (is_crostini) {
-    vm_mount = crostini::ContainerChromeOSBaseDirectory();
-    vm_name = crostini::kCrostiniDefaultVmName;
-  } else if (is_plugin_vm) {
-    vm_mount = plugin_vm::ChromeOSBaseDirectory();
-    vm_name = plugin_vm::kPluginVmName;
-  }
+  bool is_crostini = vm_name == crostini::kCrostiniDefaultVmName;
 
   for (ui::FileInfo& info : vm_paths) {
     base::FilePath path = std::move(info.path);
@@ -121,9 +122,9 @@
     // /mnt/chromeos for crostini; in //ChromeOS for Plugin VM), otherwise
     // prefix with 'vmfile:<vm_name>:' to avoid VMs spoofing host paths.
     // E.g. crostini /etc/mime.types => vmfile:termina:/etc/mime.types.
-    if (is_crostini || is_plugin_vm) {
+    if (!vm_name.empty() && vm_name != arc::kArcVmName) {
       if (file_manager::util::ConvertPathInsideVMToFileSystemURL(
-              primary_profile, path, vm_mount,
+              primary_profile, path, GetVmMount(vm_name),
               /*map_crostini_home=*/is_crostini, &url)) {
         path = url.path();
         // Only allow write to clipboard for paths that are shared.
@@ -163,27 +164,11 @@
 // Share |files| with |target| VM and invoke |callback| with translated file:
 // URLs.
 void ShareAndTranslateHostToVM(
-    ui::EndpointType target,
-    std::vector<FileInfo> file_infos,
+    const std::string& vm_name,
+    const std::vector<FileInfo>& file_infos,
     base::OnceCallback<void(std::vector<std::string>)> callback) {
   Profile* primary_profile = ProfileManager::GetPrimaryUserProfile();
-  bool is_arc = target == ui::EndpointType::kArc;
-  bool is_crostini = target == ui::EndpointType::kCrostini;
-  bool is_plugin_vm = target == ui::EndpointType::kPluginVm;
-
-  base::FilePath vm_mount;
-  std::string vm_name;
-  if (is_arc) {
-    // For ARC, |share_required| below will always be false and |vm_name| will
-    // not be used. Setting it to arc::kArcVmName has no effect.
-    vm_name = arc::kArcVmName;
-  } else if (is_crostini) {
-    vm_mount = crostini::ContainerChromeOSBaseDirectory();
-    vm_name = crostini::kCrostiniDefaultVmName;
-  } else if (is_plugin_vm) {
-    vm_mount = plugin_vm::ChromeOSBaseDirectory();
-    vm_name = plugin_vm::kPluginVmName;
-  }
+  bool is_crostini = vm_name == crostini::kCrostiniDefaultVmName;
 
   const std::string vm_prefix =
       base::StrCat({kVmFileScheme, ":", vm_name, ":"});
@@ -194,7 +179,7 @@
   for (auto& info : file_infos) {
     std::string file_url;
     bool share_required = false;
-    if (is_arc) {
+    if (vm_name == arc::kArcVmName) {
       GURL arc_url;
       if (!file_manager::util::ConvertPathToArcUrl(info.path, &arc_url,
                                                    &share_required)) {
@@ -202,7 +187,7 @@
         continue;
       }
       file_url = arc_url.spec();
-    } else if (is_crostini || is_plugin_vm) {
+    } else if (!vm_name.empty()) {
       base::FilePath path;
       // Check if it is a path inside the VM: 'vmfile:<vm_name>:'.
       if (base::StartsWith(info.path.value(), vm_prefix,
@@ -210,7 +195,7 @@
         file_url = ui::FilePathToFileURL(
             base::FilePath(info.path.value().substr(vm_prefix.size())));
       } else if (file_manager::util::ConvertFileSystemURLToPathInsideVM(
-                     primary_profile, info.url, vm_mount,
+                     primary_profile, info.url, GetVmMount(vm_name),
                      /*map_crostini_home=*/is_crostini, &path)) {
         // Convert to path inside the VM.
         file_url = ui::FilePathToFileURL(path);
@@ -230,7 +215,7 @@
   }
 
   if (!paths_to_share.empty()) {
-    if (!is_plugin_vm) {
+    if (vm_name != plugin_vm::kPluginVmName) {
       auto vm_info =
           guest_os::GuestOsSessionTracker::GetForProfile(primary_profile)
               ->GetVmInfo(vm_name);
@@ -252,7 +237,7 @@
                 }
 
                 // Still send the data, even if sharing failed.
-                std::move(callback).Run(file_urls);
+                std::move(callback).Run(std::move(file_urls));
               },
               std::move(callback), std::move(file_urls)));
       return;
@@ -273,9 +258,9 @@
 
 // static
 std::vector<base::FilePath> TranslateVMPathsToHost(
-    ui::EndpointType source,
+    const std::string& vm_name,
     const std::vector<ui::FileInfo>& vm_paths) {
-  std::vector<FileInfo> translated = TranslateVMToHost(source, vm_paths);
+  std::vector<FileInfo> translated = TranslateVMToHost(vm_name, vm_paths);
   std::vector<base::FilePath> result;
   for (auto& info : translated) {
     result.push_back(std::move(info.path));
@@ -285,10 +270,10 @@
 
 // static
 void ShareWithVMAndTranslateToFileUrls(
-    ui::EndpointType target,
+    const std::string& vm_name,
     const std::vector<base::FilePath>& files,
     base::OnceCallback<void(std::vector<std::string>)> callback) {
-  ShareAndTranslateHostToVM(target, CrackPaths(files), std::move(callback));
+  ShareAndTranslateHostToVM(vm_name, CrackPaths(files), std::move(callback));
 }
 
 ChromeSecurityDelegate::ChromeSecurityDelegate() = default;
@@ -328,7 +313,8 @@
     const std::vector<uint8_t>& data) const {
   std::vector<ui::FileInfo> result;
   std::vector<FileInfo> file_infos = TranslateVMToHost(
-      source, ui::URIListToFileInfos(std::string(data.begin(), data.end())));
+      GetVmName(source),
+      ui::URIListToFileInfos(std::string(data.begin(), data.end())));
   for (auto& info : file_infos) {
     result.push_back(ui::FileInfo(std::move(info.path), base::FilePath()));
   }
@@ -345,7 +331,7 @@
   }
 
   ShareAndTranslateHostToVM(
-      target, CrackPaths(std::move(paths)),
+      GetVmName(target), CrackPaths(std::move(paths)),
       base::BindOnce(&SendAfterShare, target, std::move(callback)));
 }
 
@@ -379,11 +365,16 @@
   }
 
   ShareAndTranslateHostToVM(
-      target, std::move(file_infos),
+      GetVmName(target), std::move(file_infos),
       base::BindOnce(&SendAfterShare, target, std::move(callback)));
 }
 
-std::string ChromeSecurityDelegate::GetVmName() const {
+std::string ChromeSecurityDelegate::GetVmName(ui::EndpointType target) const {
+  if (target == ui::EndpointType::kArc) {
+    return arc::kArcVmName;
+  } else if (target == ui::EndpointType::kPluginVm) {
+    return plugin_vm::kPluginVmName;
+  }
   return std::string();
 }
 
diff --git a/chrome/browser/ash/exo/chrome_security_delegate.h b/chrome/browser/ash/exo/chrome_security_delegate.h
index da2d6e57..f373b8e 100644
--- a/chrome/browser/ash/exo/chrome_security_delegate.h
+++ b/chrome/browser/ash/exo/chrome_security_delegate.h
@@ -13,14 +13,14 @@
 // Translate paths from |source| VM to valid paths in the host. Invalid paths
 // are ignored.
 std::vector<base::FilePath> TranslateVMPathsToHost(
-    ui::EndpointType source,
+    const std::string& vm_name,
     const std::vector<ui::FileInfo>& vm_paths);
 
 // Share |files| with |target| VM, and translate |files| to be "file://" URLs
 // which can be used inside the vm. |callback| is invoked with translated
 // "file://" URLs.
 void ShareWithVMAndTranslateToFileUrls(
-    ui::EndpointType target,
+    const std::string& vm_name,
     const std::vector<base::FilePath>& files,
     base::OnceCallback<void(std::vector<std::string>)> callback);
 
@@ -45,7 +45,7 @@
                   const base::Pickle& pickle,
                   SendDataCallback callback) override;
 
-  virtual std::string GetVmName() const;
+  virtual std::string GetVmName(ui::EndpointType target) const;
 };
 
 }  // namespace ash
diff --git a/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc b/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
index 16a81fe7..ad97c426 100644
--- a/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
+++ b/chrome/browser/ash/exo/chrome_security_delegate_unittest.cc
@@ -11,10 +11,14 @@
 #include "base/memory/ref_counted_memory.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/bind.h"
+#include "chrome/browser/ash/bruschetta/bruschetta_util.h"
 #include "chrome/browser/ash/crostini/crostini_manager.h"
+#include "chrome/browser/ash/crostini/crostini_security_delegate.h"
 #include "chrome/browser/ash/crostini/crostini_test_helper.h"
 #include "chrome/browser/ash/crostini/crostini_util.h"
 #include "chrome/browser/ash/file_manager/path_util.h"
+#include "chrome/browser/ash/guest_os/guest_os_security_delegate.h"
 #include "chrome/browser/ash/guest_os/guest_os_share_path.h"
 #include "chrome/browser/ash/plugin_vm/plugin_vm_util.h"
 #include "chrome/test/base/testing_profile.h"
@@ -162,6 +166,8 @@
                                           shared_path);
   guest_os_share_path->RegisterSharedPath(plugin_vm::kPluginVmName,
                                           shared_path);
+  guest_os_share_path->RegisterSharedPath(bruschetta::kBruschettaVmName,
+                                          shared_path);
 
   // Multiple lines should be parsed.
   // Arc should not translate paths.
@@ -175,30 +181,31 @@
   EXPECT_EQ("", files[1].display_name.value());
 
   // Crostini shared paths should be mapped.
-  files = security_delegate.GetFilenames(
+  guest_os::GuestOsSecurityDelegate crostini_security_delegate("termina");
+  files = crostini_security_delegate.GetFilenames(
       ui::EndpointType::kCrostini,
       Data("file:///mnt/chromeos/MyFiles/shared/file"));
   EXPECT_EQ(1u, files.size());
   EXPECT_EQ(shared_path.Append("file"), files[0].path);
 
   // Crostini homedir should be mapped.
-  files = security_delegate.GetFilenames(ui::EndpointType::kCrostini,
-                                         Data("file:///home/testuser/file"));
+  files = crostini_security_delegate.GetFilenames(
+      ui::EndpointType::kCrostini, Data("file:///home/testuser/file"));
   EXPECT_EQ(1u, files.size());
   EXPECT_EQ(crostini_dir_.Append("file"), files[0].path);
 
   // Crostini internal paths should be mapped.
-  files = security_delegate.GetFilenames(ui::EndpointType::kCrostini,
-                                         Data("file:///etc/hosts"));
+  files = crostini_security_delegate.GetFilenames(ui::EndpointType::kCrostini,
+                                                  Data("file:///etc/hosts"));
   EXPECT_EQ(1u, files.size());
   EXPECT_EQ("vmfile:termina:/etc/hosts", files[0].path.value());
 
   // Unshared paths should fail.
-  files = security_delegate.GetFilenames(
+  files = crostini_security_delegate.GetFilenames(
       ui::EndpointType::kCrostini,
       Data("file:///mnt/chromeos/MyFiles/unshared/file"));
   EXPECT_EQ(0u, files.size());
-  files = security_delegate.GetFilenames(
+  files = crostini_security_delegate.GetFilenames(
       ui::EndpointType::kCrostini,
       Data("file:///mnt/chromeos/MyFiles/shared/file1\r\n"
            "file:///mnt/chromeos/MyFiles/unshared/file2"));
@@ -206,29 +213,29 @@
   EXPECT_EQ(shared_path.Append("file1"), files[0].path);
 
   // file:/path should fail.
-  files = security_delegate.GetFilenames(
+  files = crostini_security_delegate.GetFilenames(
       ui::EndpointType::kCrostini, Data("file:/mnt/chromeos/MyFiles/file"));
   EXPECT_EQ(0u, files.size());
 
   // file:path should fail.
-  files = security_delegate.GetFilenames(
+  files = crostini_security_delegate.GetFilenames(
       ui::EndpointType::kCrostini, Data("file:mnt/chromeos/MyFiles/file"));
   EXPECT_EQ(0u, files.size());
 
   // file:// should fail.
-  files = security_delegate.GetFilenames(ui::EndpointType::kCrostini,
-                                         Data("file://"));
+  files = crostini_security_delegate.GetFilenames(ui::EndpointType::kCrostini,
+                                                  Data("file://"));
   EXPECT_EQ(0u, files.size());
 
   // file:/// maps to internal root.
-  files = security_delegate.GetFilenames(ui::EndpointType::kCrostini,
-                                         Data("file:///"));
+  files = crostini_security_delegate.GetFilenames(ui::EndpointType::kCrostini,
+                                                  Data("file:///"));
   EXPECT_EQ(1u, files.size());
   EXPECT_EQ("vmfile:termina:/", files[0].path.value());
 
   // /path should fail.
-  files = security_delegate.GetFilenames(ui::EndpointType::kCrostini,
-                                         Data("/mnt/chromeos/MyFiles/file"));
+  files = crostini_security_delegate.GetFilenames(
+      ui::EndpointType::kCrostini, Data("/mnt/chromeos/MyFiles/file"));
   EXPECT_EQ(0u, files.size());
 
   // Plugin VM shared paths should be mapped.
@@ -254,6 +261,38 @@
            "file://ChromeOS/MyFiles/unshared/file2"));
   EXPECT_EQ(1u, files.size());
   EXPECT_EQ(shared_path.Append("file1"), files[0].path);
+
+  // Bruschetta shared paths should be mapped.
+  guest_os::GuestOsSecurityDelegate bru_security_delegate("bru");
+  files = bru_security_delegate.GetFilenames(
+      ui::EndpointType::kCrostini,
+      Data("file:///mnt/shared/MyFiles/shared/file"));
+  EXPECT_EQ(1u, files.size());
+  EXPECT_EQ(shared_path.Append("file"), files[0].path);
+
+  // Bruschetta homedir is mapped as an internal path.
+  files = bru_security_delegate.GetFilenames(
+      ui::EndpointType::kCrostini, Data("file:///home/testuser/file"));
+  EXPECT_EQ(1u, files.size());
+  EXPECT_EQ("vmfile:bru:/home/testuser/file", files[0].path.value());
+
+  // Bruschetta internal paths should be mapped.
+  files = bru_security_delegate.GetFilenames(ui::EndpointType::kCrostini,
+                                             Data("file:///etc/hosts"));
+  EXPECT_EQ(1u, files.size());
+  EXPECT_EQ("vmfile:bru:/etc/hosts", files[0].path.value());
+
+  // Unshared paths should fail.
+  files = bru_security_delegate.GetFilenames(
+      ui::EndpointType::kCrostini,
+      Data("file:///mnt/shared/MyFiles/unshared/file"));
+  EXPECT_EQ(0u, files.size());
+  files = bru_security_delegate.GetFilenames(
+      ui::EndpointType::kCrostini,
+      Data("file:///mnt/shared/MyFiles/shared/file1\r\n"
+           "file:///mnt/shared/MyFiles/unshared/file2"));
+  EXPECT_EQ(1u, files.size());
+  EXPECT_EQ(shared_path.Append("file1"), files[0].path);
 }
 
 TEST_F(ChromeSecurityDelegateTest, SendFileInfoConvertPaths) {
@@ -288,14 +327,16 @@
       data);
 
   // Crostini should convert path to inside VM, and share the path.
-  security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
-                                 base::BindOnce(&Capture, &data));
+  guest_os::GuestOsSecurityDelegate crostini_security_delegate("termina");
+  crostini_security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
+                                          base::BindOnce(&Capture, &data));
   task_environment_.RunUntilIdle();
   EXPECT_EQ("file:///mnt/chromeos/MyFiles/file1", data);
 
   // Crostini should join lines with CRLF.
-  security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1, file2},
-                                 base::BindOnce(&Capture, &data));
+  crostini_security_delegate.SendFileInfo(ui::EndpointType::kCrostini,
+                                          {file1, file2},
+                                          base::BindOnce(&Capture, &data));
   task_environment_.RunUntilIdle();
   EXPECT_EQ(
       "file:///mnt/chromeos/MyFiles/file1"
@@ -309,17 +350,24 @@
   task_environment_.RunUntilIdle();
   EXPECT_EQ("file://ChromeOS/MyFiles/file1", data);
 
+  // Bruschetta should convert path to inside VM, and share the path.
+  guest_os::GuestOsSecurityDelegate bru_security_delegate("bru");
+  bru_security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
+                                     base::BindOnce(&Capture, &data));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ("file:///mnt/shared/MyFiles/file1", data);
+
   // Crostini should handle vmfile:termina:/etc/hosts.
   file1.path = base::FilePath("vmfile:termina:/etc/hosts");
-  security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
-                                 base::BindOnce(&Capture, &data));
+  crostini_security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
+                                          base::BindOnce(&Capture, &data));
   task_environment_.RunUntilIdle();
   EXPECT_EQ("file:///etc/hosts", data);
 
   // Crostini should ignore vmfile:PvmDefault:C:/WINDOWS/notepad.exe.
   file1.path = base::FilePath("vmfile:PvmDefault:C:/WINDOWS/notepad.exe");
-  security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
-                                 base::BindOnce(&Capture, &data));
+  crostini_security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
+                                          base::BindOnce(&Capture, &data));
   task_environment_.RunUntilIdle();
   EXPECT_EQ("", data);
 
@@ -336,10 +384,24 @@
                                  base::BindOnce(&Capture, &data));
   task_environment_.RunUntilIdle();
   EXPECT_EQ("", data);
+
+  // Bruschetta should handle vmfile:bru:/etc/hosts.
+  file1.path = base::FilePath("vmfile:bru:/etc/hosts");
+  bru_security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
+                                     base::BindOnce(&Capture, &data));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ("file:///etc/hosts", data);
+
+  // Bruschetta should ignore vmfile:termina:/etc/hosts.
+  file1.path = base::FilePath("vmfile:termina:/etc/hosts");
+  bru_security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file1},
+                                     base::BindOnce(&Capture, &data));
+  task_environment_.RunUntilIdle();
+  EXPECT_EQ("", data);
 }
 
 TEST_F(ChromeSecurityDelegateTest, SendFileInfoSharePathsCrostini) {
-  ChromeSecurityDelegate security_delegate;
+  guest_os::GuestOsSecurityDelegate crostini_security_delegate("termina");
 
   // A path which is already shared should not be shared again.
   base::FilePath shared_path = myfiles_dir_.Append("shared");
@@ -350,16 +412,16 @@
   ui::FileInfo file(shared_path, base::FilePath());
   EXPECT_FALSE(FakeSeneschalClient::Get()->share_path_called());
   std::string data;
-  security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file},
-                                 base::BindOnce(&Capture, &data));
+  crostini_security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file},
+                                          base::BindOnce(&Capture, &data));
   task_environment_.RunUntilIdle();
   EXPECT_EQ("file:///mnt/chromeos/MyFiles/shared", data);
   EXPECT_FALSE(FakeSeneschalClient::Get()->share_path_called());
 
   // A path which is not already shared should be shared.
   file = ui::FileInfo(myfiles_dir_.Append("file"), base::FilePath());
-  security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file},
-                                 base::BindOnce(&Capture, &data));
+  crostini_security_delegate.SendFileInfo(ui::EndpointType::kCrostini, {file},
+                                          base::BindOnce(&Capture, &data));
   task_environment_.RunUntilIdle();
   EXPECT_EQ("file:///mnt/chromeos/MyFiles/file", data);
   EXPECT_TRUE(FakeSeneschalClient::Get()->share_path_called());
diff --git a/chrome/browser/ash/extensions/file_manager/event_router.cc b/chrome/browser/ash/extensions/file_manager/event_router.cc
index 5c1127b..4d70ef90 100644
--- a/chrome/browser/ash/extensions/file_manager/event_router.cc
+++ b/chrome/browser/ash/extensions/file_manager/event_router.cc
@@ -58,6 +58,7 @@
 #include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/extensions/api/file_manager_private.h"
 #include "chrome/common/extensions/extension_constants.h"
@@ -1570,4 +1571,11 @@
                  fmp::OnDeviceConnectionStatusChanged::Create(result));
 }
 
+void EventRouter::OnLocalUserFilesPolicyChanged() {
+  if (!base::FeatureList::IsEnabled(features::kSkyVault)) {
+    return;
+  }
+  OnFileManagerPrefsChanged();
+}
+
 }  // namespace file_manager
diff --git a/chrome/browser/ash/extensions/file_manager/event_router.h b/chrome/browser/ash/extensions/file_manager/event_router.h
index eb42f320..ffc14ef 100644
--- a/chrome/browser/ash/extensions/file_manager/event_router.h
+++ b/chrome/browser/ash/extensions/file_manager/event_router.h
@@ -30,6 +30,7 @@
 #include "chrome/browser/ash/guest_os/guest_os_share_path.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_mount_provider_registry.h"
+#include "chrome/browser/ash/policy/local_user_files/observer.h"
 #include "chrome/common/extensions/api/file_manager_private.h"
 #include "chromeos/ash/components/settings/timezone_settings.h"
 #include "chromeos/dbus/dlp/dlp_client.h"
@@ -77,7 +78,8 @@
       guest_os::GuestOsMountProviderRegistry::Observer,
       chromeos::DlpClient::Observer,
       apps::AppRegistryCache::Observer,
-      network::NetworkConnectionTracker::NetworkConnectionObserver {
+      network::NetworkConnectionTracker::NetworkConnectionObserver,
+      policy::local_user_files::Observer {
  public:
   using DispatchDirectoryChangeEventImplCallback =
       base::RepeatingCallback<void(const base::FilePath& virtual_path,
@@ -218,6 +220,9 @@
   // network::NetworkConnectionTracker::NetworkConnectionObserver:
   void OnConnectionChanged(const network::mojom::ConnectionType type) override;
 
+  // policy::local_user_files::Observer:
+  void OnLocalUserFilesPolicyChanged() override;
+
   // Use this method for unit tests to bypass checking if there are any SWA
   // windows.
   void ForceBroadcastingForTesting(bool enabled) {
diff --git a/chrome/browser/ash/extensions/file_manager/event_router_unittest.cc b/chrome/browser/ash/extensions/file_manager/event_router_unittest.cc
index b51dda5..4d1376f0 100644
--- a/chrome/browser/ash/extensions/file_manager/event_router_unittest.cc
+++ b/chrome/browser/ash/extensions/file_manager/event_router_unittest.cc
@@ -19,6 +19,10 @@
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ash/file_manager/volume_manager_factory.h"
 #include "chrome/browser/ash/fileapi/file_system_backend.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/ash/components/disks/fake_disk_mount_manager.h"
 #include "content/public/test/browser_task_environment.h"
@@ -115,7 +119,8 @@
 
 class FileManagerEventRouterTest : public testing::Test {
  public:
-  FileManagerEventRouterTest() = default;
+  FileManagerEventRouterTest()
+      : scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()) {}
   FileManagerEventRouterTest(const FileManagerEventRouterTest&) = delete;
   FileManagerEventRouterTest& operator=(const FileManagerEventRouterTest&) =
       delete;
@@ -162,8 +167,9 @@
     return io_task::EntryStatus(std::move(url), base::File::FILE_OK);
   }
 
+  ScopedTestingLocalState scoped_testing_local_state_;
   content::BrowserTaskEnvironment task_environment_;
-  display::test::TestScreen test_screen_{/*create_dispay=*/true,
+  display::test::TestScreen test_screen_{/*create_display=*/true,
                                          /*register_screen=*/true};
   base::ScopedTempDir temp_dir_;
   std::unique_ptr<TestingProfile> profile_;
@@ -173,6 +179,10 @@
   ash::disks::FakeDiskMountManager disk_mount_manager_;
 };
 
+MATCHER(ExpectNoArgs, "") {
+  return arg.size() == 0;
+}
+
 // A matcher that matches an `extensions::Event::event_args` and attempts to
 // extract the "outputs" key. It then looks at the output at `index` and matches
 // the `field` against the `expected_value`.
@@ -390,5 +400,40 @@
   run_loop.Run();
 }
 
+class FileManagerEventRouterLocalFilesTest : public FileManagerEventRouterTest {
+ public:
+  FileManagerEventRouterLocalFilesTest() {
+    scoped_feature_list_.InitAndEnableFeature(features::kSkyVault);
+  }
+  ~FileManagerEventRouterLocalFilesTest() override = default;
+
+  void SetLocalUserFilesPolicy(bool allowed) {
+    scoped_testing_local_state_.Get()->SetBoolean(prefs::kLocalUserFilesAllowed,
+                                                  allowed);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(FileManagerEventRouterLocalFilesTest, OnLocalUserFilesPolicyChanged) {
+  // Set up event routers.
+  extensions::TestEventRouter* test_event_router =
+      extensions::CreateAndUseTestEventRouter(profile_.get());
+  TestEventRouterObserver observer(test_event_router);
+  auto event_router = std::make_unique<EventRouter>(profile_.get());
+  event_router->ForceBroadcastingForTesting(true);
+
+  // Expect the preferences changed event.
+  base::Value::List event_args =
+      extensions::api::file_manager_private::OnPreferencesChanged::Create();
+  base::RunLoop run_loop;
+  EXPECT_CALL(observer, OnBroadcastEvent(Field(&extensions::Event::event_args,
+                                               AllOf(ExpectNoArgs()))))
+      .WillOnce(RunClosure(run_loop.QuitClosure()));
+  SetLocalUserFilesPolicy(/*allowed=*/false);
+  run_loop.Run();
+}
+
 }  // namespace
 }  // namespace file_manager
diff --git a/chrome/browser/ash/file_manager/copy_or_move_io_task_unittest.cc b/chrome/browser/ash/file_manager/copy_or_move_io_task_unittest.cc
index 8d2b898..2270719 100644
--- a/chrome/browser/ash/file_manager/copy_or_move_io_task_unittest.cc
+++ b/chrome/browser/ash/file_manager/copy_or_move_io_task_unittest.cc
@@ -28,6 +28,8 @@
 #include "chrome/browser/ash/file_manager/path_util.h"
 #include "chrome/browser/ash/file_manager/volume_manager.h"
 #include "chrome/browser/ash/file_manager/volume_manager_factory.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/ash/components/disks/fake_disk_mount_manager.h"
 #include "content/public/test/browser_task_environment.h"
@@ -91,6 +93,10 @@
 }
 
 class CopyOrMoveIOTaskTestBase : public testing::Test {
+ public:
+  CopyOrMoveIOTaskTestBase()
+      : scoped_testing_local_state_(TestingBrowserProcess::GetGlobal()) {}
+
  protected:
   void SetUp() override {
     // Define a VolumeManager to associate with the testing profile.
@@ -110,6 +116,7 @@
         base::FilePath::FromUTF8Unsafe(path));
   }
 
+  ScopedTestingLocalState scoped_testing_local_state_;
   content::BrowserTaskEnvironment task_environment_;
   ash::disks::FakeDiskMountManager disk_mount_manager_;
   TestingProfile profile_;
diff --git a/chrome/browser/ash/file_manager/volume_manager.cc b/chrome/browser/ash/file_manager/volume_manager.cc
index e41d823..a51d1441 100644
--- a/chrome/browser/ash/file_manager/volume_manager.cc
+++ b/chrome/browser/ash/file_manager/volume_manager.cc
@@ -10,6 +10,7 @@
 #include "ash/constants/ash_features.h"
 #include "base/auto_reset.h"
 #include "base/base64url.h"
+#include "base/feature_list.h"
 #include "base/files/file_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
@@ -31,8 +32,10 @@
 #include "chrome/browser/ash/file_manager/snapshot_manager.h"
 #include "chrome/browser/ash/file_manager/volume_manager_factory.h"
 #include "chrome/browser/ash/file_manager/volume_manager_observer.h"
+#include "chrome/browser/ash/policy/local_user_files/policy_utils.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h"
+#include "chrome/common/chrome_features.h"
 #include "chromeos/components/disks/disks_prefs.h"
 #include "components/prefs/pref_service.h"
 #include "components/storage_monitor/storage_monitor.h"
@@ -957,6 +960,17 @@
   *volume_id = volume_id->substr(prefix).insert(0, "provided:");
 }
 
+void VolumeManager::OnLocalUserFilesPolicyChanged() {
+  if (!base::FeatureList::IsEnabled(features::kSkyVault)) {
+    return;
+  }
+  if (policy::local_user_files::LocalUserFilesAllowed()) {
+    MountLocalFolders();
+  } else {
+    UnmountLocalFolders();
+  }
+}
+
 void VolumeManager::OnProvidedFileSystemUnmount(
     const ash::file_system_provider::ProvidedFileSystemInfo& file_system_info,
     base::File::Error error) {
@@ -1597,4 +1611,16 @@
   }
 }
 
+void VolumeManager::MountLocalFolders() {
+  // TODO(b/322779059): Mount arc.
+  // TODO(b/322779967): Mount crostini.
+  // TODO(b/325006828): Mount My Files.
+}
+
+void VolumeManager::UnmountLocalFolders() {
+  // TODO(b/322779059): Unmount arc.
+  // TODO(b/322779967): Unmount crostini.
+  // TODO(b/325006828): Unmount My Files.
+}
+
 }  // namespace file_manager
diff --git a/chrome/browser/ash/file_manager/volume_manager.h b/chrome/browser/ash/file_manager/volume_manager.h
index b0af976..429b71b 100644
--- a/chrome/browser/ash/file_manager/volume_manager.h
+++ b/chrome/browser/ash/file_manager/volume_manager.h
@@ -26,6 +26,7 @@
 #include "chrome/browser/ash/file_system_provider/observer.h"
 #include "chrome/browser/ash/file_system_provider/service.h"
 #include "chrome/browser/ash/guest_os/public/types.h"
+#include "chrome/browser/ash/policy/local_user_files/observer.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/storage_monitor/removable_storage_observer.h"
 #include "services/device/public/mojom/mtp_manager.mojom.h"
@@ -63,7 +64,8 @@
                       ash::file_system_provider::Observer,
                       storage_monitor::RemovableStorageObserver,
                       ui::ClipboardObserver,
-                      DocumentsProviderRootManager::Observer {
+                      DocumentsProviderRootManager::Observer,
+                      policy::local_user_files::Observer {
  public:
   // An alternate to device::mojom::MtpManager::GetStorageInfo.
   // Used for injecting fake MTP manager for testing in VolumeManagerTest.
@@ -263,6 +265,9 @@
 
   void ConvertFuseBoxFSPVolumeIdToFSPIfNeeded(std::string* volume_id) const;
 
+  // policy::local_user_files::Observer:
+  void OnLocalUserFilesPolicyChanged() override;
+
   SnapshotManager* snapshot_manager() { return snapshot_manager_.get(); }
 
   io_task::IOTaskController* io_task_controller() {
@@ -337,6 +342,11 @@
                                     RemoveSftpGuestOsVolumeCallback callback,
                                     ash::MountError error);
 
+  // Mounts local folders (My Files, Play and Linux files).
+  void MountLocalFolders();
+  // Unmounts local folders (My Files, Play and Linux files).
+  void UnmountLocalFolders();
+
   static int counter_;
   const int id_ = ++counter_;  // Only used in log traces
 
diff --git a/chrome/browser/ash/guest_os/guest_os_security_delegate.cc b/chrome/browser/ash/guest_os/guest_os_security_delegate.cc
index bf9b6b1..f1ec6b76 100644
--- a/chrome/browser/ash/guest_os/guest_os_security_delegate.cc
+++ b/chrome/browser/ash/guest_os/guest_os_security_delegate.cc
@@ -34,7 +34,7 @@
       base::BindOnce(std::move(callback), cap_ptr));
 }
 
-std::string GuestOsSecurityDelegate::GetVmName() const {
+std::string GuestOsSecurityDelegate::GetVmName(ui::EndpointType target) const {
   return vm_name_;
 }
 
diff --git a/chrome/browser/ash/guest_os/guest_os_security_delegate.h b/chrome/browser/ash/guest_os/guest_os_security_delegate.h
index f1f77415..2a6222e5 100644
--- a/chrome/browser/ash/guest_os/guest_os_security_delegate.h
+++ b/chrome/browser/ash/guest_os/guest_os_security_delegate.h
@@ -36,7 +36,8 @@
                               std::unique_ptr<exo::WaylandServerHandle>)>
           callback);
 
-  std::string GetVmName() const override;
+  // ash::ChromeSecurityDelegate:
+  std::string GetVmName(ui::EndpointType target) const override;
 
  private:
   std::string vm_name_;
diff --git a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
index 242e155..c1d023e 100644
--- a/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
+++ b/chrome/browser/ash/login/oobe_quick_start/target_device_bootstrap_controller_unittest.cc
@@ -789,6 +789,7 @@
 
 TEST_F(TargetDeviceBootstrapControllerTest,
        NoUserVerificationRequiredWhenResumeAfterUpdate) {
+  GetSessionContext()->FillOrResetSession();
   ResumeAfterUpdate();
   bootstrap_controller_->StartAdvertisingAndMaybeGetQRCode();
   EXPECT_EQ(fake_observer_->last_status.step,
diff --git a/chrome/browser/ash/policy/local_user_files/observer.cc b/chrome/browser/ash/policy/local_user_files/observer.cc
index aec4c99..5c840be8 100644
--- a/chrome/browser/ash/policy/local_user_files/observer.cc
+++ b/chrome/browser/ash/policy/local_user_files/observer.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/policy/local_user_files/observer.h"
 
+#include "base/check_is_test.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/common/pref_names.h"
 
@@ -11,6 +12,11 @@
 
 Observer::Observer()
     : pref_change_registrar_(std::make_unique<PrefChangeRegistrar>()) {
+  if (!g_browser_process->local_state()) {
+    // Can be NULL in tests.
+    CHECK_IS_TEST();
+    return;
+  }
   pref_change_registrar_->Init(g_browser_process->local_state());
   pref_change_registrar_->Add(
       prefs::kLocalUserFilesAllowed,
diff --git a/chrome/browser/autocomplete/in_memory_url_index_factory.cc b/chrome/browser/autocomplete/in_memory_url_index_factory.cc
index b6979852..4e4646ac 100644
--- a/chrome/browser/autocomplete/in_memory_url_index_factory.cc
+++ b/chrome/browser/autocomplete/in_memory_url_index_factory.cc
@@ -9,6 +9,7 @@
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "components/bookmarks/browser/bookmark_model.h"
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/omnibox/browser/in_memory_url_index.h"
 #include "content/public/common/url_constants.h"
diff --git a/chrome/browser/compose/compose_session.cc b/chrome/browser/compose/compose_session.cc
index 4a67139..6eee182 100644
--- a/chrome/browser/compose/compose_session.cc
+++ b/chrome/browser/compose/compose_session.cc
@@ -35,6 +35,7 @@
 #include "components/compose/core/browser/compose_features.h"
 #include "components/compose/core/browser/compose_manager_impl.h"
 #include "components/compose/core/browser/compose_metrics.h"
+#include "components/compose/core/browser/compose_utils.h"
 #include "components/compose/core/browser/config.h"
 #include "components/optimization_guide/core/model_execution/optimization_guide_model_execution_error.h"
 #include "components/optimization_guide/core/model_quality/feature_type_map.h"
@@ -61,20 +62,8 @@
     return false;
   }
 
-  base::StringTokenizer tokenizer(
-      prompt, " ", base::StringTokenizer::WhitespacePolicy::kSkipOver);
-  unsigned int word_count = 0;
-  while (tokenizer.GetNext()) {
-    ++word_count;
-    if (word_count > config.input_max_words) {
-      return false;
-    }
-  }
-
-  if (word_count < config.input_min_words) {
-    return false;
-  }
-  return true;
+  return compose::IsWordCountWithinBounds(prompt, config.input_min_words,
+                                          config.input_max_words);
 }
 
 const char kComposeBugReportURL[] = "https://goto.google.com/ccbrfd";
diff --git a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
index 69aa2af..a26c98c 100644
--- a/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
+++ b/chrome/browser/extensions/api/declarative_net_request/declarative_net_request_browsertest.cc
@@ -13,6 +13,7 @@
 #include <vector>
 
 #include "base/containers/contains.h"
+#include "base/containers/to_value_list.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/bind.h"
@@ -146,15 +147,6 @@
 
 constexpr char kDefaultRulesetID[] = "id";
 
-template <class T>
-base::Value::List VectorToList(const std::vector<T>& values) {
-  base::Value::List lv;
-  for (const auto& value : values) {
-    lv.Append(value);
-  }
-  return lv;
-}
-
 // Returns true if |window.scriptExecuted| is true for the given frame.
 bool WasFrameWithScriptLoaded(content::RenderFrameHost* render_frame_host) {
   if (!render_frame_host) {
@@ -462,8 +454,8 @@
           });
     )";
 
-    base::Value::List ids_to_disable = VectorToList(rule_ids_to_disable);
-    base::Value::List ids_to_enable = VectorToList(rule_ids_to_enable);
+    base::Value::List ids_to_disable = base::ToValueList(rule_ids_to_disable);
+    base::Value::List ids_to_enable = base::ToValueList(rule_ids_to_enable);
 
     const std::string script = content::JsReplace(
         kScript, ruleset_id, base::Value(std::move(ids_to_disable)),
@@ -505,7 +497,7 @@
                 : ['expected:', expected, '; actual:', actual].join(''));
           });
     )";
-    base::Value::List expected = VectorToList(expected_disabled_rule_ids);
+    base::Value::List expected = base::ToValueList(expected_disabled_rule_ids);
     std::string result = ExecuteScriptInBackgroundPageAndReturnString(
         extension_id,
         content::JsReplace(kScript, ruleset_id_string, std::move(expected)));
@@ -743,15 +735,6 @@
         });
     )";
 
-    // Serialize |rules_to_add|.
-    base::Value::List rules_to_add_builder;
-    for (const auto& rule : rules_to_add)
-      rules_to_add_builder.Append(rule.ToValue());
-
-    // Serialize |rule_ids|.
-    base::Value::List rule_ids_to_remove_value =
-        VectorToList(rule_ids_to_remove);
-
     const char* function_name = nullptr;
     switch (scope) {
       case RulesetScope::kDynamic:
@@ -764,8 +747,8 @@
 
     const std::string script =
         content::JsReplace(base::StringPrintf(kScript, function_name),
-                           base::Value(std::move(rules_to_add_builder)),
-                           base::Value(std::move(rule_ids_to_remove_value)));
+                           base::ToValueList(rules_to_add, &TestRule::ToValue),
+                           base::ToValueList(rule_ids_to_remove));
     ASSERT_EQ("success", ExecuteScriptInBackgroundPageAndReturnString(
                              extension_id, script));
   }
@@ -898,12 +881,9 @@
       });
     )";
 
-    base::Value::List ids_to_remove = VectorToList(ruleset_ids_to_remove);
-    base::Value::List ids_to_add = VectorToList(ruleset_ids_to_add);
-
     const std::string script =
-        content::JsReplace(kScript, base::Value(std::move(ids_to_remove)),
-                           base::Value(std::move(ids_to_add)));
+        content::JsReplace(kScript, base::ToValueList(ruleset_ids_to_remove),
+                           base::ToValueList(ruleset_ids_to_add));
     return ExecuteScriptInBackgroundPageAndReturnString(extension_id, script);
   }
 
diff --git a/chrome/browser/extensions/install_signer.cc b/chrome/browser/extensions/install_signer.cc
index e58671a2..68d2c02 100644
--- a/chrome/browser/extensions/install_signer.cc
+++ b/chrome/browser/extensions/install_signer.cc
@@ -12,6 +12,7 @@
 
 #include "base/base64.h"
 #include "base/command_line.h"
+#include "base/containers/to_value_list.h"
 #include "base/json/json_reader.h"
 #include "base/json/json_writer.h"
 #include "base/json/values_util.h"
@@ -119,15 +120,6 @@
   return true;
 }
 
-// Helper for serialization of ExtensionIdSets to/from a base::Value::List.
-[[nodiscard]] base::Value::List ExtensionIdSetToList(
-    const ExtensionIdSet& ids) {
-  base::Value::List id_list;
-  base::ranges::for_each(ids,
-                         [&id_list](const auto& id) { id_list.Append(id); });
-  return id_list;
-}
-
 [[nodiscard]] std::optional<ExtensionIdSet> ExtensionIdSetFromList(
     const base::Value::List& list) {
   ExtensionIdSet ids;
@@ -150,8 +142,8 @@
 base::Value::Dict InstallSignature::ToDict() const {
   base::Value::Dict dict;
   dict.Set(kSignatureFormatVersionKey, kSignatureFormatVersion);
-  dict.Set(kIdsKey, ExtensionIdSetToList(ids));
-  dict.Set(kInvalidIdsKey, ExtensionIdSetToList(invalid_ids));
+  dict.Set(kIdsKey, base::ToValueList(ids));
+  dict.Set(kInvalidIdsKey, base::ToValueList(invalid_ids));
   dict.Set(kExpireDateKey, expire_date);
   dict.Set(kSaltKey, base::Base64Encode(salt));
   dict.Set(kSignatureKey, base::Base64Encode(signature));
diff --git a/chrome/browser/extensions/menu_manager.cc b/chrome/browser/extensions/menu_manager.cc
index c3242c6..584dad2 100644
--- a/chrome/browser/extensions/menu_manager.cc
+++ b/chrome/browser/extensions/menu_manager.cc
@@ -10,6 +10,7 @@
 
 #include "base/check_op.h"
 #include "base/containers/contains.h"
+#include "base/containers/to_value_list.h"
 #include "base/functional/bind.h"
 #include "base/json/json_writer.h"
 #include "base/notreached.h"
@@ -97,13 +98,6 @@
   return items;
 }
 
-base::Value::List MenuItemsToValue(const MenuItem::List& items) {
-  base::Value::List list;
-  for (const auto* item : items)
-    list.Append(item->ToValue());
-  return list;
-}
-
 bool GetStringList(const base::Value::Dict& dict,
                    const std::string& key,
                    std::vector<std::string>* out) {
@@ -874,8 +868,9 @@
     observer.WillWriteToStorage(extension_key.extension_id);
 
   if (store_) {
-    store_->SetExtensionValue(extension_key.extension_id, kContextMenusKey,
-                              base::Value(MenuItemsToValue(all_items)));
+    store_->SetExtensionValue(
+        extension_key.extension_id, kContextMenusKey,
+        base::Value(base::ToValueList(all_items, &MenuItem::ToValue)));
   }
 }
 
diff --git a/chrome/browser/extensions/process_map_browsertest.cc b/chrome/browser/extensions/process_map_browsertest.cc
index 800591b3..b25598b 100644
--- a/chrome/browser/extensions/process_map_browsertest.cc
+++ b/chrome/browser/extensions/process_map_browsertest.cc
@@ -17,6 +17,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
 #include "extensions/browser/app_window/app_window.h"
 #include "extensions/browser/app_window/app_window_registry.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
@@ -221,6 +222,9 @@
       extension_dir->WriteFile(
           FILE_PATH_LITERAL("parent.html"),
           base::StringPrintf(kPageContent, e1_page_url.spec().c_str()));
+      // Create a page that is not listed as a web_accessible_resource.
+      extension_dir->WriteFile(FILE_PATH_LITERAL("private_page.html"),
+                               R"(<html>E2 Private</html>)");
       extension2 = LoadExtension(extension_dir->UnpackedPath());
       extension_dirs_.push_back(std::move(extension_dir));
     }
@@ -739,6 +743,28 @@
   // doesn't mean the extension it contains is "sandboxed".
   EXPECT_FALSE(ExtensionFrameIsSandboxed(main_frame));
   EXPECT_FALSE(ExtensionFrameIsSandboxed(sandboxed_child_frame));
+
+  // Attempt to have `extension1` (in `sandboxed_child_frame`) load a
+  // non-web-accessible resource from `extension2`. This should fail. The fact
+  // that the child is sandboxed doesn't matter.
+  GURL e2_private_page_url = extension2->GetResourceURL("private_page.html");
+  const char kJsScript[] =
+      R"(
+        frm = document.createElement('iframe');
+        frm.src = $1;
+        document.body.appendChild(frm);
+      )";
+  content::TestNavigationObserver observer(GetActiveTab(), 1);
+  EXPECT_TRUE(ExecJs(sandboxed_child_frame,
+                     content::JsReplace(kJsScript, e2_private_page_url)));
+  observer.Wait();
+
+  EXPECT_FALSE(observer.last_navigation_succeeded());
+  EXPECT_EQ(net::ERR_BLOCKED_BY_CLIENT, observer.last_net_error_code());
+  content::RenderFrameHost* grand_child_frame =
+      content::ChildFrameAt(sandboxed_child_frame, 0);
+  EXPECT_NE(nullptr, grand_child_frame);
+  EXPECT_EQ(e2_private_page_url, grand_child_frame->GetLastCommittedURL());
 }
 
 // Tests that sandboxed extension frames are considered privileged
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index e8936a0..37cedddd2 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1810,6 +1810,11 @@
     "expiry_milestone": -1
   },
   {
+    "name": "disable-fullscreen-scrolling",
+    "owners": [ "ajuma@google.com", "alionadangla@google.com", "bling-flags@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "disable-idle-sockets-close-on-memory-pressure",
     "owners": [ "pmarko@chromium.org" ],
     "expiry_milestone": 112
@@ -3440,12 +3445,12 @@
   {
     "name": "enable-save-to-drive",
     "owners": [ "qpubert@google.com", "djean@google.com", "olivierrobin@google.com", "bling-flags@google.com" ],
-    "expiry_milestone": 123
+    "expiry_milestone": 125
   },
   {
     "name": "enable-save-to-photos",
     "owners": [ "qpubert@google.com", "djean@google.com", "bling-flags@google.com" ],
-    "expiry_milestone": 123
+    "expiry_milestone": 125
   },
   {
     "name": "enable-seamless-refresh-rate-switching",
@@ -6618,14 +6623,6 @@
     "expiry_milestone": 123
   },
   {
-    "name": "privacy-guide-android",
-    "owners": [
-      "rubindl@chromium.org",
-      "msramek@chromium.org",
-      "chrome-privacy-controls@google.com"],
-    "expiry_milestone": 120
-  },
-  {
     "name": "privacy-guide-android-3",
     "owners": [
       "rubindl@chromium.org",
diff --git a/chrome/browser/media/cdm_pref_service_helper.cc b/chrome/browser/media/cdm_pref_service_helper.cc
index 1c187a8..c94ea011 100644
--- a/chrome/browser/media/cdm_pref_service_helper.cc
+++ b/chrome/browser/media/cdm_pref_service_helper.cc
@@ -7,6 +7,7 @@
 #include <optional>
 
 #include "base/base64.h"
+#include "base/containers/to_value_list.h"
 #include "base/json/values_util.h"
 #include "base/logging.h"
 #include "base/time/time.h"
@@ -35,15 +36,6 @@
   return time >= start && (end.is_null() || time <= end);
 }
 
-// Converts a std::vector<base::Time> to a base::Value::List
-base::Value::List TimesToList(const std::vector<base::Time>& times) {
-  base::Value::List times_list;
-  for (auto time : times) {
-    times_list.Append(base::TimeToValue(time));
-  }
-  return times_list;
-}
-
 // Converts a base::Value::List of Time to std::vector<base::Time>
 std::vector<base::Time> ListToTimes(const base::Value::List& time_list) {
   std::vector<base::Time> times;
@@ -90,7 +82,8 @@
              base::TimeToValue(pref_data.client_token_creation_time()));
   }
   dict.Set(prefs::kHardwareSecureDecryptionDisabledTimes,
-           TimesToList(pref_data.hw_secure_decryption_disable_times()));
+           base::ToValueList(pref_data.hw_secure_decryption_disable_times(),
+                             &base::TimeToValue));
   return dict;
 }
 
diff --git a/chrome/browser/password_manager/DEPS b/chrome/browser/password_manager/DEPS
index a5c67a3..f7cf4d6 100644
--- a/chrome/browser/password_manager/DEPS
+++ b/chrome/browser/password_manager/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+dbus",
+  "+components/device_reauth",
   "+components/webauthn/android",
 ]
 
diff --git a/chrome/browser/password_manager/android/OWNERS b/chrome/browser/password_manager/android/OWNERS
index a609b60..61b70fe7 100644
--- a/chrome/browser/password_manager/android/OWNERS
+++ b/chrome/browser/password_manager/android/OWNERS
@@ -1,2 +1,10 @@
 fhorschig@chromium.org
 ioanap@chromium.org
+
+per-file password_manager_android_util*=vsemeniuk@google.com
+per-file password_store_android_account_backend*=vsemeniuk@google.com
+# For this one use a '.', there are other files matching the prefix.
+per-file password_store_android_backend.*=vsemeniuk@google.com
+per-file password_store_android_local_backend*=vsemeniuk@google.com
+per-file password_store_backend_migration_decorator*=vsemeniuk@google.com
+per-file password_store_proxy_backend*=vsemeniuk@google.com
diff --git a/chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator.cc b/chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator.cc
index ab11bae..85f7157 100644
--- a/chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator.cc
+++ b/chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator.cc
@@ -517,6 +517,8 @@
       case MigrationType::kNonSyncableToBuiltInBackend:
         break;
     }
+    prefs_->SetBoolean(prefs::kShouldShowPostPasswordMigrationSheetAtStartup,
+                       true);
   }
 
   migration_in_progress_type_ = MigrationType::kNone;
diff --git a/chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator_unittest.cc b/chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator_unittest.cc
index 5c36e4d..dc9afcd 100644
--- a/chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator_unittest.cc
+++ b/chrome/browser/password_manager/android/built_in_backend_to_android_backend_migrator_unittest.cc
@@ -89,6 +89,8 @@
         prefs::kPasswordsUseUPMLocalAndSeparateStores,
         static_cast<int>(
             password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOff));
+    prefs_.registry()->RegisterBooleanPref(
+        prefs::kShouldShowPostPasswordMigrationSheetAtStartup, false);
     CreateMigrator(&built_in_backend_, &android_backend_, &prefs_);
   }
 
@@ -184,6 +186,8 @@
   EXPECT_EQ(
       base::Time::Now().InSecondsFSinceUnixEpoch(),
       prefs()->GetDouble(password_manager::prefs::kTimeOfLastMigrationAttempt));
+  EXPECT_TRUE(prefs()->GetBoolean(
+      prefs::kShouldShowPostPasswordMigrationSheetAtStartup));
 }
 
 TEST_F(BuiltInBackendToAndroidBackendMigratorTest,
@@ -700,6 +704,8 @@
         prefs::kPasswordsUseUPMLocalAndSeparateStores,
         static_cast<int>(
             password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOff));
+    prefs()->registry()->RegisterBooleanPref(
+        prefs::kShouldShowPostPasswordMigrationSheetAtStartup, false);
 
     if (GetParam().migration_ran_before) {
       // Setup the pref to indicate that the initial migration has happened
diff --git a/chrome/browser/password_manager/android/password_manager_android_util.cc b/chrome/browser/password_manager/android/password_manager_android_util.cc
index 0669b32..fbbfba6 100644
--- a/chrome/browser/password_manager/android/password_manager_android_util.cc
+++ b/chrome/browser/password_manager/android/password_manager_android_util.cc
@@ -264,8 +264,8 @@
   }
 }
 
-// Must only be called if the state pref is kOn, to set it to kOff if any of
-// these happened:
+// Must only be called if the state pref is kOn or kOffAndMigrationPending, to
+// set it to kOff if any of these happened:
 // - The user downgraded GmsCore and can no longer use the local UPM properly.
 // - The min GmsCore version for the A/B experiment was bumped server-side.
 // - The A/B experiment was stopped due to bugs.
@@ -273,7 +273,7 @@
 void MaybeDeactivateSplitStoresAndLocalUpm(
     PrefService* pref_service,
     const base::FilePath& login_db_directory) {
-  CHECK_EQ(GetSplitStoresAndLocalUpmPrefValue(pref_service), kOn);
+  CHECK_NE(GetSplitStoresAndLocalUpmPrefValue(pref_service), kOff);
 
   // Only deactivate based on the *NoMigration* flag.
   // - If problems arise when rolling out NoMigration (first launch), disable
@@ -332,7 +332,8 @@
       login_db_directory.Append(password_manager::kLoginDataForProfileFileName);
   base::FilePath account_db_path =
       login_db_directory.Append(password_manager::kLoginDataForAccountFileName);
-  if (IsPasswordSyncEnabled(pref_service) &&
+  if (GetSplitStoresAndLocalUpmPrefValue(pref_service) == kOn &&
+      IsPasswordSyncEnabled(pref_service) &&
       !base::ReplaceFile(account_db_path, profile_db_path, /*error=*/nullptr)) {
     return;
   }
@@ -359,12 +360,9 @@
 void SetUsesSplitStoresAndUPMForLocal(
     PrefService* pref_service,
     const base::FilePath& login_db_directory) {
-  if (GetSplitStoresAndLocalUpmPrefValue(pref_service) == kOn) {
+  if (GetSplitStoresAndLocalUpmPrefValue(pref_service) != kOff) {
     MaybeDeactivateSplitStoresAndLocalUpm(pref_service, login_db_directory);
   } else {
-    // Reset the migration pending state if needed.
-    pref_service->SetInteger(kPasswordsUseUPMLocalAndSeparateStores,
-                             static_cast<int>(kOff));
     MaybeActivateSplitStoresAndLocalUpm(pref_service, login_db_directory);
   }
 
diff --git a/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc b/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc
index 69dba2a..0d3593026 100644
--- a/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc
+++ b/chrome/browser/password_manager/android/password_manager_android_util_unittest.cc
@@ -572,6 +572,75 @@
 }
 
 TEST_F(PasswordManagerAndroidUtilTest,
+       SetUsesSplitStoresAndUPMForLocal_KeepMigrationPendingIfSyncEnabled) {
+  // Set up a user who was signed out with saved passwords (thus got into
+  // kOffAndMigrationPending), failed to migrate (thus stayed in
+  // kOffAndMigrationPending) and later enabled sync.
+  // kLoginDataForAccountFileName exists because the account store was created
+  // when the migration got scheduled, even if it was never used.
+  base::HistogramTester histogram_tester;
+  base::test::ScopedFeatureList enable_local_upm;
+  enable_local_upm.InitWithFeatures(
+      {password_manager::features::
+           kUnifiedPasswordManagerLocalPasswordsAndroidNoMigration,
+       password_manager::features::
+           kUnifiedPasswordManagerLocalPasswordsAndroidWithMigration},
+      {});
+  pref_service()->SetInteger(kPasswordsUseUPMLocalAndSeparateStores,
+                             static_cast<int>(kOffAndMigrationPending));
+  pref_service()->SetBoolean(
+      password_manager::prefs::kEmptyProfileStoreLoginDatabase, false);
+  SetPasswordSyncEnabledPref(true);
+  base::WriteFile(login_db_directory().Append(
+                      password_manager::kLoginDataForAccountFileName),
+                  "");
+  ASSERT_TRUE(base::PathExists(login_db_directory().Append(
+      password_manager::kLoginDataForProfileFileName)));
+  ASSERT_TRUE(base::PathExists(login_db_directory().Append(
+      password_manager::kLoginDataForAccountFileName)));
+
+  SetUsesSplitStoresAndUPMForLocal(pref_service(), login_db_directory());
+
+  // The browser should keep trying to migrate existing passwords to the *local*
+  // Android backend. The login database files should be untouched.
+  EXPECT_EQ(pref_service()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores),
+            static_cast<int>(kOffAndMigrationPending));
+  EXPECT_TRUE(base::PathExists(login_db_directory().Append(
+      password_manager::kLoginDataForProfileFileName)));
+  EXPECT_TRUE(base::PathExists(login_db_directory().Append(
+      password_manager::kLoginDataForAccountFileName)));
+  histogram_tester.ExpectUniqueSample("PasswordManager.LocalUpmActivated",
+                                      false, 1);
+
+  // Advanced case: roll back too.
+  base::test::ScopedFeatureList disable_local_upm;
+  disable_local_upm.InitWithFeatures(
+      {}, {password_manager::features::
+               kUnifiedPasswordManagerLocalPasswordsAndroidNoMigration,
+           password_manager::features::
+               kUnifiedPasswordManagerLocalPasswordsAndroidWithMigration});
+
+  SetUsesSplitStoresAndUPMForLocal(pref_service(), login_db_directory());
+
+  // kOn syncing users that get rolled back will "undo" the login DB file move,
+  // i.e. they replace the "profile" loginDB with the "account" one. This isn't
+  // always perfect, see comment MaybeDeactivateSplitStoresAndLocalUpm(). The
+  // "account" DB might even be empty and overwrite a non-empty "profile" one.
+  // However: for kOffAndMigrationPending users, the "account" DB is *surely*
+  // empty (password sync is suppressed). So replacing the file can only be
+  // worse. Instead, the DB files should just be untouched. The account one is
+  // empty anyway, so no data is leftover.
+  EXPECT_EQ(pref_service()->GetInteger(kPasswordsUseUPMLocalAndSeparateStores),
+            static_cast<int>(kOff));
+  EXPECT_TRUE(base::PathExists(login_db_directory().Append(
+      password_manager::kLoginDataForProfileFileName)));
+  EXPECT_TRUE(base::PathExists(login_db_directory().Append(
+      password_manager::kLoginDataForAccountFileName)));
+  histogram_tester.ExpectUniqueSample("PasswordManager.LocalUpmActivated",
+                                      false, 2);
+}
+
+TEST_F(PasswordManagerAndroidUtilTest,
        SetUsesSplitStoresAndUPMForLocal_SyncingHealthy) {
   auto histogram_tester = std::make_unique<base::HistogramTester>();
   base::test::ScopedFeatureList disable_local_upm;
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index f41a0e0..a05091c 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -466,6 +466,27 @@
 }
 #endif
 
+bool ChromePasswordManagerClient::CanUseBiometricAuthForFilling(
+    device_reauth::DeviceAuthenticator* authenticator) {
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
+  if (!GetLocalStatePrefs() || !GetPrefs() || !authenticator) {
+    return false;
+  }
+  return GetPasswordFeatureManager()
+      ->IsBiometricAuthenticationBeforeFillingEnabled();
+#elif BUILDFLAG(IS_ANDROID)
+  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
+    CHECK(authenticator);
+    return true;
+  }
+  return authenticator && authenticator->CanAuthenticateWithBiometrics() &&
+         base::FeatureList::IsEnabled(
+             password_manager::features::kBiometricTouchToFill);
+#else
+  return false;
+#endif
+}
+
 std::unique_ptr<device_reauth::DeviceAuthenticator>
 ChromePasswordManagerClient::GetDeviceAuthenticator() {
 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || \
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index 3706b0c..b07ee27 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -149,6 +149,8 @@
       bool is_webauthn_form) override;
 #endif
 
+  bool CanUseBiometricAuthForFilling(
+      device_reauth::DeviceAuthenticator* authenticator) override;
   // Returns a pointer to the DeviceAuthenticator which is created on demand.
   // This is currently only implemented for Android, Mac and Windows. On all
   // other platforms this will always be null.
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 c7504e0c..fdb9d574 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
@@ -31,6 +31,8 @@
 #include "chrome/browser/sync/sync_service_factory.h"
 #include "chrome/browser/ui/autofill/chrome_autofill_client.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/autofill/content/browser/content_autofill_client.h"
 #include "components/autofill/content/browser/content_autofill_driver.h"
@@ -45,6 +47,7 @@
 #include "components/autofill/core/common/form_data.h"
 #include "components/autofill/core/common/signatures.h"
 #include "components/autofill/core/common/unique_ids.h"
+#include "components/device_reauth/mock_device_authenticator.h"
 #include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "components/password_manager/content/browser/password_manager_log_router_factory.h"
 #include "components/password_manager/core/browser/credential_cache.h"
@@ -55,10 +58,12 @@
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
 #include "components/password_manager/core/browser/password_store/mock_password_store_interface.h"
 #include "components/password_manager/core/browser/password_store/password_store_consumer.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/safe_browsing/buildflags.h"
 #include "components/safe_browsing/core/common/features.h"
 #include "components/sessions/content/content_record_password_state.h"
 #include "components/sync/test/test_sync_service.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/navigation_entry.h"
 #include "content/public/browser/web_contents.h"
@@ -331,7 +336,8 @@
  public:
   ChromePasswordManagerClientTest()
       : ChromeRenderViewHostTestHarness(
-            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME),
+        local_state_(TestingBrowserProcess::GetGlobal()) {
     scoped_feature_list_.InitAndEnableFeature(safe_browsing::kDelayedWarnings);
   }
   ~ChromePasswordManagerClientTest() override = default;
@@ -377,6 +383,7 @@
   bool WasLoggingActivationMessageSent(bool* activation_flag);
 
   FakePasswordAutofillAgent fake_agent_;
+  ScopedTestingLocalState local_state_;
 
   bool metrics_enabled_ = false;
 
@@ -747,6 +754,117 @@
       GURL("https://passwords.google.com/path?query=1")));
 }
 
+#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
+// Test that authentication is not possible if the `authenticator` is `nullptr`.
+TEST_F(ChromePasswordManagerClientTest, CanUseBiometricAuthNoAuthenticator) {
+  EXPECT_FALSE(
+      GetClient()->CanUseBiometricAuthForFilling(/*authenticator=*/nullptr));
+}
+
+// Test that authentication is not possible if the device doesn't have
+// necessary hardware for biometric authentication.
+TEST_F(ChromePasswordManagerClientTest, CanUseBiometricAuthNoBiometrics) {
+  device_reauth::MockDeviceAuthenticator authenticator;
+  // Both prefs are registered by the `PasswordManager`.
+  local_state_.Get()->SetBoolean(
+      password_manager::prefs::kHadBiometricsAvailable, false);
+  profile()->GetTestingPrefService()->SetBoolean(
+      password_manager::prefs::kBiometricAuthenticationBeforeFilling, true);
+  EXPECT_FALSE(GetClient()->CanUseBiometricAuthForFilling(&authenticator));
+}
+
+// Test that authentication is not possible if the user didn't configure the
+// corresponding setting.
+TEST_F(ChromePasswordManagerClientTest, CanUseBiometricAuthSettingDisabled) {
+  device_reauth::MockDeviceAuthenticator authenticator;
+  // Both prefs are registered by the `PasswordManager`.
+  local_state_.Get()->SetBoolean(
+      password_manager::prefs::kHadBiometricsAvailable, true);
+  profile()->GetTestingPrefService()->SetBoolean(
+      password_manager::prefs::kBiometricAuthenticationBeforeFilling, false);
+  EXPECT_FALSE(GetClient()->CanUseBiometricAuthForFilling(&authenticator));
+}
+
+// Test that authentication is possible if both the biometric authentication
+// hardware is available and the user configured the corresponding setting.
+TEST_F(ChromePasswordManagerClientTest, CanUseBiometricAuthSettingEnabled) {
+  device_reauth::MockDeviceAuthenticator authenticator;
+  // Both prefs are registered by the `PasswordManager`.
+  local_state_.Get()->SetBoolean(
+      password_manager::prefs::kHadBiometricsAvailable, true);
+  profile()->GetTestingPrefService()->SetBoolean(
+      password_manager::prefs::kBiometricAuthenticationBeforeFilling, true);
+  EXPECT_TRUE(GetClient()->CanUseBiometricAuthForFilling(&authenticator));
+}
+
+#elif BUILDFLAG(IS_ANDROID)
+// Test that authentication is not possible if the `authenticator` is `nullptr`.
+TEST_F(ChromePasswordManagerClientTest, CanUseBiometricAuthAndroid) {
+  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
+    // Authentication is always available for automotive and the `authenticator`
+    // is always available.
+    device_reauth::MockDeviceAuthenticator authenticator;
+    EXPECT_TRUE(GetClient()->CanUseBiometricAuthForFilling(&authenticator));
+  } else {
+    EXPECT_FALSE(
+        GetClient()->CanUseBiometricAuthForFilling(/*authenticator=*/nullptr));
+  }
+}
+
+// Test that authentication is not possible if the `kBiometricTouchToFill`
+// feature is not enabled.
+TEST_F(ChromePasswordManagerClientTest,
+       CanUseBiometricAuthAndroidFeatureIsDisabled) {
+  // Authentication is always available for automotive.
+  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
+    GTEST_SKIP();
+  }
+  device_reauth::MockDeviceAuthenticator authenticator;
+  ON_CALL(authenticator, CanAuthenticateWithBiometrics)
+      .WillByDefault(Return(true));
+  EXPECT_FALSE(GetClient()->CanUseBiometricAuthForFilling(&authenticator));
+}
+
+// Test that authentication is not possible if the
+// `CanAuthenticateWithBiometrics` returns `false` when `kBiometricTouchToFill`
+// is enabled.
+TEST_F(ChromePasswordManagerClientTest,
+       CanUseBiometricAuthAndroidAuthDisabled) {
+  // Authentication is always available for automotive.
+  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
+    GTEST_SKIP();
+  }
+  base::test::ScopedFeatureList enabled_features(
+      password_manager::features::kBiometricTouchToFill);
+  device_reauth::MockDeviceAuthenticator authenticator;
+  ON_CALL(authenticator, CanAuthenticateWithBiometrics)
+      .WillByDefault(Return(false));
+  EXPECT_FALSE(GetClient()->CanUseBiometricAuthForFilling(&authenticator));
+}
+
+// Test that authentication is possible if the `CanAuthenticateWithBiometrics`
+// returns `true` when `kBiometricTouchToFill` is enabled.
+TEST_F(ChromePasswordManagerClientTest, CanUseBiometricAuthAndroidAuthEnabled) {
+  // Authentication is always available for automotive.
+  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
+    GTEST_SKIP();
+  }
+  base::test::ScopedFeatureList enabled_features(
+      password_manager::features::kBiometricTouchToFill);
+  device_reauth::MockDeviceAuthenticator authenticator;
+  ON_CALL(authenticator, CanAuthenticateWithBiometrics)
+      .WillByDefault(Return(true));
+  EXPECT_TRUE(GetClient()->CanUseBiometricAuthForFilling(&authenticator));
+}
+
+#else
+// Test that authentication is not possible on other platforms.
+TEST_F(ChromePasswordManagerClientTest, CanUseBiometricAuth) {
+  device_reauth::MockDeviceAuthenticator authenticator;
+  EXPECT_FALSE(GetClient()->CanUseBiometricAuthForFilling(&authenticator));
+}
+#endif  // BUILDFLAG(IS_ANDROID)
+
 namespace {
 
 struct SchemeTestCase {
diff --git a/chrome/browser/preloading/preview/COMMON_METADATA b/chrome/browser/preloading/preview/COMMON_METADATA
new file mode 100644
index 0000000..d9e17ee
--- /dev/null
+++ b/chrome/browser/preloading/preview/COMMON_METADATA
@@ -0,0 +1 @@
+team_email: "loading-dev@chromium.org"
diff --git a/chrome/browser/preloading/preview/DIR_METADATA b/chrome/browser/preloading/preview/DIR_METADATA
new file mode 100644
index 0000000..ae58a56
--- /dev/null
+++ b/chrome/browser/preloading/preview/DIR_METADATA
@@ -0,0 +1 @@
+mixins: "//chrome/browser/preloading/preview/COMMON_METADATA"
diff --git a/chrome/browser/preloading/preview/preview_tab.cc b/chrome/browser/preloading/preview/preview_tab.cc
index 7c498c72..9e73154 100644
--- a/chrome/browser/preloading/preview/preview_tab.cc
+++ b/chrome/browser/preloading/preview/preview_tab.cc
@@ -68,8 +68,10 @@
       return;
     }
 
-    if (!is_event_for_preview_window &&
-        event->type() == ui::ET_MOUSE_RELEASED) {
+    // This doesn't triggered for long press trigger.
+    //
+    // TODO(b:320386573): Cancel preview when focus lost.
+    if (!is_event_for_preview_window && event->type() == ui::ET_MOUSE_PRESSED) {
       event->SetHandled();
       preview_manager_->Cancel(content::PreviewCancelReason::Build(
           content::PreviewFinalStatus::kCancelledByWindowClose));
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 2682273..aae767a 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -1429,7 +1429,7 @@
   int enum_id =
       FindUMAEnumValueForCommand(id, UmaEnumIdLookupType::GeneralEnumId);
   if (enum_id == -1) {
-    NOTREACHED() << "Update kUmaEnumToControlId. Unhanded IDC: " << id;
+    NOTREACHED() << "Update GetIdcToUmaMap. Unhandled IDC: " << id;
     return;
   }
 
@@ -1542,7 +1542,7 @@
     } else {
       // Just warning here. It's harder to maintain list of all possibly
       // visible items than executable items.
-      DLOG(ERROR) << "Update kUmaEnumToControlId. Unhanded IDC: " << id;
+      DLOG(ERROR) << "Update GetIdcToUmaMap. Unhandled IDC: " << id;
     }
   }
 
diff --git a/chrome/browser/resources/chromeos/BUILD.gn b/chrome/browser/resources/chromeos/BUILD.gn
index acf417d..e4ca582 100644
--- a/chrome/browser/resources/chromeos/BUILD.gn
+++ b/chrome/browser/resources/chromeos/BUILD.gn
@@ -34,8 +34,7 @@
     "kerberos:resources",
     "launcher_internals:resources",
     "lock_screen_reauth:resources",
-    "login:conditional_resources",
-    "login:unconditional_resources",
+    "login:resources",
     "mako:resources",
     "manage_mirrorsync:resources",
     "multidevice_internals:resources",
diff --git a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js
index 66adb0e..5a29869c 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/cursors/recovery_strategy_test.js
@@ -24,6 +24,8 @@
 };
 
 
+// TODO(https://issuetracker.google.com/issues/263127143) Recovery can likely be
+// simplified now that most ids are stable.
 AX_TEST_F(
     'AccessibilityExtensionRecoveryStrategyTest', 'ReparentedRecovery',
     async function() {
@@ -54,30 +56,21 @@
       assertFalse(
           bAncestryRecovery.requiresRecovery(),
           'bAncestryRecovery.requiresRecovery');
-      assertTrue(
+      assertFalse(
           pAncestryRecovery.requiresRecovery(),
           'pAncestryRecovery.requiresRecovery()');
-      assertTrue(
+      assertFalse(
           sAncestryRecovery.requiresRecovery(),
           'sAncestryRecovery.requiresRecovery()');
       assertFalse(
           bTreePathRecovery.requiresRecovery(),
           'bTreePathRecovery.requiresRecovery()');
-      assertTrue(
+      assertFalse(
           pTreePathRecovery.requiresRecovery(),
           'pTreePathRecovery.requiresRecovery()');
-      assertTrue(
+      assertFalse(
           sTreePathRecovery.requiresRecovery(),
           'sTreePathRecovery.requiresRecovery()');
-
-      assertEquals(RoleType.BUTTON, bAncestryRecovery.node.role);
-      assertEquals(root, pAncestryRecovery.node);
-      assertEquals(root, sAncestryRecovery.node);
-
-      assertEquals(b, bTreePathRecovery.node);
-      assertEquals(b, pTreePathRecovery.node);
-      assertEquals(b, sTreePathRecovery.node);
-
       assertFalse(
           bAncestryRecovery.requiresRecovery(),
           'bAncestryRecovery.requiresRecovery()');
diff --git a/chrome/browser/resources/chromeos/login/BUILD.gn b/chrome/browser/resources/chromeos/login/BUILD.gn
index 51f7a12..5ee4e74 100644
--- a/chrome/browser/resources/chromeos/login/BUILD.gn
+++ b/chrome/browser/resources/chromeos/login/BUILD.gn
@@ -2,115 +2,14 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//chrome/common/features.gni")
-import("//tools/grit/grit_rule.gni")
-import("//tools/grit/preprocess_if_expr.gni")
-import("//tools/typescript/ts_library.gni")
-import("//ui/webui/resources/tools/generate_grd.gni")
-import("./login.gni")
+import("//ui/webui/resources/tools/build_webui.gni")
 
 assert(is_chromeos, "OOBE UI is only available on ChromeOS builds")
 
-existing_unconditional_structure_files_manifest =
-    "existing_unconditional_structure_files_manifest.json"
+build_webui("build") {
+  grd_prefix = "oobe"
 
-# Name is aligned with the constant used in
-# tools/typescript/validate_tsconfig.py to allow intermediate JS files.
-oobe_preprocessed_folder = "preprocessed"
-
-##############################
-#### CONDITONAL RESOURCES ####
-##############################
-
-# Set of OOBE resources that need to be served conditionally during runtime
-# depending on a feature/flag/condition/etc. Based on a manually created GRD
-# for convenience.
-# -----------------------------------------------------------------------------
-grit("conditional_resources") {
-  defines = chrome_grit_defines
-
-  # Needed since some of the files are generated during build time.
-  enable_input_discovery_for_gn_analyze = false
-
-  source = "$target_gen_dir/oobe_conditional_resources.grd"
-  outputs = [
-    "grit/oobe_conditional_resources.h",
-    "grit/oobe_conditional_resources_map.h",
-    "grit/oobe_conditional_resources_map.cc",
-    "oobe_conditional_resources.pak",
-  ]
-
-  deps = [ ":build_oobe_conditional_grd" ]
-  output_dir = "$root_gen_dir/chrome"
-}
-
-generate_grd("build_oobe_conditional_grd") {
-  grd_prefix = "oobe_conditional"
-  out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
-  deps = [
-    ":build_ts",
-    ":preprocess_conditional",
-  ]
-  input_files_base_dir = rebase_path("$target_gen_dir/tsc", root_build_dir)
-  input_files = conditional_js_files
-}
-
-# Files that are served conditionally
-preprocess_if_expr("preprocess_conditional") {
-  deps = [
-    "components/oobe_vars:css_wrapper_files",
-    "debug:web_components",
-    "test_api:copy_ts",
-  ]
-  in_folder = target_gen_dir
-  out_folder = "$target_gen_dir/$oobe_preprocessed_folder"
-  in_files = conditional_files
-}
-
-################################
-#### UNCONDITONAL RESOURCES ####
-################################
-
-# OOBE's default set of resources based on an automatically generated GRD file.
-# These resources are made available to the WebUI through the generated C++ resource
-# map. Since we use the AddResourcePaths method to add the full map instead of adding
-# each resource path individually in oobe_ui.cc, this section is not suitable for
-# serving files in a conditional manner. Thus the naming - unconditional_resources.
-# -----------------------------------------------------------------------------
-grit("unconditional_resources") {
-  defines = chrome_grit_defines
-
-  # This is necessary since the GRD is generated during build time.
-  enable_input_discovery_for_gn_analyze = false
-
-  source = "$target_gen_dir/oobe_unconditional_resources.grd"
-  deps = [ ":build_oobe_grd" ]
-  outputs = [
-    "grit/oobe_unconditional_resources.h",
-    "grit/oobe_unconditional_resources_map.h",
-    "grit/oobe_unconditional_resources_map.cc",
-    "oobe_unconditional_resources.pak",
-  ]
-  output_dir = "$root_gen_dir/chrome"
-}
-
-# Generates OOBE's default GRD file that contains only resources that are
-# served unconditionally.
-# -----------------------------------------------------------------------------
-generate_grd("build_oobe_grd") {
-  grd_prefix = "oobe_unconditional"
-  out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
-  deps = [
-    ":build_ts",
-    ":preprocess_unconditional_existing_structure",
-    "../supervision:build_oobe_grdp",
-  ]
-  manifest_files = [
-    "$target_gen_dir/build_ts_manifest.json",
-    "$target_gen_dir/$existing_unconditional_structure_files_manifest",
-  ]
-  input_files_base_dir = rebase_path(".", "//")
-  input_files = [
+  static_files = [
     # Lottie animation resources
     "animations/all_set.json",
     "animations/checking_for_update.json",
@@ -143,61 +42,186 @@
     "images/2x/thumbnail-theme-dark-2x.png",
     "images/1x/thumbnail-theme-auto-1x.png",
     "images/2x/thumbnail-theme-auto-2x.png",
-  ]
 
-  grdp_files = [ "$root_gen_dir/chrome/browser/resources/chromeos/supervision/supervision_oobe_resources.grdp" ]
-}
-
-# Preprocess existing and autogenerated files by copying them to an
-# intermediate location and generating a manifest file to be used when
-# generating OOBE's GRD file.
-# -----------------------------------------------------------------------------
-
-# Preprocess existing (not autogenerated) files.
-preprocess_if_expr("preprocess_unconditional_existing") {
-  out_folder = "$target_gen_dir/$oobe_preprocessed_folder"
-  in_files = unconditional_existing_files
-}
-
-# These files shouldn't be wrapped and compiled thus they remain in a
-# separate target.
-preprocess_if_expr("preprocess_unconditional_existing_structure") {
-  out_folder = "$target_gen_dir/$oobe_preprocessed_folder"
-  out_manifest =
-      "$target_gen_dir/$existing_unconditional_structure_files_manifest"
-  in_files = [
+    # Structure files
     "oobe.css",
     "oobe.html",
     "oobe_popup_overlay.css",
     "oobe_screen.css",
   ]
-}
 
-preprocess_if_expr("preprocess_unconditional_autogenerated") {
-  defines = chrome_grit_defines
-  deps = [ ":web_components" ]
-  in_folder = target_gen_dir
-  out_folder = "$target_gen_dir/$oobe_preprocessed_folder"
-  in_files = unconditional_autogenerated_files
-}
+  web_component_files = [
+    # Oobe screens
+    "screens/oobe/auto_enrollment_check.ts",
+    "screens/oobe/consumer_update.ts",
+    "screens/oobe/demo_preferences.ts",
+    "screens/oobe/demo_setup.ts",
+    "screens/oobe/enable_debugging.ts",
+    "screens/oobe/enterprise_enrollment.ts",
+    "screens/oobe/hid_detection.ts",
+    "screens/oobe/oobe_network.ts",
+    "screens/oobe/packaged_license.ts",
+    "screens/oobe/update.ts",
+    "screens/oobe/welcome.ts",
+    "screens/oobe/welcome_dialog.ts",
 
-group("web_components") {
-  public_deps = [
-    "components:web_components",
-    "screens/common:web_components",
-    "screens/login:web_components",
-    "screens/oobe:web_components",
-    "screens/osauth:web_components",
+    # Common screens
+    "screens/common/adb_sideloading.ts",
+    "screens/common/add_child.ts",
+    "screens/common/app_downloading.ts",
+    "screens/common/app_launch_splash.ts",
+    "screens/common/assistant_optin.ts",
+    "screens/common/autolaunch.ts",
+    "screens/common/choobe.ts",
+    "screens/common/consolidated_consent.ts",
+    "screens/common/device_disabled.ts",
+    "screens/common/display_size.ts",
+    "screens/common/drive_pinning.ts",
+    "screens/common/enable_kiosk.ts",
+    "screens/common/error_message.ts",
+    "screens/common/family_link_notice.ts",
+    "screens/common/gaia_info.ts",
+    "screens/common/gaia_signin.ts",
+    "screens/common/gesture_navigation.ts",
+    "screens/common/guest_tos.ts",
+    "screens/common/hw_data_collection.ts",
+    "screens/common/install_attributes_error.ts",
+    "screens/common/local_state_error.ts",
+    "screens/common/managed_terms_of_service.ts",
+    "screens/common/marketing_opt_in.ts",
+    "screens/common/multidevice_setup.ts",
+    "screens/common/online_authentication_screen.ts",
+    "screens/common/oobe_reset.ts",
+    "screens/common/os_install.ts",
+    "screens/common/os_trial.ts",
+    "screens/common/parental_handoff.ts",
+    "screens/common/quick_start.ts",
+    "screens/common/remote_activity_notification.ts",
+
+    # Template used by the `tools/oobe/generate_screen_template.py` script.
+    "screens/common/placeholder.ts",
+    "screens/common/recommend_apps.ts",
+    "screens/common/saml_confirm_password.ts",
+    "screens/common/signin_fatal_error.ts",
+    "screens/common/smart_privacy_protection.ts",
+    "screens/common/sync_consent.ts",
+    "screens/common/theme_selection.ts",
+    "screens/common/touchpad_scroll.ts",
+    "screens/common/tpm_error.ts",
+    "screens/common/user_allowlist_check_screen.ts",
+    "screens/common/user_creation.ts",
+    "screens/common/wrong_hwid.ts",
+
+    # Login screens
+    "screens/login/arc_vm_data_migration.ts",
+    "screens/login/checking_downloading_update.ts",
+    "screens/login/encryption_migration.ts",
+    "screens/login/lacros_data_backward_migration.ts",
+    "screens/login/lacros_data_migration.ts",
+    "screens/login/management_transition.ts",
+    "screens/login/offline_login.ts",
+    "screens/login/update_required_card.ts",
+
+    # Osauth screens
+    "screens/osauth/apply_online_password.ts",
+    "screens/osauth/cryptohome_recovery.ts",
+    "screens/osauth/cryptohome_recovery_setup.ts",
+    "screens/osauth/factor_setup_success.ts",
+    "screens/osauth/fingerprint_setup.ts",
+    "screens/osauth/gaia_password_changed.ts",
+    "screens/osauth/local_password_setup.ts",
+    "screens/osauth/local_data_loss_warning.ts",
+    "screens/osauth/enter_old_password.ts",
+    "screens/osauth/osauth_error.ts",
+    "screens/osauth/password_selection.ts",
+    "screens/osauth/pin_setup.ts",
+
+    # Components
+    "components/dialogs/oobe_adaptive_dialog.ts",
+    "components/dialogs/oobe_content_dialog.ts",
+    "components/dialogs/oobe_loading_dialog.ts",
+    "components/dialogs/oobe_modal_dialog.ts",
+    "components/buttons/oobe_back_button.ts",
+    "components/buttons/oobe_icon_button.ts",
+    "components/buttons/oobe_next_button.ts",
+    "components/buttons/oobe_text_button.ts",
+    "components/api_keys_notice.ts",
+    "components/gaia_button.ts",
+    "components/gaia_dialog.ts",
+    "components/hd_iron_icon.ts",
+    "components/network_select_login.ts",
+    "components/notification_card.ts",
+    "components/oobe_a11y_option.ts",
+    "components/oobe_apps_list.ts",
+    "components/oobe_carousel.ts",
+    "components/oobe_cr_lottie.ts",
+    "components/oobe_display_size_selector.ts",
+    "components/oobe_i18n_dropdown.ts",
+    "components/oobe_screens_list.ts",
+    "components/oobe_slide.ts",
+    "components/progress_list_item.ts",
+    "components/security_token_pin.ts",
+    "components/throbber_notice.ts",
+    "components/quick_start_pin.ts",
+    "components/quick_start_entry_point.ts",
+
+    # Conditional
+    "debug/quick_start_debugger.ts",
   ]
-}
 
-ts_library("build_ts") {
-  tsconfig_base = "//tools/typescript/tsconfig_base_polymer.json"
-  root_dir = "$target_gen_dir/$oobe_preprocessed_folder"
-  out_dir = "$target_gen_dir/tsc"
-  in_files = unconditional_autogenerated_files + unconditional_existing_files +
-             conditional_files
-  definitions = [
+  non_web_component_files = [
+    "components/behaviors/oobe_dialog_host_behavior.js",
+    "components/behaviors/oobe_focus_behavior.js",
+    "components/behaviors/oobe_i18n_behavior.js",
+    "components/behaviors/oobe_scrollable_behavior.js",
+    "components/behaviors/login_screen_behavior.js",
+    "components/behaviors/multi_step_behavior.js",
+    "components/buttons/oobe_base_button.ts",
+    "components/display_manager_types.ts",
+    "components/keyboard_utils.ts",
+    "components/keyboard_utils_oobe.ts",
+    "components/long_touch_detector.ts",
+    "components/oobe_select.ts",
+    "components/oobe_types.ts",
+    "components/qr_code_canvas.ts",
+    "components/web_view_helper.ts",
+    "components/web_view_loader.ts",
+    "cr_ui.ts",
+    "debug/debug.js",
+    "debug/no_debug.js",
+    "display_manager.ts",
+    "i18n_setup.ts",
+    "install_oobe_error_store.ts",
+    "lazy_load_screens.ts",
+    "login_ui_tools.ts",
+    "multi_tap_detector.ts",
+    "oobe.ts",
+    "oobe_trace.ts",
+    "oobe_trace_start.ts",
+    "priority_screens_common_flow.ts",
+    "priority_screens_oobe_flow.ts",
+    "screens.ts",
+    "test_api/no_test_api.ts",
+    "test_api/test_api.ts",
+  ]
+
+  icons_html_files = [
+    "components/oobe_icons.html",
+    "components/oobe_illo_icons.html",
+    "components/oobe_network_icons.html",
+  ]
+
+  css_files = [
+    "components/common_styles/cr_card_radio_group_styles.css",
+    "components/common_styles/oobe_common_styles.css",
+    "components/common_styles/oobe_dialog_host_styles.css",
+    "components/common_styles/oobe_flex_layout_styles.css",
+    "components/oobe_vars/oobe_shared_vars.css",
+    "components/oobe_vars/oobe_custom_vars.css",
+    "components/oobe_vars/oobe_custom_vars_remora.css",
+  ]
+
+  ts_definitions = [
     "//chrome/browser/resources/chromeos/accessibility/definitions/tts.d.ts",
     "//chrome/browser/resources/gaia_auth_host/saml_password_attributes.d.ts",
     "//chrome/browser/resources/gaia_auth_host/authenticator.d.ts",
@@ -211,7 +235,8 @@
     "//tools/typescript/definitions/web_request.d.ts",
     "//tools/typescript/definitions/quick_unlock_private.d.ts",
   ]
-  deps = [
+
+  ts_deps = [
     "//ash/webui/common/resources:build_ts",
     "//ash/webui/common/resources/cr_elements:build_ts",
     "//third_party/cros-components:cros_components_ts",
@@ -220,20 +245,12 @@
     "//ui/webui/resources/js:build_ts",
     "//ui/webui/resources/mojo:build_ts",
   ]
-  extra_deps = [
-    ":preprocess_conditional",
-    ":preprocess_unconditional_autogenerated",
-    ":preprocess_unconditional_existing",
-    ":web_components",
-    "components:html_wrapper_files",
-    "components/oobe_vars:css_wrapper_files",
-  ]
 
-  path_mappings = [ "//oobe/gaia_auth_host/*|" +
-                    rebase_path("//chrome/browser/resources/gaia_auth_host/*",
-                                target_gen_dir) ]
+  ts_path_mappings =
+      [ "//oobe/gaia_auth_host/*|" +
+        rebase_path("//chrome/browser/resources/gaia_auth_host/*",
+                    target_gen_dir) ]
 
-  # These files can be added conditionally in the runtime, so they shouldn't
-  # be added to the same grd file as the rest of the sources.
-  manifest_excludes = conditional_js_files
+  extra_grdp_deps = [ "../supervision:build_oobe_grdp" ]
+  extra_grdp_files = [ "$root_gen_dir/chrome/browser/resources/chromeos/supervision/supervision_oobe_resources.grdp" ]
 }
diff --git a/chrome/browser/resources/chromeos/login/components/BUILD.gn b/chrome/browser/resources/chromeos/login/components/BUILD.gn
deleted file mode 100644
index 6c94642f9..0000000
--- a/chrome/browser/resources/chromeos/login/components/BUILD.gn
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright 2020 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/html_to_wrapper.gni")
-import("../login.gni")
-
-group("web_components") {
-  public_deps = [
-    ":copy_ts",
-    ":html_wrapper_files",
-    "./behaviors:copy_js",
-    "./buttons:web_components",
-    "./common_styles:css_wrapper_files",
-    "./dialogs:web_components",
-    "./oobe_vars:css_wrapper_files",
-  ]
-}
-
-copy("copy_ts") {
-  sources = rebase_path(components_ts_files, "./components", ".")
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
-
-html_to_wrapper("html_wrapper_files") {
-  in_files = rebase_path(components_html_files, "./components", ".")
-}
diff --git a/chrome/browser/resources/chromeos/login/components/behaviors/BUILD.gn b/chrome/browser/resources/chromeos/login/components/behaviors/BUILD.gn
deleted file mode 100644
index 30e7a924..0000000
--- a/chrome/browser/resources/chromeos/login/components/behaviors/BUILD.gn
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-copy("copy_js") {
-  sources = [
-    "login_screen_behavior.js",
-    "multi_step_behavior.js",
-    "oobe_dialog_host_behavior.js",
-    "oobe_focus_behavior.js",
-    "oobe_i18n_behavior.js",
-    "oobe_scrollable_behavior.js",
-  ]
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
diff --git a/chrome/browser/resources/chromeos/login/components/buttons/BUILD.gn b/chrome/browser/resources/chromeos/login/components/buttons/BUILD.gn
deleted file mode 100644
index 4f3ed06f..0000000
--- a/chrome/browser/resources/chromeos/login/components/buttons/BUILD.gn
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/html_to_wrapper.gni")
-import("../../login.gni")
-
-group("web_components") {
-  public_deps = [
-    ":copy_ts",
-    ":generate_web_component_html_wrapper_files",
-  ]
-}
-
-html_to_wrapper("generate_web_component_html_wrapper_files") {
-  in_files = rebase_path(buttons_html_files, "./components/buttons", ".")
-}
-
-copy("copy_ts") {
-  sources = rebase_path(buttons_ts_files, "./components/buttons", ".")
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
diff --git a/chrome/browser/resources/chromeos/login/components/common_styles/BUILD.gn b/chrome/browser/resources/chromeos/login/components/common_styles/BUILD.gn
deleted file mode 100644
index fc8b5f7..0000000
--- a/chrome/browser/resources/chromeos/login/components/common_styles/BUILD.gn
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/css_to_wrapper.gni")
-
-css_to_wrapper("css_wrapper_files") {
-  in_files = [
-    "cr_card_radio_group_styles.css",
-    "oobe_common_styles.css",
-    "oobe_dialog_host_styles.css",
-    "oobe_flex_layout_styles.css",
-  ]
-  use_js = true
-}
diff --git a/chrome/browser/resources/chromeos/login/components/dialogs/BUILD.gn b/chrome/browser/resources/chromeos/login/components/dialogs/BUILD.gn
deleted file mode 100644
index 086fda1..0000000
--- a/chrome/browser/resources/chromeos/login/components/dialogs/BUILD.gn
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/html_to_wrapper.gni")
-import("../../login.gni")
-
-group("web_components") {
-  public_deps = [
-    ":copy_ts",
-    ":generate_web_component_html_wrapper_files",
-  ]
-}
-
-html_to_wrapper("generate_web_component_html_wrapper_files") {
-  in_files = rebase_path(dialogs_html_files, "./components/dialogs", ".")
-}
-
-copy("copy_ts") {
-  sources = rebase_path(dialogs_ts_files, "./components/dialogs", ".")
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
diff --git a/chrome/browser/resources/chromeos/login/components/oobe_vars/BUILD.gn b/chrome/browser/resources/chromeos/login/components/oobe_vars/BUILD.gn
deleted file mode 100644
index 09efb96..0000000
--- a/chrome/browser/resources/chromeos/login/components/oobe_vars/BUILD.gn
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/css_to_wrapper.gni")
-
-css_to_wrapper("css_wrapper_files") {
-  in_files = [
-    "oobe_custom_vars.css",
-    "oobe_custom_vars_remora.css",
-    "oobe_shared_vars.css",
-  ]
-}
diff --git a/chrome/browser/resources/chromeos/login/debug/BUILD.gn b/chrome/browser/resources/chromeos/login/debug/BUILD.gn
deleted file mode 100644
index 7b2d7d7..0000000
--- a/chrome/browser/resources/chromeos/login/debug/BUILD.gn
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/html_to_wrapper.gni")
-
-group("web_components") {
-  public_deps = [
-    ":copy_ts",
-    ":generate_web_component_html_wrapper_files",
-  ]
-}
-
-# Copy existing files to output directory.
-copy("copy_ts") {
-  sources = [
-    "debug.js",
-    "no_debug.js",
-    "quick_start_debugger.ts",
-  ]
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
-
-html_to_wrapper("generate_web_component_html_wrapper_files") {
-  in_files = [
-    "quick_start_debugger.html",
-  ]
-}
diff --git a/chrome/browser/resources/chromeos/login/login.gni b/chrome/browser/resources/chromeos/login/login.gni
deleted file mode 100644
index 2a946e5..0000000
--- a/chrome/browser/resources/chromeos/login/login.gni
+++ /dev/null
@@ -1,282 +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.
-
-conditional_files = [
-  "debug/no_debug.js",
-  "test_api/no_test_api.ts",
-  "components/oobe_vars/oobe_custom_vars.css.ts",
-  "components/oobe_vars/oobe_custom_vars_remora.css.ts",
-  "debug/debug.js",
-  "debug/quick_start_debugger.ts",
-  "debug/quick_start_debugger.html.ts",
-  "test_api/test_api.ts",
-]
-
-conditional_js_files = []
-foreach(f, conditional_files) {
-  # TODO(b/322301624): Refactor once all conditional files are migrated.
-  extension = get_path_info(f, "extension")
-  if (extension == "ts") {
-    conditional_js_files += [ string_replace(f, ".ts", ".js") ]
-  } else {
-    conditional_js_files += [ f ]
-  }
-}
-
-unconditional_existing_files = [
-  "cr_ui.ts",
-  "display_manager.ts",
-  "i18n_setup.ts",
-  "install_oobe_error_store.ts",
-  "lazy_load_screens.ts",
-  "login_ui_tools.ts",
-  "multi_tap_detector.ts",
-  "oobe.ts",
-  "oobe_trace.ts",
-  "oobe_trace_start.ts",
-  "priority_screens_common_flow.ts",
-  "priority_screens_oobe_flow.ts",
-  "screens.ts",
-]
-
-unconditional_autogenerated_files = [
-  "components/behaviors/oobe_dialog_host_behavior.js",
-  "components/behaviors/oobe_focus_behavior.js",
-  "components/behaviors/oobe_i18n_behavior.js",
-  "components/behaviors/oobe_scrollable_behavior.js",
-  "components/behaviors/login_screen_behavior.js",
-  "components/behaviors/multi_step_behavior.js",
-  "components/common_styles/cr_card_radio_group_styles.css.js",
-  "components/common_styles/oobe_common_styles.css.js",
-  "components/common_styles/oobe_dialog_host_styles.css.js",
-  "components/common_styles/oobe_flex_layout_styles.css.js",
-  "components/oobe_vars/oobe_shared_vars.css.ts",
-]
-
-oobe_screens_ts_files = [
-  "screens/oobe/auto_enrollment_check.ts",
-  "screens/oobe/consumer_update.ts",
-  "screens/oobe/demo_preferences.ts",
-  "screens/oobe/demo_setup.ts",
-  "screens/oobe/enable_debugging.ts",
-  "screens/oobe/enterprise_enrollment.ts",
-  "screens/oobe/hid_detection.ts",
-  "screens/oobe/oobe_network.ts",
-  "screens/oobe/packaged_license.ts",
-  "screens/oobe/update.ts",
-  "screens/oobe/welcome.ts",
-  "screens/oobe/welcome_dialog.ts",
-]
-
-oobe_screens_html_files = [
-  "components/oobe_icons.html",
-  "components/oobe_illo_icons.html",
-  "components/oobe_network_icons.html",
-]
-
-foreach(f, oobe_screens_ts_files) {
-  oobe_screens_html_files += [ string_replace(f, ".ts", ".html") ]
-}
-
-oobe_screens_html_wrapped_files = []
-foreach(f, oobe_screens_html_files) {
-  oobe_screens_html_wrapped_files += [ string_replace(f, ".html", ".html.ts") ]
-}
-
-common_screens_ts_files = [
-  "screens/common/adb_sideloading.ts",
-  "screens/common/add_child.ts",
-  "screens/common/app_downloading.ts",
-  "screens/common/app_launch_splash.ts",
-  "screens/common/assistant_optin.ts",
-  "screens/common/autolaunch.ts",
-  "screens/common/choobe.ts",
-  "screens/common/consolidated_consent.ts",
-  "screens/common/device_disabled.ts",
-  "screens/common/display_size.ts",
-  "screens/common/drive_pinning.ts",
-  "screens/common/enable_kiosk.ts",
-  "screens/common/error_message.ts",
-  "screens/common/family_link_notice.ts",
-  "screens/common/gaia_info.ts",
-  "screens/common/gaia_signin.ts",
-  "screens/common/gesture_navigation.ts",
-  "screens/common/guest_tos.ts",
-  "screens/common/hw_data_collection.ts",
-  "screens/common/install_attributes_error.ts",
-  "screens/common/local_state_error.ts",
-  "screens/common/managed_terms_of_service.ts",
-  "screens/common/marketing_opt_in.ts",
-  "screens/common/multidevice_setup.ts",
-  "screens/common/online_authentication_screen.ts",
-  "screens/common/oobe_reset.ts",
-  "screens/common/os_install.ts",
-  "screens/common/os_trial.ts",
-  "screens/common/parental_handoff.ts",
-  "screens/common/quick_start.ts",
-  "screens/common/remote_activity_notification.ts",
-
-  # Template used by the `tools/oobe/generate_screen_template.py` script.
-  "screens/common/placeholder.ts",
-  "screens/common/recommend_apps.ts",
-  "screens/common/saml_confirm_password.ts",
-  "screens/common/signin_fatal_error.ts",
-  "screens/common/smart_privacy_protection.ts",
-  "screens/common/sync_consent.ts",
-  "screens/common/theme_selection.ts",
-  "screens/common/touchpad_scroll.ts",
-  "screens/common/tpm_error.ts",
-  "screens/common/user_allowlist_check_screen.ts",
-  "screens/common/user_creation.ts",
-  "screens/common/wrong_hwid.ts",
-]
-
-common_screens_html_files = []
-foreach(f, common_screens_ts_files) {
-  common_screens_html_files += [ string_replace(f, ".ts", ".html") ]
-}
-
-common_screens_html_wrapped_files = []
-foreach(f, common_screens_html_files) {
-  common_screens_html_wrapped_files +=
-      [ string_replace(f, ".html", ".html.ts") ]
-}
-
-login_screens_ts_files = [
-  "screens/login/arc_vm_data_migration.ts",
-  "screens/login/checking_downloading_update.ts",
-  "screens/login/encryption_migration.ts",
-  "screens/login/lacros_data_backward_migration.ts",
-  "screens/login/lacros_data_migration.ts",
-  "screens/login/management_transition.ts",
-  "screens/login/offline_login.ts",
-  "screens/login/update_required_card.ts",
-]
-
-login_screens_html_files = []
-foreach(f, login_screens_ts_files) {
-  login_screens_html_files += [ string_replace(f, ".ts", ".html") ]
-}
-
-login_screens_html_wrapped_files = []
-foreach(f, login_screens_html_files) {
-  login_screens_html_wrapped_files += [ string_replace(f, ".html", ".html.ts") ]
-}
-
-osauth_screens_ts_files = [
-  "screens/osauth/apply_online_password.ts",
-  "screens/osauth/cryptohome_recovery.ts",
-  "screens/osauth/cryptohome_recovery_setup.ts",
-  "screens/osauth/factor_setup_success.ts",
-  "screens/osauth/fingerprint_setup.ts",
-  "screens/osauth/gaia_password_changed.ts",
-  "screens/osauth/local_password_setup.ts",
-  "screens/osauth/local_data_loss_warning.ts",
-  "screens/osauth/enter_old_password.ts",
-  "screens/osauth/osauth_error.ts",
-  "screens/osauth/password_selection.ts",
-  "screens/osauth/pin_setup.ts",
-]
-
-osauth_screens_html_files = []
-foreach(f, osauth_screens_ts_files) {
-  osauth_screens_html_files += [ string_replace(f, ".ts", ".html") ]
-}
-
-osauth_screens_html_wrapped_files = []
-foreach(f, osauth_screens_html_files) {
-  osauth_screens_html_wrapped_files +=
-      [ string_replace(f, ".html", ".html.ts") ]
-}
-
-dialogs_ts_files = [
-  "components/dialogs/oobe_adaptive_dialog.ts",
-  "components/dialogs/oobe_content_dialog.ts",
-  "components/dialogs/oobe_loading_dialog.ts",
-  "components/dialogs/oobe_modal_dialog.ts",
-]
-
-dialogs_html_files = []
-foreach(f, dialogs_ts_files) {
-  dialogs_html_files += [ string_replace(f, ".ts", ".html") ]
-}
-
-dialogs_html_wrapped_files = []
-foreach(f, dialogs_html_files) {
-  dialogs_html_wrapped_files += [ string_replace(f, ".html", ".html.ts") ]
-}
-
-buttons_ts_files = [
-  "components/buttons/oobe_back_button.ts",
-  "components/buttons/oobe_icon_button.ts",
-  "components/buttons/oobe_next_button.ts",
-  "components/buttons/oobe_text_button.ts",
-]
-
-buttons_html_files = []
-foreach(f, buttons_ts_files) {
-  buttons_html_files += [ string_replace(f, ".ts", ".html") ]
-}
-
-buttons_html_wrapped_files = []
-foreach(f, buttons_html_files) {
-  buttons_html_wrapped_files += [ string_replace(f, ".html", ".html.ts") ]
-}
-
-buttons_ts_files += [ "components/buttons/oobe_base_button.ts" ]
-
-components_ts_files = [
-  "components/api_keys_notice.ts",
-  "components/gaia_button.ts",
-  "components/gaia_dialog.ts",
-  "components/hd_iron_icon.ts",
-  "components/network_select_login.ts",
-  "components/notification_card.ts",
-  "components/oobe_a11y_option.ts",
-  "components/oobe_apps_list.ts",
-  "components/oobe_carousel.ts",
-  "components/oobe_cr_lottie.ts",
-  "components/oobe_display_size_selector.ts",
-  "components/oobe_i18n_dropdown.ts",
-  "components/oobe_screens_list.ts",
-  "components/oobe_slide.ts",
-  "components/progress_list_item.ts",
-  "components/security_token_pin.ts",
-  "components/throbber_notice.ts",
-  "components/quick_start_pin.ts",
-  "components/quick_start_entry_point.ts",
-]
-
-components_html_files = []
-foreach(f, components_ts_files) {
-  components_html_files += [ string_replace(f, ".ts", ".html") ]
-}
-
-components_html_wrapped_files = []
-foreach(f, components_html_files) {
-  components_html_wrapped_files += [ string_replace(f, ".html", ".html.ts") ]
-}
-
-# The following components do not have corresponding html files, we are adding
-# them after the list of html files has been populated
-components_ts_files += [
-  "components/display_manager_types.ts",
-  "components/keyboard_utils.ts",
-  "components/keyboard_utils_oobe.ts",
-  "components/long_touch_detector.ts",
-  "components/oobe_select.ts",
-  "components/oobe_types.ts",
-  "components/qr_code_canvas.ts",
-  "components/web_view_helper.ts",
-  "components/web_view_loader.ts",
-]
-
-unconditional_autogenerated_files +=
-    oobe_screens_ts_files + oobe_screens_html_wrapped_files +
-    common_screens_ts_files + common_screens_html_wrapped_files +
-    login_screens_ts_files + login_screens_html_wrapped_files +
-    osauth_screens_ts_files + osauth_screens_html_wrapped_files +
-    dialogs_ts_files + dialogs_html_wrapped_files + buttons_ts_files +
-    buttons_html_wrapped_files + components_ts_files +
-    components_html_wrapped_files
diff --git a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
deleted file mode 100644
index dbef53a..0000000
--- a/chrome/browser/resources/chromeos/login/screens/common/BUILD.gn
+++ /dev/null
@@ -1,24 +0,0 @@
-# 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("//tools/polymer/html_to_wrapper.gni")
-import("../../login.gni")
-
-assert(is_chromeos, "OOBE UI is only available on ChromeOS builds")
-
-group("web_components") {
-  public_deps = [
-    ":copy_ts",
-    ":generate_web_component_html_wrapper_files",
-  ]
-}
-
-copy("copy_ts") {
-  sources = rebase_path(common_screens_ts_files, "./screens/common", ".")
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
-
-html_to_wrapper("generate_web_component_html_wrapper_files") {
-  in_files = rebase_path(common_screens_html_files, "./screens/common", ".")
-}
diff --git a/chrome/browser/resources/chromeos/login/screens/login/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/login/BUILD.gn
deleted file mode 100644
index 551c8cf8..0000000
--- a/chrome/browser/resources/chromeos/login/screens/login/BUILD.gn
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/html_to_wrapper.gni")
-import("../../login.gni")
-
-group("web_components") {
-  public_deps = [
-    ":copy_ts",
-    ":generate_web_component_html_wrapper_files",
-  ]
-}
-
-copy("copy_ts") {
-  sources = rebase_path(login_screens_ts_files, "./screens/login", ".")
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
-
-html_to_wrapper("generate_web_component_html_wrapper_files") {
-  in_files = rebase_path(login_screens_html_files, "./screens/login", ".")
-}
diff --git a/chrome/browser/resources/chromeos/login/screens/oobe/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/oobe/BUILD.gn
deleted file mode 100644
index b414df8..0000000
--- a/chrome/browser/resources/chromeos/login/screens/oobe/BUILD.gn
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2021 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/html_to_wrapper.gni")
-import("../../login.gni")
-
-group("web_components") {
-  public_deps = [
-    ":copy_ts",
-    ":generate_web_component_html_wrapper_files",
-  ]
-}
-
-copy("copy_ts") {
-  sources = rebase_path(oobe_screens_ts_files, "./screens/oobe", ".")
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
-
-html_to_wrapper("generate_web_component_html_wrapper_files") {
-  in_files = rebase_path(oobe_screens_html_files, "./screens/oobe", ".")
-}
diff --git a/chrome/browser/resources/chromeos/login/screens/osauth/BUILD.gn b/chrome/browser/resources/chromeos/login/screens/osauth/BUILD.gn
deleted file mode 100644
index 538551f8..0000000
--- a/chrome/browser/resources/chromeos/login/screens/osauth/BUILD.gn
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2023 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//tools/polymer/html_to_wrapper.gni")
-import("../../login.gni")
-
-group("web_components") {
-  public_deps = [
-    ":copy_ts",
-    ":generate_web_component_html_wrapper_files",
-  ]
-}
-
-copy("copy_ts") {
-  sources = rebase_path(osauth_screens_ts_files, "./screens/osauth", ".")
-  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
-}
-
-html_to_wrapper("generate_web_component_html_wrapper_files") {
-  in_files = rebase_path(osauth_screens_html_files, "./screens/osauth", ".")
-}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index a8d5a97..eb2d8f0 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1501,6 +1501,8 @@
       "tabs/organization/trigger_policies.h",
       "tabs/pinned_tab_codec.cc",
       "tabs/pinned_tab_codec.h",
+      "tabs/pinned_tab_collection.cc",
+      "tabs/pinned_tab_collection.h",
       "tabs/pinned_tab_service.cc",
       "tabs/pinned_tab_service.h",
       "tabs/pinned_tab_service_factory.cc",
@@ -1521,6 +1523,8 @@
       "tabs/supports_handles.h",
       "tabs/tab_change_type.h",
       "tabs/tab_collection.h",
+      "tabs/tab_collection_storage.cc",
+      "tabs/tab_collection_storage.h",
       "tabs/tab_group.cc",
       "tabs/tab_group.h",
       "tabs/tab_group_controller.h",
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
index 2c8500f..9bba40f64 100644
--- a/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.cc
@@ -50,6 +50,7 @@
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/omnibox/chrome_omnibox_navigation_observer.h"
 #include "chrome/browser/ui/omnibox/omnibox_tab_helper.h"
+#include "components/bookmarks/browser/bookmark_model.h"
 #include "components/favicon/content/content_favicon_driver.h"
 #include "components/favicon/core/favicon_service.h"
 #include "components/omnibox/browser/autocomplete_controller_emitter.h"
@@ -157,7 +158,7 @@
   return profile_->GetPrefs();
 }
 
-bookmarks::BookmarkModel* ChromeOmniboxClient::GetBookmarkModel() {
+bookmarks::CoreBookmarkModel* ChromeOmniboxClient::GetBookmarkModel() {
   return BookmarkModelFactory::GetForBrowserContext(profile_);
 }
 
diff --git a/chrome/browser/ui/omnibox/chrome_omnibox_client.h b/chrome/browser/ui/omnibox/chrome_omnibox_client.h
index db1e94d..991712e 100644
--- a/chrome/browser/ui/omnibox/chrome_omnibox_client.h
+++ b/chrome/browser/ui/omnibox/chrome_omnibox_client.h
@@ -44,7 +44,7 @@
   bool IsDefaultSearchProviderEnabled() const override;
   SessionID GetSessionID() const override;
   PrefService* GetPrefs() override;
-  bookmarks::BookmarkModel* GetBookmarkModel() override;
+  bookmarks::CoreBookmarkModel* GetBookmarkModel() override;
   AutocompleteControllerEmitter* GetAutocompleteControllerEmitter() override;
   TemplateURLService* GetTemplateURLService() override;
   const AutocompleteSchemeClassifier& GetSchemeClassifier() const override;
diff --git a/chrome/browser/ui/tabs/pinned_tab_collection.cc b/chrome/browser/ui/tabs/pinned_tab_collection.cc
new file mode 100644
index 0000000..05c3cf0a
--- /dev/null
+++ b/chrome/browser/ui/tabs/pinned_tab_collection.cc
@@ -0,0 +1,80 @@
+// 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 <memory>
+#include <optional>
+
+#include "chrome/browser/ui/tabs/pinned_tab_collection.h"
+#include "chrome/browser/ui/tabs/tab_collection_storage.h"
+#include "chrome/browser/ui/tabs/tab_model.h"
+
+namespace tabs {
+
+PinnedTabCollection::PinnedTabCollection() {
+  impl_ = std::make_unique<TabCollectionStorage>(*this);
+}
+
+PinnedTabCollection::~PinnedTabCollection() = default;
+
+void PinnedTabCollection::AddTab(std::unique_ptr<TabModel> tab_model,
+                                 size_t index) {
+  NOTIMPLEMENTED();
+}
+
+void PinnedTabCollection::AppendTab(std::unique_ptr<TabModel> tab_model) {
+  NOTIMPLEMENTED();
+}
+
+void PinnedTabCollection::MoveTab(TabModel* tab_model, size_t index) {
+  NOTIMPLEMENTED();
+}
+
+void PinnedTabCollection::CloseTab(TabModel* tab_model) {
+  NOTIMPLEMENTED();
+}
+
+bool PinnedTabCollection::ContainsTabRecursive(TabModel* tab_model) const {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+bool PinnedTabCollection::ContainsCollection(TabCollection* collection) const {
+  NOTIMPLEMENTED();
+  return false;
+}
+
+std::optional<size_t> PinnedTabCollection::GetIndexOfTabRecursive(
+    TabModel* tab_model) const {
+  NOTIMPLEMENTED();
+  return std::nullopt;
+}
+
+std::optional<size_t> PinnedTabCollection::GetIndexOfCollection(
+    TabCollection* collection) const {
+  NOTIMPLEMENTED();
+  return std::nullopt;
+}
+
+std::unique_ptr<TabModel> PinnedTabCollection::MaybeRemoveTab(
+    TabModel* tab_model) {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+size_t PinnedTabCollection::ChildCount() const {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+size_t PinnedTabCollection::TabCountRecursive() const {
+  NOTIMPLEMENTED();
+  return 0;
+}
+
+std::unique_ptr<TabCollection> PinnedTabCollection::MaybeRemoveCollection(
+    TabCollection* collection) {
+  return nullptr;
+}
+
+}  // namespace tabs
diff --git a/chrome/browser/ui/tabs/pinned_tab_collection.h b/chrome/browser/ui/tabs/pinned_tab_collection.h
new file mode 100644
index 0000000..52c0e14a
--- /dev/null
+++ b/chrome/browser/ui/tabs/pinned_tab_collection.h
@@ -0,0 +1,72 @@
+// 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_TABS_PINNED_TAB_COLLECTION_H_
+#define CHROME_BROWSER_UI_TABS_PINNED_TAB_COLLECTION_H_
+
+#include <memory>
+#include <optional>
+
+#include "chrome/browser/ui/tabs/tab_collection.h"
+
+namespace tabs {
+
+class TabModel;
+class TabCollectionStorage;
+
+class PinnedTabCollection : public TabCollection {
+ public:
+  PinnedTabCollection();
+  ~PinnedTabCollection() override;
+  PinnedTabCollection(const PinnedTabCollection&) = delete;
+  PinnedTabCollection& operator=(const PinnedTabCollection&) = delete;
+
+  // Adds a `tab_model` to the `impl_` at a particular index.
+  void AddTab(std::unique_ptr<TabModel> tab_model, size_t index);
+  // Appends a `tab_model` to the end of a `impl_`.
+  void AppendTab(std::unique_ptr<TabModel> tab_model);
+  // Moves a `tab_model` to the `dst_index` within `impl_`.
+  void MoveTab(TabModel* tab_model, size_t dst_index);
+  // Removes and cleans the `tab_model`.
+  void CloseTab(TabModel* tab_model);
+
+  // TabCollection:
+  // This is non-recursive for pinned tab collection as it does not contain
+  // another collection.
+  bool ContainsTabRecursive(TabModel* tab_model) const override;
+  // This is false as pinned tab collection does not contain another collection.
+  bool ContainsCollection(TabCollection* collection) const override;
+  // This is non-recursive for pinned tab collection as it does not contain
+  // another collection.
+  std::optional<size_t> GetIndexOfTabRecursive(
+      TabModel* tab_model) const override;
+  // This is nullopt as pinned tab collection does not contain another
+  // collection.
+  std::optional<size_t> GetIndexOfCollection(
+      TabCollection* collection) const override;
+  std::unique_ptr<TabModel> MaybeRemoveTab(TabModel* tab_model) override;
+  // This is the same as number of tabs `impl_` contains as pinned tab
+  // collection does not contain another collection.
+  size_t ChildCount() const override;
+  // This is non-recursive for pinned tab collection as it does not contain
+  // another collection.
+  size_t TabCountRecursive() const override;
+
+  // TabCollection interface methods that are currently not supported by the
+  // collection.
+  std::unique_ptr<TabCollection> MaybeRemoveCollection(
+      TabCollection* collection) override;
+
+  TabCollectionStorage* GetTabCollectionStorageForTesting() {
+    return impl_.get();
+  }
+
+ private:
+  // Underlying implementation for the storage of children.
+  std::unique_ptr<TabCollectionStorage> impl_;
+};
+
+}  // namespace tabs
+
+#endif  // CHROME_BROWSER_UI_TABS_PINNED_TAB_COLLECTION_H_
diff --git a/chrome/browser/ui/tabs/tab_collection.h b/chrome/browser/ui/tabs/tab_collection.h
index a1ab895..9daf55d 100644
--- a/chrome/browser/ui/tabs/tab_collection.h
+++ b/chrome/browser/ui/tabs/tab_collection.h
@@ -8,6 +8,7 @@
 #include <cstddef>
 #include <memory>
 #include <optional>
+
 #include "base/memory/raw_ptr.h"
 #include "base/types/pass_key.h"
 
@@ -17,7 +18,7 @@
 
 class TabCollection {
  public:
-  TabCollection();
+  TabCollection() = default;
   virtual ~TabCollection() = default;
   TabCollection(const TabCollection&) = delete;
   TabCollection& operator=(const TabCollection&) = delete;
diff --git a/chrome/browser/ui/tabs/tab_collection_storage.cc b/chrome/browser/ui/tabs/tab_collection_storage.cc
new file mode 100644
index 0000000..d2a9129
--- /dev/null
+++ b/chrome/browser/ui/tabs/tab_collection_storage.cc
@@ -0,0 +1,98 @@
+// 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 <memory>
+
+#include "base/notimplemented.h"
+#include "chrome/browser/ui/tabs/tab_collection.h"
+#include "chrome/browser/ui/tabs/tab_collection_storage.h"
+#include "chrome/browser/ui/tabs/tab_model.h"
+
+namespace tabs {
+
+TabCollectionStorage::TabCollectionStorage(TabCollection& owner)
+    : owning_collection_(owner) {}
+
+TabCollectionStorage::~TabCollectionStorage() = default;
+
+bool TabCollectionStorage::ContainsTab(TabModel* tab_model) const {
+  return GetIndexOfTab(tab_model).has_value();
+}
+
+TabModel* TabCollectionStorage::AddTab(std::unique_ptr<TabModel> tab_model,
+                                       size_t index) {
+  CHECK(index <= GetChildrenCount());
+  CHECK(tab_model);
+
+  TabModel* tab_model_ptr = tab_model.get();
+  children_.insert(children_.begin() + index, std::move(tab_model));
+  return tab_model_ptr;
+}
+
+void TabCollectionStorage::MoveTab(TabModel* tab_model, size_t dst_index) {
+  CHECK(tab_model);
+  std::unique_ptr<TabModel> tab_model_to_move = RemoveTab(tab_model);
+  CHECK(tab_model_to_move);
+  AddTab(std::move(tab_model_to_move), dst_index);
+}
+
+std::unique_ptr<TabModel> TabCollectionStorage::RemoveTab(TabModel* tab_model) {
+  CHECK(tab_model);
+  for (size_t i = 0; i < children_.size(); ++i) {
+    if (std::holds_alternative<std::unique_ptr<TabModel>>(children_[i])) {
+      auto& stored_tab_model =
+          std::get<std::unique_ptr<TabModel>>(children_[i]);
+      if (stored_tab_model.get() == tab_model) {
+        auto removed_tab_model = std::move(stored_tab_model);
+        children_.erase(children_.begin() + i);
+        return removed_tab_model;
+      }
+    }
+  }
+  NOTREACHED_NORETURN();
+}
+
+void TabCollectionStorage::CloseTab(TabModel* tab) {
+  std::unique_ptr<TabModel> removed_tab_model = RemoveTab(tab);
+  removed_tab_model.reset();
+}
+
+TabCollection* TabCollectionStorage::AddCollection(
+    std::unique_ptr<TabCollection> collection,
+    size_t index) {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+void TabCollectionStorage::MoveCollection(TabCollection* collection,
+                                          size_t dst_index) {
+  NOTIMPLEMENTED();
+}
+
+std::unique_ptr<TabCollection> TabCollectionStorage::RemoveCollection(
+    TabCollection* collection) {
+  NOTIMPLEMENTED();
+  return nullptr;
+}
+
+void TabCollectionStorage::CloseCollection(TabCollection* collection) {
+  // This should free all the children as well.
+  NOTIMPLEMENTED();
+}
+
+std::optional<size_t> TabCollectionStorage::GetIndexOfTab(
+    TabModel* tab_model) const {
+  const auto it = std::find_if(
+      children_.begin(), children_.end(), [tab_model](const auto& child) {
+        return std::holds_alternative<std::unique_ptr<TabModel>>(child) &&
+               std::get<std::unique_ptr<TabModel>>(child).get() == tab_model;
+      });
+  return it == children_.end() ? std::nullopt
+                               : std::optional<size_t>(it - children_.begin());
+}
+
+size_t TabCollectionStorage::GetChildrenCount() const {
+  return children_.size();
+}
+}  // namespace tabs
diff --git a/chrome/browser/ui/tabs/tab_collection_storage.h b/chrome/browser/ui/tabs/tab_collection_storage.h
new file mode 100644
index 0000000..f7f5bac
--- /dev/null
+++ b/chrome/browser/ui/tabs/tab_collection_storage.h
@@ -0,0 +1,99 @@
+// 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_TABS_TAB_COLLECTION_STORAGE_H_
+#define CHROME_BROWSER_UI_TABS_TAB_COLLECTION_STORAGE_H_
+
+#include <memory>
+#include <optional>
+#include <variant>
+#include <vector>
+
+#include "base/memory/raw_ref.h"
+
+namespace tabs {
+
+class TabModel;
+class TabCollection;
+
+using ChildrenVector = std::vector<
+    std::variant<std::unique_ptr<TabCollection>, std::unique_ptr<TabModel>>>;
+
+// Provides reusable functionality useful to most TabCollections for storing
+// and manipulating a vector of child tabs and collections.
+// Note that a TabCollectionStorage *is not* a TabCollection, and it
+// does not have:
+// - a parent TabCollection: a TabCollectionStorage doesn't live in the
+// collection tree
+// - MaybeRemoveTab/MaybeRemoveCollection - the storage layer doesn't get to say
+// no
+class TabCollectionStorage {
+ public:
+  explicit TabCollectionStorage(TabCollection& owner);
+  virtual ~TabCollectionStorage();
+  TabCollectionStorage(const TabCollectionStorage&) = delete;
+  TabCollectionStorage& operator=(const TabCollectionStorage&) = delete;
+
+  // Inserts a Tab into the TabCollectionStorage. The `index` represents the
+  // position in the direct children vector (non-recursive).
+  TabModel* AddTab(std::unique_ptr<TabModel> tab_model, size_t index);
+
+  // Moves a tab already within this TabCollectionStorage to `dst_index`. Shifts
+  // other tabs and collections in the collection as needed. Will check if index
+  // is OOB.
+  void MoveTab(TabModel* tab_model, size_t dst_index);
+
+  // Removes `tab_model` from storage and returns it to the caller.
+  [[nodiscard]] std::unique_ptr<TabModel> RemoveTab(TabModel* tab_model);
+
+  // Removes a Tab in the TabCollectionStorage and frees the memory.
+  void CloseTab(TabModel* tab_model);
+
+  // Inserts a TabCollection into the TabCollectionStorage. The `index`
+  // represents the position in the direct children vector (non-recursive).
+  TabCollection* AddCollection(std::unique_ptr<TabCollection> collection,
+                               size_t index);
+
+  // Moves a collection already within this TabCollectionStorage to a new
+  // `index` which is the destination before its move. Shifts other tabs
+  // and collections in the collection as needed. Will check if index
+  // is OOB.
+  void MoveCollection(TabCollection* collection, size_t dst_index);
+
+  // Removes a TabCollection from storage and returns it to the caller. If no
+  // collection is found, returns nullptr.
+  [[nodiscard]] std::unique_ptr<TabCollection> RemoveCollection(
+      TabCollection* collection);
+
+  // Closes a stored TabCollection, and all tabs and collections it recursively
+  // contains. This frees the memory as well.
+  void CloseCollection(TabCollection* collection);
+
+  // Returns true if the `tab_model` is owned by the `children_`.
+  bool ContainsTab(TabModel* tab_model) const;
+
+  // Returns the index of the `tab_model` in `children_`. It returns a nullopt
+  // if the `tab_model` is not present in the `children_`.
+  std::optional<size_t> GetIndexOfTab(TabModel* tab_model) const;
+
+  // Returns the total number of elements stored in `children_`. This is
+  // equivalent to the sum of TabModel and TabCollection present in `children_`.
+  size_t GetChildrenCount() const;
+
+  // Returns read only version of `children_` for clients to query
+  // information about the individual elements.
+  const ChildrenVector& GetChildren() const { return children_; }
+
+ private:
+  // This is where the actual storage is present. `children_` is a vector of
+  // either a `TabModel`or a `TabCollection` and has ownership of the elements.
+  ChildrenVector children_;
+
+  // The collection that owns this TabCollectionStorage.
+  const raw_ref<TabCollection> owning_collection_;
+};
+
+}  // namespace tabs
+
+#endif  // CHROME_BROWSER_UI_TABS_TAB_COLLECTION_STORAGE_H_
diff --git a/chrome/browser/ui/tabs/tab_collection_storage_unittest.cc b/chrome/browser/ui/tabs/tab_collection_storage_unittest.cc
new file mode 100644
index 0000000..be744e7d
--- /dev/null
+++ b/chrome/browser/ui/tabs/tab_collection_storage_unittest.cc
@@ -0,0 +1,224 @@
+// 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 <memory>
+
+#include "chrome/browser/ui/tabs/pinned_tab_collection.h"
+#include "chrome/browser/ui/tabs/tab_collection.h"
+#include "chrome/browser/ui/tabs/tab_collection_storage.h"
+#include "chrome/browser/ui/tabs/tab_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
+#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h"
+#include "chrome/browser/ui/ui_features.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/browser_task_environment.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class TabCollectionStorageTest : public ::testing::Test {
+ public:
+  TabCollectionStorageTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {features::kTabStripCollectionStorage}, {});
+    pinned_collection_ = std::make_unique<tabs::PinnedTabCollection>();
+    testing_profile_ = std::make_unique<TestingProfile>();
+    tab_strip_model_delegate_ = std::make_unique<TestTabStripModelDelegate>();
+    tab_strip_model_ = std::make_unique<TabStripModel>(
+        tab_strip_model_delegate_.get(), testing_profile_.get());
+  }
+  TabCollectionStorageTest(const TabCollectionStorageTest&) = delete;
+  TabCollectionStorageTest& operator=(const TabCollectionStorageTest&) = delete;
+  ~TabCollectionStorageTest() override { pinned_collection_.reset(); }
+
+  tabs::TabCollectionStorage* GetTabCollectionStorage() {
+    return pinned_collection_->GetTabCollectionStorageForTesting();
+  }
+
+  TabStripModel* GetTabStripModel() { return tab_strip_model_.get(); }
+
+  void AddTabs(int num) {
+    for (int i = 0; i < num; i++) {
+      std::unique_ptr<tabs::TabModel> tab_model =
+          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+      tabs::TabModel* tab_model_ptr = tab_model.get();
+
+      tabs::TabModel* inserted_tab_model_ptr =
+          GetTabCollectionStorage()->AddTab(
+              std::move(tab_model),
+              GetTabCollectionStorage()->GetChildrenCount());
+      EXPECT_EQ(tab_model_ptr, inserted_tab_model_ptr);
+      EXPECT_EQ(GetTabCollectionStorage()->GetIndexOfTab(tab_model_ptr),
+                GetTabCollectionStorage()->GetChildrenCount() - 1);
+    }
+  }
+
+  void SetChildID(tabs::TabModel* tab_model, int id) {
+    tab_handle_to_id_map_[tab_model->GetHandle()] = id;
+  }
+
+  void ResetChildrenIDs(int start) {
+    int i = 0;
+    const auto& children = GetTabCollectionStorage()->GetChildren();
+    for (const auto& child : children) {
+      if (std::holds_alternative<std::unique_ptr<tabs::TabModel>>(child)) {
+        SetChildID(std::get<std::unique_ptr<tabs::TabModel>>(child).get(),
+                   start + i);
+        i += 1;
+      }
+    }
+  }
+
+  std::vector<int> IDString() {
+    std::vector<int> res;
+    const auto& children = GetTabCollectionStorage()->GetChildren();
+    for (const auto& child : children) {
+      if (std::holds_alternative<std::unique_ptr<tabs::TabModel>>(child)) {
+        tabs::TabModel* tab_model =
+            std::get<std::unique_ptr<tabs::TabModel>>(child).get();
+        res.push_back(tab_handle_to_id_map_[tab_model->GetHandle()]);
+      }
+    }
+    return res;
+  }
+
+ private:
+  content::BrowserTaskEnvironment task_environment_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<tabs::PinnedTabCollection> pinned_collection_;
+  std::unique_ptr<TabStripModel> tab_strip_model_;
+  std::unique_ptr<Profile> testing_profile_;
+  std::unique_ptr<TestTabStripModelDelegate> tab_strip_model_delegate_;
+  std::map<tabs::TabHandle, int> tab_handle_to_id_map_;
+};
+
+TEST_F(TabCollectionStorageTest, AddTabOperation) {
+  auto tab_model_one =
+      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+  auto tab_model_two =
+      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+
+  tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
+  tabs::TabModel* tab_model_two_ptr = tab_model_two.get();
+
+  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
+  collection_storage->AddTab(std::move(tab_model_one), 0);
+
+  EXPECT_TRUE(collection_storage->ContainsTab(tab_model_one_ptr));
+  EXPECT_FALSE(collection_storage->ContainsTab(tab_model_two.get()));
+
+  // Add four more tabs.
+  AddTabs(4);
+  ResetChildrenIDs(0);
+
+  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
+
+  // Annotate `tab_model_two_ptr` with an id of 5.
+  SetChildID(tab_model_two_ptr, 5);
+  collection_storage->AddTab(std::move(tab_model_two), 3ul);
+  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_two_ptr), 3ul);
+  EXPECT_EQ(IDString(), (std::vector<int>{0, 1, 2, 5, 3, 4}));
+}
+
+TEST_F(TabCollectionStorageTest, RemoveTabOperation) {
+  auto tab_model_one =
+      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+  tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
+
+  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
+
+  // Add four tabs
+  AddTabs(4);
+
+  // Add `tab_model_one` to index 3.
+  collection_storage->AddTab(std::move(tab_model_one), 3ul);
+  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
+  ResetChildrenIDs(0);
+
+  auto removed_tab_model = collection_storage->RemoveTab(tab_model_one_ptr);
+
+  EXPECT_EQ(collection_storage->GetChildrenCount(), 4ul);
+  EXPECT_EQ(removed_tab_model.get(), tab_model_one_ptr);
+  // `tab_model_one_ptr` was removed from index 3.
+  EXPECT_EQ(IDString(), (std::vector<int>{0, 1, 2, 4}));
+}
+
+TEST_F(TabCollectionStorageTest, CloseTabOperation) {
+  auto tab_model_one =
+      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+  tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
+
+  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
+
+  // Add four tabs
+  AddTabs(4);
+
+  // Add `tab_model_one` to index 3.
+  collection_storage->AddTab(std::move(tab_model_one), 3ul);
+  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
+  ResetChildrenIDs(0);
+
+  collection_storage->CloseTab(tab_model_one_ptr);
+
+  EXPECT_EQ(collection_storage->GetChildrenCount(), 4ul);
+  EXPECT_EQ(IDString(), (std::vector<int>{0, 1, 2, 4}));
+}
+
+TEST_F(TabCollectionStorageTest, MoveTabOperation) {
+  auto tab_model_one =
+      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+  tabs::TabModel* tab_model_one_ptr = tab_model_one.get();
+
+  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
+
+  // Add four tabs
+  AddTabs(4);
+
+  // Add `tab_model_one` to index 3.
+  collection_storage->AddTab(std::move(tab_model_one), 3ul);
+  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_one_ptr), 3ul);
+  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
+  ResetChildrenIDs(0);
+
+  collection_storage->MoveTab(tab_model_one_ptr, 1ul);
+
+  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
+  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_one_ptr), 1ul);
+  EXPECT_EQ(IDString(), (std::vector<int>{0, 3, 1, 2, 4}));
+
+  collection_storage->MoveTab(tab_model_one_ptr, 4ul);
+  EXPECT_EQ(collection_storage->GetChildrenCount(), 5ul);
+  EXPECT_EQ(collection_storage->GetIndexOfTab(tab_model_one_ptr), 4ul);
+  EXPECT_EQ(IDString(), (std::vector<int>{0, 1, 2, 4, 3}));
+}
+
+TEST_F(TabCollectionStorageTest, InvalidArgumentsTabOperations) {
+  auto tab_model_one =
+      std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel());
+  tabs::TabCollectionStorage* collection_storage = GetTabCollectionStorage();
+  std::unique_ptr<tabs::TabModel> empty_ptr;
+
+  EXPECT_DEATH(
+      collection_storage->AddTab(
+          std::make_unique<tabs::TabModel>(nullptr, GetTabStripModel()), 10ul),
+      "");
+  EXPECT_DEATH(collection_storage->AddTab(std::move(empty_ptr), 1ul), "");
+
+  EXPECT_DEATH(
+      {
+        std::unique_ptr<tabs::TabModel> tab_model =
+            collection_storage->RemoveTab(tab_model_one.get());
+      },
+      "");
+  EXPECT_DEATH(
+      {
+        std::unique_ptr<tabs::TabModel> tab_model =
+            collection_storage->RemoveTab(nullptr);
+      },
+      "");
+
+  EXPECT_DEATH(collection_storage->MoveTab(tab_model_one.get(), 0ul), "");
+  collection_storage->AddTab(std::move(tab_model_one), 0ul);
+  EXPECT_DEATH(collection_storage->MoveTab(tab_model_one.get(), 10ul), "");
+  EXPECT_DEATH(collection_storage->MoveTab(nullptr, 10ul), "");
+}
diff --git a/chrome/browser/ui/webui/ash/assistant_optin/assistant_optin_ui.cc b/chrome/browser/ui/webui/ash/assistant_optin/assistant_optin_ui.cc
index c25a8154..b96e5dd 100644
--- a/chrome/browser/ui/webui/ash/assistant_optin/assistant_optin_ui.cc
+++ b/chrome/browser/ui/webui/ash/assistant_optin/assistant_optin_ui.cc
@@ -28,7 +28,6 @@
 #include "chrome/grit/assistant_optin_resources.h"
 #include "chrome/grit/assistant_optin_resources_map.h"
 #include "chrome/grit/browser_resources.h"
-#include "chrome/grit/oobe_conditional_resources.h"
 #include "chromeos/ash/components/assistant/buildflags.h"
 #include "chromeos/ash/services/assistant/public/cpp/assistant_prefs.h"
 #include "chromeos/ash/services/assistant/public/cpp/features.h"
diff --git a/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.cc b/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.cc
index b50400a0..e67b0310 100644
--- a/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.cc
+++ b/chrome/browser/ui/webui/ash/lock_screen_reauth/lock_screen_start_reauth_ui.cc
@@ -25,7 +25,6 @@
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/lock_screen_reauth_resources.h"
 #include "chrome/grit/lock_screen_reauth_resources_map.h"
-#include "chrome/grit/oobe_unconditional_resources_map.h"
 #include "components/prefs/pref_service.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "ui/base/l10n/l10n_util.h"
diff --git a/chrome/browser/ui/webui/ash/login/oobe_ui.cc b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
index fe7bde2..a12dd3f3 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_ui.cc
+++ b/chrome/browser/ui/webui/ash/login/oobe_ui.cc
@@ -143,9 +143,8 @@
 #include "chrome/grit/gaia_auth_host_resources.h"
 #include "chrome/grit/gaia_auth_host_resources_map.h"
 #include "chrome/grit/generated_resources.h"
-#include "chrome/grit/oobe_conditional_resources.h"
-#include "chrome/grit/oobe_unconditional_resources.h"
-#include "chrome/grit/oobe_unconditional_resources_map.h"
+#include "chrome/grit/oobe_resources.h"
+#include "chrome/grit/oobe_resources_map.h"
 #include "chromeos/ash/components/assistant/buildflags.h"
 #include "chromeos/ash/services/auth_factor_config/in_process_instances.h"
 #include "chromeos/ash/services/cellular_setup/public/mojom/esim_manager.mojom.h"
@@ -267,25 +266,23 @@
     base::SysInfo::CrashIfChromeOSNonTestImage();
   }
 
-  source->AddResourcePath(kDebuggerMJSPath,
-                          dev_overlay_enabled
-                              ? IDR_OOBE_CONDITIONAL_DEBUG_DEBUG_JS
-                              : IDR_OOBE_CONDITIONAL_DEBUG_NO_DEBUG_JS);
+  source->AddResourcePath(kDebuggerMJSPath, dev_overlay_enabled
+                                                ? IDR_OOBE_DEBUG_DEBUG_JS
+                                                : IDR_OOBE_DEBUG_NO_DEBUG_JS);
 
-  source->AddResourcePath(
-      kQuickStartDebuggerPath,
-      quick_start_debugger_enabled
-          ? IDR_OOBE_CONDITIONAL_DEBUG_QUICK_START_DEBUGGER_JS
-          : IDR_OOBE_CONDITIONAL_DEBUG_NO_DEBUG_JS);
+  source->AddResourcePath(kQuickStartDebuggerPath,
+                          quick_start_debugger_enabled
+                              ? IDR_OOBE_DEBUG_QUICK_START_DEBUGGER_JS
+                              : IDR_OOBE_DEBUG_NO_DEBUG_JS);
 }
 
 void AddTestAPIResources(content::WebUIDataSource* source) {
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   const bool enabled = command_line->HasSwitch(switches::kEnableOobeTestAPI);
 
-  source->AddResourcePath(
-      kTestAPIJsMPath, enabled ? IDR_OOBE_CONDITIONAL_TEST_API_TEST_API_JS
-                               : IDR_OOBE_CONDITIONAL_TEST_API_NO_TEST_API_JS);
+  source->AddResourcePath(kTestAPIJsMPath,
+                          enabled ? IDR_OOBE_TEST_API_TEST_API_JS
+                                  : IDR_OOBE_TEST_API_NO_TEST_API_JS);
 }
 
 // Creates a WebUIDataSource for chrome://oobe
@@ -302,7 +299,7 @@
 
   OobeUI::AddOobeComponents(source);
 
-  source->SetDefaultResource(IDR_OOBE_UNCONDITIONAL_OOBE_HTML);
+  source->SetDefaultResource(IDR_OOBE_OOBE_HTML);
 
   // Add boolean variables that are used to add screens
   // dynamically depending on the flow type.
@@ -742,11 +739,23 @@
 }
 
 // static
-
 void OobeUI::AddOobeComponents(content::WebUIDataSource* source) {
   // Add all resources from OOBE's autogenerated GRD.
-  source->AddResourcePaths(base::make_span(kOobeUnconditionalResources,
-                                           kOobeUnconditionalResourcesSize));
+  const base::flat_set<std::string_view> kConditionalResources = {
+      "debug/debug.js",
+      "debug/no_debug.js",
+      "debug/quick_start_debugger.js",
+      "debug/quick_start_debugger.html.js",
+      "components/oobe_vars/oobe_custom_vars.css.js",
+      "components/oobe_vars/oobe_custom_vars_remora.css.js",
+      "test_api/no_test_api.js",
+      "test_api/test_api.js",
+  };
+  for (const auto& path : base::make_span(kOobeResources, kOobeResourcesSize)) {
+    if (!kConditionalResources.contains(path.path)) {
+      source->AddResourcePath(path.path, path.id);
+    }
+  }
   // Add Gaia Authenticator resources
   source->AddResourcePaths(
       base::make_span(kGaiaAuthHostResources, kGaiaAuthHostResourcesSize));
@@ -754,11 +763,11 @@
   if (policy::EnrollmentRequisitionManager::IsRemoraRequisition()) {
     source->AddResourcePath(
         kOobeCustomVarsCssJs,
-        IDR_OOBE_CONDITIONAL_COMPONENTS_OOBE_VARS_OOBE_CUSTOM_VARS_REMORA_CSS_JS);
+        IDR_OOBE_COMPONENTS_OOBE_VARS_OOBE_CUSTOM_VARS_REMORA_CSS_JS);
   } else {
     source->AddResourcePath(
         kOobeCustomVarsCssJs,
-        IDR_OOBE_CONDITIONAL_COMPONENTS_OOBE_VARS_OOBE_CUSTOM_VARS_CSS_JS);
+        IDR_OOBE_COMPONENTS_OOBE_VARS_OOBE_CUSTOM_VARS_CSS_JS);
   }
 
   source->OverrideContentSecurityPolicy(
diff --git a/chrome/browser/ui/webui/ash/login/oobe_ui.h b/chrome/browser/ui/webui/ash/login/oobe_ui.h
index bb31228..2c316af0 100644
--- a/chrome/browser/ui/webui/ash/login/oobe_ui.h
+++ b/chrome/browser/ui/webui/ash/login/oobe_ui.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 #include "ash/webui/common/chrome_os_webui_config.h"
+#include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/ref_counted.h"
 #include "base/observer_list.h"
diff --git a/chrome/browser/ui/webui/net_internals/net_internals_ui.cc b/chrome/browser/ui/webui/net_internals/net_internals_ui.cc
index b040490..fca7aab8 100644
--- a/chrome/browser/ui/webui/net_internals/net_internals_ui.cc
+++ b/chrome/browser/ui/webui/net_internals/net_internals_ui.cc
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "base/base64.h"
+#include "base/containers/to_value_list.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
@@ -73,11 +74,8 @@
 // This function converts std::vector<net::IPEndPoint> to base::Value::List.
 base::Value::List IPEndpointsToBaseList(
     const std::vector<net::IPEndPoint>& resolved_addresses) {
-  base::Value::List resolved_addresses_list;
-  for (const net::IPEndPoint& resolved_address : resolved_addresses) {
-    resolved_addresses_list.Append(resolved_address.ToStringWithoutPort());
-  }
-  return resolved_addresses_list;
+  return base::ToValueList(resolved_addresses,
+                           &net::IPEndPoint::ToStringWithoutPort);
 }
 
 // This function converts std::optional<net::HostResolverEndpointResults> to
diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos.cc
index 6e51eeeb..594e4f2 100644
--- a/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos.cc
+++ b/chrome/browser/ui/webui/print_preview/print_preview_handler_chromeos.cc
@@ -12,6 +12,7 @@
 
 #include "base/check.h"
 #include "base/check_op.h"
+#include "base/containers/to_value_list.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
@@ -73,11 +74,9 @@
 
 base::Value::List ConvertPrintersToValues(
     const std::vector<crosapi::mojom::LocalDestinationInfoPtr>& printers) {
-  base::Value::List list;
-  for (const crosapi::mojom::LocalDestinationInfoPtr& p : printers) {
-    list.Append(LocalPrinterHandlerChromeos::PrinterToValue(*p));
-  }
-  return list;
+  return base::ToValueList(printers, [](const auto& printer) {
+    return LocalPrinterHandlerChromeos::PrinterToValue(*printer);
+  });
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/webui/realbox/realbox_handler.cc b/chrome/browser/ui/webui/realbox/realbox_handler.cc
index 175ab567..79ffd4a 100644
--- a/chrome/browser/ui/webui/realbox/realbox_handler.cc
+++ b/chrome/browser/ui/webui/realbox/realbox_handler.cc
@@ -309,7 +309,7 @@
 
 std::vector<omnibox::mojom::AutocompleteMatchPtr> CreateAutocompleteMatches(
     const AutocompleteResult& result,
-    bookmarks::BookmarkModel* bookmark_model,
+    bookmarks::CoreBookmarkModel* bookmark_model,
     const omnibox::GroupConfigMap& suggestion_groups_map) {
   std::vector<omnibox::mojom::AutocompleteMatchPtr> matches;
   int line = 0;
@@ -424,7 +424,7 @@
 omnibox::mojom::AutocompleteResultPtr CreateAutocompleteResult(
     const std::u16string& input,
     const AutocompleteResult& result,
-    bookmarks::BookmarkModel* bookmark_model,
+    bookmarks::CoreBookmarkModel* bookmark_model,
     PrefService* prefs) {
   return omnibox::mojom::AutocompleteResult::New(
       input,
@@ -472,7 +472,7 @@
   bool IsPasteAndGoEnabled() const override;
   SessionID GetSessionID() const override;
   PrefService* GetPrefs() override;
-  bookmarks::BookmarkModel* GetBookmarkModel() override;
+  bookmarks::CoreBookmarkModel* GetBookmarkModel() override;
   AutocompleteControllerEmitter* GetAutocompleteControllerEmitter() override;
   TemplateURLService* GetTemplateURLService() override;
   const AutocompleteSchemeClassifier& GetSchemeClassifier() const override;
@@ -538,7 +538,7 @@
   return profile_->GetPrefs();
 }
 
-bookmarks::BookmarkModel* RealboxOmniboxClient::GetBookmarkModel() {
+bookmarks::CoreBookmarkModel* RealboxOmniboxClient::GetBookmarkModel() {
   return BookmarkModelFactory::GetForBrowserContext(profile_);
 }
 
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 62eff6e..33cfe10 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -516,8 +516,6 @@
 
     if (is_chromeos) {
       sources += [
-        "preinstalled_web_apps/app_mall.cc",
-        "preinstalled_web_apps/app_mall.h",
         "preinstalled_web_apps/calculator.cc",
         "preinstalled_web_apps/calculator.h",
         "preinstalled_web_apps/google_calendar.cc",
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager_unittest.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager_unittest.cc
index 48f0ea6f..4a0bb03 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager_unittest.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_update_manager_unittest.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/containers/to_vector.h"
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -23,7 +24,6 @@
 #include "base/test/scoped_feature_list.h"
 #include "base/test/test_future.h"
 #include "base/test/test_timeouts.h"
-#include "base/test/to_vector.h"
 #include "base/test/values_test_util.h"
 #include "base/time/time.h"
 #include "base/version.h"
@@ -69,8 +69,8 @@
 namespace web_app {
 namespace {
 
+using base::ToVector;
 using base::test::DictionaryHasValue;
-using base::test::ToVector;
 using base::test::ValueIs;
 using ::testing::_;
 using ::testing::AllOf;
diff --git a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.cc b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.cc
index 7fce3e8..a30eee74 100644
--- a/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/policy/isolated_web_app_policy_manager.cc
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "base/barrier_callback.h"
+#include "base/containers/to_value_list.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/functional/bind.h"
@@ -44,15 +45,6 @@
 
 namespace {
 
-template <typename Range, typename Proj = std::identity>
-base::Value::List ToList(const Range& items, Proj proj = {}) {
-  base::Value::List list;
-  for (const auto& item : items) {
-    list.Append(std::invoke(proj, item));
-  }
-  return list;
-}
-
 base::File::Error CreateDirectoryWithStatus(const base::FilePath& path) {
   base::File::Error err = base::File::FILE_OK;
   base::CreateDirectoryAndGetError(path, &err);
@@ -575,20 +567,23 @@
   }
 
   debug_info.Set("apps_in_policy",
-                 ToList(apps_in_policy, [](const auto& options) {
+                 base::ToValueList(apps_in_policy, [](const auto& options) {
                    return options.web_bundle_id().id();
                  }));
-  debug_info.Set("installed_apps",
-                 ToList(installed_apps, &web_package::SignedWebBundleId::id));
   debug_info.Set(
-      "to_be_installed", ToList(to_be_installed, [](const auto& options) {
+      "installed_apps",
+      base::ToValueList(installed_apps, &web_package::SignedWebBundleId::id));
+  debug_info.Set(
+      "to_be_installed",
+      base::ToValueList(to_be_installed, [](const auto& options) {
         return base::Value::Dict()
             .Set("id", options.web_bundle_id().id())
             .Set("update_manifest_url",
                  options.update_manifest_url().possibly_invalid_spec());
       }));
-  debug_info.Set("to_be_removed",
-                 ToList(to_be_removed, &web_package::SignedWebBundleId::id));
+  debug_info.Set(
+      "to_be_removed",
+      base::ToValueList(to_be_removed, &web_package::SignedWebBundleId::id));
   current_process_log_.Merge(debug_info.Clone());
 
   auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
@@ -626,7 +621,8 @@
     }
   }
   current_process_log_.Set(
-      "uninstall_results", ToList(uninstall_results, [](const auto& result) {
+      "uninstall_results",
+      base::ToValueList(uninstall_results, [](const auto& result) {
         const auto& [web_bundle_id, uninstall_result] = result;
         return base::Value::Dict()
             .Set("id", web_bundle_id.id())
@@ -666,7 +662,8 @@
     }
   }
   current_process_log_.Set(
-      "install_results", ToList(install_results, [](const auto& result) {
+      "install_results",
+      base::ToValueList(install_results, [](const auto& result) {
         const auto& [web_bundle_id, install_result] = result;
         return base::Value::Dict()
             .Set("id", web_bundle_id.id())
@@ -711,7 +708,7 @@
 }
 
 base::Value IsolatedWebAppPolicyManager::ProcessLogs::ToDebugValue() const {
-  return base::Value(ToList(logs_, &base::Value::Dict::Clone));
+  return base::Value(base::ToValueList(logs_, &base::Value::Dict::Clone));
 }
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/preinstalled_app_install_features.cc b/chrome/browser/web_applications/preinstalled_app_install_features.cc
index 0ab7d630f..eacf58fc 100644
--- a/chrome/browser/web_applications/preinstalled_app_install_features.cc
+++ b/chrome/browser/web_applications/preinstalled_app_install_features.cc
@@ -27,11 +27,7 @@
 // After a feature flag has been shipped and should be cleaned up, move it into
 // kShippedPreinstalledAppInstallFeatures to ensure any external installation
 // configs that reference it continue to see it as enabled.
-constexpr const base::Feature* kPreinstalledAppInstallFeatures[] = {
-#if BUILDFLAG(IS_CHROMEOS)
-    &chromeos::features::kCrosMall,
-#endif
-};
+constexpr const base::Feature* kPreinstalledAppInstallFeatures[] = {};
 
 constexpr const base::StringPiece kShippedPreinstalledAppInstallFeatures[] = {
     // Enables installing the PWA version of the chrome os calculator instead of
diff --git a/chrome/browser/web_applications/preinstalled_web_apps/app_mall.cc b/chrome/browser/web_applications/preinstalled_web_apps/app_mall.cc
deleted file mode 100644
index d0f41d0..0000000
--- a/chrome/browser/web_applications/preinstalled_web_apps/app_mall.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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/web_applications/preinstalled_web_apps/app_mall.h"
-
-#include <memory>
-
-#include "base/functional/bind.h"
-#include "chrome/browser/web_applications/external_install_options.h"
-#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
-#include "chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_app_definition_utils.h"
-#include "chrome/browser/web_applications/web_app_install_info.h"
-#include "chrome/grit/preinstalled_web_apps_resources.h"
-#include "chromeos/constants/chromeos_features.h"
-
-namespace web_app {
-
-ExternalInstallOptions GetConfigForAppMall() {
-  ExternalInstallOptions options(
-      /*install_url=*/GURL("https://discover.apps.chrome/"),
-      /*user_display_mode=*/mojom::UserDisplayMode::kStandalone,
-      /*install_source=*/ExternalInstallSource::kExternalDefault);
-
-  options.user_type_allowlist = {"unmanaged"};
-  options.gate_on_feature = chromeos::features::kCrosMall.name;
-
-  options.load_and_await_service_worker_registration = false;
-  options.only_use_app_info_factory = false;
-
-  // This App Info Factory is temporary, to help with prototyping.
-  // TODO(b/327080071): Remove.
-  options.app_info_factory = base::BindRepeating([]() {
-    auto info = std::make_unique<WebAppInstallInfo>();
-    info->title = u"Get Apps and Games";
-    info->start_url = GURL("https://discover.apps.chrome/");
-    info->manifest_id = GURL("https://discover.apps.chrome/");
-    info->display_mode = DisplayMode::kStandalone;
-    info->icon_bitmaps.any = LoadBundledIcons(
-        {IDR_PREINSTALLED_WEB_APPS_GOOGLE_SHEETS_ICON_192_PNG});
-    return info;
-  });
-
-  return options;
-}
-
-}  // namespace web_app
diff --git a/chrome/browser/web_applications/preinstalled_web_apps/app_mall.h b/chrome/browser/web_applications/preinstalled_web_apps/app_mall.h
deleted file mode 100644
index 57bfb03..0000000
--- a/chrome/browser/web_applications/preinstalled_web_apps/app_mall.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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_WEB_APPLICATIONS_PREINSTALLED_WEB_APPS_APP_MALL_H_
-#define CHROME_BROWSER_WEB_APPLICATIONS_PREINSTALLED_WEB_APPS_APP_MALL_H_
-
-#include "chrome/browser/web_applications/external_install_options.h"
-
-namespace web_app {
-
-ExternalInstallOptions GetConfigForAppMall();
-
-}  // namespace web_app
-
-#endif  // CHROME_BROWSER_WEB_APPLICATIONS_PREINSTALLED_WEB_APPS_APP_MALL_H_
diff --git a/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.cc b/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.cc
index c7fa851..a52d0445 100644
--- a/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.cc
+++ b/chrome/browser/web_applications/preinstalled_web_apps/preinstalled_web_apps.cc
@@ -27,7 +27,6 @@
 #if BUILDFLAG(IS_CHROMEOS)
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/web_applications/preinstalled_web_apps/app_mall.h"
 #include "chrome/browser/web_applications/preinstalled_web_apps/calculator.h"
 #include "chrome/browser/web_applications/preinstalled_web_apps/google_calendar.h"
 #include "chrome/browser/web_applications/preinstalled_web_apps/google_chat.h"
@@ -79,7 +78,6 @@
       GetConfigForGoogleSlides(is_standalone_tabbed),
       GetConfigForYouTube(),
 #if BUILDFLAG(IS_CHROMEOS)
-      GetConfigForAppMall(),
       GetConfigForCalculator(),
       GetConfigForGoogleCalendar(),
       GetConfigForGoogleChat(),
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index a7f2d83..6176109 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1709164492-6288f66c85f9dc6799707634bc3a1860e9d514b2-19f774aa7739fac564fc3816b1c5d1f5596db9ab.profdata
+chrome-android32-main-1709186356-93559781e9690b9e55432fbfd8166bb7987a35c9-248b5659e1d1fbb9b258b554b81f1fd00f330fb2.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index cce47b6..121d2285 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1709143178-4f64a456c85743b5cc5e5f4b03be4963bead106d-9a354d5fa97fbe84ba3e505ef4efbb4252c24d52.profdata
+chrome-linux-main-1709186356-02794aa8bc1d2b1b6af82e52230a764c74522837-248b5659e1d1fbb9b258b554b81f1fd00f330fb2.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index bc4c572..367c8c30 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1709178867-df03373861caa8b82efa8359359cd07254332c76-6046afcc87040c3b54b2c48838f0b797c71f510c.profdata
+chrome-mac-arm-main-1709207912-fd591abbe16e6948fed9e2ec00ad114d32d6c267-2d819bfbac4fb4878136cb51604920b8d923177b.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 9eae478..5b7fe93 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1709164492-206dd0714e97f2cc11a2279e72dcfe214277a020-19f774aa7739fac564fc3816b1c5d1f5596db9ab.profdata
+chrome-mac-main-1709186356-ba3ade54f201b6cf0664673d73b9426259a14b77-248b5659e1d1fbb9b258b554b81f1fd00f330fb2.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 79a11d01..fbebfdfa 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1709164492-de17ea76f0d4d78c8045ececd53014373fd50ecc-19f774aa7739fac564fc3816b1c5d1f5596db9ab.profdata
+chrome-win-arm64-main-1709207912-5ecdf22b3ee625cf221e95f8f02c1197d1b3c9a9-2d819bfbac4fb4878136cb51604920b8d923177b.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 8c97a93..529f2c6 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1709164492-253bd6167de3270ba7ff11b7de3142addf91a091-19f774aa7739fac564fc3816b1c5d1f5596db9ab.profdata
+chrome-win32-main-1709196988-bc081fad4c34f433d5f6c03c346d0ba135c9245d-d37490bdd3ab3ae5ecdee3e75decb68862646baa.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index f4e8fba..6cc34c56 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1709164492-273b526152379e597770e53a70c963e24842c057-19f774aa7739fac564fc3816b1c5d1f5596db9ab.profdata
+chrome-win64-main-1709196988-ee1211a6840a65dcaf67c68cabcc2e232d0da890-d37490bdd3ab3ae5ecdee3e75decb68862646baa.profdata
diff --git a/chrome/chrome_paks.gni b/chrome/chrome_paks.gni
index d90256a3..76fbf92 100644
--- a/chrome/chrome_paks.gni
+++ b/chrome/chrome_paks.gni
@@ -283,8 +283,7 @@
         "$root_gen_dir/chrome/network_ui_resources.pak",
         "$root_gen_dir/chrome/notification_tester_resources.pak",
         "$root_gen_dir/chrome/office_fallback_resources.pak",
-        "$root_gen_dir/chrome/oobe_conditional_resources.pak",
-        "$root_gen_dir/chrome/oobe_unconditional_resources.pak",
+        "$root_gen_dir/chrome/oobe_resources.pak",
         "$root_gen_dir/chrome/orca_resources.pak",
         "$root_gen_dir/chrome/os_settings_resources.pak",
         "$root_gen_dir/chrome/parent_access_resources.pak",
diff --git a/chrome/renderer/BUILD.gn b/chrome/renderer/BUILD.gn
index b5e29a0..dde8502 100644
--- a/chrome/renderer/BUILD.gn
+++ b/chrome/renderer/BUILD.gn
@@ -101,6 +101,8 @@
     "url_loader_throttle_provider_impl.h",
     "v8_unwinder.cc",
     "v8_unwinder.h",
+    "web_link_preview_triggerer_impl.cc",
+    "web_link_preview_triggerer_impl.h",
     "websocket_handshake_throttle_provider_impl.cc",
     "websocket_handshake_throttle_provider_impl.h",
     "worker_content_settings_client.cc",
diff --git a/chrome/renderer/OWNERS b/chrome/renderer/OWNERS
index 5a76272..4a01ff05 100644
--- a/chrome/renderer/OWNERS
+++ b/chrome/renderer/OWNERS
@@ -20,3 +20,6 @@
 per-file chrome_content_renderer_client.cc=*
 per-file chrome_content_renderer_client.h=*
 per-file chrome_content_renderer_client_unittest.cc=*
+
+# Link Preview.
+per-file web_link_preview_triggerer_impl*=file://chrome/browser/preloading/preview/OWNERS
diff --git a/chrome/renderer/autofill/form_autofill_browsertest.cc b/chrome/renderer/autofill/form_autofill_browsertest.cc
index 1e1b91ed..228414e 100644
--- a/chrome/renderer/autofill/form_autofill_browsertest.cc
+++ b/chrome/renderer/autofill/form_autofill_browsertest.cc
@@ -1372,9 +1372,6 @@
     ExecuteJavaScriptForTests("document.getElementById('firstname').focus();");
     ApplyFormAction(input_element.GetDocument(), form.fields,
                     mojom::ActionPersistence::kPreview);
-    // The selection should be set after the fifth character.
-    EXPECT_EQ(5u, input_element.SelectionStart());
-    EXPECT_EQ(5u, input_element.SelectionEnd());
 
     // Fill the form.
     ApplyFormAction(input_element.GetDocument(), form.fields,
@@ -1481,10 +1478,6 @@
     expected.is_user_edited = false;
     expected.max_length = 0;
     EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[5]);
-
-    // Verify that the cursor position has been updated.
-    EXPECT_EQ(5u, input_element.SelectionStart());
-    EXPECT_EQ(5u, input_element.SelectionEnd());
   }
 
   // Similar to TestFillFormAndModifyValues().
@@ -1538,9 +1531,6 @@
     ExecuteJavaScriptForTests("document.getElementById('firstname').focus();");
     ApplyFormAction(input_element.GetDocument(), form.fields,
                     mojom::ActionPersistence::kPreview);
-    // The selection should be set after the fifth character.
-    EXPECT_EQ(5u, input_element.SelectionStart());
-    EXPECT_EQ(5u, input_element.SelectionEnd());
 
     // Fill the form.
     ApplyFormAction(input_element.GetDocument(), form.fields,
@@ -1599,10 +1589,6 @@
     }
     expected.is_autofilled = false;
     EXPECT_FORM_FIELD_DATA_EQUALS(expected, fields2[2]);
-
-    // Verify that the cursor position has been updated.
-    EXPECT_EQ(5u, input_element.SelectionStart());
-    EXPECT_EQ(5u, input_element.SelectionEnd());
   }
 
   // Similar to TestFillFormAndModifyValues().
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 3c359dc..086c1d1 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -65,6 +65,7 @@
 #include "chrome/renderer/trusted_vault_encryption_keys_extension.h"
 #include "chrome/renderer/url_loader_throttle_provider_impl.h"
 #include "chrome/renderer/v8_unwinder.h"
+#include "chrome/renderer/web_link_preview_triggerer_impl.h"
 #include "chrome/renderer/websocket_handshake_throttle_provider_impl.h"
 #include "chrome/renderer/worker_content_settings_client.h"
 #include "chrome/services/speech/buildflags/buildflags.h"
@@ -1850,3 +1851,8 @@
                   network::mojom::ContentSecurityPolicySource::kHTTP});
 #endif
 }
+
+std::unique_ptr<blink::WebLinkPreviewTriggerer>
+ChromeContentRendererClient::CreateLinkPreviewTriggerer() {
+  return ::CreateWebLinkPreviewTriggerer();
+}
diff --git a/chrome/renderer/chrome_content_renderer_client.h b/chrome/renderer/chrome_content_renderer_client.h
index 68d8afb..3b7c2fe4 100644
--- a/chrome/renderer/chrome_content_renderer_client.h
+++ b/chrome/renderer/chrome_content_renderer_client.h
@@ -210,6 +210,8 @@
   void AppendContentSecurityPolicy(
       const blink::WebURL& url,
       blink::WebVector<blink::WebContentSecurityPolicyHeader>* csp) override;
+  std::unique_ptr<blink::WebLinkPreviewTriggerer> CreateLinkPreviewTriggerer()
+      override;
 
 #if BUILDFLAG(ENABLE_PLUGINS)
   static blink::WebPlugin* CreatePlugin(
diff --git a/chrome/renderer/web_link_preview_triggerer_impl.cc b/chrome/renderer/web_link_preview_triggerer_impl.cc
new file mode 100644
index 0000000..78d5a9a
--- /dev/null
+++ b/chrome/renderer/web_link_preview_triggerer_impl.cc
@@ -0,0 +1,178 @@
+// 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/renderer/web_link_preview_triggerer_impl.h"
+
+#include "base/functional/bind.h"
+#include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/input/web_input_event.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+
+namespace {
+
+constexpr base::TimeDelta kHoverThreshold = base::Milliseconds(800);
+constexpr base::TimeDelta kLongPressThreshold = base::Milliseconds(800);
+
+blink::WebURL GetURL(blink::WebElement& anchor_element) {
+  if (anchor_element.IsNull()) {
+    return blink::WebURL();
+  }
+
+  blink::WebString href = anchor_element.GetAttribute("href");
+  if (href.IsNull()) {
+    return blink::WebURL();
+  }
+
+  // TODO(b:325558426): Mimic HTMLAnchorElement::Href and
+  // StripLeadingAndTrailingHTMLSpaces.
+  blink::WebURL url = anchor_element.GetDocument().CompleteURL(href);
+
+  if (!url.IsValid()) {
+    return blink::WebURL();
+  }
+
+  return url;
+}
+
+}  // namespace
+
+std::unique_ptr<blink::WebLinkPreviewTriggerer>
+CreateWebLinkPreviewTriggerer() {
+  if (!base::FeatureList::IsEnabled(blink::features::kLinkPreview)) {
+    return nullptr;
+  }
+
+  switch (blink::features::kLinkPreviewTriggerType.Get()) {
+    // Alt+click is handled by navigation policy.
+    case blink::features::LinkPreviewTriggerType::kAltClick:
+      return nullptr;
+    case blink::features::LinkPreviewTriggerType::kAltHover:
+      return std::make_unique<WebLinkPreviewTriggererAltHover>();
+    case blink::features::LinkPreviewTriggerType::kLongPress:
+      return std::make_unique<WebLinkPreviewTriggererLongPress>();
+  }
+}
+
+WebLinkPreviewTriggererAltHover::WebLinkPreviewTriggererAltHover()
+    : timer_(std::make_unique<base::OneShotTimer>()) {}
+
+WebLinkPreviewTriggererAltHover::~WebLinkPreviewTriggererAltHover() = default;
+
+WebLinkPreviewTriggererAltHover::WebLinkPreviewTriggererAltHover(
+    WebLinkPreviewTriggererAltHover&& other) = default;
+
+WebLinkPreviewTriggererAltHover& WebLinkPreviewTriggererAltHover::operator=(
+    WebLinkPreviewTriggererAltHover&& other) = default;
+
+void WebLinkPreviewTriggererAltHover::MaybeChangedKeyEventModifier(
+    int modifiers) {
+  bool is_alt_on = (modifiers & blink::WebInputEvent::kAltKey) != 0;
+  UpdateState(is_alt_on, anchor_element_);
+}
+
+void WebLinkPreviewTriggererAltHover::DidChangeHoverElement(
+    blink::WebElement element) {
+  blink::WebElement most_inner_anchor_element;
+  for (blink::WebNode curr = element; !curr.IsNull() && curr.IsElementNode();
+       curr = curr.ParentNode()) {
+    blink::WebElement curr_as_element = curr.To<blink::WebElement>();
+    if (curr_as_element.HasHTMLTagName("a")) {
+      most_inner_anchor_element = curr_as_element;
+      break;
+    }
+  }
+
+  UpdateState(is_alt_on_, most_inner_anchor_element);
+}
+
+void WebLinkPreviewTriggererAltHover::UpdateState(
+    bool is_alt_on,
+    blink::WebElement anchor_element) {
+  if (is_alt_on_ == is_alt_on && anchor_element_ == anchor_element) {
+    return;
+  }
+
+  is_alt_on_ = is_alt_on;
+  anchor_element_ = anchor_element;
+
+  if (is_alt_on_ && !GetURL(anchor_element).IsNull()) {
+    timer_->Start(
+        FROM_HERE, kHoverThreshold,
+        base::BindOnce(&WebLinkPreviewTriggererAltHover::InitiatePreview,
+                       // base::Unretained() is safe since `this` owns `timer_`.
+                       base::Unretained(this)));
+  } else {
+    timer_->Stop();
+  }
+}
+
+void WebLinkPreviewTriggererAltHover::InitiatePreview() {
+  blink::WebDocument document = anchor_element_.GetDocument();
+  if (document.IsNull()) {
+    return;
+  }
+
+  blink::WebURL url = GetURL(anchor_element_);
+  if (url.IsNull()) {
+    return;
+  }
+
+  document.InitiatePreview(url);
+}
+
+WebLinkPreviewTriggererLongPress::WebLinkPreviewTriggererLongPress()
+    : timer_(std::make_unique<base::OneShotTimer>()) {}
+
+WebLinkPreviewTriggererLongPress::~WebLinkPreviewTriggererLongPress() = default;
+
+WebLinkPreviewTriggererLongPress::WebLinkPreviewTriggererLongPress(
+    WebLinkPreviewTriggererLongPress&& other) = default;
+
+WebLinkPreviewTriggererLongPress& WebLinkPreviewTriggererLongPress::operator=(
+    WebLinkPreviewTriggererLongPress&& other) = default;
+
+void WebLinkPreviewTriggererLongPress::DidAnchorElementReceiveMouseEvent(
+    blink::WebElement anchor_element,
+    blink::WebMouseEvent mouse_event) {
+  if (mouse_event.GetType() == blink::WebInputEvent::Type::kMouseDown &&
+      mouse_event.button == blink::WebMouseEvent::Button::kLeft &&
+      mouse_event.ClickCount() == 1) {
+    anchor_element_ = anchor_element;
+    timer_->Start(
+        FROM_HERE, kLongPressThreshold,
+        base::BindOnce(&WebLinkPreviewTriggererLongPress::InitiatePreview,
+                       // base::Unretained() is safe since `this` owns `timer_`.
+                       base::Unretained(this)));
+    return;
+  }
+
+  if (mouse_event.GetType() == blink::WebInputEvent::Type::kMouseUp &&
+      mouse_event.button == blink::WebMouseEvent::Button::kLeft) {
+    anchor_element_ = blink::WebElement();
+    timer_->Stop();
+    return;
+  }
+
+  if (mouse_event.GetType() == blink::WebInputEvent::Type::kMouseLeave) {
+    anchor_element_ = blink::WebElement();
+    timer_->Stop();
+    return;
+  }
+}
+
+void WebLinkPreviewTriggererLongPress::InitiatePreview() {
+  blink::WebDocument document = anchor_element_.GetDocument();
+  if (document.IsNull()) {
+    return;
+  }
+
+  blink::WebURL url = GetURL(anchor_element_);
+  if (url.IsNull()) {
+    return;
+  }
+
+  document.InitiatePreview(url);
+}
diff --git a/chrome/renderer/web_link_preview_triggerer_impl.h b/chrome/renderer/web_link_preview_triggerer_impl.h
new file mode 100644
index 0000000..f98fda8
--- /dev/null
+++ b/chrome/renderer/web_link_preview_triggerer_impl.h
@@ -0,0 +1,82 @@
+// 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_RENDERER_WEB_LINK_PREVIEW_TRIGGERER_IMPL_H_
+#define CHROME_RENDERER_WEB_LINK_PREVIEW_TRIGGERER_IMPL_H_
+
+#include "base/timer/timer.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
+
+// Creates appropriate WebLinkPreviewTriggerer depending on feature flag and
+// params.
+std::unique_ptr<blink::WebLinkPreviewTriggerer> CreateWebLinkPreviewTriggerer();
+
+// Observes events in frame and triggers Link Preview: Alt+hover trigger.
+//
+// This class tracks the state "is Alt key pressed" and mouse hovered anchor
+// element. Alt key tracking is done by observing modifiers for each keyboard
+// events and mouse leave event. See the comment of `last_key_event_modifiers_`
+// for more details and limitations.
+class WebLinkPreviewTriggererAltHover final
+    : public blink::WebLinkPreviewTriggerer {
+ public:
+  WebLinkPreviewTriggererAltHover();
+
+  ~WebLinkPreviewTriggererAltHover() override;
+
+  // Movable but not copyable.
+  WebLinkPreviewTriggererAltHover(WebLinkPreviewTriggererAltHover&& other);
+  WebLinkPreviewTriggererAltHover& operator=(
+      WebLinkPreviewTriggererAltHover&& other);
+  WebLinkPreviewTriggererAltHover(const WebLinkPreviewTriggererAltHover&) =
+      delete;
+  WebLinkPreviewTriggererAltHover& operator=(
+      const WebLinkPreviewTriggererAltHover&) = delete;
+
+  // Implements blink::WebLinkPreviewTriggerer.
+  void MaybeChangedKeyEventModifier(int modifiers) override;
+  void DidChangeHoverElement(blink::WebElement element) override;
+
+ private:
+  void UpdateState(bool is_alt_on, blink::WebElement anchor_element);
+
+  void InitiatePreview();
+
+  std::unique_ptr<base::OneShotTimer> timer_;
+
+  bool is_alt_on_ = false;
+  blink::WebElement anchor_element_;
+};
+
+// Observes events in frame and triggers Link Preview: Long press trigger.
+class WebLinkPreviewTriggererLongPress final
+    : public blink::WebLinkPreviewTriggerer {
+ public:
+  WebLinkPreviewTriggererLongPress();
+
+  ~WebLinkPreviewTriggererLongPress() override;
+
+  // Movable but not copyable.
+  WebLinkPreviewTriggererLongPress(WebLinkPreviewTriggererLongPress&& other);
+  WebLinkPreviewTriggererLongPress& operator=(
+      WebLinkPreviewTriggererLongPress&& other);
+  WebLinkPreviewTriggererLongPress(const WebLinkPreviewTriggererLongPress&) =
+      delete;
+  WebLinkPreviewTriggererLongPress& operator=(
+      const WebLinkPreviewTriggererLongPress&) = delete;
+
+  // Implements blink::WebLinkPreviewTriggerer.
+  void DidAnchorElementReceiveMouseEvent(
+      blink::WebElement anchor_element,
+      blink::WebMouseEvent mouse_event) override;
+
+ private:
+  void InitiatePreview();
+
+  std::unique_ptr<base::OneShotTimer> timer_;
+
+  blink::WebElement anchor_element_;
+};
+
+#endif  // CHROME_RENDERER_WEB_LINK_PREVIEW_TRIGGERER_IMPL_H_
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9de4120..d5ea605 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -7890,6 +7890,7 @@
       "../browser/ui/tabs/pinned_tab_service_unittest.cc",
       "../browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service_unittest.cc",
       "../browser/ui/tabs/supports_handles_unittest.cc",
+      "../browser/ui/tabs/tab_collection_storage_unittest.cc",
       "../browser/ui/tabs/tab_menu_model_unittest.cc",
       "../browser/ui/tabs/tab_strip_model_stats_recorder_unittest.cc",
       "../browser/ui/tabs/tab_strip_model_unittest.cc",
diff --git a/chrome/test/data/extensions/api_test/automation/tests/tabs/document_selection.js b/chrome/test/data/extensions/api_test/automation/tests/tabs/document_selection.js
index 067573a..765df550 100644
--- a/chrome/test/data/extensions/api_test/automation/tests/tabs/document_selection.js
+++ b/chrome/test/data/extensions/api_test/automation/tests/tabs/document_selection.js
@@ -33,19 +33,26 @@
   },
 
   function selectInTextField() {
-    listenOnce(rootNode, EventType.DOCUMENT_SELECTION_CHANGED, function(evt1) {
-      listenOnce(textField, EventType.TEXT_SELECTION_CHANGED, function(evt2) {
-        assertTrue(evt1.target === rootNode);
-        assertTrue(evt2.target == textField);
-        assertEq(textField, rootNode.anchorObject);
-        assertEq(0, rootNode.anchorOffset);
-        assertEq(textField, rootNode.focusObject);
-        assertEq(0, rootNode.focusOffset);
-        chrome.automation.setDocumentSelection({anchorObject: textField,
-                                                anchorOffset: 1,
-                                                focusObject: textField,
-                                                focusOffset: 3});
-        listenOnce(rootNode, EventType.DOCUMENT_SELECTION_CHANGED,
+    var textField = rootNode.find({role: RoleType.TEXT_FIELD});
+    textField.focus();
+    assertTrue(!!textField);
+    // Focusing the textfield will cause a text selection changed event in it.
+    listenOnce(textField, EventType.TEXT_SELECTION_CHANGED, function(evt2) {
+      assertTrue(evt2.target == textField);
+      assertEq(textField, rootNode.anchorObject);
+      assertEq(0, rootNode.anchorOffset);
+      assertEq(textField, rootNode.focusObject);
+      assertEq(0, rootNode.focusOffset);
+
+      // Setting selection within the textfield causes a document selection and
+      // a textfield selection event.
+      chrome.automation.setDocumentSelection({anchorObject: textField,
+                                              anchorOffset: 1,
+                                              focusObject: textField,
+                                              focusOffset: 3});
+      listenOnce(rootNode, EventType.DOCUMENT_SELECTION_CHANGED,
+                 function(evt) {
+        listenOnce(textField, EventType.TEXT_SELECTION_CHANGED,
                    function(evt) {
           assertEq(textField, rootNode.anchorObject);
           assertEq(1, rootNode.anchorOffset);
@@ -55,10 +62,6 @@
         });
       });
     });
-
-    var textField = rootNode.find({role: RoleType.TEXT_FIELD});
-    assertTrue(!!textField);
-    textField.focus();
   },
 ];
 
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 4297b6f..d29f23f 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -73,10 +73,6 @@
              "CrosComponents",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enables an app to discover and install other apps. This flag will be enabled
-// with Finch.
-BASE_FEATURE(kCrosMall, "CrosMall", base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Enables the behaviour difference between web apps and browser created
 // shortcut backed by the web app system on Chrome OS.
 BASE_FEATURE(kCrosShortstand,
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h
index 9909949..94b9cad 100644
--- a/chromeos/constants/chromeos_features.h
+++ b/chromeos/constants/chromeos_features.h
@@ -39,7 +39,6 @@
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 BASE_DECLARE_FEATURE(kCrosAppsBackgroundEventHandling);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kCrosComponents);
-COMPONENT_EXPORT(CHROMEOS_CONSTANTS) BASE_DECLARE_FEATURE(kCrosMall);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
 BASE_DECLARE_FEATURE(kCrosShortstand);
 COMPONENT_EXPORT(CHROMEOS_CONSTANTS)
diff --git a/chromeos/version/version_loader.cc b/chromeos/version/version_loader.cc
index 28cfd17b..4b4839f 100644
--- a/chromeos/version/version_loader.cc
+++ b/chromeos/version/version_loader.cc
@@ -7,6 +7,7 @@
 #include <stddef.h>
 
 #include <string>
+#include <string_view>
 #include <utility>
 #include <vector>
 
@@ -108,7 +109,7 @@
   //   fixed. So we just match kFirmwarePrefix at the start of the line and find
   //   the first character that is not "|" or space
 
-  base::StringPiece firmware_prefix(kFirmwarePrefix);
+  std::string_view firmware_prefix(kFirmwarePrefix);
   for (const std::string& line : base::SplitString(
            contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
     if (base::StartsWith(line, firmware_prefix,
diff --git a/clank b/clank
index ed4b774..306e4ba 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit ed4b774419588a1496c267e4b9ba422d20ea8552
+Subproject commit 306e4ba09278acec4c40f05cc49e5eb5f42275e5
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 00679048..4c2e5e8 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -50,7 +50,9 @@
 #include "third_party/blink/public/web/web_form_element.h"
 #include "third_party/blink/public/web/web_form_related_change_type.h"
 #include "third_party/blink/public/web/web_input_element.h"
+#include "third_party/blink/public/web/web_local_frame.h"
 #include "third_party/blink/public/web/web_node.h"
+#include "third_party/blink/public/web/web_range.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/events/keycodes/keyboard_codes.h"
 
@@ -64,7 +66,9 @@
 using blink::WebFrame;
 using blink::WebInputElement;
 using blink::WebKeyboardEvent;
+using blink::WebLocalFrame;
 using blink::WebNode;
+using blink::WebRange;
 using blink::WebString;
 
 namespace autofill {
@@ -771,12 +775,9 @@
             previewed_elements_.emplace_back(last_queried_element_,
                                              form_control.GetAutofillState());
             form_control.SetSuggestedValue(WebString::FromUTF16(value));
-            form_util::PreviewSuggestion(form_control.SuggestedValue().Utf16(),
-                                         form_control.Value().Utf16(),
-                                         form_control);
             break;
           case mojom::FieldActionType::kSelectAll:
-            DCHECK(value.empty());
+            NOTIMPLEMENTED() << "Previewing select all is not implemented";
             break;
         }
         break;
@@ -794,6 +795,7 @@
           }
           case mojom::FieldActionType::kSelectAll:
             DCHECK(value.empty());
+            form_control.SelectText(/*select_all=*/true);
             break;
         }
         break;
@@ -810,15 +812,20 @@
             << "Previewing replacement of selection is not implemented";
         break;
       case mojom::ActionPersistence::kFill:
-        if (action_type == mojom::FieldActionType::kSelectAll) {
-          DCHECK(value.empty());
-          break;
+        switch (action_type) {
+          case mojom::FieldActionType::kSelectAll:
+            DCHECK(value.empty());
+            content_editable.SelectText(/*select_all=*/true);
+            break;
+          case mojom::FieldActionType::kReplaceAll:
+            [[fallthrough]];
+          case mojom::FieldActionType::kReplaceSelection:
+            content_editable.PasteText(
+                WebString::FromUTF16(value),
+                /*replace_all=*/
+                (action_type == mojom::FieldActionType::kReplaceAll));
+            break;
         }
-        content_editable.PasteText(
-            WebString::FromUTF16(value),
-            /*replace_all=*/
-            (action_type == mojom::FieldActionType::kReplaceAll));
-        break;
     }
   }
 }
diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc
index 1ada583..43ad032 100644
--- a/components/autofill/content/renderer/form_autofill_util.cc
+++ b/components/autofill/content/renderer/form_autofill_util.cc
@@ -1091,7 +1091,6 @@
 // Sets the |field|'s "suggested" (non JS visible) value to the value in |data|.
 // Also sets the "autofilled" attribute, causing the background to be blue.
 void PreviewFormField(const FormFieldData::FillData& data,
-                      bool is_initiating_node,
                       WebFormControlElement& field,
                       FieldDataManager& field_data_manager) {
   CHECK(!IsCheckableElement(field));
@@ -1112,13 +1111,6 @@
     field.SetSuggestedValue(WebString::FromUTF16(data.value));
     field.SetAutofillState(new_autofill_state);
   }
-
-  if (is_initiating_node &&
-      (IsTextInput(input_element) || IsTextAreaElement(field))) {
-    // Select the part of the text that the user didn't type.
-    PreviewSuggestion(field.SuggestedValue().Utf16(), field.Value().Utf16(),
-                      field);
-  }
 }
 
 // A less-than comparator for FormFieldData's pointer by their FieldRendererId.
@@ -2239,11 +2231,6 @@
     SetPreventHighlightingOfAutofilledFields(document, true);
   }
 
-  auto fill_or_preview =
-      action_persistence == mojom::ActionPersistence::kPreview
-          ? &PreviewFormField
-          : &FillFormField;
-
   // This container stores the FormFieldData::FillData* of `form.fields` that
   // will be filled into their corresponding blink elements.
   std::vector<std::pair<FieldRef, WebAutofillState>> filled_fields;
@@ -2308,8 +2295,13 @@
             features::kAutofillHighlightOnlyChangedValuesInPreviewMode)) {
       filled_fields.emplace_back(focused_field.element,
                                  focused_field.element.GetAutofillState());
-      fill_or_preview(*focused_field.data, /*is_initiating_element=*/true,
+      if (action_persistence == mojom::ActionPersistence::kFill) {
+        FillFormField(*focused_field.data, /*is_initiating_node=*/true,
                       focused_field.element, field_data_manager);
+      } else {
+        PreviewFormField(*focused_field.data, focused_field.element,
+                         field_data_manager);
+      }
     }
   }
 
@@ -2330,7 +2322,12 @@
   // events.
   for (Field& field : unfocused_fields) {
     filled_fields.emplace_back(field.element, field.element.GetAutofillState());
-    fill_or_preview(*field.data, false, field.element, field_data_manager);
+    if (action_persistence == mojom::ActionPersistence::kFill) {
+      FillFormField(*field.data, /*is_initiating_node=*/false, field.element,
+                    field_data_manager);
+    } else {
+      PreviewFormField(*field.data, field.element, field_data_manager);
+    }
   }
 
   // Step 4: A focus event is emitted for the initiating element after
@@ -2423,14 +2420,6 @@
   return true;
 }
 
-void PreviewSuggestion(const std::u16string& suggestion,
-                       const std::u16string& user_input,
-                       WebFormControlElement& input_element) {
-  input_element.SetSelectionRange(
-      base::checked_cast<unsigned>(user_input.length()),
-      base::checked_cast<unsigned>(suggestion.length()));
-}
-
 std::u16string FindChildText(const WebNode& node) {
   return FindChildTextWithIgnoreList(node, std::set<WebNode>());
 }
diff --git a/components/autofill/content/renderer/form_autofill_util.h b/components/autofill/content/renderer/form_autofill_util.h
index 6f76188..b5dfb9e 100644
--- a/components/autofill/content/renderer/form_autofill_util.h
+++ b/components/autofill/content/renderer/form_autofill_util.h
@@ -385,15 +385,6 @@
 // are of the type <script>, <meta>, or <title>.
 bool IsWebElementEmpty(const blink::WebElement& element);
 
-// Previews |suggestion| in |input_element| and highlights the suffix of
-// |suggestion| not included in the |input_element| text. |input_element| must
-// not be null. |user_input| should be the text typed by the user into
-// |input_element|. Note that |user_input| cannot be easily derived from
-// |input_element| by calling value(), because of http://crbug.com/507714.
-void PreviewSuggestion(const std::u16string& suggestion,
-                       const std::u16string& user_input,
-                       blink::WebFormControlElement& input_element);
-
 // Returns the aggregated values of the descendants of |element| that are
 // non-empty text nodes.  This is a faster alternative to |innerText()| for
 // performance critical operations.  It does a full depth-first search so can be
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn
index 631cfd2..2caff2e 100644
--- a/components/autofill/core/browser/BUILD.gn
+++ b/components/autofill/core/browser/BUILD.gn
@@ -1143,6 +1143,7 @@
     "metrics/field_filling_stats_and_score_metrics_unittest.cc",
     "metrics/form_events/address_form_event_logger_unittest.cc",
     "metrics/form_events/form_event_logger_base_unittest.cc",
+    "metrics/manual_fallback_metrics_unittest.cc",
     "metrics/payments/card_metadata_metrics_unittest.cc",
     "metrics/payments/cvc_storage_metrics_unittest.cc",
     "metrics/payments/iban_metrics_unittest.cc",
diff --git a/components/autofill/core/browser/browser_autofill_manager.cc b/components/autofill/core/browser/browser_autofill_manager.cc
index 104ea13..e827701 100644
--- a/components/autofill/core/browser/browser_autofill_manager.cc
+++ b/components/autofill/core/browser/browser_autofill_manager.cc
@@ -148,6 +148,35 @@
 // our analysis.
 constexpr size_t kMinFormSizeToTriggerUserPerceptionSurvey = 4;
 
+// Checks if the user triggered address Autofill through the
+// Chrome context menu on a field not classified as address.
+// `popup_item_id` defines the suggestion type shown.
+// `autofill_field` is the `AutofillField` from where the user triggered
+// suggestions.
+bool IsAddressAutofillManuallyTriggeredOnNonAddressField(
+    PopupItemId popup_item_id,
+    const AutofillField* autofill_field) {
+  return GetFillingProductFromPopupItemId(popup_item_id) ==
+             FillingProduct::kAddress &&
+         (!autofill_field ||
+          !IsAddressType(autofill_field->Type().GetStorableType()));
+}
+
+// Checks if the user triggered payments Autofill through the
+// Chrome context menu on a field not classified as credit card.
+// `popup_item_id` defines the suggestion type shown.
+// `autofill_field` is the `AutofillField` from where the user triggered
+// suggestions.
+bool IsCreditCardAutofillManuallyTriggeredOnNonCreditCardField(
+    PopupItemId popup_item_id,
+    const AutofillField* autofill_field) {
+  return GetFillingProductFromPopupItemId(popup_item_id) ==
+             FillingProduct::kCreditCard &&
+         (!autofill_field ||
+          GroupTypeOfFieldType(autofill_field->Type().GetStorableType()) !=
+              FieldTypeGroup::kCreditCard);
+}
+
 // Converts `filling_stats` to a key-value representation, where the key
 // is the "stats category" and the value is the number of fields that match
 // such category. This is used to show users a survey that will measure the
@@ -1281,6 +1310,18 @@
           form.global_id(), base::make_span(&const_field, 1u),
           base::make_span(&const_autofill_field, 1u));
     }
+
+    const bool is_address_manual_fallback_on_non_address_field =
+        IsAddressAutofillManuallyTriggeredOnNonAddressField(
+            popup_item_id, const_autofill_field);
+    const bool is_payments_manual_fallback_on_non_payments_field =
+        IsCreditCardAutofillManuallyTriggeredOnNonCreditCardField(
+            popup_item_id, const_autofill_field);
+    if (is_address_manual_fallback_on_non_address_field ||
+        is_payments_manual_fallback_on_non_payments_field) {
+      manual_fallback_logger_->OnDidFillSuggestion(
+          GetFillingProductFromPopupItemId(popup_item_id));
+    }
   }
 }
 
@@ -1445,8 +1486,36 @@
 
   FormStructure* form_structure = nullptr;
   AutofillField* autofill_field = nullptr;
-  // TODO(crbug.com/1493361): Adapt for the unclassified forms.
-  if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) {
+  const bool has_cached_form_and_field =
+      GetCachedFormAndField(form, field, &form_structure, &autofill_field);
+
+  // Check if Autofill was triggered via manual fallback on a field that was
+  // either unclassified or classified differently as the target
+  // `FillingProduct`.
+  // Note that in this type of flow we purposely do not log key metrics so we do
+  // not mess with the current denominator (classified forms).
+  const bool is_address_manual_fallback_on_non_address_field =
+      base::ranges::any_of(
+          shown_suggestions_types, [autofill_field](PopupItemId popup_item_id) {
+            return IsAddressAutofillManuallyTriggeredOnNonAddressField(
+                popup_item_id, autofill_field);
+          });
+  const bool is_payments_manual_fallback_on_non_payments_field =
+      base::ranges::any_of(
+          shown_suggestions_types, [autofill_field](PopupItemId popup_item_id) {
+            return IsCreditCardAutofillManuallyTriggeredOnNonCreditCardField(
+                popup_item_id, autofill_field);
+          });
+  if (is_address_manual_fallback_on_non_address_field) {
+    manual_fallback_logger_->OnDidShowSuggestions(FillingProduct::kAddress);
+    return;
+  }
+  if (is_payments_manual_fallback_on_non_payments_field) {
+    manual_fallback_logger_->OnDidShowSuggestions(FillingProduct::kCreditCard);
+    return;
+  }
+
+  if (!has_cached_form_and_field) {
     return;
   }
 
diff --git a/components/autofill/core/browser/browser_autofill_manager_unittest.cc b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
index e229859..ae8528878 100644
--- a/components/autofill/core/browser/browser_autofill_manager_unittest.cc
+++ b/components/autofill/core/browser/browser_autofill_manager_unittest.cc
@@ -750,10 +750,12 @@
                                                     trigger_source);
   }
 
-  void DidShowAutofillSuggestions(const FormData& form,
-                                  size_t field_index = 0) {
+  void DidShowAutofillSuggestions(
+      const FormData& form,
+      size_t field_index = 0,
+      PopupItemId popup_item_id = PopupItemId::kAddressEntry) {
     browser_autofill_manager_->DidShowSuggestions(
-        std::vector<PopupItemId>({PopupItemId::kAddressEntry}), form,
+        std::vector<PopupItemId>({popup_item_id}), form,
         form.fields[field_index]);
   }
 
@@ -6182,7 +6184,8 @@
   FormsSeen({form});
 
   base::HistogramTester histogram_tester;
-  DidShowAutofillSuggestions(form);
+  DidShowAutofillSuggestions(form, /*field_index=*/0,
+                             PopupItemId::kCreditCardEntry);
   histogram_tester.ExpectBucketCount("Autofill.UserHappiness",
                                      AutofillMetrics::SUGGESTIONS_SHOWN, 1);
   histogram_tester.ExpectBucketCount("Autofill.UserHappiness.CreditCard",
diff --git a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
index e1076f8..6ded0fe7 100644
--- a/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
+++ b/components/autofill/core/browser/metrics/autofill_metrics_unittest.cc
@@ -1798,7 +1798,8 @@
   // Simulate showing a credit card suggestion polled from "Name on card" field.
   {
     base::UserActionTester user_action_tester;
-    DidShowAutofillSuggestions(form);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_EQ(1, user_action_tester.GetActionCount(
                      "Autofill_ShowedCreditCardSuggestions"));
   }
@@ -1807,7 +1808,8 @@
   // field.
   {
     base::UserActionTester user_action_tester;
-    DidShowAutofillSuggestions(form, /*field_index=*/1);
+    DidShowAutofillSuggestions(form, /*field_index=*/1,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_EQ(1, user_action_tester.GetActionCount(
                      "Autofill_ShowedCreditCardSuggestions"));
   }
@@ -1832,7 +1834,8 @@
   // field along with a "Clear form" footer suggestion.
   {
     base::UserActionTester user_action_tester;
-    DidShowAutofillSuggestions(form, /*field_index=*/1);
+    DidShowAutofillSuggestions(form, /*field_index=*/1,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_EQ(1, user_action_tester.GetActionCount(
                      "Autofill_ShowedCreditCardSuggestions"));
   }
@@ -1873,7 +1876,8 @@
   // field, this time to submit the form.
   {
     base::UserActionTester user_action_tester;
-    DidShowAutofillSuggestions(form, /*field_index=*/1);
+    DidShowAutofillSuggestions(form, /*field_index=*/1,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_EQ(1, user_action_tester.GetActionCount(
                      "Autofill_ShowedCreditCardSuggestions"));
   }
@@ -2347,7 +2351,8 @@
   {
     // Simulating new popup being shown.
     base::HistogramTester histogram_tester;
-    DidShowAutofillSuggestions(form);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_THAT(
         histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
         BucketsInclude(Bucket(FORM_EVENT_SUGGESTIONS_SHOWN, 1),
@@ -2365,8 +2370,10 @@
   {
     // Simulating two popups in the same page load.
     base::HistogramTester histogram_tester;
-    DidShowAutofillSuggestions(form);
-    DidShowAutofillSuggestions(form);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_THAT(
         histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
         BucketsInclude(Bucket(FORM_EVENT_SUGGESTIONS_SHOWN, 2),
@@ -2427,7 +2434,8 @@
     // Simulate new popup being shown.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_THAT(
         histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
         BucketsInclude(
@@ -2453,8 +2461,10 @@
     // Simulating two popups in the same page load.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_THAT(
         histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
         BucketsInclude(
@@ -2517,8 +2527,10 @@
     // logged, but suggestions shown with virtual card should not.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_THAT(
         histogram_tester.GetAllSamples("Autofill.FormEvents.CreditCard"),
         BucketsInclude(
@@ -3424,7 +3436,8 @@
 
   // Simulating submission with suggestion shown, but not selected.
   base::HistogramTester histogram_tester;
-  DidShowAutofillSuggestions(form);
+  DidShowAutofillSuggestions(form, /*field_index=*/0,
+                             PopupItemId::kCreditCardEntry);
   autofill_manager().OnAskForValuesToFillTest(form, form.fields[0]);
   SubmitForm(form);
   histogram_tester.ExpectBucketCount(
@@ -3455,7 +3468,8 @@
 
   // Simulating submission with suggestion shown, but not selected.
   base::HistogramTester histogram_tester;
-  DidShowAutofillSuggestions(form);
+  DidShowAutofillSuggestions(form, /*field_index=*/0,
+                             PopupItemId::kCreditCardEntry);
   autofill_manager().OnAskForValuesToFillTest(form, form.fields[0]);
   SubmitForm(form);
   histogram_tester.ExpectBucketCount(
@@ -3489,7 +3503,8 @@
 
   // Simulating submission with suggestion shown, but not selected.
   base::HistogramTester histogram_tester;
-  DidShowAutofillSuggestions(form);
+  DidShowAutofillSuggestions(form, /*field_index=*/0,
+                             PopupItemId::kCreditCardEntry);
   autofill_manager().OnAskForValuesToFillTest(form, form.fields[0]);
   SubmitForm(form);
   histogram_tester.ExpectBucketCount(
@@ -3523,7 +3538,8 @@
 
   // Simulating submission with suggestion shown, but not selected.
   base::HistogramTester histogram_tester;
-  DidShowAutofillSuggestions(form);
+  DidShowAutofillSuggestions(form, /*field_index=*/0,
+                             PopupItemId::kCreditCardEntry);
   autofill_manager().OnAskForValuesToFillTest(form, form.fields[0]);
   SubmitForm(form);
   histogram_tester.ExpectBucketCount(
@@ -3557,7 +3573,8 @@
 
   // Simulating submission with suggestion shown, but not selected.
   base::HistogramTester histogram_tester;
-  DidShowAutofillSuggestions(form);
+  DidShowAutofillSuggestions(form, /*field_index=*/0,
+                             PopupItemId::kCreditCardEntry);
   autofill_manager().OnAskForValuesToFillTest(form, form.fields[0]);
   SubmitForm(form);
   histogram_tester.ExpectBucketCount(
@@ -3591,7 +3608,8 @@
 
   // Simulating submission with suggestion shown and selected.
   base::HistogramTester histogram_tester;
-  DidShowAutofillSuggestions(form);
+  DidShowAutofillSuggestions(form, /*field_index=*/0,
+                             PopupItemId::kCreditCardEntry);
   autofill_manager().OnAskForValuesToFillTest(form, form.fields[0]);
   autofill_manager().AuthenticateThenFillCreditCardForm(
       form, form.fields.back(),
@@ -3689,7 +3707,8 @@
   {
     // Simulating submission with suggestion shown.
     base::HistogramTester histogram_tester;
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
     SubmitForm(form);
     EXPECT_THAT(
@@ -3731,7 +3750,8 @@
     // autofill manager is reset before UploadFormDataAsyncCallback is
     // triggered.
     base::HistogramTester histogram_tester;
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
     SubmitForm(form);
     // Trigger UploadFormDataAsyncCallback.
@@ -4128,7 +4148,8 @@
   {
     // Simulating submission with suggestion shown.
     base::HistogramTester histogram_tester;
-    DidShowAutofillSuggestions(form);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
     autofill_manager().OnAskForValuesToFillTest(form, form.fields[0]);
     SubmitForm(form);
     EXPECT_THAT(
@@ -4368,7 +4389,8 @@
     // popup being shown and filling a local card suggestion.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     autofill_manager().AuthenticateThenFillCreditCardForm(
         form, form.fields.front(),
         *personal_data().GetCreditCardByGUID(kTestLocalCardId),
@@ -4414,7 +4436,8 @@
     // logged to offer sub-histogram.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     // Select the masked server card with the linked offer.
     autofill_manager().AuthenticateThenFillCreditCardForm(
         form, form.fields.back(),
@@ -4460,7 +4483,8 @@
     // logged to offer sub-histogram.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     // Select another card, and still log to offer
     // sub-histogram because user has another masked server card with offer.
     autofill_manager().AuthenticateThenFillCreditCardForm(
@@ -4513,7 +4537,8 @@
     // new popup being shown and filling a local card suggestion.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     // Select the card with linked offer, though metrics should not record it
     // since the offer is expired.
     autofill_manager().AuthenticateThenFillCreditCardForm(
@@ -4579,7 +4604,8 @@
     // for crbug/1198751.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     // Select the masked server card with the linked offer.
     autofill_manager().AuthenticateThenFillCreditCardForm(
         form, form.fields.back(),
@@ -4591,7 +4617,8 @@
     // Simulate user showing suggestions but then submitting form with
     // previously filled card info.
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     SubmitForm(form);
     EXPECT_THAT(
         histogram_tester.GetAllSamples(
@@ -4630,7 +4657,8 @@
     // related form events are correctly logged to offer sub-histogram.
     base::HistogramTester histogram_tester;
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     // Select the masked server card with the linked offer, but fail the CVC
     // check.
     autofill_manager().AuthenticateThenFillCreditCardForm(
@@ -4681,7 +4709,8 @@
 
     // Show suggestions and select the card with offer.
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     autofill_manager().AuthenticateThenFillCreditCardForm(
         form, form.fields.back(),
         *personal_data().GetCreditCardByGUID(kMaskedServerCardIds[2]),
@@ -4691,7 +4720,8 @@
 
     // Show suggestions again, and select a local card instead.
     autofill_manager().OnAskForValuesToFillTest(form, form.fields.back());
-    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1);
+    DidShowAutofillSuggestions(form, /*field_index=*/form.fields.size() - 1,
+                               PopupItemId::kCreditCardEntry);
     autofill_manager().AuthenticateThenFillCreditCardForm(
         form, form.fields.back(),
         *personal_data().GetCreditCardByGUID(kTestLocalCardId),
@@ -5948,8 +5978,10 @@
   {
     SCOPED_TRACE("Separate pop-ups");
     base::HistogramTester histogram_tester;
-    DidShowAutofillSuggestions(form);
-    DidShowAutofillSuggestions(form);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_THAT(
         histogram_tester.GetAllSamples("Autofill.UserHappiness"),
         BucketsInclude(Bucket(AutofillMetrics::SUGGESTIONS_SHOWN, 2),
@@ -5968,8 +6000,10 @@
   {
     SCOPED_TRACE("Multiple keystrokes");
     base::HistogramTester histogram_tester;
-    DidShowAutofillSuggestions(form);
-    DidShowAutofillSuggestions(form);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
+    DidShowAutofillSuggestions(form, /*field_index=*/0,
+                               PopupItemId::kCreditCardEntry);
     EXPECT_THAT(
         histogram_tester.GetAllSamples("Autofill.UserHappiness"),
         BucketsInclude(Bucket(AutofillMetrics::SUGGESTIONS_SHOWN, 2),
@@ -5984,7 +6018,8 @@
   {
     SCOPED_TRACE("Different field");
     base::HistogramTester histogram_tester;
-    DidShowAutofillSuggestions(form, /*field_index=*/1);
+    DidShowAutofillSuggestions(form, /*field_index=*/1,
+                               PopupItemId::kCreditCardEntry);
     histogram_tester.ExpectUniqueSample("Autofill.UserHappiness",
                                         AutofillMetrics::SUGGESTIONS_SHOWN, 1);
     histogram_tester.ExpectUniqueSample("Autofill.UserHappiness.CreditCard",
diff --git a/components/autofill/core/browser/metrics/manual_fallback_metrics.cc b/components/autofill/core/browser/metrics/manual_fallback_metrics.cc
index 455a5a7..968e1868 100644
--- a/components/autofill/core/browser/metrics/manual_fallback_metrics.cc
+++ b/components/autofill/core/browser/metrics/manual_fallback_metrics.cc
@@ -19,6 +19,36 @@
                                 "Address");
   EmitExplicitlyTriggeredMetric(not_classified_as_target_filling_credit_card,
                                 "CreditCard");
+  EmitFillAfterSuggestionMetric(address_suggestions_state_, "Address");
+  EmitFillAfterSuggestionMetric(credit_card_suggestions_state_, "CreditCard");
+}
+
+void ManualFallbackEventLogger::OnDidShowSuggestions(
+    FillingProduct target_filling_product) {
+  CHECK(target_filling_product == FillingProduct::kAddress ||
+        target_filling_product == FillingProduct::kCreditCard);
+  SuggestionState& suggestion_state =
+      target_filling_product == FillingProduct::kAddress
+          ? address_suggestions_state_
+          : credit_card_suggestions_state_;
+
+  if (suggestion_state == SuggestionState::kNotShown) {
+    suggestion_state = SuggestionState::kShown;
+  }
+}
+
+void ManualFallbackEventLogger::OnDidFillSuggestion(
+    FillingProduct target_filling_product) {
+  CHECK(target_filling_product == FillingProduct::kAddress ||
+        target_filling_product == FillingProduct::kCreditCard);
+  SuggestionState& suggestion_state =
+      target_filling_product == FillingProduct::kAddress
+          ? address_suggestions_state_
+          : credit_card_suggestions_state_;
+
+  if (suggestion_state == SuggestionState::kShown) {
+    suggestion_state = SuggestionState::kFilled;
+  }
 }
 
 void ManualFallbackEventLogger::ContextMenuEntryShown(
@@ -68,4 +98,17 @@
   base::UmaHistogramBoolean(metric_name("Total"), was_accepted);
 }
 
+void ManualFallbackEventLogger::EmitFillAfterSuggestionMetric(
+    SuggestionState suggestion_state,
+    std::string_view bucket) {
+  if (suggestion_state == SuggestionState::kNotShown) {
+    return;
+  }
+  base::UmaHistogramBoolean(
+      base::StrCat({"Autofill.Funnel.NotClassifiedAsTargetFilling."
+                    "FillAfterSuggestion.",
+                    bucket}),
+      suggestion_state == SuggestionState::kFilled);
+}
+
 }  // namespace autofill::autofill_metrics
diff --git a/components/autofill/core/browser/metrics/manual_fallback_metrics.h b/components/autofill/core/browser/metrics/manual_fallback_metrics.h
index 017e68b..ccfffce 100644
--- a/components/autofill/core/browser/metrics/manual_fallback_metrics.h
+++ b/components/autofill/core/browser/metrics/manual_fallback_metrics.h
@@ -22,6 +22,14 @@
   // Emits metrics before destruction.
   ~ManualFallbackEventLogger();
 
+  // Called when a suggestion is shown on an unclassified field or a field that
+  // does not match the `target_filling_product`.
+  void OnDidShowSuggestions(FillingProduct target_filling_product);
+
+  // Called when a suggestion is triggered from an unclassified field or a field
+  // that does not match the `target_filling_product`
+  void OnDidFillSuggestion(FillingProduct target_filling_product);
+
   // Called when context menu was opened on a qualifying field.
   // `address_fallback_present` indicates where the address fallback was
   // added. Similarly, `credit_cards_fallback_present` indicates whether a
@@ -30,24 +38,37 @@
                              bool credit_cards_fallback_present);
 
   // Called when a fallback option was accepted (not just hovered).
-  // `target_filling_product` specifies which of the available options was
+  // `target_filling_product` specifies of the available options was
   // chosen.
   void ContextMenuEntryAccepted(FillingProduct target_filling_product);
 
  private:
   enum class ContextMenuEntryState { kNotShown = 0, kShown = 1, kAccepted = 2 };
+  enum class SuggestionState { kNotShown = 0, kShown = 1, kFilled = 2 };
 
   // If according to the `state` the context menu was used, emits into the
   // `bucket` (address or credit_card) whether an entry was accepted or not
   void EmitExplicitlyTriggeredMetric(ContextMenuEntryState state,
                                      std::string_view bucket);
 
+  // If suggestions for an unclassified field or a field that has a different
+  // classification from the target `FillingProduct` were shown, emits whether
+  // they were filled based on their respective `suggestion_state_`.
+  // `bucket` defines the `FillingProduct`, i.e. address or credit_cards.
+  void EmitFillAfterSuggestionMetric(SuggestionState suggestion_state,
+                                     std::string_view bucket);
+
   // For addresses and credit cards filling, tracks if the manual fallback
   // context menu entry was shown or accepted.
   ContextMenuEntryState not_classified_as_target_filling_address =
       ContextMenuEntryState::kNotShown;
   ContextMenuEntryState not_classified_as_target_filling_credit_card =
       ContextMenuEntryState::kNotShown;
+
+  // Tracks if address suggestions were shown/filled.
+  SuggestionState address_suggestions_state_ = SuggestionState::kNotShown;
+  // Tracks if credit card suggestions were shown/filled.
+  SuggestionState credit_card_suggestions_state_ = SuggestionState::kNotShown;
 };
 
 }  // namespace autofill::autofill_metrics
diff --git a/components/autofill/core/browser/metrics/manual_fallback_metrics_unittest.cc b/components/autofill/core/browser/metrics/manual_fallback_metrics_unittest.cc
new file mode 100644
index 0000000..ddc8c01
--- /dev/null
+++ b/components/autofill/core/browser/metrics/manual_fallback_metrics_unittest.cc
@@ -0,0 +1,163 @@
+// 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/autofill/core/browser/metrics/manual_fallback_metrics.h"
+
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
+#include "components/autofill/core/browser/autofill_test_utils.h"
+#include "components/autofill/core/browser/metrics/autofill_metrics_test_base.h"
+#include "components/autofill/core/common/autofill_features.h"
+#include "components/autofill/core/common/form_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace autofill::autofill_metrics {
+
+namespace {
+
+// Test parameter data for asserting that the expected metrics are emitted
+// depending on the fallback option selected.
+struct ManualFallbackTestParams {
+  const AutofillSuggestionTriggerSource manual_fallback_option;
+  const std::string test_name;
+};
+
+class ManualFallbackEventLoggerTest
+    : public AutofillMetricsBaseTest,
+      public testing::TestWithParam<ManualFallbackTestParams> {
+ protected:
+  void SetUp() override { SetUpHelper(); }
+  void TearDown() override { TearDownHelper(); }
+
+  // Show suggestions on the first field of the `form`.
+  void ShowSuggestions(
+      const FormData& form,
+      AutofillSuggestionTriggerSource fallback_trigger_source) {
+    autofill_manager().OnAskForValuesToFillTest(
+        form, form.fields[0], /*bounding_box=*/{}, fallback_trigger_source);
+    DidShowAutofillSuggestions(
+        form, /*field_index=*/0,
+        fallback_trigger_source ==
+                AutofillSuggestionTriggerSource::kManualFallbackAddress
+            ? PopupItemId::kAddressEntry
+            : PopupItemId::kCreditCardEntry);
+  }
+
+  // Fills the first field in the form by calling `FillOrPreviewField()`. Uses a
+  // hardcoded value to be filled but makes the `popup_item_id` passed to the
+  // filling function depend on whether the `manual_fallback_option` param
+  // attribute is `AutofillSuggestionTriggerSource::kManualFallbackAddress` or
+  // `AutofillSuggestionTriggerSource::kManualFallbackPayments`. Using
+  // `PopupItemId::kAddressFieldByFieldFilling` for the former and
+  // `PopupItemId::kCreditCardFieldByFieldFilling` for the latter.
+  void FillFirstFormField(const FormData& form) {
+    const ManualFallbackTestParams& params = GetParam();
+    autofill_manager().FillOrPreviewField(
+        mojom::ActionPersistence::kFill, mojom::FieldActionType::kReplaceAll,
+        form, form.fields[0], u"value to fill",
+        params.manual_fallback_option ==
+                AutofillSuggestionTriggerSource::kManualFallbackAddress
+            ? PopupItemId::kAddressFieldByFieldFilling
+            : PopupItemId::kCreditCardFieldByFieldFilling);
+  }
+
+  std::string ExpectedBucketNameForManualFallbackOption() const {
+    return GetParam().manual_fallback_option ==
+                   AutofillSuggestionTriggerSource::kManualFallbackAddress
+               ? "Address"
+               : "CreditCard";
+  }
+};
+
+// Tests that when suggestions on an unclassified field are shown and filled,
+// the FillAfterSuggestion metric is emitted correctly. Note that only
+// field-by-field filling is available in this flow.
+TEST_P(ManualFallbackEventLoggerTest, FillAfterSuggestion_Filled) {
+  FormData form = test::GetFormData(
+      {.fields = {{.label = u"unclassified", .name = u"unclassified"}}});
+  SeeForm(form);
+  const ManualFallbackTestParams& params = GetParam();
+
+  ShowSuggestions(form,
+                  /*fallback_trigger_source=*/params.manual_fallback_option);
+  // Fill the suggestion.
+  FillFirstFormField(form);
+
+  base::HistogramTester histogram_tester;
+  ResetDriverToCommitMetrics();
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.Funnel.NotClassifiedAsTargetFilling."
+      "FillAfterSuggestion." +
+          ExpectedBucketNameForManualFallbackOption(),
+      true, 1);
+}
+
+// Tests that when suggestions on an unclassified field are shown,
+// but not selected, FillAfterSuggestion metric is
+// emitted correctly. Note that only field-by-field filling is available in this
+// flow.
+TEST_P(ManualFallbackEventLoggerTest, FillAfterSuggestion_NotFilled) {
+  FormData form = test::GetFormData(
+      {.fields = {{.label = u"unclassified", .name = u"unclassified"}}});
+  SeeForm(form);
+  const ManualFallbackTestParams& params = GetParam();
+
+  ShowSuggestions(form,
+                  /*fallback_trigger_source=*/params.manual_fallback_option);
+
+  base::HistogramTester histogram_tester;
+  // No suggestion was selected.
+  ResetDriverToCommitMetrics();
+  histogram_tester.ExpectUniqueSample(
+      "Autofill.Funnel.NotClassifiedAsTargetFilling."
+      "FillAfterSuggestion." +
+          ExpectedBucketNameForManualFallbackOption(),
+      false, 1);
+}
+
+// Tests that regular field-by-field filling suggestion acceptance (i.e, on a
+// classified field), does not emit the metric.
+TEST_P(ManualFallbackEventLoggerTest,
+       FillAfterSuggestion_RegularFieldByFieldFillingSuggestion) {
+  const ManualFallbackTestParams& params = GetParam();
+  bool const is_address_filling =
+      params.manual_fallback_option ==
+      AutofillSuggestionTriggerSource::kManualFallbackAddress;
+  FormData form = is_address_filling
+                      ? test::CreateTestAddressFormData()
+                      : test::CreateTestCreditCardFormData(
+                            /*is_https=*/true, /*use_month_type=*/false);
+  SeeForm(form);
+
+  ShowSuggestions(form,
+                  /*fallback_trigger_source=*/params.manual_fallback_option);
+  // Fill the suggestion.
+  FillFirstFormField(form);
+
+  base::HistogramTester histogram_tester;
+  // No suggestion was selected.
+  ResetDriverToCommitMetrics();
+  histogram_tester.ExpectTotalCount(
+      "Autofill.Funnel.NotClassifiedAsTargetFilling."
+      "FillAfterSuggestion." +
+          ExpectedBucketNameForManualFallbackOption(),
+      0);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    ManualFallbackEventLoggerTest,
+    ::testing::ValuesIn(std::vector<ManualFallbackTestParams>(
+        {{.manual_fallback_option =
+              AutofillSuggestionTriggerSource::kManualFallbackAddress,
+          .test_name = "_AddressManualFallback"},
+         {.manual_fallback_option =
+              AutofillSuggestionTriggerSource::kManualFallbackPayments,
+          .test_name = "_PaymentsManualFallback"}})),
+    [](const ::testing::TestParamInfo<ManualFallbackEventLoggerTest::ParamType>&
+           info) { return info.param.test_name; });
+
+}  // namespace
+
+}  // namespace autofill::autofill_metrics
diff --git a/components/autofill/core/browser/ui/suggestion.h b/components/autofill/core/browser/ui/suggestion.h
index 546c037..15b449e 100644
--- a/components/autofill/core/browser/ui/suggestion.h
+++ b/components/autofill/core/browser/ui/suggestion.h
@@ -141,6 +141,8 @@
 #if DCHECK_IS_ON()
   bool Invariant() const {
     switch (popup_item_id) {
+      case PopupItemId::kFillPassword:
+        return absl::holds_alternative<ValueToFill>(payload);
       case PopupItemId::kSeePromoCodeDetails:
         return absl::holds_alternative<GURL>(payload);
       case PopupItemId::kIbanEntry:
diff --git a/components/autofill/core/browser/ui/suggestion_test_helpers.cc b/components/autofill/core/browser/ui/suggestion_test_helpers.cc
index f06d39a3..3e5cff9c 100644
--- a/components/autofill/core/browser/ui/suggestion_test_helpers.cc
+++ b/components/autofill/core/browser/ui/suggestion_test_helpers.cc
@@ -28,4 +28,13 @@
   return AllOf(EqualsSuggestion(id, main_text), Field(&Suggestion::icon, icon));
 }
 
+::testing::Matcher<Suggestion> EqualsSuggestion(
+    PopupItemId id,
+    const std::u16string& main_text,
+    Suggestion::Icon icon,
+    const Suggestion::Payload& payload) {
+  return AllOf(EqualsSuggestion(id, main_text, icon),
+               Field(&Suggestion::payload, payload));
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/ui/suggestion_test_helpers.h b/components/autofill/core/browser/ui/suggestion_test_helpers.h
index 9c8cf4b..e86034d 100644
--- a/components/autofill/core/browser/ui/suggestion_test_helpers.h
+++ b/components/autofill/core/browser/ui/suggestion_test_helpers.h
@@ -21,6 +21,12 @@
                                                 const std::u16string& main_text,
                                                 Suggestion::Icon icon);
 
+::testing::Matcher<Suggestion> EqualsSuggestion(
+    PopupItemId id,
+    const std::u16string& main_text,
+    Suggestion::Icon icon,
+    const Suggestion::Payload& payload);
+
 template <class... Matchers>
 inline auto SuggestionVectorIdsAre(const Matchers&... matchers) {
   return ::testing::ElementsAre(::testing::Field(
diff --git a/components/autofill_strings.grdp b/components/autofill_strings.grdp
index f832c36..5784a8e7 100644
--- a/components/autofill_strings.grdp
+++ b/components/autofill_strings.grdp
@@ -23,12 +23,6 @@
     </message>
   </if>
 
-  <if expr="is_ios">
-    <message name="IDS_AUTOFILL_REMOVE_LOCAL_COPY_BUTTON" desc="The label of the button that removes the local copy of a Wallet Credit Card." formatter_data="android_java">
-      Remove copy
-    </message>
-  </if>
-
   <message name="IDS_AUTOFILL_WARNING_INSECURE_CONNECTION" desc="Warning text to show when credit card autofill is disabled because the website is not using a secure connection.">
     Automatic credit card filling is disabled because this form does not use a secure connection.
   </message>
diff --git a/components/compose/core/browser/BUILD.gn b/components/compose/core/browser/BUILD.gn
index f52efc5..ae1ba01 100644
--- a/components/compose/core/browser/BUILD.gn
+++ b/components/compose/core/browser/BUILD.gn
@@ -22,6 +22,8 @@
     "compose_manager_impl.h",
     "compose_metrics.cc",
     "compose_metrics.h",
+    "compose_utils.cc",
+    "compose_utils.h",
     "config.cc",
     "config.h",
   ]
diff --git a/components/compose/core/browser/compose_manager_impl.cc b/components/compose/core/browser/compose_manager_impl.cc
index d152b0ce..23a0a89c 100644
--- a/components/compose/core/browser/compose_manager_impl.cc
+++ b/components/compose/core/browser/compose_manager_impl.cc
@@ -10,12 +10,15 @@
 
 #include "base/feature_list.h"
 #include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/autofill_manager.h"
 #include "components/autofill/core/browser/browser_autofill_manager.h"
 #include "components/compose/core/browser/compose_client.h"
 #include "components/compose/core/browser/compose_features.h"
 #include "components/compose/core/browser/compose_metrics.h"
+#include "components/compose/core/browser/compose_utils.h"
+#include "components/compose/core/browser/config.h"
 
 namespace {
 
@@ -93,10 +96,31 @@
     client_->getPageUkmTracker()->ShowDialogAbortedDueToMissingFormFieldData();
     return;
   }
+
   autofill::AutofillManager& manager = driver->GetAutofillManager();
-  auto compose_callback =
-      base::BindOnce(&FillTextWithAutofill, manager.GetWeakPtr(),
-                     form_data.value(), *form_field_data);
+  ComposeCallback compose_callback;
+
+  if (base::FeatureList::IsEnabled(compose::features::kComposeTextSelection) &&
+      IsWordCountWithinBounds(base::UTF16ToUTF8(form_field_data->selected_text),
+                              0, compose::GetComposeConfig().input_min_words)) {
+    // Select all words.
+    static_cast<autofill::BrowserAutofillManager*>(&manager)
+        ->FillOrPreviewField(autofill::mojom::ActionPersistence::kFill,
+                             autofill::mojom::FieldActionType::kSelectAll,
+                             form_data.value(), *form_field_data, u"",
+                             autofill::PopupItemId::kCompose);
+
+    // Update form_field_data to use the newly selected text.
+    autofill::FormFieldData updated_form_field_data =
+        autofill::FormFieldData(*form_field_data);
+
+    updated_form_field_data.selected_text = form_field_data->value;
+
+    form_field_data = &updated_form_field_data;
+  }
+
+  compose_callback = base::BindOnce(&FillTextWithAutofill, manager.GetWeakPtr(),
+                                    form_data.value(), *form_field_data);
 
   OpenComposeWithFormFieldData(ui_entry_point, *form_field_data,
                                manager.client().GetPopupScreenLocation(),
diff --git a/components/compose/core/browser/compose_utils.cc b/components/compose/core/browser/compose_utils.cc
new file mode 100644
index 0000000..7f7c9ea
--- /dev/null
+++ b/components/compose/core/browser/compose_utils.cc
@@ -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.
+
+#include "components/compose/core/browser/compose_utils.h"
+
+#include "base/strings/string_tokenizer.h"
+
+namespace compose {
+
+namespace {
+const std::string kWhitespace = " ,.\r\n\t\f\v";
+}
+
+bool IsWordCountWithinBounds(const std::string& prompt,
+                             unsigned int minimum,
+                             unsigned int maximum) {
+  base::StringTokenizer tokenizer(
+      prompt, kWhitespace, base::StringTokenizer::WhitespacePolicy::kSkipOver);
+
+  unsigned int word_count = 0;
+  while (tokenizer.GetNext()) {
+    ++word_count;
+    if (word_count > maximum) {
+      return false;
+    }
+  }
+
+  if (word_count < minimum) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace compose
diff --git a/components/compose/core/browser/compose_utils.h b/components/compose/core/browser/compose_utils.h
new file mode 100644
index 0000000..8163a52
--- /dev/null
+++ b/components/compose/core/browser/compose_utils.h
@@ -0,0 +1,20 @@
+// 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_COMPOSE_CORE_BROWSER_COMPOSE_UTILS_H_
+#define COMPONENTS_COMPOSE_CORE_BROWSER_COMPOSE_UTILS_H_
+
+#include <string>
+
+namespace compose {
+
+// Returns true if the number of words in `prompt` is between minimum and
+// maximum. False otherwise.
+bool IsWordCountWithinBounds(const std::string& prompt,
+                             unsigned int minimum,
+                             unsigned int maximum);
+
+}  // namespace compose
+
+#endif  // COMPONENTS_COMPOSE_CORE_BROWSER_COMPOSE_UTILS_H_
diff --git a/components/omnibox/browser/in_memory_url_index.cc b/components/omnibox/browser/in_memory_url_index.cc
index a24582d..2819515 100644
--- a/components/omnibox/browser/in_memory_url_index.cc
+++ b/components/omnibox/browser/in_memory_url_index.cc
@@ -75,7 +75,7 @@
 
 // InMemoryURLIndex ------------------------------------------------------------
 
-InMemoryURLIndex::InMemoryURLIndex(bookmarks::BookmarkModel* bookmark_model,
+InMemoryURLIndex::InMemoryURLIndex(bookmarks::CoreBookmarkModel* bookmark_model,
                                    history::HistoryService* history_service,
                                    TemplateURLService* template_url_service,
                                    const base::FilePath& history_dir,
diff --git a/components/omnibox/browser/in_memory_url_index.h b/components/omnibox/browser/in_memory_url_index.h
index 25cb364..f9d8ae0 100644
--- a/components/omnibox/browser/in_memory_url_index.h
+++ b/components/omnibox/browser/in_memory_url_index.h
@@ -38,7 +38,7 @@
 }
 
 namespace bookmarks {
-class BookmarkModel;
+class CoreBookmarkModel;
 }
 
 namespace history {
@@ -74,7 +74,7 @@
                          public base::trace_event::MemoryDumpProvider {
  public:
   // `history_service` may be null during unit testing.
-  InMemoryURLIndex(bookmarks::BookmarkModel* bookmark_model,
+  InMemoryURLIndex(bookmarks::CoreBookmarkModel* bookmark_model,
                    history::HistoryService* history_service,
                    TemplateURLService* template_url_service,
                    const base::FilePath& history_dir,
@@ -191,7 +191,7 @@
   const SchemeSet& scheme_allowlist() { return scheme_allowlist_; }
 
   // The BookmarkModel; may be null when testing.
-  raw_ptr<bookmarks::BookmarkModel> bookmark_model_;
+  raw_ptr<bookmarks::CoreBookmarkModel> bookmark_model_;
 
   // The HistoryService; may be null when testing.
   raw_ptr<history::HistoryService> history_service_;
diff --git a/components/omnibox/browser/omnibox_client.cc b/components/omnibox/browser/omnibox_client.cc
index 4c9a5ae..6678d48 100644
--- a/components/omnibox/browser/omnibox_client.cc
+++ b/components/omnibox/browser/omnibox_client.cc
@@ -37,7 +37,7 @@
   return true;
 }
 
-bookmarks::BookmarkModel* OmniboxClient::GetBookmarkModel() {
+bookmarks::CoreBookmarkModel* OmniboxClient::GetBookmarkModel() {
   return nullptr;
 }
 
diff --git a/components/omnibox/browser/omnibox_client.h b/components/omnibox/browser/omnibox_client.h
index 59589e2b..139a3d11 100644
--- a/components/omnibox/browser/omnibox_client.h
+++ b/components/omnibox/browser/omnibox_client.h
@@ -31,7 +31,7 @@
 struct OmniboxLog;
 
 namespace bookmarks {
-class BookmarkModel;
+class CoreBookmarkModel;
 }
 
 namespace gfx {
@@ -95,7 +95,7 @@
       omnibox::mojom::NavigationPredictor navigation_predictor) {}
 
   virtual PrefService* GetPrefs() = 0;
-  virtual bookmarks::BookmarkModel* GetBookmarkModel();
+  virtual bookmarks::CoreBookmarkModel* GetBookmarkModel();
   virtual AutocompleteControllerEmitter* GetAutocompleteControllerEmitter() = 0;
   virtual TemplateURLService* GetTemplateURLService();
   virtual const AutocompleteSchemeClassifier& GetSchemeClassifier() const = 0;
diff --git a/components/omnibox/browser/omnibox_edit_model.cc b/components/omnibox/browser/omnibox_edit_model.cc
index 38006c1..811c4bd 100644
--- a/components/omnibox/browser/omnibox_edit_model.cc
+++ b/components/omnibox/browser/omnibox_edit_model.cc
@@ -24,7 +24,7 @@
 #include "base/trace_event/typed_macros.h"
 #include "build/branding_buildflags.h"
 #include "build/build_config.h"
-#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/core_bookmark_model.h"
 #include "components/dom_distiller/core/url_constants.h"
 #include "components/dom_distiller/core/url_utils.h"
 #include "components/navigation_metrics/navigation_metrics.h"
@@ -84,7 +84,7 @@
 #include "components/vector_icons/vector_icons.h"  // nogncheck
 #endif
 
-using bookmarks::BookmarkModel;
+using bookmarks::CoreBookmarkModel;
 using metrics::OmniboxEventProto;
 using omnibox::mojom::NavigationPredictor;
 
@@ -2572,7 +2572,8 @@
           deviation_char_in_hostname);
     }
 
-    BookmarkModel* bookmark_model = controller_->client()->GetBookmarkModel();
+    CoreBookmarkModel* bookmark_model =
+        controller_->client()->GetBookmarkModel();
     if (bookmark_model && bookmark_model->IsBookmarked(destination_url)) {
       controller_->client()->OnBookmarkLaunched();
     }
diff --git a/components/omnibox/browser/omnibox_view.cc b/components/omnibox/browser/omnibox_view.cc
index 7974d08b..5e51c40 100644
--- a/components/omnibox/browser/omnibox_view.cc
+++ b/components/omnibox/browser/omnibox_view.cc
@@ -18,7 +18,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
-#include "components/bookmarks/browser/bookmark_model.h"
+#include "components/bookmarks/browser/core_bookmark_model.h"
 #include "components/omnibox/browser/autocomplete_controller.h"
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/autocomplete_match.h"
@@ -247,7 +247,7 @@
   // If it's never called, the vector icon we provide below should remain.
 
   // For bookmarked suggestions, display bookmark icon.
-  bookmarks::BookmarkModel* bookmark_model =
+  bookmarks::CoreBookmarkModel* bookmark_model =
       controller_->client()->GetBookmarkModel();
   const bool is_bookmarked =
       bookmark_model && bookmark_model->IsBookmarked(match.destination_url);
diff --git a/components/omnibox/browser/test_omnibox_client.h b/components/omnibox/browser/test_omnibox_client.h
index 07b1f06d..86d561e 100644
--- a/components/omnibox/browser/test_omnibox_client.h
+++ b/components/omnibox/browser/test_omnibox_client.h
@@ -65,7 +65,7 @@
                const AutocompleteMatch& alternative_nav_match,
                IDNA2008DeviationCharacter deviation_char_in_hostname));
   MOCK_METHOD(LocationBarModel*, GetLocationBarModel, ());
-  MOCK_METHOD(bookmarks::BookmarkModel*, GetBookmarkModel, ());
+  MOCK_METHOD(bookmarks::CoreBookmarkModel*, GetBookmarkModel, ());
   MOCK_METHOD(PrefService*, GetPrefs, ());
 
   base::WeakPtr<OmniboxClient> AsWeakPtr() override;
diff --git a/components/omnibox/browser/url_index_private_data.cc b/components/omnibox/browser/url_index_private_data.cc
index 6e25194..ef3f8015 100644
--- a/components/omnibox/browser/url_index_private_data.cc
+++ b/components/omnibox/browser/url_index_private_data.cc
@@ -29,8 +29,8 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "base/trace_event/memory_usage_estimator.h"
-#include "components/bookmarks/browser/bookmark_model.h"
 #include "components/bookmarks/browser/bookmark_utils.h"
+#include "components/bookmarks/browser/core_bookmark_model.h"
 #include "components/history/core/browser/history_database.h"
 #include "components/history/core/browser/history_db_task.h"
 #include "components/history/core/browser/history_service.h"
@@ -129,7 +129,7 @@
     size_t cursor_position,
     const std::string& host_filter,
     size_t max_matches,
-    bookmarks::BookmarkModel* bookmark_model,
+    bookmarks::CoreBookmarkModel* bookmark_model,
     TemplateURLService* template_url_service,
     OmniboxTriggeredFeatureService* triggered_feature_service) {
   // This list will contain the original search string and any other string
@@ -647,7 +647,7 @@
     const std::u16string& lower_raw_string,
     const std::string& host_filter,
     const TemplateURLService* template_url_service,
-    bookmarks::BookmarkModel* bookmark_model,
+    bookmarks::CoreBookmarkModel* bookmark_model,
     ScoredHistoryMatches* scored_items,
     OmniboxTriggeredFeatureService* triggered_feature_service) const {
   if (history_ids.empty())
diff --git a/components/omnibox/browser/url_index_private_data.h b/components/omnibox/browser/url_index_private_data.h
index ddb274e..de8b8ad 100644
--- a/components/omnibox/browser/url_index_private_data.h
+++ b/components/omnibox/browser/url_index_private_data.h
@@ -26,7 +26,7 @@
 class TemplateURLService;
 
 namespace bookmarks {
-class BookmarkModel;
+class CoreBookmarkModel;
 }
 
 namespace history {
@@ -72,7 +72,7 @@
       size_t cursor_position,
       const std::string& host_filter,
       size_t max_matches,
-      bookmarks::BookmarkModel* bookmark_model,
+      bookmarks::CoreBookmarkModel* bookmark_model,
       TemplateURLService* template_url_service,
       OmniboxTriggeredFeatureService* triggered_feature_service);
 
@@ -254,7 +254,7 @@
       const std::u16string& lower_raw_string,
       const std::string& host_filter,
       const TemplateURLService* template_url_service,
-      bookmarks::BookmarkModel* bookmark_model,
+      bookmarks::CoreBookmarkModel* bookmark_model,
       ScoredHistoryMatches* scored_items,
       OmniboxTriggeredFeatureService* triggered_feature_service) const;
 
diff --git a/components/password_manager/core/browser/features/password_features.cc b/components/password_manager/core/browser/features/password_features.cc
index 86237ca..140fb2c 100644
--- a/components/password_manager/core/browser/features/password_features.cc
+++ b/components/password_manager/core/browser/features/password_features.cc
@@ -161,7 +161,7 @@
 
 BASE_FEATURE(kUsernameFirstFlowWithIntermediateValues,
              "UsernameFirstFlowWithIntermediateValues",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 extern const base::FeatureParam<int> kSingleUsernameTimeToLive{
     &kUsernameFirstFlowWithIntermediateValues, /*name=*/"ttl",
     /*default_value=*/5};
diff --git a/components/password_manager/core/browser/password_autofill_manager.cc b/components/password_manager/core/browser/password_autofill_manager.cc
index 2236387..e7c8bba 100644
--- a/components/password_manager/core/browser/password_autofill_manager.cc
+++ b/components/password_manager/core/browser/password_autofill_manager.cc
@@ -37,7 +37,6 @@
 #include "components/password_manager/core/browser/password_manager_driver.h"
 #include "components/password_manager/core/browser/password_manager_metrics_recorder.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
-#include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/browser/password_manual_fallback_flow.h"
 #include "components/password_manager/core/browser/password_ui_utils.h"
 #include "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
@@ -267,8 +266,8 @@
           password_client_->GetDeviceAuthenticator();
       // Note: this is currently only implemented on Android, Mac and Windows.
       // For other platforms, the `authenticator` will be null.
-      if (!password_manager_util::CanUseBiometricAuth(authenticator.get(),
-                                                      password_client_)) {
+      if (!password_client_->CanUseBiometricAuthForFilling(
+              authenticator.get())) {
         bool success = FillSuggestion(
             GetUsernameFromSuggestion(suggestion.main_text.value),
             suggestion.popup_item_id);
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index c7a76d0..fe030f64 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -63,13 +63,8 @@
 #include "ui/gfx/image/image_unittest_util.h"
 
 #if BUILDFLAG(IS_ANDROID)
-#include "base/android/build_info.h"
 #include "components/webauthn/android/cred_man_support.h"
 #include "components/webauthn/android/webauthn_cred_man_delegate.h"
-#elif BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-#include "components/password_manager/core/common/password_manager_pref_names.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/testing_pref_service.h"
 #endif
 
 // The name of the username/password element in the form.
@@ -111,8 +106,6 @@
 class AutofillPopupDelegate;
 }
 
-class PrefService;
-
 namespace password_manager {
 
 namespace {
@@ -201,12 +194,14 @@
               GetWebAuthnCredentialsDelegateForDriver,
               (PasswordManagerDriver*),
               (override));
-  MOCK_METHOD(PrefService*, GetPrefs, (), (const, override));
-  MOCK_METHOD(PrefService*, GetLocalStatePrefs, (), (const, override));
   MOCK_METHOD(std::unique_ptr<device_reauth::DeviceAuthenticator>,
               GetDeviceAuthenticator,
               (),
               (override));
+  MOCK_METHOD(bool,
+              CanUseBiometricAuthForFilling,
+              (device_reauth::DeviceAuthenticator*),
+              (override));
 
  private:
   MockPasswordManagerDriver driver_;
@@ -340,15 +335,6 @@
     webauthn_credentials_delegate_ =
         std::make_unique<MockWebAuthnCredentialsDelegate>();
 
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-    test_pref_service_ = std::make_unique<TestingPrefServiceSimple>();
-    test_pref_service_->registry()->RegisterBooleanPref(
-        password_manager::prefs::kBiometricAuthenticationBeforeFilling, true);
-    ON_CALL(*client, GetPrefs())
-        .WillByDefault(Return(test_pref_service_.get()));
-    ON_CALL(*client, GetLocalStatePrefs())
-        .WillByDefault(Return(test_pref_service_.get()));
-#endif
     ON_CALL(*client, GetWebAuthnCredentialsDelegateForDriver)
         .WillByDefault(Return(webauthn_credentials_delegate_.get()));
 
@@ -376,14 +362,6 @@
     EXPECT_CALL(*authenticator, AuthenticateWithMessage);
   }
 
-#if BUILDFLAG(IS_ANDROID)
-  void ExpectAndSimulateAuthenticationSuccess(
-      device_reauth::MockDeviceAuthenticator* authenticator) {
-    EXPECT_CALL(*authenticator, AuthenticateWithMessage)
-        .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/true));
-  }
-#endif
-
   MockPasswordSuggestionFlow& manual_fallback_flow() {
     return *static_cast<MockPasswordSuggestionFlow*>(
         password_autofill_manager_->manual_fallback_flow());
@@ -406,9 +384,6 @@
   // The TestAutofillDriver uses a SequencedWorkerPool which expects the
   // existence of a MessageLoop.
   base::test::SingleThreadTaskEnvironment task_environment_;
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  std::unique_ptr<TestingPrefServiceSimple> test_pref_service_;
-#endif
 };
 
 TEST_F(PasswordAutofillManagerTest, FillSuggestion) {
@@ -453,16 +428,6 @@
 // suggestions.
 TEST_F(PasswordAutofillManagerTest, ExternalDelegatePasswordSuggestions) {
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_ANDROID)
-  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
-    auto authenticator =
-        std::make_unique<device_reauth::MockDeviceAuthenticator>();
-    ExpectAndSimulateAuthenticationSuccess(authenticator.get());
-    ON_CALL(client, GetDeviceAuthenticator)
-        .WillByDefault(Return(testing::ByMove(std::move(authenticator))));
-  }
-#endif
-
   NiceMock<MockAutofillClient> autofill_client;
   InitializePasswordAutofillManager(&client, &autofill_client);
 
@@ -517,16 +482,6 @@
 TEST_F(PasswordAutofillManagerTest,
        ExternalDelegateAccountStorePasswordSuggestions) {
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_ANDROID)
-  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
-    auto authenticator =
-        std::make_unique<device_reauth::MockDeviceAuthenticator>();
-    ExpectAndSimulateAuthenticationSuccess(authenticator.get());
-    ON_CALL(client, GetDeviceAuthenticator)
-        .WillByDefault(Return(testing::ByMove(std::move(authenticator))));
-  }
-#endif
-
   NiceMock<MockAutofillClient> autofill_client;
   InitializePasswordAutofillManager(&client, &autofill_client);
 
@@ -1174,16 +1129,6 @@
 TEST_F(PasswordAutofillManagerTest, PreviewAndFillEmptyUsernameSuggestion) {
   // Initialize PasswordAutofillManager with credentials without username.
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_ANDROID)
-  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
-    auto authenticator =
-        std::make_unique<device_reauth::MockDeviceAuthenticator>();
-    ExpectAndSimulateAuthenticationSuccess(authenticator.get());
-    ON_CALL(client, GetDeviceAuthenticator)
-        .WillByDefault(Return(testing::ByMove(std::move(authenticator))));
-  }
-#endif
-
   NiceMock<MockAutofillClient> autofill_client;
   fill_data().preferred_login.username_value.clear();
   InitializePasswordAutofillManager(&client, &autofill_client);
@@ -1512,12 +1457,6 @@
 }
 
 TEST_F(PasswordAutofillManagerTest, FillsSuggestionIfAuthNotAvailable) {
-#if BUILDFLAG(IS_ANDROID)
-  if (base::android::BuildInfo::GetInstance()->is_automotive()) {
-    GTEST_SKIP();
-  }
-#endif
-
   TestPasswordManagerClient client;
   NiceMock<MockAutofillClient> autofill_client;
 
@@ -1548,12 +1487,7 @@
   EXPECT_CALL(*client.mock_driver(),
               FillSuggestion(test_username_, test_password_));
 
-#if BUILDFLAG(IS_ANDROID)
-  // The authenticator exists, but cannot be used for authentication.
-  EXPECT_CALL(*authenticator, CanAuthenticateWithBiometrics)
-      .WillOnce(Return(false));
-#endif
-
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(false));
   EXPECT_CALL(client, GetDeviceAuthenticator)
       .WillOnce(Return(testing::ByMove(std::move(authenticator))));
 
@@ -1565,20 +1499,9 @@
       SuggestionPosition{.row = 1});
 }
 
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
 TEST_F(PasswordAutofillManagerTest, FillsSuggestionIfAuthSuccessful) {
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  ON_CALL(*client.GetPasswordFeatureManager(),
-          IsBiometricAuthenticationBeforeFillingEnabled)
-      .WillByDefault(Return(true));
-#else
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      password_manager::features::kBiometricTouchToFill);
-#endif
   NiceMock<MockAutofillClient> autofill_client;
-
   InitializePasswordAutofillManager(&client, &autofill_client);
 
   // Show the popup
@@ -1611,8 +1534,7 @@
       std::make_unique<device_reauth::MockDeviceAuthenticator>();
 
   // The authenticator exists and is available.
-  ON_CALL(*authenticator, CanAuthenticateWithBiometrics)
-      .WillByDefault(Return(true));
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(true));
   EXPECT_CALL(*authenticator, AuthenticateWithMessage)
       .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/true));
 
@@ -1629,15 +1551,6 @@
 
 TEST_F(PasswordAutofillManagerTest, DoesntFillSuggestionIfAuthFailed) {
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  ON_CALL(*client.GetPasswordFeatureManager(),
-          IsBiometricAuthenticationBeforeFillingEnabled)
-      .WillByDefault(Return(true));
-#else
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      password_manager::features::kBiometricTouchToFill);
-#endif
   NiceMock<MockAutofillClient> autofill_client;
   auto authenticator =
       std::make_unique<device_reauth::MockDeviceAuthenticator>();
@@ -1671,8 +1584,7 @@
       HideAutofillPopup(autofill::PopupHidingReason::kAcceptSuggestion));
 
   // The authenticator exists and is available.
-  ON_CALL(*authenticator, CanAuthenticateWithBiometrics)
-      .WillByDefault(Return(true));
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(true));
   EXPECT_CALL(*authenticator, AuthenticateWithMessage)
       .WillOnce(RunOnceCallback<1>(/*auth_succeeded=*/false));
 
@@ -1689,15 +1601,6 @@
 
 TEST_F(PasswordAutofillManagerTest, CancelsOngoingBiometricAuthOnDestroy) {
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  ON_CALL(*client.GetPasswordFeatureManager(),
-          IsBiometricAuthenticationBeforeFillingEnabled)
-      .WillByDefault(Return(true));
-#else
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      password_manager::features::kBiometricTouchToFill);
-#endif
   NiceMock<MockAutofillClient> autofill_client;
   auto authenticator =
       std::make_unique<device_reauth::MockDeviceAuthenticator>();
@@ -1726,7 +1629,8 @@
       .Times(0);
 
   // The authenticator exists and is available.
-  ExpectAndAllowAuthentication(authenticator_ptr);
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(true));
+  EXPECT_CALL(*authenticator, AuthenticateWithMessage);
 
   EXPECT_CALL(client, GetDeviceAuthenticator)
       .WillOnce(Return(testing::ByMove(std::move(authenticator))));
@@ -1744,15 +1648,6 @@
 TEST_F(PasswordAutofillManagerTest,
        CancelsOngoingBiometricAuthOnDeleteFillData) {
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  ON_CALL(*client.GetPasswordFeatureManager(),
-          IsBiometricAuthenticationBeforeFillingEnabled)
-      .WillByDefault(Return(true));
-#else
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      password_manager::features::kBiometricTouchToFill);
-#endif
   NiceMock<MockAutofillClient> autofill_client;
   auto authenticator =
       std::make_unique<device_reauth::MockDeviceAuthenticator>();
@@ -1781,7 +1676,8 @@
       .Times(0);
 
   // The authenticator exists and is available.
-  ExpectAndAllowAuthentication(authenticator_ptr);
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(true));
+  EXPECT_CALL(*authenticator, AuthenticateWithMessage);
 
   EXPECT_CALL(client, GetDeviceAuthenticator)
       .WillOnce(Return(testing::ByMove(std::move(authenticator))));
@@ -1800,15 +1696,6 @@
 TEST_F(PasswordAutofillManagerTest,
        CancelsOngoingBiometricAuthOnFillDataChange) {
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  ON_CALL(*client.GetPasswordFeatureManager(),
-          IsBiometricAuthenticationBeforeFillingEnabled)
-      .WillByDefault(Return(true));
-#else
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      password_manager::features::kBiometricTouchToFill);
-#endif
   NiceMock<MockAutofillClient> autofill_client;
   auto authenticator =
       std::make_unique<device_reauth::MockDeviceAuthenticator>();
@@ -1837,7 +1724,8 @@
       .Times(0);
 
   // The authenticator exists and is available.
-  ExpectAndAllowAuthentication(authenticator_ptr);
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(true));
+  EXPECT_CALL(*authenticator, AuthenticateWithMessage);
 
   EXPECT_CALL(client, GetDeviceAuthenticator)
       .WillOnce(Return(testing::ByMove(std::move(authenticator))));
@@ -1855,15 +1743,6 @@
 
 TEST_F(PasswordAutofillManagerTest, CancelsOngoingBiometricAuthOnNewRequest) {
   TestPasswordManagerClient client;
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
-  ON_CALL(*client.GetPasswordFeatureManager(),
-          IsBiometricAuthenticationBeforeFillingEnabled)
-      .WillByDefault(Return(true));
-#else
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      password_manager::features::kBiometricTouchToFill);
-#endif
   NiceMock<MockAutofillClient> autofill_client;
   auto authenticator =
       std::make_unique<device_reauth::MockDeviceAuthenticator>();
@@ -1874,7 +1753,8 @@
   password_autofill_manager_->OnShowPasswordSuggestions(
       kElementId, kDefaultTriggerSource, base::i18n::RIGHT_TO_LEFT,
       std::u16string(), ShowWebAuthnCredentials(false), gfx::RectF());
-  ExpectAndAllowAuthentication(authenticator_ptr);
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(true));
+  EXPECT_CALL(*authenticator_ptr, AuthenticateWithMessage);
 
   EXPECT_CALL(client, GetDeviceAuthenticator)
       .WillOnce(Return(testing::ByMove(std::move(authenticator))));
@@ -1890,7 +1770,8 @@
 
   // Triggering new authentication should cancel ongoing authentication.
   EXPECT_CALL(*authenticator_ptr, Cancel());
-  ExpectAndAllowAuthentication(authenticator_ptr2);
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(true));
+  EXPECT_CALL(*authenticator_ptr2, AuthenticateWithMessage);
 
   EXPECT_CALL(client, GetDeviceAuthenticator)
       .WillOnce(Return(testing::ByMove(std::move(authenticator2))));
@@ -1904,16 +1785,11 @@
   EXPECT_CALL(*authenticator_ptr2, Cancel());
 }
 
-#endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN)
 
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
 TEST_F(PasswordAutofillManagerTest, MetricsRecordedForBiometricAuth) {
   base::ScopedMockElapsedTimersForTest mock_elapsed_timers_;
   base::HistogramTester histograms;
   TestPasswordManagerClient client;
-  ON_CALL(*client.GetPasswordFeatureManager(),
-          IsBiometricAuthenticationBeforeFillingEnabled)
-      .WillByDefault(Return(true));
   NiceMock<MockAutofillClient> autofill_client;
   auto authenticator =
       std::make_unique<device_reauth::MockDeviceAuthenticator>();
@@ -1930,6 +1806,7 @@
   EXPECT_CALL(*authenticator, AuthenticateWithMessage)
       .WillOnce(MoveArg<1>(&auth_callback));
 
+  EXPECT_CALL(client, CanUseBiometricAuthForFilling).WillOnce(Return(true));
   EXPECT_CALL(client, GetDeviceAuthenticator)
       .WillOnce(Return(testing::ByMove(std::move(authenticator))));
 
@@ -1954,7 +1831,6 @@
       "PasswordManager.PasswordFilling.AuthenticationTime", kMockElapsedTime,
       1);
 }
-#endif
 
 TEST_F(PasswordAutofillManagerTest, ShowsWebAuthnSuggestions) {
 #if BUILDFLAG(IS_ANDROID)
@@ -2273,7 +2149,6 @@
 
 #endif  // !BUILDFLAG(IS_ANDROID)
 
-#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
 TEST_F(PasswordAutofillManagerTest, NoPreviewSuggestionWithAuthBeforeFilling) {
   TestPasswordManagerClient client;
   ON_CALL(*client.GetPasswordFeatureManager(),
@@ -2287,7 +2162,6 @@
       password_autofill_manager_->PreviewSuggestionForTest(test_username_));
   testing::Mock::VerifyAndClearExpectations(client.mock_driver());
 }
-#endif
 
 TEST_F(PasswordAutofillManagerTest, ManualFallback_InvokesFlow) {
   TestPasswordManagerClient client;
diff --git a/components/password_manager/core/browser/password_manager_client.cc b/components/password_manager/core/browser/password_manager_client.cc
index 546d7d64..fb8467f 100644
--- a/components/password_manager/core/browser/password_manager_client.cc
+++ b/components/password_manager/core/browser/password_manager_client.cc
@@ -42,6 +42,11 @@
 }
 #endif
 
+bool PasswordManagerClient::CanUseBiometricAuthForFilling(
+    device_reauth::DeviceAuthenticator*) {
+  return false;
+}
+
 std::unique_ptr<device_reauth::DeviceAuthenticator>
 PasswordManagerClient::GetDeviceAuthenticator() {
   return nullptr;
diff --git a/components/password_manager/core/browser/password_manager_client.h b/components/password_manager/core/browser/password_manager_client.h
index 7eacba8..5a847213 100644
--- a/components/password_manager/core/browser/password_manager_client.h
+++ b/components/password_manager/core/browser/password_manager_client.h
@@ -210,6 +210,9 @@
       bool is_webauthn_form);
 #endif
 
+  virtual bool CanUseBiometricAuthForFilling(
+      device_reauth::DeviceAuthenticator* authenticator);
+
   // Returns a pointer to a DeviceAuthenticator. Might be null if
   // BiometricAuthentication is not available for a given platform.
   virtual std::unique_ptr<device_reauth::DeviceAuthenticator>
diff --git a/components/password_manager/core/browser/password_manager_util.h b/components/password_manager/core/browser/password_manager_util.h
index 548efb3..7692548 100644
--- a/components/password_manager/core/browser/password_manager_util.h
+++ b/components/password_manager/core/browser/password_manager_util.h
@@ -143,6 +143,7 @@
 #endif
 
 // Helper which checks if biometric authentication is available.
+// TODO(b/326735413): Remove this function.
 bool CanUseBiometricAuth(device_reauth::DeviceAuthenticator* authenticator,
                          password_manager::PasswordManagerClient* client);
 
diff --git a/components/password_manager/core/browser/password_suggestion_generator.cc b/components/password_manager/core/browser/password_suggestion_generator.cc
index f5fc4b0..536fc0e 100644
--- a/components/password_manager/core/browser/password_suggestion_generator.cc
+++ b/components/password_manager/core/browser/password_suggestion_generator.cc
@@ -244,11 +244,14 @@
       username, autofill::PopupItemId::kPasswordFieldByFieldFilling));
 }
 
-void AddFillPasswordChildSuggestion(autofill::Suggestion& suggestion) {
-  suggestion.children.push_back(autofill::Suggestion(
+void AddFillPasswordChildSuggestion(autofill::Suggestion& suggestion,
+                                    const std::u16string& password) {
+  autofill::Suggestion fill_password(
       l10n_util::GetStringUTF16(
           IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_FILL_PASSWORD_ENTRY),
-      autofill::PopupItemId::kFillPassword));
+      autofill::PopupItemId::kFillPassword);
+  fill_password.payload = autofill::Suggestion::ValueToFill(password);
+  suggestion.children.push_back(fill_password);
 }
 
 void AddViewPasswordDetailsChildSuggestion(autofill::Suggestion& suggestion) {
@@ -274,7 +277,7 @@
   if (!replaced) {
     AddPasswordUsernameChildSuggestion(maybe_username, suggestion);
   }
-  AddFillPasswordChildSuggestion(suggestion);
+  AddFillPasswordChildSuggestion(suggestion, credential.password);
   suggestion.children.push_back(
       autofill::Suggestion(autofill::PopupItemId::kSeparator));
   AddViewPasswordDetailsChildSuggestion(suggestion);
diff --git a/components/password_manager/core/browser/password_suggestion_generator_unittest.cc b/components/password_manager/core/browser/password_suggestion_generator_unittest.cc
index 7ed3344..2dfef0a 100644
--- a/components/password_manager/core/browser/password_suggestion_generator_unittest.cc
+++ b/components/password_manager/core/browser/password_suggestion_generator_unittest.cc
@@ -193,7 +193,8 @@
           EqualsSuggestion(
               PopupItemId::kFillPassword,
               l10n_util::GetStringUTF16(
-                  IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_FILL_PASSWORD_ENTRY)),
+                  IDS_PASSWORD_MANAGER_MANUAL_FALLBACK_FILL_PASSWORD_ENTRY),
+              Suggestion::Icon::kNoIcon, Suggestion::ValueToFill(u"password")),
           EqualsSuggestion(PopupItemId::kSeparator),
           EqualsSuggestion(
               PopupItemId::kViewPasswordDetails,
diff --git a/components/password_manager/core/browser/possible_username_data_unittest.cc b/components/password_manager/core/browser/possible_username_data_unittest.cc
index f7f7900b..d5a7fee 100644
--- a/components/password_manager/core/browser/possible_username_data_unittest.cc
+++ b/components/password_manager/core/browser/possible_username_data_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
 #include "components/autofill/core/common/unique_ids.h"
+#include "components/password_manager/core/browser/features/password_features.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using base::Time;
@@ -32,15 +33,17 @@
       /*is_likely_otp=*/false};
 };
 
-// Check that if more than |kPossibleUsernameExpirationTimeout| time has
-// passed since the last change of |possible_username_data_|, then it is stale.
+// Checks that `possible_username_data_` becomes stale over some time.
 TEST_F(IsPossibleUsernameValidTest, IsPossibleUsernameStale) {
   EXPECT_FALSE(possible_username_data_.IsStale());
 
   // Fast forward for a little less than expiration time, but not
   // exactly to not flake the test.
-  task_environment_.FastForwardBy(kPossibleUsernameExpirationTimeout -
-                                  base::Seconds(3));
+  task_environment_.FastForwardBy(
+      base::FeatureList::IsEnabled(
+          features::kUsernameFirstFlowWithIntermediateValues)
+          ? base::Minutes(features::kSingleUsernameTimeToLive.Get())
+          : kPossibleUsernameExpirationTimeout - base::Seconds(3));
   EXPECT_FALSE(possible_username_data_.IsStale());
 
   // Fast forward more until the data becomes stale.
diff --git a/components/services/app_service/public/cpp/permission.cc b/components/services/app_service/public/cpp/permission.cc
index 16b29d5f6..1b1d820 100644
--- a/components/services/app_service/public/cpp/permission.cc
+++ b/components/services/app_service/public/cpp/permission.cc
@@ -6,6 +6,7 @@
 
 #include <sstream>
 
+#include "base/containers/to_value_list.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 
 namespace apps {
@@ -165,11 +166,7 @@
 }
 
 base::Value::List ConvertPermissionsToList(const Permissions& permissions) {
-  base::Value::List list;
-  for (const auto& permission : permissions) {
-    list.Append(ConvertPermissionToDict(permission));
-  }
-  return list;
+  return base::ToValueList(permissions, &ConvertPermissionToDict);
 }
 
 Permissions ConvertListToPermissions(const base::Value::List* list) {
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 6bd5d01..14a5a76 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -985,8 +985,6 @@
     "file_system_access/file_system_access_access_handle_host_impl.h",
     "file_system_access/file_system_access_bucket_path_watcher.cc",
     "file_system_access/file_system_access_bucket_path_watcher.h",
-    "file_system_access/file_system_access_capacity_allocation_host_impl.cc",
-    "file_system_access/file_system_access_capacity_allocation_host_impl.h",
     "file_system_access/file_system_access_change_source.cc",
     "file_system_access/file_system_access_change_source.h",
     "file_system_access/file_system_access_data_transfer_token_impl.cc",
@@ -999,6 +997,8 @@
     "file_system_access/file_system_access_file_delegate_host_impl.h",
     "file_system_access/file_system_access_file_handle_impl.cc",
     "file_system_access/file_system_access_file_handle_impl.h",
+    "file_system_access/file_system_access_file_modification_host_impl.cc",
+    "file_system_access/file_system_access_file_modification_host_impl.h",
     "file_system_access/file_system_access_file_writer_impl.cc",
     "file_system_access/file_system_access_file_writer_impl.h",
     "file_system_access/file_system_access_handle_base.cc",
diff --git a/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc b/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
index b4289bc3..d18ee37 100644
--- a/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
+++ b/content/browser/accessibility/ax_platform_node_textrangeprovider_win_browsertest.cc
@@ -2929,18 +2929,19 @@
   EXPECT_UIA_TEXTRANGE_EQ(text_range_provider,
                           L"Before frame\nText in iframe\nAfter frame");
 
+  // Traversing by word should include trailing whitespace.
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                   /*count*/ 2,
                   /*expected_text*/ L"Text ",
                   /*expected_count*/ 2);
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
                   /*count*/ -1,
-                  /*expected_text*/ L"frame",
+                  /*expected_text*/ L"frame\n",
                   /*expected_count*/ -1);
   EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
       text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Character,
       /*count*/ 2,
-      /*expected_text*/ L"frame\nT",
+      /*expected_text*/ L"frame\nTe",
       /*expected_count*/ 2);
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Character,
                   /*count*/ 7,
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index 5b5fdd27b..2ac028e 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -858,7 +858,10 @@
 const ui::AXUniqueId& BrowserAccessibility::GetUniqueId() const {
   // This is not the same as GetData().id which comes from Blink, because
   // those ids are only unique within the Blink process. We need one that is
-  // unique for the browser process.
+  // unique per OS window.
+  // For example, Windows ATs use this to retrieve IA2 event targets for events
+  // that are fired on an OS-level window with an id. They also use it to
+  // save positions via IAccessible2::get_uniqueID().
   return unique_id_;
 }
 
diff --git a/content/browser/accessibility/browser_accessibility.h b/content/browser/accessibility/browser_accessibility.h
index 3c3e9d9..09da79a 100644
--- a/content/browser/accessibility/browser_accessibility.h
+++ b/content/browser/accessibility/browser_accessibility.h
@@ -532,6 +532,9 @@
   static bool HasInvalidAttribute(const ui::TextAttributeList& attributes);
 
   // A unique ID, since node IDs are frame-local.
+  // TODO(accessibility) We should be able to get rid of this, because node IDs
+  // are actually local to the renderer process, and each renderer process has
+  // its own OS-level window, which is all the uniqueness we need.
   ui::AXUniqueId unique_id_;
 };
 
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 8fbcc79..51791d9 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -161,7 +161,7 @@
 // static
 ui::AXTreeUpdate BrowserAccessibilityManager::GetEmptyDocument() {
   ui::AXNodeData empty_document;
-  empty_document.id = 1;
+  empty_document.id = ui::kInitialEmptyDocumentRootNodeID;
   empty_document.role = ax::mojom::Role::kRootWebArea;
   ui::AXTreeUpdate update;
   update.root_id = empty_document.id;
diff --git a/content/browser/accessibility/browser_accessibility_manager_android.cc b/content/browser/accessibility/browser_accessibility_manager_android.cc
index 03062124..9bc927a 100644
--- a/content/browser/accessibility/browser_accessibility_manager_android.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_android.cc
@@ -66,7 +66,7 @@
 // static
 ui::AXTreeUpdate BrowserAccessibilityManagerAndroid::GetEmptyDocument() {
   ui::AXNodeData empty_document;
-  empty_document.id = 1;
+  empty_document.id = ui::kInitialEmptyDocumentRootNodeID;
   empty_document.role = ax::mojom::Role::kRootWebArea;
   empty_document.SetRestriction(ax::mojom::Restriction::kReadOnly);
   ui::AXTreeUpdate update;
diff --git a/content/browser/accessibility/browser_accessibility_manager_auralinux.cc b/content/browser/accessibility/browser_accessibility_manager_auralinux.cc
index 64c3f938..0f976bb 100644
--- a/content/browser/accessibility/browser_accessibility_manager_auralinux.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_auralinux.cc
@@ -58,7 +58,7 @@
 // static
 ui::AXTreeUpdate BrowserAccessibilityManagerAuraLinux::GetEmptyDocument() {
   ui::AXNodeData empty_document;
-  empty_document.id = 1;
+  empty_document.id = ui::kInitialEmptyDocumentRootNodeID;
   empty_document.role = ax::mojom::Role::kRootWebArea;
   ui::AXTreeUpdate update;
   update.root_id = empty_document.id;
@@ -301,9 +301,25 @@
       FireReadonlyChangedEvent(wrapper);
       break;
     case ui::AXEventGenerator::Event::RANGE_VALUE_CHANGED:
-      DCHECK(wrapper->GetData().IsRangeValueSupported());
-      FireEvent(wrapper, ax::mojom::Event::kValueChanged);
+      // Before firing the platform event, check to see that the object's
+      // properties still support range values, because some of the properties
+      // may have been updated after the event was generated.
+      if (wrapper->GetData().IsRangeValueSupported()) {
+        FireEvent(wrapper, ax::mojom::Event::kValueChanged);
+      }
       break;
+    case ui::AXEventGenerator::Event::ALERT:
+    case ui::AXEventGenerator::Event::ROLE_CHANGED: {
+      // In ATK, there is no role change event, and instead, role changes are
+      // mapped to a subtree being removed and re-added. Since the AXNodeID
+      // (and therefore the AXNode) in such cases may remain the same, this must
+      // be done manually.
+      ui::AXPlatformNodeAuraLinux* platform_node =
+          ToBrowserAccessibilityAuraLinux(wrapper)->GetNode();
+      platform_node->OnSubtreeWillBeDeleted();
+      platform_node->OnSubtreeCreated();
+      break;
+    }
     case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED:
       FireEvent(wrapper, ax::mojom::Event::kSelectedChildrenChanged);
       break;
@@ -332,7 +348,6 @@
     // Currently unused events on this platform.
     case ui::AXEventGenerator::Event::NONE:
     case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED:
-    case ui::AXEventGenerator::Event::ALERT:
     case ui::AXEventGenerator::Event::ATOMIC_CHANGED:
     case ui::AXEventGenerator::Event::AUTO_COMPLETE_CHANGED:
     case ui::AXEventGenerator::Event::AUTOFILL_AVAILABILITY_CHANGED:
@@ -375,7 +390,6 @@
     case ui::AXEventGenerator::Event::RANGE_VALUE_STEP_CHANGED:
     case ui::AXEventGenerator::Event::RELATED_NODE_CHANGED:
     case ui::AXEventGenerator::Event::REQUIRED_STATE_CHANGED:
-    case ui::AXEventGenerator::Event::ROLE_CHANGED:
     case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED:
     case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED:
     case ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED:
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.mm b/content/browser/accessibility/browser_accessibility_manager_mac.mm
index ce3623b..96979b6 100644
--- a/content/browser/accessibility/browser_accessibility_manager_mac.mm
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.mm
@@ -63,7 +63,7 @@
 // static
 ui::AXTreeUpdate BrowserAccessibilityManagerMac::GetEmptyDocument() {
   ui::AXNodeData empty_document;
-  empty_document.id = 1;
+  empty_document.id = ui::kInitialEmptyDocumentRootNodeID;
   empty_document.role = ax::mojom::Role::kRootWebArea;
   ui::AXTreeUpdate update;
   update.root_id = empty_document.id;
diff --git a/content/browser/accessibility/browser_accessibility_manager_win.cc b/content/browser/accessibility/browser_accessibility_manager_win.cc
index 60ab68326..9368849c 100644
--- a/content/browser/accessibility/browser_accessibility_manager_win.cc
+++ b/content/browser/accessibility/browser_accessibility_manager_win.cc
@@ -442,6 +442,7 @@
       HandleAriaPropertiesChangedEvent(*wrapper);
       break;
     case ui::AXEventGenerator::Event::ROLE_CHANGED:
+      FireWinAccessibilityEvent(IA2_EVENT_ROLE_CHANGED, wrapper);
       FireUiaPropertyChangedEvent(UIA_AriaRolePropertyId, wrapper);
       break;
     case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED:
diff --git a/content/browser/file_system_access/file_system_access_access_handle_host_impl.cc b/content/browser/file_system_access/file_system_access_access_handle_host_impl.cc
index 4bd602b..2a7342a 100644
--- a/content/browser/file_system_access/file_system_access_access_handle_host_impl.cc
+++ b/content/browser/file_system_access/file_system_access_access_handle_host_impl.cc
@@ -6,8 +6,8 @@
 
 #include "base/feature_list.h"
 #include "base/functional/callback_helpers.h"
-#include "content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.h"
 #include "content/browser/file_system_access/file_system_access_file_delegate_host_impl.h"
+#include "content/browser/file_system_access/file_system_access_file_modification_host_impl.h"
 #include "storage/browser/file_system/file_system_context.h"
 #include "third_party/blink/public/common/features_generated.h"
 
@@ -22,8 +22,8 @@
         receiver,
     mojo::PendingReceiver<blink::mojom::FileSystemAccessFileDelegateHost>
         file_delegate_receiver,
-    mojo::PendingReceiver<blink::mojom::FileSystemAccessCapacityAllocationHost>
-        capacity_allocation_host_receiver,
+    mojo::PendingReceiver<blink::mojom::FileSystemAccessFileModificationHost>
+        file_modification_host_receiver,
     int64_t file_size,
     base::ScopedClosureRunner on_close_callback)
     : manager_(manager),
@@ -48,13 +48,13 @@
                 std::move(file_delegate_receiver))
           : nullptr;
 
-  // Only create a capacity allocation host in non-incognito mode.
-  capacity_allocation_host_ =
+  // Only create a file modification host in non-incognito mode.
+  file_modification_host_ =
       !manager_->context()->is_incognito()
-          ? std::make_unique<FileSystemAccessCapacityAllocationHostImpl>(
+          ? std::make_unique<FileSystemAccessFileModificationHostImpl>(
                 manager_, url_,
                 base::PassKey<FileSystemAccessAccessHandleHostImpl>(),
-                std::move(capacity_allocation_host_receiver), file_size)
+                std::move(file_modification_host_receiver), file_size)
           : nullptr;
 
   receiver_.set_disconnect_handler(
@@ -72,8 +72,8 @@
     return;
   }
 
-  // Run `callback` when this instance is destroyed, after capacity allocation
-  // has been released.
+  // Run `callback` when this instance is destroyed, after file modification
+  // host has been released.
   close_callback_ = base::ScopedClosureRunner(std::move(callback));
 
   // Removes `this`.
diff --git a/content/browser/file_system_access/file_system_access_access_handle_host_impl.h b/content/browser/file_system_access/file_system_access_access_handle_host_impl.h
index 3d7c5ef9..ae589651 100644
--- a/content/browser/file_system_access/file_system_access_access_handle_host_impl.h
+++ b/content/browser/file_system_access/file_system_access_access_handle_host_impl.h
@@ -10,8 +10,8 @@
 #include "base/functional/callback_helpers.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/scoped_refptr.h"
-#include "content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.h"
 #include "content/browser/file_system_access/file_system_access_file_delegate_host_impl.h"
+#include "content/browser/file_system_access/file_system_access_file_modification_host_impl.h"
 #include "content/browser/file_system_access/file_system_access_manager_impl.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -39,9 +39,8 @@
           receiver,
       mojo::PendingReceiver<blink::mojom::FileSystemAccessFileDelegateHost>
           file_delegate_receiver,
-      mojo::PendingReceiver<
-          blink::mojom::FileSystemAccessCapacityAllocationHost>
-          capacity_allocation_host_receiver,
+      mojo::PendingReceiver<blink::mojom::FileSystemAccessFileModificationHost>
+          file_modification_host_receiver,
       int64_t file_size,
       base::ScopedClosureRunner on_close_callback);
   FileSystemAccessAccessHandleHostImpl(
@@ -56,9 +55,9 @@
   // Returns the the total capacity allocated for the file whose capacity is
   // managed through this host.
   int64_t granted_capacity() const {
-    DCHECK(capacity_allocation_host_)
-        << "Capacity allocation requires a CapacityAllocationHost";
-    return capacity_allocation_host_->granted_capacity();
+    DCHECK(file_modification_host_)
+        << "Capacity allocation requires a FileModificationHost";
+    return file_modification_host_->granted_capacity();
   }
 
   storage::FileSystemURL url() const { return url_; }
@@ -81,7 +80,7 @@
   // Non-incognito file I/O operations on Access Handles are performed in the
   // renderer process. Before increasing a file's size, the renderer must
   // request additional capacity from the
-  // FileSystemAccessCapacityAllocationHostImpl. The host grants capacity if the
+  // FileSystemAccessFileModificationHostImpl. The host grants capacity if the
   // quota management system allows it. From the browser's perspective, all
   // granted capacity is fully used by the file.
   //
@@ -89,8 +88,8 @@
   // between the perceived file size, as reported by `granted_capacity()`, and
   // the actual file size on disk. This step is
   // performed by the FileSystemAccessManagerImpl owning this host.
-  std::unique_ptr<FileSystemAccessCapacityAllocationHostImpl>
-      capacity_allocation_host_;
+  std::unique_ptr<FileSystemAccessFileModificationHostImpl>
+      file_modification_host_;
 
   const storage::FileSystemURL url_;
 
diff --git a/content/browser/file_system_access/file_system_access_file_handle_impl.cc b/content/browser/file_system_access/file_system_access_file_handle_impl.cc
index e445f031..47e3a0d 100644
--- a/content/browser/file_system_access/file_system_access_file_handle_impl.cc
+++ b/content/browser/file_system_access/file_system_access_file_handle_impl.cc
@@ -37,10 +37,10 @@
 #include "storage/common/file_system/file_system_types.h"
 #include "third_party/blink/public/mojom/blob/blob.mojom.h"
 #include "third_party/blink/public/mojom/blob/serialized_blob.mojom.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_cloud_identifier.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_error.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_transfer_token.mojom.h"
 
 #if BUILDFLAG(IS_WIN)
@@ -400,12 +400,12 @@
   }
   DCHECK_GE(length_or_error.value(), 0);
 
-  mojo::PendingRemote<blink::mojom::FileSystemAccessCapacityAllocationHost>
-      capacity_allocation_host_remote;
+  mojo::PendingRemote<blink::mojom::FileSystemAccessFileModificationHost>
+      file_modification_host_remote;
   mojo::PendingRemote<blink::mojom::FileSystemAccessAccessHandleHost>
       access_handle_host_remote = manager()->CreateAccessHandleHost(
           url(), mojo::NullReceiver(),
-          capacity_allocation_host_remote.InitWithNewPipeAndPassReceiver(),
+          file_modification_host_remote.InitWithNewPipeAndPassReceiver(),
           length_or_error.value(), std::move(lock),
           std::move(on_close_callback));
 
@@ -414,7 +414,7 @@
       blink::mojom::FileSystemAccessAccessHandleFile::NewRegularFile(
           blink::mojom::FileSystemAccessRegularFile::New(
               std::move(file), length_or_error.value(),
-              std::move(capacity_allocation_host_remote))),
+              std::move(file_modification_host_remote))),
       std::move(access_handle_host_remote));
 }
 
diff --git a/content/browser/file_system_access/file_system_access_file_handle_impl.h b/content/browser/file_system_access/file_system_access_file_handle_impl.h
index 701c8461..226384e 100644
--- a/content/browser/file_system_access/file_system_access_file_handle_impl.h
+++ b/content/browser/file_system_access/file_system_access_file_handle_impl.h
@@ -17,8 +17,8 @@
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "storage/browser/file_system/file_system_url.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom.h"
 
 namespace content {
 
diff --git a/content/browser/file_system_access/file_system_access_file_handle_impl_unittest.cc b/content/browser/file_system_access/file_system_access_file_handle_impl_unittest.cc
index 7aba457..b0e3ed7 100644
--- a/content/browser/file_system_access/file_system_access_file_handle_impl_unittest.cc
+++ b/content/browser/file_system_access/file_system_access_file_handle_impl_unittest.cc
@@ -528,7 +528,7 @@
       std::move(file->get_regular_file());
   EXPECT_TRUE(regular_file->os_file.IsValid());
   EXPECT_EQ(regular_file->file_size, 0);
-  EXPECT_TRUE(regular_file->capacity_allocation_host.is_valid());
+  EXPECT_TRUE(regular_file->file_modification_host.is_valid());
   EXPECT_TRUE(access_handle_remote.is_valid());
 }
 
diff --git a/content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.cc b/content/browser/file_system_access/file_system_access_file_modification_host_impl.cc
similarity index 73%
rename from content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.cc
rename to content/browser/file_system_access/file_system_access_file_modification_host_impl.cc
index 2afbe3e..27c8208 100644
--- a/content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.cc
+++ b/content/browser/file_system_access/file_system_access_file_modification_host_impl.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 "content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.h"
+#include "content/browser/file_system_access/file_system_access_file_modification_host_impl.h"
 
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner.h"
@@ -16,13 +16,13 @@
 
 namespace content {
 
-FileSystemAccessCapacityAllocationHostImpl::
-    FileSystemAccessCapacityAllocationHostImpl(
+FileSystemAccessFileModificationHostImpl::
+    FileSystemAccessFileModificationHostImpl(
         FileSystemAccessManagerImpl* manager,
         const storage::FileSystemURL& url,
         base::PassKey<FileSystemAccessAccessHandleHostImpl> pass_key,
         mojo::PendingReceiver<
-            blink::mojom::FileSystemAccessCapacityAllocationHost> receiver,
+            blink::mojom::FileSystemAccessFileModificationHost> receiver,
         int64_t file_size)
     : manager_(manager),
       url_(url),
@@ -31,22 +31,22 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(manager_);
   // base::Unretained is safe here because this
-  // FileSystemAccessCapacityAllocationHostImpl owns `receiver_`. So, the
-  // unretained FileSystemAccessCapacityAllocationHostImpl is guaranteed to
+  // FileSystemAccessFileModificationHostImpl owns `receiver_`. So, the
+  // unretained FileSystemAccessFileModificationHostImpl is guaranteed to
   // outlive `receiver_` and the closure that it uses.
   receiver_.set_disconnect_handler(base::BindOnce(
-      &FileSystemAccessCapacityAllocationHostImpl::OnReceiverDisconnect,
+      &FileSystemAccessFileModificationHostImpl::OnReceiverDisconnect,
       base::Unretained(this)));
 }
 
 // Constructor for testing.
-FileSystemAccessCapacityAllocationHostImpl::
-    FileSystemAccessCapacityAllocationHostImpl(
+FileSystemAccessFileModificationHostImpl::
+    FileSystemAccessFileModificationHostImpl(
         FileSystemAccessManagerImpl* manager,
         const storage::FileSystemURL& url,
-        base::PassKey<FileSystemAccessCapacityAllocationHostImplTest> pass_key,
+        base::PassKey<FileSystemAccessFileModificationHostImplTest> pass_key,
         mojo::PendingReceiver<
-            blink::mojom::FileSystemAccessCapacityAllocationHost> receiver,
+            blink::mojom::FileSystemAccessFileModificationHost> receiver,
         int64_t file_size)
     : manager_(manager),
       url_(url),
@@ -55,23 +55,23 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(manager_);
   // base::Unretained is safe here because this
-  // FileSystemAccessCapacityAllocationHostImpl owns `receiver_`. So, the
-  // unretained FileSystemAccessCapacityAllocationHostImpl is guaranteed to
+  // FileSystemAccessFileModificationHostImpl owns `receiver_`. So, the
+  // unretained FileSystemAccessFileModificationHostImpl is guaranteed to
   // outlive `receiver_` and the closure that it uses.
   receiver_.set_disconnect_handler(base::BindOnce(
-      &FileSystemAccessCapacityAllocationHostImpl::OnReceiverDisconnect,
+      &FileSystemAccessFileModificationHostImpl::OnReceiverDisconnect,
       base::Unretained(this)));
 }
 
-FileSystemAccessCapacityAllocationHostImpl::
-    ~FileSystemAccessCapacityAllocationHostImpl() = default;
+FileSystemAccessFileModificationHostImpl::
+    ~FileSystemAccessFileModificationHostImpl() = default;
 
-void FileSystemAccessCapacityAllocationHostImpl::OnReceiverDisconnect() {
+void FileSystemAccessFileModificationHostImpl::OnReceiverDisconnect() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   receiver_.reset();
 }
 
-void FileSystemAccessCapacityAllocationHostImpl::RequestCapacityChange(
+void FileSystemAccessFileModificationHostImpl::RequestCapacityChange(
     int64_t capacity_delta,
     RequestCapacityChangeCallback callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -92,11 +92,11 @@
       url_.storage_key(), blink::mojom::StorageType::kTemporary,
       base::SequencedTaskRunner::GetCurrentDefault(),
       base::BindOnce(
-          &FileSystemAccessCapacityAllocationHostImpl::DidGetUsageAndQuota,
+          &FileSystemAccessFileModificationHostImpl::DidGetUsageAndQuota,
           weak_factory_.GetWeakPtr(), capacity_delta, std::move(callback)));
 }
 
-void FileSystemAccessCapacityAllocationHostImpl::DidGetUsageAndQuota(
+void FileSystemAccessFileModificationHostImpl::DidGetUsageAndQuota(
     int64_t capacity_delta,
     RequestCapacityChangeCallback callback,
     blink::mojom::QuotaStatusCode status,
@@ -121,7 +121,7 @@
   std::move(callback).Run(capacity_delta);
 }
 
-void FileSystemAccessCapacityAllocationHostImpl::OnContentsModified() {
+void FileSystemAccessFileModificationHostImpl::OnContentsModified() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (const storage::ChangeObserverList* change_observers =
diff --git a/content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.h b/content/browser/file_system_access/file_system_access_file_modification_host_impl.h
similarity index 65%
rename from content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.h
rename to content/browser/file_system_access/file_system_access_file_modification_host_impl.h
index 1d0a91c..8ab49c7 100644
--- a/content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.h
+++ b/content/browser/file_system_access/file_system_access_file_modification_host_impl.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 CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_CAPACITY_ALLOCATION_HOST_IMPL_H_
-#define CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_CAPACITY_ALLOCATION_HOST_IMPL_H_
+#ifndef CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_FILE_MODIFICATION_HOST_IMPL_H_
+#define CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_FILE_MODIFICATION_HOST_IMPL_H_
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -15,7 +15,7 @@
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "storage/browser/file_system/file_system_context.h"
 #include "storage/browser/file_system/file_system_url.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom.h"
 
 namespace storage {
 class QuotaManagerProxy;
@@ -23,43 +23,43 @@
 
 namespace content {
 
-class FileSystemAccessCapacityAllocationHostImplTest;
+class FileSystemAccessFileModificationHostImplTest;
 
 // This is the browser side implementation of the
-// FileSystemAccessCapacityAllocationHost mojom interface. Instances of this
+// FileSystemAccessFileModificationHost mojom interface. Instances of this
 // class are owned by the FileSystemAccessHandleHost instance passed in to the
 // constructor.
-class CONTENT_EXPORT FileSystemAccessCapacityAllocationHostImpl
-    : public blink::mojom::FileSystemAccessCapacityAllocationHost {
+class CONTENT_EXPORT FileSystemAccessFileModificationHostImpl
+    : public blink::mojom::FileSystemAccessFileModificationHost {
  public:
-  // Creates a FileSystemAccessCapacityAllocationHost that manages capacity
-  // reservations for the file. CapacityAllocationHosts should only be created
+  // Creates a FileSystemAccessFileModificationHost that manages capacity
+  // reservations for the file. FileModificationHosts should only be created
   // via the FileSystemAccessHandleHost.
-  FileSystemAccessCapacityAllocationHostImpl(
+  FileSystemAccessFileModificationHostImpl(
       FileSystemAccessManagerImpl* manager,
       const storage::FileSystemURL& url,
       base::PassKey<FileSystemAccessAccessHandleHostImpl> pass_key,
-      mojo::PendingReceiver<
-          blink::mojom::FileSystemAccessCapacityAllocationHost> receiver,
+      mojo::PendingReceiver<blink::mojom::FileSystemAccessFileModificationHost>
+          receiver,
       int64_t file_size);
 
   // Constructor for testing
-  FileSystemAccessCapacityAllocationHostImpl(
+  FileSystemAccessFileModificationHostImpl(
       FileSystemAccessManagerImpl* manager,
       const storage::FileSystemURL& url,
-      base::PassKey<FileSystemAccessCapacityAllocationHostImplTest> pass_key,
-      mojo::PendingReceiver<
-          blink::mojom::FileSystemAccessCapacityAllocationHost> receiver,
+      base::PassKey<FileSystemAccessFileModificationHostImplTest> pass_key,
+      mojo::PendingReceiver<blink::mojom::FileSystemAccessFileModificationHost>
+          receiver,
       int64_t file_size);
 
-  FileSystemAccessCapacityAllocationHostImpl(
-      const FileSystemAccessCapacityAllocationHostImpl&) = delete;
-  FileSystemAccessCapacityAllocationHostImpl& operator=(
-      const FileSystemAccessCapacityAllocationHostImpl&) = delete;
+  FileSystemAccessFileModificationHostImpl(
+      const FileSystemAccessFileModificationHostImpl&) = delete;
+  FileSystemAccessFileModificationHostImpl& operator=(
+      const FileSystemAccessFileModificationHostImpl&) = delete;
 
-  ~FileSystemAccessCapacityAllocationHostImpl() override;
+  ~FileSystemAccessFileModificationHostImpl() override;
 
-  // blink::mojom::FileSystemAccessCapacityAllocationHost:
+  // blink::mojom::FileSystemAccessFileModificationHost:
   void RequestCapacityChange(int64_t capacity_delta,
                              RequestCapacityChangeCallback callback) override;
   void OnContentsModified() override;
@@ -94,7 +94,7 @@
   // URL of the file whose capacity is managed through this host.
   const storage::FileSystemURL url_;
 
-  mojo::Receiver<blink::mojom::FileSystemAccessCapacityAllocationHost> receiver_
+  mojo::Receiver<blink::mojom::FileSystemAccessFileModificationHost> receiver_
       GUARDED_BY_CONTEXT(sequence_checker_);
 
   // Total capacity granted to the file managed through this host. Initially,
@@ -102,10 +102,10 @@
   // reaching `RequestCapacityChange()`.
   int64_t granted_capacity_ GUARDED_BY_CONTEXT(sequence_checker_);
 
-  base::WeakPtrFactory<FileSystemAccessCapacityAllocationHostImpl> weak_factory_
+  base::WeakPtrFactory<FileSystemAccessFileModificationHostImpl> weak_factory_
       GUARDED_BY_CONTEXT(sequence_checker_){this};
 };
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_CAPACITY_ALLOCATION_HOST_IMPL_H_
+#endif  // CONTENT_BROWSER_FILE_SYSTEM_ACCESS_FILE_SYSTEM_ACCESS_FILE_MODIFICATION_HOST_IMPL_H_
diff --git a/content/browser/file_system_access/file_system_access_capacity_allocation_host_impl_browsertest.cc b/content/browser/file_system_access/file_system_access_file_modification_host_impl_browsertest.cc
similarity index 96%
rename from content/browser/file_system_access/file_system_access_capacity_allocation_host_impl_browsertest.cc
rename to content/browser/file_system_access/file_system_access_file_modification_host_impl_browsertest.cc
index 07b1644..d371423 100644
--- a/content/browser/file_system_access/file_system_access_capacity_allocation_host_impl_browsertest.cc
+++ b/content/browser/file_system_access/file_system_access_file_modification_host_impl_browsertest.cc
@@ -26,8 +26,8 @@
 namespace content {
 
 // This browser test implements end-to-end tests for
-// FileSystemAccessCapacityAllocationHostImpl.
-class FileSystemAccessCapacityAllocationHostImplBrowserTest
+// FileSystemAccessFileModificationHostImpl.
+class FileSystemAccessFileModificationHostImplBrowserTest
     : public ContentBrowserTest {
  public:
   void SetUpOnMainThread() override {
@@ -73,7 +73,7 @@
   base::ScopedTempDir temp_dir_;
 };
 
-IN_PROC_BROWSER_TEST_F(FileSystemAccessCapacityAllocationHostImplBrowserTest,
+IN_PROC_BROWSER_TEST_F(FileSystemAccessFileModificationHostImplBrowserTest,
                        QuotaUsageAfterClosing) {
   const GURL& test_url =
       embedded_test_server()->GetURL("/run_async_code_on_worker.html");
@@ -114,7 +114,7 @@
   EXPECT_EQ(usage_after_operation, usage_before_operation + 10);
 }
 
-IN_PROC_BROWSER_TEST_F(FileSystemAccessCapacityAllocationHostImplBrowserTest,
+IN_PROC_BROWSER_TEST_F(FileSystemAccessFileModificationHostImplBrowserTest,
                        QuotaUsageAfterForNonemptyFile) {
   const GURL& test_url =
       embedded_test_server()->GetURL("/run_async_code_on_worker.html");
@@ -168,7 +168,7 @@
 #else
 #define MAYBE_QuotaUsageOverallocation QuotaUsageOverallocation
 #endif
-IN_PROC_BROWSER_TEST_F(FileSystemAccessCapacityAllocationHostImplBrowserTest,
+IN_PROC_BROWSER_TEST_F(FileSystemAccessFileModificationHostImplBrowserTest,
                        MAYBE_QuotaUsageOverallocation) {
   // TODO(https://crbug.com/1240056): Implement a more sophisticated test suite
   // for this feature.
@@ -215,7 +215,7 @@
 
 // TODO(crbug.com/1304977): Failing on Mac, Linux, and ChromeOS builders.
 // TODO(crbug.com/1459385): Re-enable this test
-IN_PROC_BROWSER_TEST_F(FileSystemAccessCapacityAllocationHostImplBrowserTest,
+IN_PROC_BROWSER_TEST_F(FileSystemAccessFileModificationHostImplBrowserTest,
                        DISABLED_QuotaUsageShrinks) {
   const GURL& test_url =
       embedded_test_server()->GetURL("/run_async_code_on_worker.html");
@@ -258,7 +258,7 @@
             1024 * 1024);
 }
 
-IN_PROC_BROWSER_TEST_F(FileSystemAccessCapacityAllocationHostImplBrowserTest,
+IN_PROC_BROWSER_TEST_F(FileSystemAccessFileModificationHostImplBrowserTest,
                        QuotaUsageWrite) {
   const GURL& test_url =
       embedded_test_server()->GetURL("/run_async_code_on_worker.html");
diff --git a/content/browser/file_system_access/file_system_access_capacity_allocation_host_impl_unittest.cc b/content/browser/file_system_access/file_system_access_file_modification_host_impl_unittest.cc
similarity index 88%
rename from content/browser/file_system_access/file_system_access_capacity_allocation_host_impl_unittest.cc
rename to content/browser/file_system_access/file_system_access_file_modification_host_impl_unittest.cc
index 91a8ce4..c19b170 100644
--- a/content/browser/file_system_access/file_system_access_capacity_allocation_host_impl_unittest.cc
+++ b/content/browser/file_system_access/file_system_access_file_modification_host_impl_unittest.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 "content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.h"
+#include "content/browser/file_system_access/file_system_access_file_modification_host_impl.h"
 
 #include "base/files/file_path.h"
 #include "base/files/scoped_temp_dir.h"
@@ -29,10 +29,10 @@
 
 namespace {
 
-// Synchronous proxy to FileSystemAccessCapacityAllocationHostImpl's
+// Synchronous proxy to FileSystemAccessFileModificationHostImpl's
 // RequestCapacityChange.
 int64_t RequestCapacityChangeSync(
-    FileSystemAccessCapacityAllocationHostImpl* allocation_host,
+    FileSystemAccessFileModificationHostImpl* allocation_host,
     int64_t capacity_delta) {
   base::test::TestFuture<int64_t> future;
   allocation_host->RequestCapacityChange(capacity_delta, future.GetCallback());
@@ -42,9 +42,9 @@
 
 }  // namespace
 
-class FileSystemAccessCapacityAllocationHostImplTest : public testing::Test {
+class FileSystemAccessFileModificationHostImplTest : public testing::Test {
  public:
-  FileSystemAccessCapacityAllocationHostImplTest()
+  FileSystemAccessFileModificationHostImplTest()
       : special_storage_policy_(
             base::MakeRefCounted<storage::MockSpecialStoragePolicy>()),
         task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
@@ -74,12 +74,12 @@
         base::FilePath::FromUTF8Unsafe("test"));
     test_file_url.SetBucket(
         storage::BucketLocator::ForDefaultBucket(kTestStorageKey));
-    mojo::Remote<blink::mojom::FileSystemAccessCapacityAllocationHost>
+    mojo::Remote<blink::mojom::FileSystemAccessFileModificationHost>
         allocation_host_remote;
     allocation_host_ =
-        std::make_unique<FileSystemAccessCapacityAllocationHostImpl>(
+        std::make_unique<FileSystemAccessFileModificationHostImpl>(
             manager_.get(), test_file_url,
-            base::PassKey<FileSystemAccessCapacityAllocationHostImplTest>(),
+            base::PassKey<FileSystemAccessFileModificationHostImplTest>(),
             allocation_host_remote.BindNewPipeAndPassReceiver(), 0);
   }
 
@@ -111,12 +111,12 @@
   scoped_refptr<ChromeBlobStorageContext> chrome_blob_context_;
   scoped_refptr<FileSystemAccessManagerImpl> manager_;
 
-  std::unique_ptr<FileSystemAccessCapacityAllocationHostImpl> allocation_host_;
+  std::unique_ptr<FileSystemAccessFileModificationHostImpl> allocation_host_;
   scoped_refptr<storage::MockQuotaManager> quota_manager_;
   scoped_refptr<storage::MockQuotaManagerProxy> quota_manager_proxy_;
 };
 
-TEST_F(FileSystemAccessCapacityAllocationHostImplTest,
+TEST_F(FileSystemAccessFileModificationHostImplTest,
        RequestCapacityChange_PositiveCapacity) {
   const int64_t requested_capacity = 50;
   int64_t granted_capacity =
@@ -126,7 +126,7 @@
             requested_capacity);
 }
 
-TEST_F(FileSystemAccessCapacityAllocationHostImplTest,
+TEST_F(FileSystemAccessFileModificationHostImplTest,
        RequestCapacityChange_PositiveAndNegativeCapacity) {
   const int64_t positive_requested_capacity = 50;
   const int64_t negative_requested_capacity = -40;
@@ -146,7 +146,7 @@
   EXPECT_EQ(quota_manager_proxy_->notify_bucket_modified_count(), 2);
 }
 
-TEST_F(FileSystemAccessCapacityAllocationHostImplTest,
+TEST_F(FileSystemAccessFileModificationHostImplTest,
        RequestCapacityChange_IllegalNegativeCapacity) {
   mojo::test::BadMessageObserver bad_message_observer;
   mojo::FakeMessageDispatchContext fake_dispatch_context;
diff --git a/content/browser/file_system_access/file_system_access_manager_impl.cc b/content/browser/file_system_access/file_system_access_manager_impl.cc
index 80a00595..d917e8a 100644
--- a/content/browser/file_system_access/file_system_access_manager_impl.cc
+++ b/content/browser/file_system_access/file_system_access_manager_impl.cc
@@ -58,9 +58,9 @@
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "storage/common/file_system/file_system_types.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_data_transfer_token.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_error.mojom.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom-forward.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h"
 #include "ui/shell_dialogs/select_file_dialog.h"
@@ -1217,8 +1217,8 @@
     const storage::FileSystemURL& url,
     mojo::PendingReceiver<blink::mojom::FileSystemAccessFileDelegateHost>
         file_delegate_receiver,
-    mojo::PendingReceiver<blink::mojom::FileSystemAccessCapacityAllocationHost>
-        capacity_allocation_host_receiver,
+    mojo::PendingReceiver<blink::mojom::FileSystemAccessFileModificationHost>
+        file_modification_host_receiver,
     int64_t file_size,
     scoped_refptr<FileSystemAccessLockManager::LockHandle> lock,
     base::ScopedClosureRunner on_close_callback) {
@@ -1230,7 +1230,7 @@
       std::make_unique<FileSystemAccessAccessHandleHostImpl>(
           this, url, std::move(lock), PassKey(), std::move(receiver),
           std::move(file_delegate_receiver),
-          std::move(capacity_allocation_host_receiver), file_size,
+          std::move(file_modification_host_receiver), file_size,
           std::move(on_close_callback));
   access_handle_host_receivers_.insert(std::move(access_handle_host));
 
diff --git a/content/browser/file_system_access/file_system_access_manager_impl.h b/content/browser/file_system_access/file_system_access_manager_impl.h
index 0e5c005..de72cea 100644
--- a/content/browser/file_system_access/file_system_access_manager_impl.h
+++ b/content/browser/file_system_access/file_system_access_manager_impl.h
@@ -40,9 +40,9 @@
 #include "storage/browser/file_system/file_system_operation_runner.h"
 #include "storage/browser/file_system/file_system_url.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_access_handle_host.mojom.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_data_transfer_token.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_delegate_host.mojom.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_writer.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_manager.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_observer_host.mojom.h"
@@ -269,9 +269,8 @@
       const storage::FileSystemURL& url,
       mojo::PendingReceiver<blink::mojom::FileSystemAccessFileDelegateHost>
           file_delegate_receiver,
-      mojo::PendingReceiver<
-          blink::mojom::FileSystemAccessCapacityAllocationHost>
-          capacity_allocation_host_receiver,
+      mojo::PendingReceiver<blink::mojom::FileSystemAccessFileModificationHost>
+          file_modification_host_receiver,
       int64_t file_size,
       scoped_refptr<FileSystemAccessLockManager::LockHandle> lock,
       base::ScopedClosureRunner on_close_callback);
@@ -528,7 +527,7 @@
       mojo::PendingReceiver<blink::mojom::FileSystemAccessTransferToken> token,
       const storage::FileSystemURL& url);
 
-  // FileSystemAccessCapacityAllocationHosts may reserve too much capacity
+  // FileSystemAccessFileModificationHosts may reserve too much capacity
   // from the quota system. This function determines the file's actual size
   // and corrects its capacity usage in the quota system.
   void CleanupAccessHandleCapacityAllocation(const storage::FileSystemURL& url,
diff --git a/content/browser/loader/navigation_url_loader.cc b/content/browser/loader/navigation_url_loader.cc
index 66769358..a178bfa 100644
--- a/content/browser/loader/navigation_url_loader.cc
+++ b/content/browser/loader/navigation_url_loader.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/command_line.h"
+#include "base/trace_event/trace_event.h"
 #include "content/browser/loader/cached_navigation_url_loader.h"
 #include "content/browser/loader/navigation_loader_interceptor.h"
 #include "content/browser/loader/navigation_url_loader_factory.h"
@@ -42,6 +43,7 @@
     network::mojom::URLResponseHeadPtr cached_response_head,
     std::vector<std::unique_ptr<NavigationLoaderInterceptor>>
         initial_interceptors) {
+  TRACE_EVENT0("navigation", "NavigationURLLoader::Create");
   // Prioritize CachedNavigationURLLoader over `g_loader_factory` even for tests
   // as prerendered page activation needs to run synchronously and
   // CachedNavigationURLLoader serves a fake response synchronously.
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 4fecbda..0e7ea72b 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -457,6 +457,9 @@
 
 // TODO(kinuko): Fix the method ordering and move these methods after the ctor.
 NavigationURLLoaderImpl::~NavigationURLLoaderImpl() {
+  TRACE_EVENT_WITH_FLOW0("navigation",
+                         "NavigationURLLoaderImpl::~NavigationURLLoaderImpl",
+                         TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN);
   // If neither OnCompleted nor OnReceivedResponse has been invoked, the
   // request was canceled before receiving a response, so log a cancellation.
   // Results after receiving a non-error response are logged in the renderer,
@@ -472,6 +475,9 @@
 }
 
 void NavigationURLLoaderImpl::Start() {
+  TRACE_EVENT_WITH_FLOW0("navigation", "NavigationURLLoaderImpl::Start",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(!started_);
   started_ = true;
@@ -532,6 +538,10 @@
 }
 
 void NavigationURLLoaderImpl::CreateInterceptors() {
+  TRACE_EVENT_WITH_FLOW0("navigation",
+                         "NavigationURLLoaderImpl::CreateInterceptors",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   if (prefetched_signed_exchange_cache_) {
     std::unique_ptr<NavigationLoaderInterceptor>
         prefetched_signed_exchange_interceptor =
@@ -866,6 +876,10 @@
     scoped_refptr<network::SharedURLLoaderFactory> factory,
     std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
         additional_throttles) {
+  TRACE_EVENT_WITH_FLOW0(
+      "navigation", "NavigationURLLoaderImpl::CreateThrottlingLoaderAndStart",
+      TRACE_ID_LOCAL(this),
+      TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   CHECK(!url_loader_);
 
   std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
@@ -1313,6 +1327,10 @@
 
 std::vector<std::unique_ptr<blink::URLLoaderThrottle>>
 NavigationURLLoaderImpl::CreateURLLoaderThrottles() {
+  TRACE_EVENT_WITH_FLOW0("navigation",
+                         "NavigationURLLoaderImpl::CreateURLLoaderThrottles",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   auto throttles = CreateContentBrowserURLLoaderThrottles(
       *resource_request_, browser_context_, web_contents_getter_,
       navigation_ui_data_.get(), frame_tree_node_id_,
@@ -1436,6 +1454,9 @@
       ukm_source_id_(FrameTreeNode::GloballyFindByID(frame_tree_node_id_)
                          ->navigation_request()
                          ->GetNextPageUkmSourceId()) {
+  TRACE_EVENT_WITH_FLOW0("navigation",
+                         "NavigationURLLoaderImpl::NavigationURLLoaderImpl",
+                         TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_OUT);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
diff --git a/content/browser/media/capture/desktop_capture_device.cc b/content/browser/media/capture/desktop_capture_device.cc
index 162de4c..693e14e 100644
--- a/content/browser/media/capture/desktop_capture_device.cc
+++ b/content/browser/media/capture/desktop_capture_device.cc
@@ -647,7 +647,7 @@
           gfx::Size(output_size.width(), output_size.height()),
           requested_frame_rate_, media::PIXEL_FORMAT_ARGB),
       frame_color_space, 0 /* clockwise_rotation */, false /* flip_y */, now,
-      now - first_ref_time_);
+      now - first_ref_time_, std::nullopt);
 
   ScheduleNextCaptureFrame();
 }
diff --git a/content/browser/media/capture/desktop_capture_device_unittest.cc b/content/browser/media/capture/desktop_capture_device_unittest.cc
index cd17a39..ecd3d23 100644
--- a/content/browser/media/capture/desktop_capture_device_unittest.cc
+++ b/content/browser/media/capture/desktop_capture_device_unittest.cc
@@ -282,6 +282,7 @@
                  bool /* flip_y */,
                  base::TimeTicks /* reference_time */,
                  base::TimeDelta /* timestamp */,
+                 std::optional<base::TimeTicks> /* capture_begin_time */,
                  int /* frame_feedback_id */) {
     ASSERT_TRUE(output_frame_);
     ASSERT_EQ(output_frame_->stride() * output_frame_->size().height(), size);
@@ -340,9 +341,9 @@
 
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted());
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted);
+  EXPECT_CALL(*client, OnIncomingCapturedData)
       .WillRepeatedly(
           DoAll(SaveArg<1>(&frame_size), SaveArg<2>(&format),
                 InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal)));
@@ -379,9 +380,9 @@
 
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted());
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted);
+  EXPECT_CALL(*client, OnIncomingCapturedData)
       .WillRepeatedly(
           DoAll(WithArg<2>(Invoke(&format_checker,
                                   &FormatChecker::ExpectAcceptableSize)),
@@ -425,9 +426,9 @@
 
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted());
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted);
+  EXPECT_CALL(*client, OnIncomingCapturedData)
       .WillRepeatedly(
           DoAll(WithArg<2>(Invoke(&format_checker,
                                   &FormatChecker::ExpectAcceptableSize)),
@@ -475,9 +476,9 @@
 
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted());
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted);
+  EXPECT_CALL(*client, OnIncomingCapturedData)
       .WillRepeatedly(
           DoAll(WithArg<2>(Invoke(&format_checker,
                                   &FormatChecker::ExpectAcceptableSize)),
@@ -527,9 +528,9 @@
 
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted());
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted);
+  EXPECT_CALL(*client, OnIncomingCapturedData)
       .WillRepeatedly(
           DoAll(Invoke(this, &DesktopCaptureDeviceTest::CopyFrame),
                 SaveArg<1>(&frame_size),
@@ -576,9 +577,9 @@
 
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted());
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted);
+  EXPECT_CALL(*client, OnIncomingCapturedData)
       .WillRepeatedly(
           DoAll(Invoke(this, &DesktopCaptureDeviceTest::CopyFrame),
                 SaveArg<1>(&frame_size),
@@ -621,10 +622,9 @@
 
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted()).Times(0);
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
-      .Times(0);
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted).Times(0);
+  EXPECT_CALL(*client, OnIncomingCapturedData).Times(0);
 
   capture_device_->RequestRefreshFrame();
   capture_device_->StopAndDeAllocate();
@@ -646,9 +646,9 @@
 
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted());
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted);
+  EXPECT_CALL(*client, OnIncomingCapturedData)
       .Times(1)
       .WillRepeatedly(
           InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal));
@@ -689,9 +689,9 @@
   // Ensure that we receive two calls to OnIncomingCapturedData().
   std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
       CreateMockVideoCaptureDeviceClient());
-  EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
-  EXPECT_CALL(*client, OnStarted());
-  EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client, OnError).Times(0);
+  EXPECT_CALL(*client, OnStarted);
+  EXPECT_CALL(*client, OnIncomingCapturedData)
       .Times(2)
       .WillRepeatedly(
           DoAll(WithArg<2>(Invoke(&format_checker,
@@ -846,9 +846,9 @@
 
     std::unique_ptr<media::MockVideoCaptureDeviceClient> client(
         CreateMockVideoCaptureDeviceClient());
-    EXPECT_CALL(*client, OnError(_, _, _)).Times(0);
+    EXPECT_CALL(*client, OnError).Times(0);
     // On started is called from the capture thread.
-    EXPECT_CALL(*client, OnStarted())
+    EXPECT_CALL(*client, OnStarted)
         .WillOnce(InvokeWithoutArgs([this, &task_runner,
                                      &message_loop_task_runner] {
           message_loop_task_runner =
@@ -860,7 +860,7 @@
               task_runner, task_runner->GetMockTickClock());
         }));
 
-    EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+    EXPECT_CALL(*client, OnIncomingCapturedData)
         .WillRepeatedly(DoAll(
             WithArg<2>(
                 Invoke(&format_checker, &FormatChecker::ExpectAcceptableSize)),
diff --git a/content/browser/media/capture/io_surface_capture_device_base_mac.cc b/content/browser/media/capture/io_surface_capture_device_base_mac.cc
index b638d51..8a77491 100644
--- a/content/browser/media/capture/io_surface_capture_device_base_mac.cc
+++ b/content/browser/media/capture/io_surface_capture_device_base_mac.cc
@@ -67,7 +67,7 @@
       media::CapturedExternalVideoBuffer(std::move(handle),
                                          last_received_capture_format_,
                                          gfx::ColorSpace::CreateREC709()),
-      now, now - first_frame_time_, last_visible_rect_);
+      now, now - first_frame_time_, std::nullopt, last_visible_rect_);
 }
 
 void IOSurfaceCaptureDeviceBase::ComputeFrameSizeAndDestRect(
diff --git a/content/browser/renderer_host/media/video_capture_controller_unittest.cc b/content/browser/renderer_host/media/video_capture_controller_unittest.cc
index d3c5677..db594a9 100644
--- a/content/browser/renderer_host/media/video_capture_controller_unittest.cc
+++ b/content/browser/renderer_host/media/video_capture_controller_unittest.cc
@@ -217,7 +217,7 @@
         media::VideoFrame::AllocationSize(stub_frame->format(),
                                           stub_frame->coded_size()),
         format, color_space, rotation, false /* flip_y */, base::TimeTicks(),
-        base::TimeDelta(), frame_feedback_id);
+        base::TimeDelta(), std::nullopt, frame_feedback_id);
   }
 
   BrowserTaskEnvironment task_environment_;
@@ -426,7 +426,7 @@
 
   device_client_->OnIncomingCapturedBuffer(std::move(buffer), device_format,
                                            arbitrary_reference_time_,
-                                           arbitrary_timestamp_);
+                                           arbitrary_timestamp_, std::nullopt);
 
   base::RunLoop().RunUntilIdle();
   Mock::VerifyAndClearExpectations(client_a_.get());
@@ -460,7 +460,7 @@
 
   device_client_->OnIncomingCapturedBuffer(std::move(buffer2), device_format,
                                            arbitrary_reference_time_,
-                                           arbitrary_timestamp_);
+                                           arbitrary_timestamp_, std::nullopt);
 
   // The frame should be delivered to the clients in any order.
   EXPECT_CALL(*client_a_, DoBufferReady(ControllerIDAndSize(
@@ -491,9 +491,9 @@
     auto buffer3_access =
         buffer3.handle_provider->GetHandleForInProcessAccess();
     memset(buffer3_access->data(), buffer_no++, buffer3_access->mapped_size());
-    device_client_->OnIncomingCapturedBuffer(std::move(buffer3), device_format,
-                                             arbitrary_reference_time_,
-                                             arbitrary_timestamp_);
+    device_client_->OnIncomingCapturedBuffer(
+        std::move(buffer3), device_format, arbitrary_reference_time_,
+        arbitrary_timestamp_, std::nullopt);
   }
   // ReserveOutputBuffer ought to fail now, because the pool is depleted.
   media::VideoCaptureDevice::Client::Buffer buffer_fail;
@@ -546,7 +546,7 @@
   memset(buffer3_access->data(), buffer_no++, buffer3_access->mapped_size());
   device_client_->OnIncomingCapturedBuffer(std::move(buffer3), device_format,
                                            arbitrary_reference_time_,
-                                           arbitrary_timestamp_);
+                                           arbitrary_timestamp_, std::nullopt);
 
   media::VideoCaptureDevice::Client::Buffer buffer4;
   const auto result_code_4 = device_client_->ReserveOutputBuffer(
@@ -564,7 +564,7 @@
   memset(buffer4_access->data(), buffer_no++, buffer4_access->mapped_size());
   device_client_->OnIncomingCapturedBuffer(std::move(buffer4), device_format,
                                            arbitrary_reference_time_,
-                                           arbitrary_timestamp_);
+                                           arbitrary_timestamp_, std::nullopt);
   // B2 is the only client left, and is the only one that should
   // get the buffer.
   EXPECT_CALL(*client_b_, DoBufferReady(ControllerIDAndSize(
@@ -632,7 +632,7 @@
             reserve_result);
   device_client_->OnIncomingCapturedBuffer(std::move(buffer), device_format,
                                            arbitrary_reference_time_,
-                                           arbitrary_timestamp_);
+                                           arbitrary_timestamp_, std::nullopt);
 
   base::RunLoop().RunUntilIdle();
 }
@@ -673,7 +673,7 @@
       "Test Error");
   device_client_->OnIncomingCapturedBuffer(std::move(buffer), device_format,
                                            arbitrary_reference_time_,
-                                           arbitrary_timestamp_);
+                                           arbitrary_timestamp_, std::nullopt);
 
   EXPECT_CALL(
       *client_a_,
@@ -743,7 +743,7 @@
               result_code);
     device_client_->OnIncomingCapturedBuffer(
         std::move(buffer), arbitrary_format, arbitrary_reference_time_,
-        arbitrary_timestamp_);
+        arbitrary_timestamp_, std::nullopt);
 
     base::RunLoop().RunUntilIdle();
     Mock::VerifyAndClearExpectations(client_a_.get());
diff --git a/content/browser/renderer_host/navigation_throttle_runner.cc b/content/browser/renderer_host/navigation_throttle_runner.cc
index 6a64180a..17771461 100644
--- a/content/browser/renderer_host/navigation_throttle_runner.cc
+++ b/content/browser/renderer_host/navigation_throttle_runner.cc
@@ -149,6 +149,8 @@
 }
 
 void NavigationThrottleRunner::RegisterNavigationThrottles() {
+  TRACE_EVENT0("navigation",
+               "NavigationThrottleRunner::RegisterNavigationThrottles");
   // Note: |throttle_| might not be empty. Some NavigationThrottles might have
   // been registered with RegisterThrottleForTesting. These must reside at the
   // end of |throttles_|. TestNavigationManagerThrottle expects that the
@@ -299,11 +301,16 @@
 
 void NavigationThrottleRunner::AddThrottle(
     std::unique_ptr<NavigationThrottle> navigation_throttle) {
-  if (navigation_throttle)
+  if (navigation_throttle) {
+    TRACE_EVENT1("navigation", "NavigationThrottleRunner::AddThrottle",
+                 "navigation_throttle",
+                 navigation_throttle->GetNameForLogging());
     throttles_.push_back(std::move(navigation_throttle));
+  }
 }
 
 void NavigationThrottleRunner::ProcessInternal() {
+  TRACE_EVENT0("navigation", "NavigationThrottleRunner::ProcessInternal");
   DCHECK_NE(Event::NoEvent, current_event_);
   base::WeakPtr<NavigationThrottleRunner> weak_ref = weak_factory_.GetWeakPtr();
 
@@ -313,6 +320,8 @@
   int64_t local_navigation_id = navigation_id_;
 
   for (size_t i = next_index_; i < throttles_.size(); ++i) {
+    TRACE_EVENT0("navigation",
+                 "NavigationThrottleRunner::ProcessInternal.loop");
     TRACE_EVENT_NESTABLE_ASYNC_BEGIN1(
         "navigation", GetEventName(current_event_), local_navigation_id,
         "throttle", throttles_[i]->GetNameForLogging());
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index d77da7d..7904069 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -1595,6 +1595,9 @@
           GetProcess()->GetStoragePartition()->GetGeneratedCodeCacheContext()),
       fenced_frame_status_(fenced_frame_status),
       devtools_frame_token_(devtools_frame_token) {
+  TRACE_EVENT_WITH_FLOW0("navigation",
+                         "RenderFrameHostImpl::RenderFrameHostImpl",
+                         TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_OUT);
   TRACE_EVENT_BEGIN("navigation", "RenderFrameHostImpl",
                     perfetto::Track::FromPointer(this),
                     "render_frame_host_when_created", this);
@@ -1733,6 +1736,9 @@
 }
 
 RenderFrameHostImpl::~RenderFrameHostImpl() {
+  TRACE_EVENT_WITH_FLOW0("navigation",
+                         "RenderFrameHostImpl::~RenderFrameHostImpl",
+                         TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN);
   SCOPED_CRASH_KEY_STRING256("Bug1407526", "lifecycle",
                              LifecycleStateImplToString(lifecycle_state()));
   TRACE_EVENT("navigation", "~RenderFrameHostImpl()",
@@ -5326,6 +5332,10 @@
     const base::TimeTicks& renderer_before_unload_start_time,
     const base::TimeTicks& renderer_before_unload_end_time,
     bool for_legacy) {
+  TRACE_EVENT_WITH_FLOW0("navigation",
+                         "RenderFrameHostImpl::ProcessBeforeUnloadCompleted",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   TRACE_EVENT_NESTABLE_ASYNC_END1(
       "navigation", "RenderFrameHostImpl BeforeUnload", TRACE_ID_LOCAL(this),
       "render_frame_host", this);
@@ -9822,6 +9832,10 @@
 
 void RenderFrameHostImpl::DispatchBeforeUnload(BeforeUnloadType type,
                                                bool is_reload) {
+  TRACE_EVENT_WITH_FLOW0("navigation",
+                         "RenderFrameHostImpl::DispatchBeforeUnload",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   bool for_navigation =
       type == BeforeUnloadType::BROWSER_INITIATED_NAVIGATION ||
       type == BeforeUnloadType::RENDERER_INITIATED_NAVIGATION;
@@ -14238,6 +14252,9 @@
     bool is_reload,
     base::WeakPtr<RenderFrameHostImpl> rfh,
     bool for_legacy) {
+  TRACE_EVENT_WITH_FLOW0("navigation", "RenderFrameHostImpl::SendBeforeUnload",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   auto before_unload_closure = base::BindOnce(
       [](base::WeakPtr<RenderFrameHostImpl> impl, bool for_legacy, bool proceed,
          base::TimeTicks renderer_before_unload_start_time,
diff --git a/content/browser/service_worker/service_worker_container_host.cc b/content/browser/service_worker/service_worker_container_host.cc
index ea9aefe..67bd27d 100644
--- a/content/browser/service_worker/service_worker_container_host.cc
+++ b/content/browser/service_worker/service_worker_container_host.cc
@@ -9,6 +9,7 @@
 
 #include "base/containers/adapters.h"
 #include "base/containers/contains.h"
+#include "base/debug/crash_logging.h"
 #include "base/functional/callback_helpers.h"
 #include "base/functional/overloaded.h"
 #include "base/strings/stringprintf.h"
@@ -619,6 +620,7 @@
 void ServiceWorkerContainerHost::CountFeature(
     blink::mojom::WebFeature feature) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  SCOPED_CRASH_KEY_NUMBER("SWCH_CF", "feature", static_cast<int32_t>(feature));
 
   // CountFeature is a message about the client's controller. It should be sent
   // only for clients.
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index fdc1defc..55765cb 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -212,6 +212,8 @@
     "pseudonymization_salt.h",
     "service_worker/forwarded_race_network_request_url_loader_factory.cc",
     "service_worker/forwarded_race_network_request_url_loader_factory.h",
+    "service_worker/race_network_request_read_buffer_manager.cc",
+    "service_worker/race_network_request_read_buffer_manager.h",
     "service_worker/race_network_request_url_loader_client.cc",
     "service_worker/race_network_request_url_loader_client.h",
     "service_worker/race_network_request_write_buffer_manager.cc",
diff --git a/content/common/service_worker/race_network_request_read_buffer_manager.cc b/content/common/service_worker/race_network_request_read_buffer_manager.cc
new file mode 100644
index 0000000..7e76de6
--- /dev/null
+++ b/content/common/service_worker/race_network_request_read_buffer_manager.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 "content/common/service_worker/race_network_request_read_buffer_manager.h"
+#include "base/debug/crash_logging.h"
+#include "base/memory/scoped_refptr.h"
+#include "mojo/public/c/system/types.h"
+#include "net/base/io_buffer.h"
+#include "services/network/public/cpp/features.h"
+
+namespace content {
+RaceNetworkRequestReadBufferManager::RaceNetworkRequestReadBufferManager(
+    mojo::ScopedDataPipeConsumerHandle consumer_handle)
+    : consumer_handle_(std::move(consumer_handle)),
+      watcher_(FROM_HERE,
+               mojo::SimpleWatcher::ArmingPolicy::MANUAL,
+               base::SequencedTaskRunner::GetCurrentDefault()) {}
+
+RaceNetworkRequestReadBufferManager::~RaceNetworkRequestReadBufferManager() =
+    default;
+
+void RaceNetworkRequestReadBufferManager::Watch(
+    mojo::SimpleWatcher::ReadyCallbackWithState callback) {
+  watcher_.Watch(consumer_handle_.get(),
+                 MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+                 MOJO_WATCH_CONDITION_SATISFIED, std::move(callback));
+}
+
+void RaceNetworkRequestReadBufferManager::ArmOrNotify() {
+  watcher_.ArmOrNotify();
+}
+
+
+void RaceNetworkRequestReadBufferManager::CancelWatching() {
+  watcher_.Cancel();
+}
+
+std::pair<MojoResult, base::span<const char>>
+RaceNetworkRequestReadBufferManager::ReadData() {
+  CHECK_EQ(BytesRemaining(), 0u);
+  uint32_t num_bytes = network::features::GetDataPipeDefaultAllocationSize(
+      network::features::DataPipeAllocationSize::kLargerSizeIfPossible);
+  scoped_refptr<net::IOBuffer> buffer =
+      base::MakeRefCounted<net::IOBufferWithSize>(num_bytes);
+  MojoResult result = consumer_handle_->ReadData(buffer->data(), &num_bytes,
+                                                 MOJO_READ_DATA_FLAG_NONE);
+  if (result == MOJO_RESULT_OK) {
+    buffer_ = base::MakeRefCounted<net::DrainableIOBuffer>(std::move(buffer),
+                                                           num_bytes);
+  }
+
+  return std::make_pair(result,
+                        buffer_ ? buffer_->span() : base::span<const char>());
+}
+
+void RaceNetworkRequestReadBufferManager::ConsumeData(size_t num_bytes_read) {
+  CHECK(buffer_);
+  buffer_->DidConsume(num_bytes_read);
+}
+
+size_t RaceNetworkRequestReadBufferManager::BytesRemaining() const {
+  return buffer_ ? buffer_->BytesRemaining() : 0;
+}
+
+base::span<const char> RaceNetworkRequestReadBufferManager::RemainingBuffer()
+    const {
+  CHECK(buffer_);
+  return buffer_->span();
+}
+}  // namespace content
diff --git a/content/common/service_worker/race_network_request_read_buffer_manager.h b/content/common/service_worker/race_network_request_read_buffer_manager.h
new file mode 100644
index 0000000..77d965e
--- /dev/null
+++ b/content/common/service_worker/race_network_request_read_buffer_manager.h
@@ -0,0 +1,50 @@
+// 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_COMMON_SERVICE_WORKER_RACE_NETWORK_REQUEST_READ_BUFFER_MANAGER_H_
+#define CONTENT_COMMON_SERVICE_WORKER_RACE_NETWORK_REQUEST_READ_BUFFER_MANAGER_H_
+
+#include <optional>
+
+#include "base/containers/span.h"
+#include "base/memory/scoped_refptr.h"
+#include "content/common/content_export.h"
+#include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "net/base/io_buffer.h"
+
+namespace content {
+class CONTENT_EXPORT RaceNetworkRequestReadBufferManager {
+ public:
+  explicit RaceNetworkRequestReadBufferManager(
+      mojo::ScopedDataPipeConsumerHandle consumer_handle);
+  RaceNetworkRequestReadBufferManager(
+      const RaceNetworkRequestReadBufferManager&) = delete;
+  RaceNetworkRequestReadBufferManager& operator=(
+      const RaceNetworkRequestReadBufferManager&) = delete;
+  ~RaceNetworkRequestReadBufferManager();
+
+  void Watch(mojo::SimpleWatcher::ReadyCallbackWithState callback);
+  void ArmOrNotify();
+  void CancelWatching();
+  bool IsWatching() { return watcher_.IsWatching(); }
+
+  // Returns MojoResult of DataPipe::ReadData() result, and actual read data.
+  // The caller must call this only when |RemainingBuffer()| size is zero.
+  std::pair<MojoResult, base::span<const char>> ReadData();
+  // Consumes |buffer_| by given |num_bytes_read| bytes.
+  void ConsumeData(size_t num_bytes_read);
+
+  size_t BytesRemaining() const;
+  base::span<const char> RemainingBuffer() const;
+
+ private:
+  mojo::ScopedDataPipeConsumerHandle consumer_handle_;
+  mojo::SimpleWatcher watcher_;
+  scoped_refptr<net::DrainableIOBuffer> buffer_;
+};
+}  // namespace content
+
+#endif  // CONTENT_COMMON_SERVICE_WORKER_RACE_NETWORK_REQUEST_READ_BUFFER_MANAGER_H_
diff --git a/content/common/service_worker/race_network_request_read_buffer_manager_unittest.cc b/content/common/service_worker/race_network_request_read_buffer_manager_unittest.cc
new file mode 100644
index 0000000..fc30c36
--- /dev/null
+++ b/content/common/service_worker/race_network_request_read_buffer_manager_unittest.cc
@@ -0,0 +1,72 @@
+// 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 "content/common/service_worker/race_network_request_read_buffer_manager.h"
+#include <string_view>
+
+#include "base/containers/span.h"
+#include "base/run_loop.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/test/bind.h"
+#include "base/test/task_environment.h"
+#include "mojo/public/cpp/system/data_pipe.h"
+#include "mojo/public/cpp/system/handle_signals_state.h"
+#include "mojo/public/cpp/system/simple_watcher.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+TEST(RaceNetworkRequestReadBufferManagerTest, ReadData) {
+  mojo::ScopedDataPipeProducerHandle producer_handle;
+  mojo::ScopedDataPipeConsumerHandle consumer_handle;
+  EXPECT_EQ(mojo::CreateDataPipe(10u, producer_handle, consumer_handle),
+            MOJO_RESULT_OK);
+
+  const char expected_data[] = "abcde";
+  uint32_t num_bytes = sizeof(expected_data);
+  base::test::SingleThreadTaskEnvironment task_environment;
+  base::RunLoop run_loop;
+
+  mojo::SimpleWatcher producer_watcher(
+      FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL,
+      base::SequencedTaskRunner::GetCurrentDefault());
+  RaceNetworkRequestReadBufferManager buffer_manager(
+      std::move(consumer_handle));
+
+  producer_watcher.Watch(
+      producer_handle.get(),
+      MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
+      MOJO_WATCH_CONDITION_SATISFIED,
+      base::BindLambdaForTesting(
+          [&](MojoResult result, const mojo::HandleSignalsState& state) {
+            if (state.writable()) {
+              EXPECT_EQ(result, MOJO_RESULT_OK);
+              result = producer_handle->WriteData(expected_data, &num_bytes,
+                                                  MOJO_WRITE_DATA_FLAG_NONE);
+              EXPECT_EQ(result, MOJO_RESULT_OK);
+              buffer_manager.ArmOrNotify();
+              producer_handle.reset();
+            }
+          }));
+
+  buffer_manager.Watch(base::BindLambdaForTesting(
+      [&](MojoResult result, const mojo::HandleSignalsState& state) {
+        EXPECT_EQ(result, MOJO_RESULT_OK);
+        auto [read_result, buffer] = buffer_manager.ReadData();
+        EXPECT_EQ(read_result, MOJO_RESULT_OK);
+        EXPECT_EQ(result, MOJO_RESULT_OK);
+        EXPECT_EQ(buffer.size(), num_bytes);
+        std::string_view expected_str(expected_data);
+        EXPECT_EQ(buffer.data(), expected_str);
+        buffer_manager.ConsumeData(num_bytes);
+        base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, run_loop.QuitClosure());
+      }));
+
+  producer_watcher.ArmOrNotify();
+  run_loop.Run();
+}
+}  // namespace
+}  // namespace content
diff --git a/content/common/service_worker/race_network_request_url_loader_client.cc b/content/common/service_worker/race_network_request_url_loader_client.cc
index 8417862..9500ec9 100644
--- a/content/common/service_worker/race_network_request_url_loader_client.cc
+++ b/content/common/service_worker/race_network_request_url_loader_client.cc
@@ -39,9 +39,6 @@
     : request_(request),
       owner_(std::move(owner)),
       forwarding_client_(std::move(forwarding_client)),
-      body_consumer_watcher_(FROM_HERE,
-                             mojo::SimpleWatcher::ArmingPolicy::MANUAL,
-                             base::SequencedTaskRunner::GetCurrentDefault()),
       is_main_resource_(owner_->IsMainResourceLoader()),
       request_start_(base::TimeTicks::Now()),
       request_start_time_(base::Time::Now()) {
@@ -118,7 +115,7 @@
       head_->load_timing.request_start = request_start_;
       head_->load_timing.request_start_time = request_start_time_;
       cached_metadata_ = std::move(cached_metadata);
-      body_ = std::move(body);
+      read_buffer_manager_.emplace(std::move(body));
       WatchDataUpdate();
       break;
     case DataConsumePolicy::kForwardingOnly:
@@ -354,7 +351,9 @@
   // complete the response.
   write_buffer_manager_for_race_network_request_.CancelWatching();
   write_buffer_manager_for_fetch_handler_.CancelWatching();
-  body_consumer_watcher_.Cancel();
+  if (read_buffer_manager_.has_value() && read_buffer_manager_->IsWatching()) {
+    read_buffer_manager_->CancelWatching();
+  }
 }
 
 void ServiceWorkerRaceNetworkRequestURLLoaderClient::
@@ -384,47 +383,77 @@
 }
 
 void ServiceWorkerRaceNetworkRequestURLLoaderClient::WatchDataUpdate() {
-  auto callback_func =
+  auto write_callback =
       base::GetFieldTrialParamByFeatureAsBool(
           features::kServiceWorkerAutoPreload, "use_two_phase_write", true)
-          ? &ServiceWorkerRaceNetworkRequestURLLoaderClient::
-                ReadAndTwoPhaseWrite
-          : &ServiceWorkerRaceNetworkRequestURLLoaderClient::ReadAndWrite;
-
-  body_consumer_watcher_.Watch(
-      body_.get(), MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
-      MOJO_WATCH_CONDITION_SATISFIED,
-      base::BindRepeating(callback_func, weak_factory_.GetWeakPtr()));
-  body_consumer_watcher_.ArmOrNotify();
+          ? &ServiceWorkerRaceNetworkRequestURLLoaderClient::TwoPhaseWrite
+          : &ServiceWorkerRaceNetworkRequestURLLoaderClient::Write;
+  CHECK(read_buffer_manager_.has_value());
+  read_buffer_manager_->Watch(
+      base::BindRepeating(&ServiceWorkerRaceNetworkRequestURLLoaderClient::Read,
+                          weak_factory_.GetWeakPtr()));
+  read_buffer_manager_->ArmOrNotify();
   write_buffer_manager_for_race_network_request_.Watch(
-      base::BindRepeating(callback_func, weak_factory_.GetWeakPtr()));
+      base::BindRepeating(write_callback, weak_factory_.GetWeakPtr()));
   write_buffer_manager_for_fetch_handler_.Watch(
-      base::BindRepeating(callback_func, weak_factory_.GetWeakPtr()));
+      base::BindRepeating(write_callback, weak_factory_.GetWeakPtr()));
 }
 
-void ServiceWorkerRaceNetworkRequestURLLoaderClient::ReadAndTwoPhaseWrite(
+void ServiceWorkerRaceNetworkRequestURLLoaderClient::Read(
     MojoResult result,
     const mojo::HandleSignalsState& state) {
-  std::optional<base::span<const char>> read_buffer = StartReadData(result);
-  if (!read_buffer.has_value()) {
+  if (!IsReadyToHandleReadWrite(result)) {
     return;
   }
-  TwoPhaseWrite(read_buffer.value());
-}
 
-void ServiceWorkerRaceNetworkRequestURLLoaderClient::ReadAndWrite(
-    MojoResult result,
-    const mojo::HandleSignalsState& state) {
-  std::optional<base::span<const char>> read_buffer = StartReadData(result);
-  if (!read_buffer.has_value()) {
+  CHECK(read_buffer_manager_.has_value());
+  if (read_buffer_manager_->BytesRemaining() > 0) {
+    write_buffer_manager_for_race_network_request_.ArmOrNotify();
+    write_buffer_manager_for_fetch_handler_.ArmOrNotify();
     return;
   }
-  Write(read_buffer.value());
+
+  auto [read_result, read_buffer] = read_buffer_manager_->ReadData();
+  TRACE_EVENT_WITH_FLOW2("ServiceWorker",
+                         "ServiceWorkerRaceNetworkRequestURLLoaderClient::Read",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
+                         "url", request_.url, "read_data_result", result);
+  RecordMojoResultForDataTransfer(result, "Read");
+  switch (read_result) {
+    case MOJO_RESULT_OK:
+      CHECK(state.readable());
+      write_buffer_manager_for_race_network_request_.ArmOrNotify();
+      write_buffer_manager_for_fetch_handler_.ArmOrNotify();
+      return;
+    case MOJO_RESULT_FAILED_PRECONDITION:
+      // Successfully read the whole data. This case the peer is already closed.
+      CHECK(state.peer_closed());
+      OnDataTransferComplete();
+      return;
+    case MOJO_RESULT_BUSY:
+    case MOJO_RESULT_SHOULD_WAIT:
+      return;
+    default:
+      NOTREACHED() << "ReadData result:" << result;
+      return;
+  }
 }
 
 void ServiceWorkerRaceNetworkRequestURLLoaderClient::TwoPhaseWrite(
-    base::span<const char> read_buffer) {
-  MojoResult result;
+    MojoResult result,
+    const mojo::HandleSignalsState& state) {
+  if (!IsReadyToHandleReadWrite(result)) {
+    return;
+  }
+
+  CHECK(read_buffer_manager_.has_value());
+  if (read_buffer_manager_->BytesRemaining() == 0) {
+    read_buffer_manager_->ArmOrNotify();
+    return;
+  }
+  base::span<const char> read_buffer = read_buffer_manager_->RemainingBuffer();
+
   uint32_t num_bytes_to_consume = 0;
   if (write_buffer_manager_for_race_network_request_.IsWatching() &&
       write_buffer_manager_for_fetch_handler_.IsWatching()) {
@@ -436,7 +465,6 @@
       case MOJO_RESULT_OK:
         break;
       case MOJO_RESULT_FAILED_PRECONDITION:
-        body_->EndReadData(0);
         // The data pipe consumer is aborted.
         TransitionState(State::kAborted);
         Abort();
@@ -444,7 +472,6 @@
       case MOJO_RESULT_SHOULD_WAIT:
         // The data pipe is not writable yet. We don't consume data from |body_|
         // and write any data in this case. And retry it later.
-        body_->EndReadData(0);
         write_buffer_manager_for_race_network_request_.EndWriteData(0);
         write_buffer_manager_for_race_network_request_.ArmOrNotify();
         return;
@@ -455,7 +482,6 @@
       case MOJO_RESULT_OK:
         break;
       case MOJO_RESULT_FAILED_PRECONDITION:
-        body_->EndReadData(0);
         TransitionState(State::kAborted);
         Abort();
         return;
@@ -464,7 +490,6 @@
         // not consumed yet but the buffer is full. Stop processing the data
         // pipe for the fetch handler side, not to make the data transfer
         // process for the race network request side being stuck.
-        body_->EndReadData(0);
         write_buffer_manager_for_race_network_request_.EndWriteData(0);
         write_buffer_manager_for_fetch_handler_.EndWriteData(0);
         write_buffer_manager_for_fetch_handler_.CancelWatching();
@@ -499,12 +524,10 @@
       case MOJO_RESULT_OK:
         break;
       case MOJO_RESULT_FAILED_PRECONDITION:
-        body_->EndReadData(0);
         TransitionState(State::kAborted);
         Abort();
         return;
       case MOJO_RESULT_SHOULD_WAIT:
-        body_->EndReadData(0);
         write_buffer_manager_for_race_network_request_.EndWriteData(0);
         write_buffer_manager_for_race_network_request_.ArmOrNotify();
         return;
@@ -521,12 +544,10 @@
       case MOJO_RESULT_OK:
         break;
       case MOJO_RESULT_FAILED_PRECONDITION:
-        body_->EndReadData(0);
         TransitionState(State::kAborted);
         Abort();
         return;
       case MOJO_RESULT_SHOULD_WAIT:
-        body_->EndReadData(0);
         write_buffer_manager_for_fetch_handler_.EndWriteData(0);
         write_buffer_manager_for_fetch_handler_.ArmOrNotify();
         return;
@@ -539,8 +560,19 @@
 }
 
 void ServiceWorkerRaceNetworkRequestURLLoaderClient::Write(
-    base::span<const char> read_buffer) {
-  MojoResult result;
+    MojoResult result,
+    const mojo::HandleSignalsState& state) {
+  if (!IsReadyToHandleReadWrite(result)) {
+    return;
+  }
+
+  CHECK(read_buffer_manager_.has_value());
+  if (read_buffer_manager_->BytesRemaining() == 0) {
+    read_buffer_manager_->ArmOrNotify();
+    return;
+  }
+  base::span<const char> read_buffer = read_buffer_manager_->RemainingBuffer();
+
   size_t num_bytes_to_consume = read_buffer.size();
   if (write_buffer_manager_for_race_network_request_.IsWatching() &&
       write_buffer_manager_for_fetch_handler_.IsWatching()) {
@@ -553,7 +585,6 @@
       case MOJO_RESULT_OK:
         break;
       case MOJO_RESULT_FAILED_PRECONDITION:
-        body_->EndReadData(0);
         // The data pipe consumer is aborted.
         TransitionState(State::kAborted);
         Abort();
@@ -562,7 +593,6 @@
       case MOJO_RESULT_OUT_OF_RANGE:
         // The data pipe is not writable yet. We don't consume data from |body_|
         // and write any data in this case. And retry it later.
-        body_->EndReadData(0);
         write_buffer_manager_for_race_network_request_.ArmOrNotify();
         return;
     }
@@ -573,7 +603,6 @@
       case MOJO_RESULT_OK:
         break;
       case MOJO_RESULT_FAILED_PRECONDITION:
-        body_->EndReadData(0);
         TransitionState(State::kAborted);
         Abort();
         return;
@@ -583,7 +612,6 @@
         // not consumed yet but the buffer is full. Stop processing the data
         // pipe for the fetch handler side, not to make the data transfer
         // process for the race network request side being stuck.
-        body_->EndReadData(read_buffer.size());
         write_buffer_manager_for_fetch_handler_.CancelWatching();
         write_buffer_manager_for_race_network_request_.ArmOrNotify();
         return;
@@ -602,13 +630,11 @@
       case MOJO_RESULT_OK:
         break;
       case MOJO_RESULT_FAILED_PRECONDITION:
-        body_->EndReadData(0);
         TransitionState(State::kAborted);
         Abort();
         return;
       case MOJO_RESULT_SHOULD_WAIT:
       case MOJO_RESULT_OUT_OF_RANGE:
-        body_->EndReadData(0);
         write_buffer_manager_for_race_network_request_.ArmOrNotify();
         return;
     }
@@ -622,13 +648,11 @@
       case MOJO_RESULT_OK:
         break;
       case MOJO_RESULT_FAILED_PRECONDITION:
-        body_->EndReadData(0);
         TransitionState(State::kAborted);
         Abort();
         return;
       case MOJO_RESULT_SHOULD_WAIT:
       case MOJO_RESULT_OUT_OF_RANGE:
-        body_->EndReadData(0);
         write_buffer_manager_for_fetch_handler_.ArmOrNotify();
         return;
     }
@@ -636,64 +660,21 @@
   CompleteReadData(num_bytes_to_consume);
 }
 
-std::optional<base::span<const char>>
-ServiceWorkerRaceNetworkRequestURLLoaderClient::StartReadData(
-    MojoResult initial_mojo_result) {
+bool ServiceWorkerRaceNetworkRequestURLLoaderClient::IsReadyToHandleReadWrite(
+    MojoResult result) {
   if (!owner_) {
-    return std::nullopt;
+    return false;
   }
   if (state_ == State::kDataTransferFinished) {
-    return std::nullopt;
+    return false;
   }
 
-  RecordMojoResultForDataTransfer(initial_mojo_result, "Initial");
-  if (initial_mojo_result != MOJO_RESULT_OK) {
-    return std::nullopt;
+  RecordMojoResultForDataTransfer(result, "Initial");
+  if (result != MOJO_RESULT_OK) {
+    return false;
   }
 
-  auto [result, read_buffer] = BeginReadData();
-  TRACE_EVENT_WITH_FLOW2(
-      "ServiceWorker",
-      "ServiceWorkerRaceNetworkRequestURLLoaderClient::ReadAndWrite",
-      TRACE_ID_LOCAL(this),
-      TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "url", request_.url,
-      "read_data_result", result);
-  RecordMojoResultForDataTransfer(result, "Read");
-  switch (result) {
-    case MOJO_RESULT_OK:
-      return read_buffer;
-    case MOJO_RESULT_FAILED_PRECONDITION:
-      // Successfully read the whole data.
-      OnDataTransferComplete();
-      return std::nullopt;
-    case MOJO_RESULT_BUSY:
-    case MOJO_RESULT_SHOULD_WAIT:
-      return std::nullopt;
-    default:
-      NOTREACHED() << "BeginReadData result:" << result;
-      return std::nullopt;
-  }
-}
-
-std::pair<MojoResult, base::span<const char>>
-ServiceWorkerRaceNetworkRequestURLLoaderClient::BeginReadData() {
-  const void* buffer;
-  uint32_t buffer_num_bytes = 0;
-  base::span<const char> read_buffer;
-  MojoResult result = body_->BeginReadData(&buffer, &buffer_num_bytes,
-                                           MOJO_BEGIN_READ_DATA_FLAG_NONE);
-  if (result == MOJO_RESULT_OK) {
-    SCOPED_CRASH_KEY_NUMBER("SWRace", "num_bytes_read_buffer",
-                            buffer_num_bytes);
-    volatile const char* buffer_v = static_cast<volatile const char*>(buffer);
-    for (size_t i = 0; i < buffer_num_bytes; ++i) {
-      buffer_v[i];
-    }
-    read_buffer =
-        base::make_span(static_cast<const char*>(buffer), buffer_num_bytes);
-  }
-
-  return std::make_pair(result, read_buffer);
+  return true;
 }
 
 void ServiceWorkerRaceNetworkRequestURLLoaderClient::RecordMojoResultForWrite(
@@ -703,17 +684,19 @@
 
 void ServiceWorkerRaceNetworkRequestURLLoaderClient::CompleteReadData(
     uint32_t num_bytes_to_consume) {
-  MojoResult result = body_->EndReadData(num_bytes_to_consume);
-  CHECK_EQ(result, MOJO_RESULT_OK);
+  CHECK(read_buffer_manager_.has_value());
+  read_buffer_manager_->ConsumeData(num_bytes_to_consume);
   // Once data is written to the data pipe, start the commit process.
   MaybeCommitResponse();
-  body_consumer_watcher_.ArmOrNotify();
+  read_buffer_manager_->ArmOrNotify();
 }
 
 void ServiceWorkerRaceNetworkRequestURLLoaderClient::Abort() {
   write_buffer_manager_for_race_network_request_.Abort();
   write_buffer_manager_for_fetch_handler_.Abort();
-  body_consumer_watcher_.Cancel();
+  if (read_buffer_manager_.has_value()) {
+    read_buffer_manager_->CancelWatching();
+  }
 }
 
 void ServiceWorkerRaceNetworkRequestURLLoaderClient::SetFetchHandlerEndTiming(
@@ -828,8 +811,8 @@
   // again as we create two data pipes and propergate data from the consumer
   // handle |body_|. Even though one data pipe is canceled, the data transfer
   // process to the other data pipe has to be continued.
-  if (body_consumer_watcher_.IsWatching()) {
-    body_consumer_watcher_.ArmOrNotify();
+  if (read_buffer_manager_.has_value() && read_buffer_manager_->IsWatching()) {
+    read_buffer_manager_->ArmOrNotify();
   }
 }
 
diff --git a/content/common/service_worker/race_network_request_url_loader_client.h b/content/common/service_worker/race_network_request_url_loader_client.h
index 6816b323..ec6ead1 100644
--- a/content/common/service_worker/race_network_request_url_loader_client.h
+++ b/content/common/service_worker/race_network_request_url_loader_client.h
@@ -9,6 +9,7 @@
 #include "base/containers/span.h"
 #include "base/time/time.h"
 #include "content/common/content_export.h"
+#include "content/common/service_worker/race_network_request_read_buffer_manager.h"
 #include "content/common/service_worker/race_network_request_write_buffer_manager.h"
 #include "content/common/service_worker/service_worker_resource_loader.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -177,14 +178,19 @@
   void OnDataTransferComplete();
   void TransitionState(State new_state);
 
-  // Reads data from |body_|, and writes it into the data pipe producer handles
-  // for both the race network request and the fetch handler respectively.
+  // Reads data from RaceNetworkRequestReadBufferManager. If there is a buffer
+  // to read, notifies the write buffer manager to start write operations.
+  // If no buffer to read, calls |OnDataTransferComplete()| and return nothing.
+  void Read(MojoResult result, const mojo::HandleSignalsState& state);
+  // Writes data in RaceNetworkRequestReadBufferManager into the data
+  // pipe producer that handles for both the race network request and the fetch
+  // handler respectively.
   //
   // To guarantee the consistent data between the race network request and the
   // fetch handler, this method always writes a same chunk of data into two data
   // pipe handles. If one side fails the data write process for some reason, we
-  // don't consume |body_| data, and retry it later. |body_| data is consumed
-  // only when the both producer handles successfully write data.
+  // don't consume the buffer, and retry it later. the buffer is consumed only
+  // when the both producer handles successfully write data.
   //
   // When the first chunk of data is written to the data pipes, this starts the
   // commit process. And when the data transfer is finished, this complete the
@@ -194,31 +200,21 @@
   // process, and there could be the case if the response is not returned due to
   // the long fetch handler execution. and test case the mechanism to wait for
   // the fetch handler
-  void ReadAndTwoPhaseWrite(MojoResult result,
-                            const mojo::HandleSignalsState& state);
-  // Reads data from |body_|, and writes it into the data pipe producer handles
-  // for both the race network request and the fetch handler respectively.
+  void TwoPhaseWrite(MojoResult result, const mojo::HandleSignalsState& state);
+  // Writes data in RaceNetworkRequestReadBufferManager into the data
+  // pipe producer that handles for both the race network request and the fetch
+  // handler respectively.
   //
-  // Unlike |ReadAndTwoPhaseWrite()|, this doesn't use two-phase operations to
+  // Unlike |TwoPhaseWrite()|, this doesn't use two-phase operations to
   // write data into data pipes. However, the result should be the same as
-  // |ReadAndTwoPhaseWrite()| because mojo's |WriteData()| is expected to write
+  // |TwoPhaseWrite()| because mojo's |WriteData()| is expected to write
   // the same amount of data from the given data pipe consumer handle to read.
-  // also |ReadAndWrite()| has CHECK to guarantee that the actual written sizes
+  // also |Write()| has CHECK to guarantee that the actual written sizes
   // to data pips are exactly same.
-  void ReadAndWrite(MojoResult result, const mojo::HandleSignalsState& state);
+  void Write(MojoResult result, const mojo::HandleSignalsState& state);
 
-  void Write(base::span<const char> read_buffer);
-  void TwoPhaseWrite(base::span<const char> read_buffer);
+  bool IsReadyToHandleReadWrite(MojoResult result);
 
-  // Begins a two-phase read from |body_|, the data pipe consumer. If succeed,
-  // the read buffer is returned. If there are no data to read from the data
-  // pipe, this internally calls |OnDataTransferComplete()| and return nothing.
-  //
-  // Since this starts a two-phase read process, `EndReadData()` in |body_|
-  // has to be called after calling this function.
-  std::optional<base::span<const char>> StartReadData(
-      MojoResult initial_mojo_result);
-  std::pair<MojoResult, base::span<const char>> BeginReadData();
   void CompleteReadData(uint32_t num_bytes_to_consume);
   void WatchDataUpdate();
 
@@ -240,12 +236,11 @@
   const network::ResourceRequest request_;
   base::WeakPtr<ServiceWorkerResourceLoader> owner_;
   mojo::Remote<network::mojom::URLLoaderClient> forwarding_client_;
-  mojo::SimpleWatcher body_consumer_watcher_;
-  mojo::ScopedDataPipeConsumerHandle body_;
 
   network::mojom::URLResponseHeadPtr head_;
   std::optional<mojo_base::BigBuffer> cached_metadata_;
 
+  std::optional<RaceNetworkRequestReadBufferManager> read_buffer_manager_;
   RaceNetworkRequestWriteBufferManager
       write_buffer_manager_for_race_network_request_;
   RaceNetworkRequestWriteBufferManager write_buffer_manager_for_fetch_handler_;
diff --git a/content/public/renderer/content_renderer_client.cc b/content/public/renderer/content_renderer_client.cc
index 1e013ce..194c919b 100644
--- a/content/public/renderer/content_renderer_client.cc
+++ b/content/public/renderer/content_renderer_client.cc
@@ -282,4 +282,9 @@
 }
 #endif
 
+std::unique_ptr<blink::WebLinkPreviewTriggerer>
+ContentRendererClient::CreateLinkPreviewTriggerer() {
+  return nullptr;
+}
+
 }  // namespace content
diff --git a/content/public/renderer/content_renderer_client.h b/content/public/renderer/content_renderer_client.h
index ff20508..0b708ec 100644
--- a/content/public/renderer/content_renderer_client.h
+++ b/content/public/renderer/content_renderer_client.h
@@ -30,6 +30,7 @@
 #include "third_party/blink/public/platform/url_loader_throttle_provider.h"
 #include "third_party/blink/public/platform/web_content_settings_client.h"
 #include "third_party/blink/public/platform/websocket_handshake_throttle_provider.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/public/web/web_navigation_policy.h"
 #include "third_party/blink/public/web/web_navigation_type.h"
 #include "ui/base/page_transition_types.h"
@@ -438,6 +439,13 @@
   virtual std::unique_ptr<cast_streaming::ResourceProvider>
   CreateCastStreamingResourceProvider();
 #endif
+
+  // Creates a WebLinkPreviewTriggerer if an embedder wants to observe events
+  // and trigger preview. It is allowed to return nullptr.
+  //
+  // See blink::WebLinkPreviewTriggerer for more details.
+  virtual std::unique_ptr<blink::WebLinkPreviewTriggerer>
+  CreateLinkPreviewTriggerer();
 };
 
 }  // namespace content
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 9dbf4ba..67034e1 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -215,6 +215,7 @@
 #include "third_party/blink/public/web/web_frame_serializer.h"
 #include "third_party/blink/public/web/web_frame_widget.h"
 #include "third_party/blink/public/web/web_input_method_controller.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 #include "third_party/blink/public/web/web_navigation_control.h"
 #include "third_party/blink/public/web/web_navigation_policy.h"
@@ -6767,6 +6768,11 @@
   return web_view;
 }
 
+std::unique_ptr<blink::WebLinkPreviewTriggerer>
+RenderFrameImpl::CreateLinkPreviewTriggerer() {
+  return GetContentClient()->renderer()->CreateLinkPreviewTriggerer();
+}
+
 void RenderFrameImpl::ResetMembersUsedForDurationOfCommit() {
   pending_loader_factories_ = nullptr;
   pending_code_cache_host_.reset();
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 85da2609..f44502d 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -107,6 +107,7 @@
 #include "third_party/blink/public/web/web_frame_load_type.h"
 #include "third_party/blink/public/web/web_frame_serializer_client.h"
 #include "third_party/blink/public/web/web_history_commit_type.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/public/web/web_local_frame.h"
 #include "third_party/blink/public/web/web_local_frame_client.h"
 #include "third_party/blink/public/web/web_meaningful_layout.h"
@@ -674,6 +675,8 @@
       const std::optional<blink::Impression>& impression,
       const std::optional<blink::WebPictureInPictureWindowOptions>& pip_options,
       const blink::WebURL& base_url) override;
+  std::unique_ptr<blink::WebLinkPreviewTriggerer> CreateLinkPreviewTriggerer()
+      override;
 
   // Dispatches the current state of selection on the webpage to the browser if
   // it has changed or if the forced flag is passed. The forced flag is used
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 4cf1462..c163f72 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -1406,7 +1406,7 @@
     "../browser/file_system/file_system_url_drag_drop_browsertest.cc",
     "../browser/file_system/file_system_url_loader_factory_browsertest.cc",
     "../browser/file_system/fileapi_browsertest.cc",
-    "../browser/file_system_access/file_system_access_capacity_allocation_host_impl_browsertest.cc",
+    "../browser/file_system_access/file_system_access_file_modification_host_impl_browsertest.cc",
     "../browser/file_system_access/file_system_access_observer_browsertest.cc",
     "../browser/find_request_manager_browsertest.cc",
     "../browser/first_party_sets/first_party_sets_handler_impl_browsertest.cc",
@@ -2414,9 +2414,9 @@
     "../browser/fenced_frame/redacted_fenced_frame_config_mojom_traits_unittest.cc",
     "../browser/file_system/browser_file_system_helper_unittest.cc",
     "../browser/file_system/file_system_operation_runner_unittest.cc",
-    "../browser/file_system_access/file_system_access_capacity_allocation_host_impl_unittest.cc",
     "../browser/file_system_access/file_system_access_directory_handle_impl_unittest.cc",
     "../browser/file_system_access/file_system_access_file_handle_impl_unittest.cc",
+    "../browser/file_system_access/file_system_access_file_modification_host_impl_unittest.cc",
     "../browser/file_system_access/file_system_access_file_writer_impl_unittest.cc",
     "../browser/file_system_access/file_system_access_handle_base_unittest.cc",
     "../browser/file_system_access/file_system_access_lock_manager_unittest.cc",
@@ -2792,6 +2792,7 @@
     "../common/input/touch_event_stream_validator_unittest.cc",
     "../common/input/touchpad_pinch_event_queue_unittest.cc",
     "../common/pseudonymization_salt_unittest.cc",
+    "../common/service_worker/race_network_request_read_buffer_manager_unittest.cc",
     "../common/service_worker/race_network_request_url_loader_client_unittest.cc",
     "../common/service_worker/race_network_request_write_buffer_manager_unittest.cc",
     "../common/service_worker/service_worker_router_evaluator_unittest.cc",
diff --git a/content/test/data/accessibility/event/add-alert-with-role-change-expected-auralinux.txt b/content/test/data/accessibility/event/add-alert-with-role-change-expected-auralinux.txt
index e693afa..7fab4c0 100644
--- a/content/test/data/accessibility/event/add-alert-with-role-change-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/add-alert-with-role-change-expected-auralinux.txt
@@ -1,4 +1,2 @@
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_NOTIFICATION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
-CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_SECTION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
-PARENT-CHANGED PARENT:(role=ROLE_NOTIFICATION name='(null)') role=ROLE_STATIC name='This is an alert' ENABLED,SENSITIVE,SHOWING,VISIBLE
-TEXT-INSERT (start=0 length=16 'This is an alert') role=ROLE_NOTIFICATION name='(null)' ENABLED,SENSITIVE,SHOWING,VISIBLE
+CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_NOTIFICATION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/add-dialog-described-by-expected-auralinux.txt b/content/test/data/accessibility/event/add-dialog-described-by-expected-auralinux.txt
index e312b64..19d94ff5 100644
--- a/content/test/data/accessibility/event/add-dialog-described-by-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/add-dialog-described-by-expected-auralinux.txt
@@ -1 +1,4 @@
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_DIALOG) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_DIALOG name='(null)') role=ROLE_HEADING name='Described by dialog title' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_HEADING name='Described by dialog title' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_HEADING name='Described by dialog title' ENABLED,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/add-dialog-described-by-expected-mac.txt b/content/test/data/accessibility/event/add-dialog-described-by-expected-mac.txt
index 8104513c..823651a9 100644
--- a/content/test/data/accessibility/event/add-dialog-described-by-expected-mac.txt
+++ b/content/test/data/accessibility/event/add-dialog-described-by-expected-mac.txt
@@ -1 +1,2 @@
+AXTitleChanged on AXHeading AXTitle='Described by dialog title' AXValue=2
 AXValueChanged on AXTextField
diff --git a/content/test/data/accessibility/event/add-dialog-expected-auralinux.txt b/content/test/data/accessibility/event/add-dialog-expected-auralinux.txt
index e312b64..899cb05 100644
--- a/content/test/data/accessibility/event/add-dialog-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/add-dialog-expected-auralinux.txt
@@ -1 +1,4 @@
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_DIALOG) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_DIALOG name='Dialog title') role=ROLE_HEADING name='Dialog title' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_HEADING name='Dialog title' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_HEADING name='Dialog title' ENABLED,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/add-dialog-expected-mac.txt b/content/test/data/accessibility/event/add-dialog-expected-mac.txt
index 8104513c..be2868b 100644
--- a/content/test/data/accessibility/event/add-dialog-expected-mac.txt
+++ b/content/test/data/accessibility/event/add-dialog-expected-mac.txt
@@ -1 +1,2 @@
+AXTitleChanged on AXHeading AXTitle='Dialog title' AXValue=2
 AXValueChanged on AXTextField
diff --git a/content/test/data/accessibility/event/add-hidden-attribute-expected-win.txt b/content/test/data/accessibility/event/add-hidden-attribute-expected-win.txt
index dac2ebc4..9e004b98 100644
--- a/content/test/data/accessibility/event/add-hidden-attribute-expected-win.txt
+++ b/content/test/data/accessibility/event/add-hidden-attribute-expected-win.txt
@@ -1,3 +1,3 @@
 EVENT_OBJECT_HIDE on <div#item3> role=ROLE_SYSTEM_LISTITEM name="Item 3" level=1 PosInSet=3 SetSize=3
 EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_LIST SetSize=2
-IA2_EVENT_TEXT_REMOVED on <div> role=ROLE_SYSTEM_LIST SetSize=2 old_text={'<obj>' start=2 end=3}
+IA2_EVENT_TEXT_REMOVED on <div> role=ROLE_SYSTEM_LIST SetSize=2 old_text={'<obj>' start=2 end=3}
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/add-hidden-attribute-subtree-expected-win.txt b/content/test/data/accessibility/event/add-hidden-attribute-subtree-expected-win.txt
index 239d0ec4..04e8e63 100644
--- a/content/test/data/accessibility/event/add-hidden-attribute-subtree-expected-win.txt
+++ b/content/test/data/accessibility/event/add-hidden-attribute-subtree-expected-win.txt
@@ -1,3 +1,3 @@
 EVENT_OBJECT_HIDE on <li#item3> role=ROLE_SYSTEM_LISTITEM level=1 PosInSet=3 SetSize=3
 EVENT_OBJECT_REORDER on <ul> role=ROLE_SYSTEM_LIST SetSize=2
-IA2_EVENT_TEXT_REMOVED on <ul> role=ROLE_SYSTEM_LIST SetSize=2 old_text={'<obj>' start=2 end=3}
+IA2_EVENT_TEXT_REMOVED on <ul> role=ROLE_SYSTEM_LIST SetSize=2 old_text={'<obj>' start=2 end=3}
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/aria-hidden-changed-expected-auralinux.txt b/content/test/data/accessibility/event/aria-hidden-changed-expected-auralinux.txt
index 0f3dbf3..8586c49c 100644
--- a/content/test/data/accessibility/event/aria-hidden-changed-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/aria-hidden-changed-expected-auralinux.txt
@@ -1,5 +1,3 @@
-CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_HEADING) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
 CHILDREN-CHANGED:ADD index:1 CHILD:(role=ROLE_HEADING) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
 CHILDREN-CHANGED:ADD index:2 CHILD:(role=ROLE_HEADING) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
-CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_HEADING) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
 CHILDREN-CHANGED:REMOVE index:1 CHILD:(role=ROLE_HEADING) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/aria-hidden-single-descendant-display-none-expected-uia-win.txt b/content/test/data/accessibility/event/aria-hidden-single-descendant-display-none-expected-uia-win.txt
index 6838414..8210bcb 100644
--- a/content/test/data/accessibility/event/aria-hidden-single-descendant-display-none-expected-uia-win.txt
+++ b/content/test/data/accessibility/event/aria-hidden-single-descendant-display-none-expected-uia-win.txt
@@ -1 +1 @@
-=== Start Continuation ===
+=== Start Continuation ===
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/aria-hidden-single-descendant-expected-win.txt b/content/test/data/accessibility/event/aria-hidden-single-descendant-expected-win.txt
index 43448fa..b6e0fcf 100644
--- a/content/test/data/accessibility/event/aria-hidden-single-descendant-expected-win.txt
+++ b/content/test/data/accessibility/event/aria-hidden-single-descendant-expected-win.txt
@@ -1,5 +1,7 @@
 EVENT_OBJECT_HIDE on <div.test-case> role=ROLE_SYSTEM_GROUPING
-EVENT_OBJECT_SHOW on <button> role=ROLE_SYSTEM_PUSHBUTTON name="expect invisible subtree" INVISIBLE,FOCUSABLE
+EVENT_OBJECT_STATECHANGE on <button> role=ROLE_SYSTEM_PUSHBUTTON name="expect invisible subtree" INVISIBLE,FOCUSABLE
 === Start Continuation ===
-EVENT_OBJECT_HIDE on <button> role=ROLE_SYSTEM_PUSHBUTTON name="expect visible subtree" INVISIBLE,FOCUSABLE
 EVENT_OBJECT_SHOW on <div.test-case> role=ROLE_SYSTEM_GROUPING
+EVENT_OBJECT_STATECHANGE on <button> role=ROLE_SYSTEM_PUSHBUTTON name="expect visible subtree" FOCUSABLE
+EVENT_OBJECT_STATECHANGE on role=ROLE_SYSTEM_STATICTEXT name="expect visible subtree"
+IA2_EVENT_TEXT_ATTRIBUTE_CHANGED on role=ROLE_SYSTEM_STATICTEXT name="expect visible subtree"
diff --git a/content/test/data/accessibility/event/aria-hidden-single-descendant-visibility-hidden-expected-auralinux.txt b/content/test/data/accessibility/event/aria-hidden-single-descendant-visibility-hidden-expected-auralinux.txt
index 5ae764a..dd78fad 100644
--- a/content/test/data/accessibility/event/aria-hidden-single-descendant-visibility-hidden-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/aria-hidden-single-descendant-visibility-hidden-expected-auralinux.txt
@@ -1,7 +1,10 @@
 === Start Continuation ===
 === Start Continuation ===
-CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
 CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_SECTION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_PUSH_BUTTON name='expect invisible subtree' ENABLED,FOCUSABLE,SENSITIVE
+PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_PUSH_BUTTON name='expect visible subtree' ENABLED,FOCUSABLE,SENSITIVE
 === Start Continuation ===
 CHILDREN-CHANGED:ADD index:1 CHILD:(role=ROLE_SECTION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
-CHILDREN-CHANGED:REMOVE index:1 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_PUSH_BUTTON name='expect invisible subtree' ENABLED,FOCUSABLE,SENSITIVE
+PARENT-CHANGED PARENT:(role=ROLE_SECTION name='(null)') role=ROLE_PUSH_BUTTON name='expect visible subtree' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_PUSH_BUTTON name='expect visible subtree' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/aria-hidden-single-descendant-visibility-hidden-expected-win.txt b/content/test/data/accessibility/event/aria-hidden-single-descendant-visibility-hidden-expected-win.txt
index 24dcd174..35cf7e1 100644
--- a/content/test/data/accessibility/event/aria-hidden-single-descendant-visibility-hidden-expected-win.txt
+++ b/content/test/data/accessibility/event/aria-hidden-single-descendant-visibility-hidden-expected-win.txt
@@ -1,7 +1,9 @@
 === Start Continuation ===
 === Start Continuation ===
 EVENT_OBJECT_HIDE on <div.test-case> role=ROLE_SYSTEM_GROUPING
-EVENT_OBJECT_SHOW on <button> role=ROLE_SYSTEM_PUSHBUTTON name="expect invisible subtree" INVISIBLE,FOCUSABLE
+EVENT_OBJECT_STATECHANGE on <button> role=ROLE_SYSTEM_PUSHBUTTON name="expect invisible subtree" INVISIBLE,FOCUSABLE
 === Start Continuation ===
-EVENT_OBJECT_HIDE on <button> role=ROLE_SYSTEM_PUSHBUTTON name="expect visible subtree" INVISIBLE,FOCUSABLE
 EVENT_OBJECT_SHOW on <div.test-case> role=ROLE_SYSTEM_GROUPING
+EVENT_OBJECT_STATECHANGE on <button> role=ROLE_SYSTEM_PUSHBUTTON name="expect visible subtree" FOCUSABLE
+EVENT_OBJECT_STATECHANGE on role=ROLE_SYSTEM_STATICTEXT name="expect visible subtree"
+IA2_EVENT_TEXT_ATTRIBUTE_CHANGED on role=ROLE_SYSTEM_STATICTEXT name="expect visible subtree"
diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-android.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-android.txt
index e69de29..aec4f94b 100644
--- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-android.txt
+++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-android.txt
@@ -0,0 +1 @@
+TYPE_WINDOW_CONTENT_CHANGED - [contentTypes=64]
diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-auralinux.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-auralinux.txt
index 15a15a2..3075190 100644
--- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-auralinux.txt
@@ -1,5 +1,7 @@
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_TOGGLE_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE
-CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE
+CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_TOGGLE_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE
+STATE-CHANGE:PRESSED:TRUE role=ROLE_TOGGLE_BUTTON name='(null)' ENABLED,FOCUSABLE,PRESSED,SENSITIVE,SHOWING,VISIBLE
 === Start Continuation ===
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE
-CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_TOGGLE_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE
+CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_SECTION ENABLED,SENSITIVE,SHOWING,VISIBLE
+STATE-CHANGE:PRESSED:TRUE role=ROLE_PUSH_BUTTON name='(null)' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-mac.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-mac.txt
index 6838414..918902b 100644
--- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-mac.txt
+++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-mac.txt
@@ -1 +1,3 @@
+AXValueChanged on AXCheckBox AXSubrole=AXToggleButton AXValue=1
 === Start Continuation ===
+AXValueChanged on AXButton
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-uia-win.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-uia-win.txt
index ca8cdff..fd801e6 100644
--- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-uia-win.txt
+++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-uia-win.txt
@@ -1,7 +1,7 @@
-StructureChanged/ChildAdded on role=button
-StructureChanged/ChildRemoved on role=document
-StructureChanged/ChildrenReordered on role=document
+AriaProperties changed on role=button
+AriaRole changed on role=button
+ToggleToggleState changed on role=button
 === Start Continuation ===
-StructureChanged/ChildAdded on role=button
-StructureChanged/ChildRemoved on role=document
-StructureChanged/ChildrenReordered on role=document
+AriaProperties changed on role=button
+AriaRole changed on role=button
+ToggleToggleState changed on role=button
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-win.txt b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-win.txt
index c1f1345..fe7cb46c 100644
--- a/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-win.txt
+++ b/content/test/data/accessibility/event/aria-pressed-changes-button-role-expected-win.txt
@@ -1,11 +1,5 @@
-EVENT_OBJECT_HIDE on <button#test> role=ROLE_SYSTEM_PUSHBUTTON FOCUSABLE
-EVENT_OBJECT_REORDER on <body> role=ROLE_SYSTEM_GROUPING
-EVENT_OBJECT_SHOW on <button#test> role=ROLE_SYSTEM_PUSHBUTTON PRESSED,FOCUSABLE
-IA2_EVENT_TEXT_INSERTED on <body> role=ROLE_SYSTEM_GROUPING new_text={'<obj>' start=0 end=1}
-IA2_EVENT_TEXT_REMOVED on <body> role=ROLE_SYSTEM_GROUPING old_text={'<obj>' start=0 end=1}
+EVENT_OBJECT_STATECHANGE on <button#test> role=ROLE_SYSTEM_PUSHBUTTON PRESSED,FOCUSABLE
+IA2_EVENT_ROLE_CHANGED on <button#test> role=ROLE_SYSTEM_PUSHBUTTON PRESSED,FOCUSABLE
 === Start Continuation ===
-EVENT_OBJECT_HIDE on <button#test> role=ROLE_SYSTEM_PUSHBUTTON PRESSED,FOCUSABLE
-EVENT_OBJECT_REORDER on <body> role=ROLE_SYSTEM_GROUPING
-EVENT_OBJECT_SHOW on <button#test> role=ROLE_SYSTEM_PUSHBUTTON FOCUSABLE
-IA2_EVENT_TEXT_INSERTED on <body> role=ROLE_SYSTEM_GROUPING new_text={'<obj>' start=0 end=1}
-IA2_EVENT_TEXT_REMOVED on <body> role=ROLE_SYSTEM_GROUPING old_text={'<obj>' start=0 end=1}
+EVENT_OBJECT_STATECHANGE on <button#test> role=ROLE_SYSTEM_PUSHBUTTON FOCUSABLE
+IA2_EVENT_ROLE_CHANGED on <button#test> role=ROLE_SYSTEM_PUSHBUTTON FOCUSABLE
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/aria-textbox-children-change-expected-auralinux.txt b/content/test/data/accessibility/event/aria-textbox-children-change-expected-auralinux.txt
index be72da9..f734d430 100644
--- a/content/test/data/accessibility/event/aria-textbox-children-change-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/aria-textbox-children-change-expected-auralinux.txt
@@ -4,5 +4,4 @@
 === Start Continuation ===
 CHILDREN-CHANGED:REMOVE index:1 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_ENTRY EDITABLE,ENABLED,FOCUSABLE,MULTI-LINE,SENSITIVE,SHOWING,VISIBLE,SELECTABLE-TEXT
 === Start Continuation ===
-CHILDREN-CHANGED:ADD index:1 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_ENTRY ENABLED,SENSITIVE,SHOWING,SINGLE-LINE,VISIBLE,SELECTABLE-TEXT
-CHILDREN-CHANGED:REMOVE index:4 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_ENTRY name='role only, plain') role=ROLE_PUSH_BUTTON name='ok' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/aria-textbox-children-change-expected-win.txt b/content/test/data/accessibility/event/aria-textbox-children-change-expected-win.txt
index 5863299..33c20402 100644
--- a/content/test/data/accessibility/event/aria-textbox-children-change-expected-win.txt
+++ b/content/test/data/accessibility/event/aria-textbox-children-change-expected-win.txt
@@ -7,6 +7,4 @@
 EVENT_OBJECT_HIDE on <button#btn3> role=ROLE_SYSTEM_PUSHBUTTON name="ok" FOCUSABLE IA2_STATE_EDITABLE
 EVENT_OBJECT_VALUECHANGE on <div> role=ROLE_SYSTEM_TEXT name="editable" value="foo" FOCUSABLE IA2_STATE_EDITABLE,IA2_STATE_MULTI_LINE,IA2_STATE_SELECTABLE_TEXT
 === Start Continuation ===
-EVENT_OBJECT_HIDE on <button#btn4> role=ROLE_SYSTEM_PUSHBUTTON name="ok" FOCUSABLE
-EVENT_OBJECT_SHOW on <button#btn4> role=ROLE_SYSTEM_PUSHBUTTON name="ok" FOCUSABLE
-EVENT_OBJECT_VALUECHANGE on <div#txt4> role=ROLE_SYSTEM_TEXT name="role only, plain" value="foook" IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE
+EVENT_OBJECT_VALUECHANGE on <div#txt4> role=ROLE_SYSTEM_TEXT name="role only, plain" value="foook" IA2_STATE_SELECTABLE_TEXT,IA2_STATE_SINGLE_LINE
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/children-changed-only-on-ancestor-expected-auralinux.txt b/content/test/data/accessibility/event/children-changed-only-on-ancestor-expected-auralinux.txt
index 6713fb73..1ff02349 100644
--- a/content/test/data/accessibility/event/children-changed-only-on-ancestor-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/children-changed-only-on-ancestor-expected-auralinux.txt
@@ -1,9 +1,18 @@
-CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_TREE_ITEM) role=ROLE_TREE ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
-CHILDREN-CHANGED:ADD index:1 CHILD:(role=ROLE_TREE_ITEM) role=ROLE_TREE ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
-CHILDREN-CHANGED:ADD index:2 CHILD:(role=ROLE_TREE_ITEM) role=ROLE_TREE ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
 CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_ARTICLE) role=ROLE_TREE ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_TREE name='(null)') role=ROLE_TREE_ITEM name='grandchild1' ENABLED,FOCUSABLE,SELECTABLE,SENSITIVE
+PARENT-CHANGED PARENT:(role=ROLE_TREE name='(null)') role=ROLE_TREE_ITEM name='grandchild2' ENABLED,FOCUSABLE,SELECTABLE,SENSITIVE
+PARENT-CHANGED PARENT:(role=ROLE_TREE name='(null)') role=ROLE_TREE_ITEM name='grandchild3' ENABLED,FOCUSABLE,SELECTABLE,SENSITIVE
 === Start Continuation ===
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_ARTICLE) role=ROLE_TREE ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
-CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_TREE_ITEM) role=ROLE_TREE ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
-CHILDREN-CHANGED:REMOVE index:1 CHILD:(role=ROLE_TREE_ITEM) role=ROLE_TREE ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
-CHILDREN-CHANGED:REMOVE index:2 CHILD:(role=ROLE_TREE_ITEM) role=ROLE_TREE ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_PANEL name='(null)') role=ROLE_TREE_ITEM name='grandchild1' ENABLED,FOCUSABLE,SELECTABLE,SENSITIVE,SHOWING,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_PANEL name='(null)') role=ROLE_TREE_ITEM name='grandchild2' ENABLED,FOCUSABLE,SELECTABLE,SENSITIVE,SHOWING,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_PANEL name='(null)') role=ROLE_TREE_ITEM name='grandchild3' ENABLED,FOCUSABLE,SELECTABLE,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LINK name='grandchild1' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LINK name='grandchild1' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LINK name='grandchild2' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LINK name='grandchild2' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LINK name='grandchild3' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LINK name='grandchild3' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_SECTION name='(null)' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_SECTION name='(null)' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_SECTION name='(null)' ENABLED,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/children-changed-only-on-ancestor-expected-win.txt b/content/test/data/accessibility/event/children-changed-only-on-ancestor-expected-win.txt
index 8b7e880..1d7f65d 100644
--- a/content/test/data/accessibility/event/children-changed-only-on-ancestor-expected-win.txt
+++ b/content/test/data/accessibility/event/children-changed-only-on-ancestor-expected-win.txt
@@ -1,11 +1,14 @@
 EVENT_OBJECT_HIDE on <div#article> role=ROLE_SYSTEM_DOCUMENT
 EVENT_OBJECT_REORDER on <div#tree> role=ROLE_SYSTEM_OUTLINE IA2_STATE_VERTICAL
-EVENT_OBJECT_SHOW on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild1" INVISIBLE,FOCUSABLE,SELECTABLE level=2
-EVENT_OBJECT_SHOW on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild2" INVISIBLE,FOCUSABLE,SELECTABLE level=2
-EVENT_OBJECT_SHOW on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild3" INVISIBLE,FOCUSABLE,SELECTABLE level=2
 === Start Continuation ===
-EVENT_OBJECT_HIDE on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild1" INVISIBLE,FOCUSABLE,SELECTABLE level=2
-EVENT_OBJECT_HIDE on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild2" INVISIBLE,FOCUSABLE,SELECTABLE level=2
-EVENT_OBJECT_HIDE on <div> role=ROLE_SYSTEM_OUTLINEITEM name="grandchild3" INVISIBLE,FOCUSABLE,SELECTABLE level=2
 EVENT_OBJECT_REORDER on <div#tree> role=ROLE_SYSTEM_OUTLINE IA2_STATE_VERTICAL
 EVENT_OBJECT_SHOW on <div#article> role=ROLE_SYSTEM_DOCUMENT
+IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED on <div#child1> role=ROLE_SYSTEM_LINK name="grandchild1" LINKED
+IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED on <div#child2> role=ROLE_SYSTEM_LINK name="grandchild2" LINKED
+IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED on <div#child3> role=ROLE_SYSTEM_LINK name="grandchild3" LINKED
+IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED on <div#grandchild1> role=ROLE_SYSTEM_GROUPING
+IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED on <div#grandchild2> role=ROLE_SYSTEM_GROUPING
+IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED on <div#grandchild3> role=ROLE_SYSTEM_GROUPING
+IA2_EVENT_TEXT_ATTRIBUTE_CHANGED on <div#child1> role=ROLE_SYSTEM_LINK name="grandchild1" LINKED
+IA2_EVENT_TEXT_ATTRIBUTE_CHANGED on <div#child2> role=ROLE_SYSTEM_LINK name="grandchild2" LINKED
+IA2_EVENT_TEXT_ATTRIBUTE_CHANGED on <div#child3> role=ROLE_SYSTEM_LINK name="grandchild3" LINKED
diff --git a/content/test/data/accessibility/event/css-display-descendants-expected-win.txt b/content/test/data/accessibility/event/css-display-descendants-expected-win.txt
index 89fa29f..65b8f92 100644
--- a/content/test/data/accessibility/event/css-display-descendants-expected-win.txt
+++ b/content/test/data/accessibility/event/css-display-descendants-expected-win.txt
@@ -1,3 +1,3 @@
 EVENT_OBJECT_HIDE on <div#heading-root.a> role=ROLE_SYSTEM_GROUPING name="Heading" level=2
 EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_TOOLBAR IA2_STATE_HORIZONTAL
-EVENT_OBJECT_SHOW on <div#banner-root.b> role=ROLE_SYSTEM_GROUPING name="Banner"
+EVENT_OBJECT_SHOW on <div#banner-root.b> role=ROLE_SYSTEM_GROUPING name="Banner"
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/expanded-changed-expected-auralinux.txt b/content/test/data/accessibility/event/expanded-changed-expected-auralinux.txt
index ac303d9..b19ed295 100644
--- a/content/test/data/accessibility/event/expanded-changed-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/expanded-changed-expected-auralinux.txt
@@ -1,12 +1,18 @@
 CHILDREN-CHANGED:ADD index:1 CHILD:(role=ROLE_LIST) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+NAME-CHANGED:%E2%80%A2  role=ROLE_STATIC name='%E2%80%A2 ' ENABLED,SENSITIVE,SHOWING,VISIBLE
+NAME-CHANGED:%E2%80%A2  role=ROLE_STATIC name='%E2%80%A2 ' ENABLED,SENSITIVE,SHOWING,VISIBLE
+NAME-CHANGED:%E2%80%A2  role=ROLE_STATIC name='%E2%80%A2 ' ENABLED,SENSITIVE,SHOWING,VISIBLE
 PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_LINK name='Toggle' ENABLED,EXPANDABLE,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
 PARENT-CHANGED PARENT:(role=ROLE_LIST name='list') role=ROLE_LIST_ITEM name='list item 1' ENABLED,SENSITIVE,SHOWING,VISIBLE
 PARENT-CHANGED PARENT:(role=ROLE_LIST name='list') role=ROLE_LIST_ITEM name='list item 2' ENABLED,SENSITIVE,SHOWING,VISIBLE
 PARENT-CHANGED PARENT:(role=ROLE_LIST name='list') role=ROLE_LIST_ITEM name='list item 3' ENABLED,SENSITIVE,SHOWING,VISIBLE
 TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 1' ENABLED,SENSITIVE,SHOWING,VISIBLE
 TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 1' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 1' ENABLED,SENSITIVE,SHOWING,VISIBLE
 TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 2' ENABLED,SENSITIVE,SHOWING,VISIBLE
 TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 2' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 2' ENABLED,SENSITIVE,SHOWING,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 3' ENABLED,SENSITIVE,SHOWING,VISIBLE
 TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 3' ENABLED,SENSITIVE,SHOWING,VISIBLE
 TEXT-ATTRIBUTES-CHANGED role=ROLE_LIST_ITEM name='list item 3' ENABLED,SENSITIVE,SHOWING,VISIBLE
 === Start Continuation ===
diff --git a/content/test/data/accessibility/event/expanded-changed-expected-mac.txt b/content/test/data/accessibility/event/expanded-changed-expected-mac.txt
index 14cb8d9..49196495 100644
--- a/content/test/data/accessibility/event/expanded-changed-expected-mac.txt
+++ b/content/test/data/accessibility/event/expanded-changed-expected-mac.txt
@@ -1,5 +1,8 @@
 AXTitleChanged on AXGroup AXDescription='list item 1'
 AXTitleChanged on AXGroup AXDescription='list item 2'
 AXTitleChanged on AXGroup AXDescription='list item 3'
+AXTitleChanged on AXListMarker AXValue='%E2%80%A2 '
+AXTitleChanged on AXListMarker AXValue='%E2%80%A2 '
+AXTitleChanged on AXListMarker AXValue='%E2%80%A2 '
 === Start Continuation ===
 AXExpandedChanged on AXLink AXDescription='Toggle'
diff --git a/content/test/data/accessibility/event/menu-opened-closed-expected-auralinux.txt b/content/test/data/accessibility/event/menu-opened-closed-expected-auralinux.txt
index ba9d708..ed98b6d 100644
--- a/content/test/data/accessibility/event/menu-opened-closed-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/menu-opened-closed-expected-auralinux.txt
@@ -1,9 +1,11 @@
-CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_MENU) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_MENU_ITEM) role=ROLE_MENU ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
+CHILDREN-CHANGED:ADD index:1 CHILD:(role=ROLE_MENU_ITEM) role=ROLE_MENU ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_MENU name='menu' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_MENU name='menu' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
+TEXT-ATTRIBUTES-CHANGED role=ROLE_MENU name='menu' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
 === Start Continuation ===
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_MENU) role=ROLE_MENU_ITEM ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
+STATE-CHANGE:SHOWING:TRUE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
 === Start Continuation ===
 CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_MENU) role=ROLE_MENU_ITEM ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
 === Start Continuation ===
-CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_MENU) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='menu' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
diff --git a/content/test/data/accessibility/event/menu-opened-closed-expected-mac.txt b/content/test/data/accessibility/event/menu-opened-closed-expected-mac.txt
index b05df0cd..3fc0b88 100644
--- a/content/test/data/accessibility/event/menu-opened-closed-expected-mac.txt
+++ b/content/test/data/accessibility/event/menu-opened-closed-expected-mac.txt
@@ -1,9 +1,5 @@
-AXMenuClosed on AXWebArea
-AXMenuClosed on AXWebArea
+AXTitleChanged on AXMenu AXDescription='menu'
 === Start Continuation ===
-AXMenuClosed on AXWebArea
+AXMenuOpened on AXMenu
 === Start Continuation ===
-AXMenuClosed on AXWebArea
 === Start Continuation ===
-AXMenuClosed on AXWebArea
-AXMenuClosed on AXWebArea
diff --git a/content/test/data/accessibility/event/menu-opened-closed-expected-win.txt b/content/test/data/accessibility/event/menu-opened-closed-expected-win.txt
index 960f13a..b9c69a1 100644
--- a/content/test/data/accessibility/event/menu-opened-closed-expected-win.txt
+++ b/content/test/data/accessibility/event/menu-opened-closed-expected-win.txt
@@ -1,5 +1,9 @@
+EVENT_OBJECT_NAMECHANGE on <div#menu> role=ROLE_SYSTEM_MENUPOPUP name="menu" IA2_STATE_VERTICAL SetSize=2
+EVENT_OBJECT_STATECHANGE on <div#menu> role=ROLE_SYSTEM_MENUPOPUP name="menu" IA2_STATE_VERTICAL SetSize=2
+IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED on <div#menu> role=ROLE_SYSTEM_MENUPOPUP name="menu" IA2_STATE_VERTICAL SetSize=2
+IA2_EVENT_TEXT_ATTRIBUTE_CHANGED on <div#menu> role=ROLE_SYSTEM_MENUPOPUP name="menu" IA2_STATE_VERTICAL SetSize=2
 === Start Continuation ===
+EVENT_SYSTEM_MENUPOPUPSTART on <div#submenu> role=ROLE_SYSTEM_MENUPOPUP IA2_STATE_VERTICAL SetSize=1
 === Start Continuation ===
-EVENT_SYSTEM_MENUPOPUPEND on <div#submenu> role=ROLE_SYSTEM_MENUPOPUP IA2_STATE_VERTICAL SetSize=1
+EVENT_SYSTEM_MENUPOPUPEND on <div> role=ROLE_SYSTEM_MENUPOPUP INVISIBLE
 === Start Continuation ===
-EVENT_SYSTEM_MENUPOPUPEND on <div#menu> role=ROLE_SYSTEM_MENUPOPUP name="menu" IA2_STATE_VERTICAL SetSize=2
diff --git a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-auralinux.txt b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-auralinux.txt
index b1be3d2..15ab2fc 100644
--- a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-auralinux.txt
@@ -1,18 +1,8 @@
 STATE-CHANGE:EXPANDED:TRUE role=ROLE_MENU_ITEM name='File' ENABLED,EXPANDABLE,EXPANDED,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE
 === Start Continuation ===
 STATE-CHANGE:EXPANDED:FALSE role=ROLE_MENU_ITEM name='File' ENABLED,EXPANDABLE,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='File' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='New' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
 === Start Continuation ===
 === Start Continuation ===
 STATE-CHANGE:EXPANDED:TRUE role=ROLE_MENU_ITEM name='File' ENABLED,EXPANDABLE,EXPANDED,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE
 === Start Continuation ===
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='File' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='New' ENABLED,SENSITIVE,SHOWING,VERTICAL,VISIBLE
 === Start Continuation ===
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE
-STATE-CHANGE:SHOWING:FALSE role=ROLE_MENU name='(null)' ENABLED,SENSITIVE
diff --git a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-mac.txt b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-mac.txt
index dfc8925..143cfea 100644
--- a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-mac.txt
+++ b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-mac.txt
@@ -1,6 +1,7 @@
 AXExpandedChanged on AXMenuItem AXDescription='File'
-AXMenuClosed on AXWebArea
-AXMenuClosed on AXWebArea
+AXExpandedChanged on AXMenuItem AXDescription='New'
+AXMenuOpened on AXMenu AXDescription='File'
+AXMenuOpened on AXMenu AXDescription='New'
 === Start Continuation ===
 AXExpandedChanged on AXMenuItem AXDescription='File'
 AXMenuClosed on AXWebArea
@@ -8,8 +9,9 @@
 === Start Continuation ===
 === Start Continuation ===
 AXExpandedChanged on AXMenuItem AXDescription='File'
-AXMenuClosed on AXWebArea
-AXMenuClosed on AXWebArea
+AXExpandedChanged on AXMenuItem AXDescription='New'
+AXMenuOpened on AXMenu AXDescription='File'
+AXMenuOpened on AXMenu AXDescription='New'
 === Start Continuation ===
 AXMenuClosed on AXWebArea
 AXMenuClosed on AXWebArea
diff --git a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-uia-win.txt b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-uia-win.txt
index 31f3e1f..e5b0ed7d 100644
--- a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-uia-win.txt
+++ b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-uia-win.txt
@@ -1,22 +1,18 @@
 ExpandCollapseExpandCollapseState changed on role=menuitem, name=File
 Name changed on role=group, name=open file and new done
+Name changed on role=menu, name=File
+Name changed on role=menu, name=New
 === Start Continuation ===
 ExpandCollapseExpandCollapseState changed on role=menuitem, name=File
-MenuClosed 
-MenuClosed 
-MenuClosed 
-MenuClosed 
 Name changed on role=group, name=close file and new done
 === Start Continuation ===
 Name changed on role=group, name=open new done
 === Start Continuation ===
 ExpandCollapseExpandCollapseState changed on role=menuitem, name=File
 Name changed on role=group, name=open file done
+Name changed on role=menu, name=File
+Name changed on role=menu, name=New
 === Start Continuation ===
-MenuClosed 
-MenuClosed 
-MenuClosed 
-MenuClosed 
 Name changed on role=group, name=hide menubar done
 === Start Continuation ===
 Name changed on role=group, name=show menubar done
diff --git a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-win.txt b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-win.txt
index 1dfd790..6b52a9b 100644
--- a/content/test/data/accessibility/event/menubar-show-hide-menus-expected-win.txt
+++ b/content/test/data/accessibility/event/menubar-show-hide-menus-expected-win.txt
@@ -1,12 +1,12 @@
 EVENT_OBJECT_STATECHANGE on <li#file-menuitem> role=ROLE_SYSTEM_MENUITEM name="File" EXPANDED,HASPOPUP PosInSet=1 SetSize=2
+EVENT_OBJECT_STATECHANGE on <ul#file-menu> role=ROLE_SYSTEM_MENUPOPUP name="File" IA2_STATE_VERTICAL SetSize=3
+EVENT_OBJECT_STATECHANGE on <ul#new-menu> role=ROLE_SYSTEM_MENUPOPUP name="New" IA2_STATE_VERTICAL SetSize=3
 === Start Continuation ===
 EVENT_OBJECT_STATECHANGE on <li#file-menuitem> role=ROLE_SYSTEM_MENUITEM name="File" COLLAPSED,HASPOPUP PosInSet=1 SetSize=2
-EVENT_SYSTEM_MENUPOPUPEND on <ul#file-menu> role=ROLE_SYSTEM_MENUPOPUP name="File" IA2_STATE_VERTICAL SetSize=3
-EVENT_SYSTEM_MENUPOPUPEND on <ul#new-menu> role=ROLE_SYSTEM_MENUPOPUP name="New" IA2_STATE_VERTICAL SetSize=3
 === Start Continuation ===
 === Start Continuation ===
 EVENT_OBJECT_STATECHANGE on <li#file-menuitem> role=ROLE_SYSTEM_MENUITEM name="File" EXPANDED,HASPOPUP PosInSet=1 SetSize=2
+EVENT_OBJECT_STATECHANGE on <ul#file-menu> role=ROLE_SYSTEM_MENUPOPUP name="File" IA2_STATE_VERTICAL SetSize=3
+EVENT_OBJECT_STATECHANGE on <ul#new-menu> role=ROLE_SYSTEM_MENUPOPUP name="New" IA2_STATE_VERTICAL SetSize=3
 === Start Continuation ===
-EVENT_SYSTEM_MENUPOPUPEND on <ul#file-menu> role=ROLE_SYSTEM_MENUPOPUP name="File" IA2_STATE_VERTICAL SetSize=3
-EVENT_SYSTEM_MENUPOPUPEND on <ul#new-menu> role=ROLE_SYSTEM_MENUPOPUP name="New" IA2_STATE_VERTICAL SetSize=3
 === Start Continuation ===
diff --git a/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-auralinux.txt b/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-auralinux.txt
index bec1d9d..85c85da 100644
--- a/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-auralinux.txt
@@ -9,9 +9,7 @@
 STATE-CHANGE:FOCUSED:TRUE role=ROLE_MENU_ITEM name='Open...' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
 === Start Continuation ===
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_PANEL) role=ROLE_MENU_BAR ENABLED,HORIZONTAL,SENSITIVE,SHOWING,VISIBLE
-CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_SECTION) role=ROLE_MENU_BAR ENABLED,HORIZONTAL,SENSITIVE,SHOWING,VISIBLE
-PARENT-CHANGED PARENT:(role=ROLE_PANEL name='(null)') role=ROLE_MENU_ITEM name='About' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
-PARENT-CHANGED PARENT:(role=ROLE_PANEL name='(null)') role=ROLE_MENU_ITEM name='File' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP
+CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PANEL) role=ROLE_MENU_BAR ENABLED,HORIZONTAL,SENSITIVE,SHOWING,VISIBLE
 === Start Continuation ===
 FOCUS-EVENT:FALSE role=ROLE_MENU_ITEM name='Open...' ENABLED,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE
 FOCUS-EVENT:TRUE role=ROLE_MENU_ITEM name='Quit' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-win.txt b/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-win.txt
index fa6c831..7ed90a5 100644
--- a/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-win.txt
+++ b/content/test/data/accessibility/event/reparent-element-with-active-descendant-expected-win.txt
@@ -4,11 +4,7 @@
 EVENT_OBJECT_FOCUS on <div#open> role=ROLE_SYSTEM_MENUITEM name="Open..." FOCUSED,FOCUSABLE PosInSet=2 SetSize=3
 IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on <div#file-menu> role=ROLE_SYSTEM_MENUPOPUP name="File" FOCUSABLE IA2_STATE_VERTICAL SetSize=3
 === Start Continuation ===
-EVENT_OBJECT_HIDE on <div#extra> role=ROLE_SYSTEM_GROUPING
-EVENT_OBJECT_REORDER on <div> role=ROLE_SYSTEM_MENUBAR FOCUSED IA2_STATE_HORIZONTAL SetSize=2
-EVENT_OBJECT_SHOW on <div#extra> role=ROLE_SYSTEM_GROUPING
-IA2_EVENT_TEXT_INSERTED on <div> role=ROLE_SYSTEM_MENUBAR FOCUSED IA2_STATE_HORIZONTAL SetSize=2 new_text={'<obj>' start=0 end=1}
-IA2_EVENT_TEXT_REMOVED on <div> role=ROLE_SYSTEM_MENUBAR FOCUSED IA2_STATE_HORIZONTAL SetSize=2 old_text={'<obj>' start=0 end=1}
+IA2_EVENT_ROLE_CHANGED on <div#extra> role=ROLE_SYSTEM_GROUPING
 === Start Continuation ===
 EVENT_OBJECT_FOCUS on <div#quit> role=ROLE_SYSTEM_MENUITEM name="Quit" FOCUSED,FOCUSABLE PosInSet=3 SetSize=3
 IA2_EVENT_ACTIVE_DESCENDANT_CHANGED on <div#file-menu> role=ROLE_SYSTEM_MENUPOPUP name="File" FOCUSABLE IA2_STATE_VERTICAL SetSize=3
diff --git a/content/test/data/accessibility/event/role-changed-expected-auralinux.txt b/content/test/data/accessibility/event/role-changed-expected-auralinux.txt
index 016b37a..c40a938f 100644
--- a/content/test/data/accessibility/event/role-changed-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/role-changed-expected-auralinux.txt
@@ -1,3 +1,3 @@
 CHILDREN-CHANGED:ADD index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
-CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PANEL) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
-PARENT-CHANGED PARENT:(role=ROLE_PUSH_BUTTON name='Role will change') role=ROLE_STATIC name='Role will change' ENABLED,SENSITIVE,SHOWING,VISIBLE
+CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_PUSH_BUTTON) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+NAME-CHANGED:Role will change role=ROLE_PUSH_BUTTON name='Role will change' ENABLED,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/event/role-changed-expected-mac.txt b/content/test/data/accessibility/event/role-changed-expected-mac.txt
index e69de29..28576b0 100644
--- a/content/test/data/accessibility/event/role-changed-expected-mac.txt
+++ b/content/test/data/accessibility/event/role-changed-expected-mac.txt
@@ -0,0 +1 @@
+AXTitleChanged on AXButton AXTitle='Role will change'
diff --git a/content/test/data/accessibility/event/role-changed-expected-win.txt b/content/test/data/accessibility/event/role-changed-expected-win.txt
index 6bed0387a..5971aee8 100644
--- a/content/test/data/accessibility/event/role-changed-expected-win.txt
+++ b/content/test/data/accessibility/event/role-changed-expected-win.txt
@@ -1,5 +1 @@
-EVENT_OBJECT_HIDE on <div#a> role=ROLE_SYSTEM_GROUPING
-EVENT_OBJECT_REORDER on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSED,FOCUSABLE
-EVENT_OBJECT_SHOW on <div#a> role=ROLE_SYSTEM_PUSHBUTTON name="Role will change"
-IA2_EVENT_TEXT_INSERTED on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSED,FOCUSABLE new_text={'<obj>' start=0 end=1}
-IA2_EVENT_TEXT_REMOVED on <#document> role=ROLE_SYSTEM_DOCUMENT value~=[doc-url] FOCUSED,FOCUSABLE old_text={'<obj>' start=0 end=1}
+IA2_EVENT_ROLE_CHANGED on <div#a> role=ROLE_SYSTEM_PUSHBUTTON name="Role will change"
diff --git a/content/test/data/accessibility/event/selectlist-expected-win.txt b/content/test/data/accessibility/event/selectlist-expected-win.txt
index c644a135..b7df6d3 100644
--- a/content/test/data/accessibility/event/selectlist-expected-win.txt
+++ b/content/test/data/accessibility/event/selectlist-expected-win.txt
@@ -4,8 +4,8 @@
 EVENT_OBJECT_STATECHANGE on <option> role=ROLE_SYSTEM_LISTITEM name="Option 1" SELECTED,FOCUSED,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=3
 === Start Continuation ===
 EVENT_OBJECT_FOCUS on <option> role=ROLE_SYSTEM_LISTITEM name="Option 4" SELECTED,FOCUSED,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=3
-EVENT_OBJECT_HIDE on <listbox> role=ROLE_SYSTEM_LIST IA2_STATE_VERTICAL SetSize=3
+EVENT_OBJECT_HIDE on <listbox> role=ROLE_SYSTEM_LIST INVISIBLE
 EVENT_OBJECT_SHOW on <listbox> role=ROLE_SYSTEM_LIST IA2_STATE_VERTICAL SetSize=3
 EVENT_OBJECT_STATECHANGE on <button#ButtonA> role=ROLE_SYSTEM_COMBOBOX name="Combobox A" value="Option 1" COLLAPSED,FOCUSABLE,HASPOPUP
 EVENT_OBJECT_STATECHANGE on <button#ButtonB> role=ROLE_SYSTEM_COMBOBOX name="Combobox B" value="Option 4" EXPANDED,FOCUSABLE,HASPOPUP
-EVENT_OBJECT_STATECHANGE on <option> role=ROLE_SYSTEM_LISTITEM name="Option 4" SELECTED,FOCUSED,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=3
+EVENT_OBJECT_STATECHANGE on <option> role=ROLE_SYSTEM_LISTITEM name="Option 4" SELECTED,FOCUSED,FOCUSABLE,SELECTABLE PosInSet=1 SetSize=3
\ No newline at end of file
diff --git a/content/test/data/accessibility/event/text-selection-inside-hidden-element-expected-auralinux.txt b/content/test/data/accessibility/event/text-selection-inside-hidden-element-expected-auralinux.txt
index 892b7aa6..43bd8b4 100644
--- a/content/test/data/accessibility/event/text-selection-inside-hidden-element-expected-auralinux.txt
+++ b/content/test/data/accessibility/event/text-selection-inside-hidden-element-expected-auralinux.txt
@@ -1,4 +1,5 @@
-CHILDREN-CHANGED:ADD index:1 CHILD:(role=ROLE_SECTION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
 CHILDREN-CHANGED:REMOVE index:0 CHILD:(role=ROLE_SECTION) role=ROLE_DOCUMENT_WEB ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
+PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_COMBO_BOX name='(null)' ENABLED,EXPANDABLE,FOCUSABLE,SENSITIVE,SHOWING,VISIBLE,HAS-POPUP
+PARENT-CHANGED PARENT:(role=ROLE_DOCUMENT_WEB name='(null)') role=ROLE_SECTION name='(null)' ENABLED,FOCUSABLE,SENSITIVE
 TEXT-CARET-MOVED role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
 TEXT-SELECTION-CHANGED role=ROLE_DOCUMENT_WEB name='(null)' ENABLED,FOCUSABLE,FOCUSED,SENSITIVE,SHOWING,VISIBLE
diff --git a/content/test/data/accessibility/html/a-onclick-expected-android.txt b/content/test/data/accessibility/html/a-onclick-expected-android.txt
index e8d052d..73f0ef1 100644
--- a/content/test/data/accessibility/html/a-onclick-expected-android.txt
+++ b/content/test/data/accessibility/html/a-onclick-expected-android.txt
@@ -3,5 +3,4 @@
 ++android.view.View role_description='link' clickable link name='link with no href but onclick'
 ++++android.widget.TextView name='link with no href but onclick'
 ++android.view.View role_description='link' clickable link name='link with no href and click handler added via script'
-++++android.widget.TextView name='link with no href and click handler added via script'
-++android.widget.TextView name='Link with no event handler'
+++android.widget.TextView name='Link with no event handler'
\ No newline at end of file
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 494ce14..51a51321 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -359,7 +359,6 @@
 
 # Failing likely due to protected memory, but cause TBD.
 crbug.com/1420067 [ fuchsia fuchsia-board-astro ] Pixel_Video* [ Failure ]
-crbug.com/1420067 [ fuchsia fuchsia-board-nelson ] Pixel_Video* [ Failure ]
 crbug.com/1420067 [ fuchsia fuchsia-board-sherlock ] Pixel_Video* [ Failure ]
 
 
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 92ff37f..72a1bf2 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
@@ -456,17 +456,9 @@
 
 
 # Flaky tests
-crbug.com/1489477 [ fuchsia fuchsia-board-astro ] conformance/more/conformance/quickCheck* [ Failure ]
 crbug.com/1489477 [ fuchsia web-engine-shell ] WebglExtension_EXT_float_blend [ Failure ]
-crbug.com/1489477 [ fuchsia fuchsia-board-astro ] conformance/state/gl-object-get-calls.html [ Failure ]
-crbug.com/1489477 [ fuchsia fuchsia-board-astro ] conformance/textures/canvas/tex-2d-rgb-rgb-unsigned_short_5_6_5.html [ Failure ]
 crbug.com/1489477 [ fuchsia fuchsia-board-astro ] conformance/textures/misc/texture-size-limit.html [ Failure ]
 crbug.com/1489477 [ fuchsia fuchsia-board-sherlock ] conformance/textures/misc/texture-size-limit.html [ Failure ]
-crbug.com/1489477 [ fuchsia fuchsia-board-sherlock ] conformance/textures/misc/texture-sub-image-cube-maps.html [ Failure ]
-crbug.com/1489477 [ fuchsia fuchsia-board-astro ] conformance/textures/misc/texture-upload-size.html [ Failure ]
-crbug.com/1489477 [ fuchsia fuchsia-board-sherlock ] conformance/textures/misc/texture-upload-size.html [ Failure ]
-crbug.com/1489477 [ fuchsia fuchsia-board-astro ] conformance/textures/svg_image/tex-2d-luminance-luminance-unsigned_byte.html [ Failure ]
-crbug.com/1489477 [ fuchsia fuchsia-board-nelson ] conformance/textures/svg_image/tex-2d-luminance-luminance-unsigned_byte.html [ Failure ]
 
 # Anti-aliasing disabled on Fuchsia
 [ fuchsia ] conformance/context/context-attributes-alpha-depth-stencil-antialias.html [ Failure ]
@@ -684,8 +676,6 @@
 # Failing test in passthrough mode when experiment DrDc is enabled via fieldtrial_testing_config.json.
 crbug.com/1276552 [ android-pixel-4 android-r passthrough qualcomm renderer-skia-gl target-cpu-32 ] conformance/canvas/render-after-resize-test.html [ Failure ]
 
-# Failing test when experiment DrDc is enabled via fieldtrial_testing_config.json.
-crbug.com/1289303 [ android-pixel-4 android-r angle-disabled no-passthrough ] conformance/textures/misc/texture-video-transparent.html [ Failure ]
 
 ## Samsung failures ##
 
diff --git a/content/web_test/renderer/web_ax_object_proxy.cc b/content/web_test/renderer/web_ax_object_proxy.cc
index 3e2559a..984026e 100644
--- a/content/web_test/renderer/web_ax_object_proxy.cc
+++ b/content/web_test/renderer/web_ax_object_proxy.cc
@@ -2107,19 +2107,22 @@
     return v8::Local<v8::Object>();
   }
 
-  // Return existing object if there is a match.
+  // Return existing object if there is a match and it hasn't been detached.
+  bool found = false;
   auto persistent = ax_objects_.find(object.AxID());
   if (persistent != ax_objects_.end()) {
-    auto local = v8::Local<v8::Object>::New(isolate_, persistent->second);
-
-#if DCHECK_IS_ON()
+    found = true;
     WebAXObjectProxy* proxy = nullptr;
+    // TODO(accessibility): Can this detached check be simplified?
+    auto local = v8::Local<v8::Object>::New(isolate_, persistent->second);
     bool ok = gin::ConvertFromV8(isolate_, local, &proxy);
     DCHECK(ok);
-    DCHECK(proxy->IsEqualToObject(object));
+    if (!proxy->accessibility_object().IsDetached()) {
+#if DCHECK_IS_ON()
+      DCHECK(proxy->IsEqualToObject(object));
 #endif
-
-    return local;
+      return local;
+    }
   }
 
   // Create a new object.
@@ -2128,6 +2131,10 @@
   v8::Local<v8::Object> handle;
   if (value_handle.IsEmpty() ||
       !value_handle->ToObject(isolate_->GetCurrentContext()).ToLocal(&handle)) {
+    if (found) {
+      // Remove old detached object.
+      ax_objects_.erase(object.AxID());
+    }
     return {};
   }
 
diff --git a/content/web_test/renderer/web_ax_object_proxy.h b/content/web_test/renderer/web_ax_object_proxy.h
index 7fd0f814..6a1daa7 100644
--- a/content/web_test/renderer/web_ax_object_proxy.h
+++ b/content/web_test/renderer/web_ax_object_proxy.h
@@ -57,11 +57,11 @@
       const std::vector<ui::AXEventIntent>& event_intents);
   void Reset();
 
- protected:
   const blink::WebAXObject& accessibility_object() const {
     return accessibility_object_;
   }
 
+ protected:
   Factory* factory() const { return factory_; }
 
   bool IsDetached() const { return !factory_ || !factory_->GetAXContext(); }
diff --git a/docs/website b/docs/website
index b13d20e..7918c58 160000
--- a/docs/website
+++ b/docs/website
@@ -1 +1 @@
-Subproject commit b13d20e7483a7a2b3e600b5981693d476d43d19d
+Subproject commit 7918c586771aed5e170cb5ce33fdfc38f6203063
diff --git a/extensions/browser/url_request_util.cc b/extensions/browser/url_request_util.cc
index 74bc76d..2af3580b 100644
--- a/extensions/browser/url_request_util.cc
+++ b/extensions/browser/url_request_util.cc
@@ -91,11 +91,10 @@
     return true;
   }
 
-  // When navigating in subframe, allow if it is the same origin
-  // as the top-level frame. This can only be the case if the subframe
-  // request is coming from the extension process.
+  // When navigating in subframe, verify that the extension the resource is
+  // loaded from matches the process loading it.
   if (network::IsRequestDestinationEmbeddedFrame(destination) &&
-      process_map.Contains(child_id)) {
+      process_map.Contains(extension->id(), child_id)) {
     *allowed = true;
     return true;
   }
diff --git a/google_apis/gaia/core_account_id.cc b/google_apis/gaia/core_account_id.cc
index 51fbecea..64113806 100644
--- a/google_apis/gaia/core_account_id.cc
+++ b/google_apis/gaia/core_account_id.cc
@@ -6,6 +6,7 @@
 
 #include "base/check.h"
 #include "base/containers/contains.h"
+#include "base/containers/to_vector.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 
 namespace {
@@ -96,8 +97,5 @@
 
 std::vector<std::string> ToStringList(
     const std::vector<CoreAccountId>& account_ids) {
-  std::vector<std::string> account_ids_string;
-  for (const auto& account_id : account_ids)
-    account_ids_string.push_back(account_id.ToString());
-  return account_ids_string;
+  return base::ToVector(account_ids, &CoreAccountId::ToString);
 }
diff --git a/gpu/command_buffer/service/dawn_context_provider.cc b/gpu/command_buffer/service/dawn_context_provider.cc
index d5e7427..63fb96b6 100644
--- a/gpu/command_buffer/service/dawn_context_provider.cc
+++ b/gpu/command_buffer/service/dawn_context_provider.cc
@@ -371,11 +371,16 @@
       wgpu::FeatureName::MultiPlanarRenderTargets,
       wgpu::FeatureName::Norm16TextureFormats,
 
-      // The following features are always supported when running on the
-      // Metal backend.
+      // The following features are always supported by the the Metal backend on
+      // the Mac versions on which Chrome runs.
       wgpu::FeatureName::SharedTextureMemoryIOSurface,
       wgpu::FeatureName::SharedFenceMTLSharedEvent,
 
+      // The following features are always supported when running on the Vulkan
+      // backend on Android.
+      wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer,
+      wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD,
+
       wgpu::FeatureName::TransientAttachments,
   };
 
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index 0045abd..5adfc14b 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -1367,6 +1367,18 @@
     required_features.push_back(wgpu::FeatureName::SharedFenceMTLSharedEvent);
   }
 
+#if BUILDFLAG(IS_ANDROID)
+  if (adapter_obj.HasFeature(
+          wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer)) {
+    required_features.push_back(
+        wgpu::FeatureName::SharedTextureMemoryAHardwareBuffer);
+  }
+  if (adapter_obj.HasFeature(wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD)) {
+    required_features.push_back(
+        wgpu::FeatureName::SharedFenceVkSemaphoreSyncFD);
+  }
+#endif
+
 #if BUILDFLAG(USE_DAWN) && BUILDFLAG(DAWN_ENABLE_BACKEND_OPENGLES)
   // On Desktop GL via ANGLE, require GL texture sharing.
   if (use_webgpu_adapter_ == WebGPUAdapterName::kOpenGLES &&
@@ -1707,6 +1719,9 @@
     // NOTE: These platforms should be switched to the corresponding
     // SharedTextureMemory feature check as they are converted to using
     // SharedTextureMemory.
+    // TODO(crbug.com/327111284): Change this to check for
+    // SharedTextureMemoryAHardwareBuffer on Android-Vulkan once we've made the
+    // switch there.
     supports_external_textures = adapter.SupportsExternalImages();
 #endif
     if (!(supports_external_textures || is_swiftshader)) {
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index ac7dc153..89c62e4 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.6328.0',
+    'description': 'Run with ash-chrome version 124.0.6329.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6328.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.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.6328.0',
-          'revision': 'version:124.0.6328.0',
+          'location': 'lacros_version_skew_tests_v124.0.6329.0',
+          'revision': 'version:124.0.6329.0',
         },
       ],
     },
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index 0b3c1fb..4d5e7cb 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.6328.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 124.0.6328.0",
+    "description": "Run with ash-chrome version 124.0.6329.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.6328.0",
-          "revision": "version:124.0.6328.0"
+          "location": "lacros_version_skew_tests_v124.0.6329.0",
+          "revision": "version:124.0.6329.0"
         }
       ]
     }
diff --git a/ios/chrome/app/strings/ios_chromium_strings.grd b/ios/chrome/app/strings/ios_chromium_strings.grd
index 2c3d1a4e..7747bc60 100644
--- a/ios/chrome/app/strings/ios_chromium_strings.grd
+++ b/ios/chrome/app/strings/ios_chromium_strings.grd
@@ -206,9 +206,6 @@
       <message name="IDS_IOS_AUTOFILL_ADDRESS_MIGRATE_IN_ACCOUNT_FOOTER" desc="Footer text shown in the address migration prompt to account. [iOS only]">
           This address is currently saved to Chromium. To use it across Google products, save it in your Google Account, <ph name="USER_EMAIL">$1<ex>janedoe@google.com</ex></ph>.
       </message>
-      <message name="IDS_IOS_AUTOFILL_DESCRIBE_LOCAL_COPY" desc="Text label that describes a Wallet credit card which has been copied to the local Chromium instance. Title case. [Length: 20em] [iOS only]">
-        Copied to Chromium
-      </message>
       <message name="IDS_IOS_BANDWIDTH_MANAGEMENT_DESCRIPTION_LEARN_MORE" desc="Description text bandwidth management panel in settings with explicit Learn More link [iOS only]">
         Chromium has features that help you manage your internet data and how quickly you're able to load webpages.
 <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn more<ph name="END_LINK">END_LINK</ph>
diff --git a/ios/chrome/app/strings/ios_google_chrome_strings.grd b/ios/chrome/app/strings/ios_google_chrome_strings.grd
index ca409f12..7c1bfce 100644
--- a/ios/chrome/app/strings/ios_google_chrome_strings.grd
+++ b/ios/chrome/app/strings/ios_google_chrome_strings.grd
@@ -206,9 +206,6 @@
       <message name="IDS_IOS_AUTOFILL_ADDRESS_MIGRATE_IN_ACCOUNT_FOOTER" desc="Footer text shown in the address migration prompt to account. [iOS only]">
           This address is currently saved to Chrome. To use it across Google products, save it in your Google Account, <ph name="USER_EMAIL">$1<ex>janedoe@google.com</ex></ph>.
       </message>
-      <message name="IDS_IOS_AUTOFILL_DESCRIBE_LOCAL_COPY" desc="Text label that describes a Wallet credit card which has been copied to the local Chrome instance. Title case. [Length: 20em] [iOS only]">
-        Copied to Chrome
-      </message>
       <message name="IDS_IOS_BANDWIDTH_MANAGEMENT_DESCRIPTION_LEARN_MORE" desc="Description text bandwidth management panel in settings with explicit Learn More link [iOS only]">
         Chrome has features that help you manage your internet data and how quickly you're able to load webpages.
 <ph name="BEGIN_LINK">BEGIN_LINK</ph>Learn more<ph name="END_LINK">END_LINK</ph>
diff --git a/ios/chrome/browser/autocomplete/model/in_memory_url_index_factory.cc b/ios/chrome/browser/autocomplete/model/in_memory_url_index_factory.cc
index 2b47eba..4b435c7f9 100644
--- a/ios/chrome/browser/autocomplete/model/in_memory_url_index_factory.cc
+++ b/ios/chrome/browser/autocomplete/model/in_memory_url_index_factory.cc
@@ -11,8 +11,7 @@
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
 #include "components/omnibox/browser/in_memory_url_index.h"
-#include "ios/chrome/browser/bookmarks/model/legacy_bookmark_model.h"
-#include "ios/chrome/browser/bookmarks/model/local_or_syncable_bookmark_model_factory.h"
+#include "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"
 #include "ios/chrome/browser/history/model/history_service_factory.h"
 #include "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
 #include "ios/chrome/browser/shared/model/browser_state/browser_state_otr_helper.h"
@@ -34,8 +33,7 @@
 
   // Do not force creation of the HistoryService if saving history is disabled.
   std::unique_ptr<InMemoryURLIndex> in_memory_url_index(new InMemoryURLIndex(
-      ios::LocalOrSyncableBookmarkModelFactory::GetForBrowserState(
-          browser_state),
+      ios::BookmarkModelFactory::GetForBrowserState(browser_state),
       ios::HistoryServiceFactory::GetForBrowserState(
           browser_state, ServiceAccessType::IMPLICIT_ACCESS),
       ios::TemplateURLServiceFactory::GetForBrowserState(browser_state),
@@ -63,8 +61,7 @@
     : BrowserStateKeyedServiceFactory(
           "InMemoryURLIndex",
           BrowserStateDependencyManager::GetInstance()) {
-  DependsOn(ios::LocalOrSyncableBookmarkModelFactory::GetInstance());
-  // TODO(crbug.com/1425459): Add AccountBookmarkModelFactory support.
+  DependsOn(ios::BookmarkModelFactory::GetInstance());
   DependsOn(ios::HistoryServiceFactory::GetInstance());
   DependsOn(ios::TemplateURLServiceFactory::GetInstance());
 }
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index cbd9b1e..3015996 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -1657,6 +1657,10 @@
      flag_descriptions::kLinkedServicesSettingIosName,
      flag_descriptions::kLinkedServicesSettingIosDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kLinkedServicesSettingIos)},
+    {"disable-fullscreen-scrolling",
+     flag_descriptions::kDisableFullscreenScrollingName,
+     flag_descriptions::kDisableFullscreenScrollingDescription,
+     flags_ui::kOsIos, FEATURE_VALUE_TYPE(kDisableFullscreenScrolling)},
 };
 
 bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index a4a09ff9..fe71555 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -300,6 +300,11 @@
     "A crash report will be uploaded if the main thread is frozen more than "
     "the time specified by this flag.";
 
+const char kDisableFullscreenScrollingName[] = "Disable fullscreen scrolling";
+const char kDisableFullscreenScrollingDescription[] =
+    "When this flag is enabled and a user scroll a web page, toolbars will "
+    "stay extanded and the user will not enter in fullscreen mode.";
+
 const char kDiscoverFeedSportCardName[] = "Sport card in Discover feed";
 const char kDiscoverFeedSportCardDescription[] =
     "Enables the live sport card in the NTP's Discover feed";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index d0ebf5c..6150638 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -248,6 +248,10 @@
 extern const char kDetectMainThreadFreezeName[];
 extern const char kDetectMainThreadFreezeDescription[];
 
+// Title and description for the flag to disable the fullscreen scrolling logic.
+extern const char kDisableFullscreenScrollingName[];
+extern const char kDisableFullscreenScrollingDescription[];
+
 // Title and description for the flag that adds the sport card to the Discover
 // feed.
 extern const char kDiscoverFeedSportCardName[];
diff --git a/ios/chrome/browser/shared/public/features/features.h b/ios/chrome/browser/shared/public/features/features.h
index 12616ef..1aca3e99 100644
--- a/ios/chrome/browser/shared/public/features/features.h
+++ b/ios/chrome/browser/shared/public/features/features.h
@@ -588,4 +588,7 @@
 // Returns true if the MagicStack UICollectionView implementation is enabled.
 bool IsIOSMagicStackCollectionViewEnabled();
 
+// Feature flag to disable fullscreen scrolling logic.
+BASE_DECLARE_FEATURE(kDisableFullscreenScrolling);
+
 #endif  // IOS_CHROME_BROWSER_SHARED_PUBLIC_FEATURES_FEATURES_H_
diff --git a/ios/chrome/browser/shared/public/features/features.mm b/ios/chrome/browser/shared/public/features/features.mm
index 6a2dede5..cf9d49e 100644
--- a/ios/chrome/browser/shared/public/features/features.mm
+++ b/ios/chrome/browser/shared/public/features/features.mm
@@ -738,3 +738,7 @@
 bool IsIOSMagicStackCollectionViewEnabled() {
   return base::FeatureList::IsEnabled(kIOSMagicStackCollectionView);
 }
+
+BASE_FEATURE(kDisableFullscreenScrolling,
+             "DisableFullscreenScrolling",
+             base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.mm
index 91d70f6..6757cf9 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.mm
@@ -5,6 +5,8 @@
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_controller_impl.h"
 
 #import "base/memory/ptr_util.h"
+#import "base/metrics/user_metrics.h"
+#import "base/metrics/user_metrics_action.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/browser_state_otr_helper.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
@@ -151,14 +153,17 @@
 }
 
 void FullscreenControllerImpl::EnterFullscreen() {
+  base::RecordAction(base::UserMetricsAction("MobileFullscreenEntered"));
   mediator_.EnterFullscreen();
 }
 
 void FullscreenControllerImpl::ExitFullscreen() {
+  base::RecordAction(base::UserMetricsAction("MobileFullscreenExited"));
   mediator_.ExitFullscreen();
 }
 
 void FullscreenControllerImpl::ExitFullscreenWithoutAnimation() {
+  base::RecordAction(base::UserMetricsAction("MobileFullscreenExited"));
   mediator_.ExitFullscreenWithoutAnimation();
 }
 
diff --git a/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm b/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
index 21d5e74..7f22012 100644
--- a/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
+++ b/ios/chrome/browser/ui/fullscreen/fullscreen_model.mm
@@ -222,6 +222,9 @@
 }
 
 void FullscreenModel::SetScrollViewIsScrolling(bool scrolling) {
+  if (base::FeatureList::IsEnabled(kDisableFullscreenScrolling)) {
+    return;
+  }
   if (scrolling_ == scrolling)
     return;
   scrolling_ = scrolling;
@@ -255,6 +258,9 @@
 }
 
 void FullscreenModel::SetScrollViewIsDragging(bool dragging) {
+  if (base::FeatureList::IsEnabled(kDisableFullscreenScrolling)) {
+    return;
+  }
   if (dragging_ == dragging)
     return;
   dragging_ = dragging;
diff --git a/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h b/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h
index 21c8760f..6090eaf 100644
--- a/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h
+++ b/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.h
@@ -48,7 +48,7 @@
   bool IsDefaultSearchProviderEnabled() const override;
   SessionID GetSessionID() const override;
   PrefService* GetPrefs() override;
-  bookmarks::BookmarkModel* GetBookmarkModel() override;
+  bookmarks::CoreBookmarkModel* GetBookmarkModel() override;
   AutocompleteControllerEmitter* GetAutocompleteControllerEmitter() override;
   TemplateURLService* GetTemplateURLService() override;
   const AutocompleteSchemeClassifier& GetSchemeClassifier() const override;
diff --git a/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.mm b/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.mm
index 1ab14eb..0981b1ab 100644
--- a/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.mm
+++ b/ios/chrome/browser/ui/omnibox/chrome_omnibox_client_ios.mm
@@ -22,9 +22,8 @@
 #import "ios/chrome/browser/autocomplete/model/autocomplete_classifier_factory.h"
 #import "ios/chrome/browser/autocomplete/model/autocomplete_provider_client_impl.h"
 #import "ios/chrome/browser/autocomplete/model/shortcuts_backend_factory.h"
+#import "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"
 #import "ios/chrome/browser/bookmarks/model/bookmarks_utils.h"
-#import "ios/chrome/browser/bookmarks/model/legacy_bookmark_model.h"
-#import "ios/chrome/browser/bookmarks/model/local_or_syncable_bookmark_model_factory.h"
 #import "ios/chrome/browser/default_browser/model/utils.h"
 #import "ios/chrome/browser/https_upgrades/model/https_upgrade_service_factory.h"
 #import "ios/chrome/browser/intents/intents_donation_helper.h"
@@ -94,9 +93,8 @@
   return browser_state_->GetPrefs();
 }
 
-bookmarks::BookmarkModel* ChromeOmniboxClientIOS::GetBookmarkModel() {
-  return ios::LocalOrSyncableBookmarkModelFactory::GetForBrowserState(
-      browser_state_);
+bookmarks::CoreBookmarkModel* ChromeOmniboxClientIOS::GetBookmarkModel() {
+  return ios::BookmarkModelFactory::GetForBrowserState(browser_state_);
 }
 
 AutocompleteControllerEmitter*
diff --git a/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.mm b/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.mm
index 1f1796d..589c1e4 100644
--- a/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/autofill/autofill_credit_card_edit_table_view_controller.mm
@@ -29,7 +29,6 @@
 #import "ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h"
 #import "ios/chrome/browser/ui/settings/autofill/autofill_constants.h"
 #import "ios/chrome/browser/ui/settings/autofill/autofill_credit_card_util.h"
-#import "ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h"
 #import "ios/chrome/grit/ios_branded_strings.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ui/base/l10n/l10n_util.h"
@@ -40,7 +39,6 @@
 
 typedef NS_ENUM(NSInteger, SectionIdentifier) {
   SectionIdentifierFields = kSectionIdentifierEnumZero,
-  SectionIdentifierCopiedToChrome,
 };
 
 typedef NS_ENUM(NSInteger, ItemType) {
@@ -48,7 +46,6 @@
   ItemTypeCardNumber,
   ItemTypeExpirationMonth,
   ItemTypeExpirationYear,
-  ItemTypeCopiedToChrome,
   ItemTypeNickname,
 };
 
@@ -95,8 +92,6 @@
 - (void)editButtonPressed {
   // In the case of server cards, open the Payments editing page instead.
   if (_creditCard.record_type() ==
-          autofill::CreditCard::RecordType::kFullServerCard ||
-      _creditCard.record_type() ==
           autofill::CreditCard::RecordType::kMaskedServerCard) {
     GURL paymentsURL = autofill::payments::GetManageInstrumentsUrl();
     OpenNewTabCommand* command =
@@ -170,16 +165,6 @@
   for (AutofillEditItem* item in editItems) {
     [model addItem:item toSectionWithIdentifier:SectionIdentifierFields];
   }
-
-  if (_creditCard.record_type() ==
-      autofill::CreditCard::RecordType::kFullServerCard) {
-    // Add CopiedToChrome cell in its own section.
-    [model addSectionWithIdentifier:SectionIdentifierCopiedToChrome];
-    CopiedToChromeItem* copiedToChromeItem =
-        [[CopiedToChromeItem alloc] initWithType:ItemTypeCopiedToChrome];
-    [model addItem:copiedToChromeItem
-        toSectionWithIdentifier:SectionIdentifierCopiedToChrome];
-  }
 }
 
 #pragma mark - TableViewTextEditItemDelegate
@@ -280,14 +265,6 @@
     case ItemTypeExpirationYear:
     case ItemTypeNickname:
       break;
-    case ItemTypeCopiedToChrome: {
-      CopiedToChromeCell* copiedToChromeCell =
-          base::apple::ObjCCastStrict<CopiedToChromeCell>(cell);
-      [copiedToChromeCell.button addTarget:self
-                                    action:@selector(buttonTapped:)
-                          forControlEvents:UIControlEventTouchUpInside];
-      break;
-    }
     default:
       break;
   }
@@ -331,7 +308,6 @@
     case ItemTypeExpirationMonth:
     case ItemTypeExpirationYear:
     case ItemTypeNickname:
-    case ItemTypeCopiedToChrome:
       return YES;
   }
   NOTREACHED();
diff --git a/ios/chrome/browser/ui/settings/cells/BUILD.gn b/ios/chrome/browser/ui/settings/cells/BUILD.gn
index 6b375d1..bdaad12 100644
--- a/ios/chrome/browser/ui/settings/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/cells/BUILD.gn
@@ -8,8 +8,6 @@
     "account_sign_in_item.mm",
     "byo_textfield_item.h",
     "byo_textfield_item.mm",
-    "copied_to_chrome_item.h",
-    "copied_to_chrome_item.mm",
     "inline_promo_cell.h",
     "inline_promo_cell.mm",
     "inline_promo_item.h",
@@ -81,7 +79,6 @@
   testonly = true
   sources = [
     "byo_textfield_item_unittest.mm",
-    "copied_to_chrome_item_unittest.mm",
     "inline_promo_item_unittest.mm",
     "passphrase_error_item_unittest.mm",
     "settings_check_item_unittest.mm",
diff --git a/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h b/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h
deleted file mode 100644
index 403426f6..0000000
--- a/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_COPIED_TO_CHROME_ITEM_H_
-#define IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_COPIED_TO_CHROME_ITEM_H_
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_item.h"
-
-// Item that configures a CopiedToChromeCell.
-@interface CopiedToChromeItem : TableViewItem
-@end
-
-// A cell indicating that the credit card has been copied to Chrome. Includes a
-// button to clear the copy.
-@interface CopiedToChromeCell : TableViewCell
-
-// Text label displaying the item's text.
-@property(nonatomic, readonly, strong) UILabel* textLabel;
-
-// Button to clear the copy.
-@property(nonatomic, readonly, strong) UIButton* button;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_COPIED_TO_CHROME_ITEM_H_
diff --git a/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.mm b/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.mm
deleted file mode 100644
index 60da100..0000000
--- a/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.mm
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h"
-
-#import "components/strings/grit/components_strings.h"
-#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui/colors/semantic_color_names.h"
-#import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
-#import "ios/chrome/common/ui/util/constraints_ui_util.h"
-#import "ios/chrome/grit/ios_branded_strings.h"
-#import "ui/base/l10n/l10n_util_mac.h"
-
-@implementation CopiedToChromeItem
-
-- (instancetype)initWithType:(NSInteger)type {
-  self = [super initWithType:type];
-  if (self) {
-    self.cellClass = [CopiedToChromeCell class];
-  }
-  return self;
-}
-
-@end
-
-@implementation CopiedToChromeCell
-
-@synthesize textLabel = _textLabel;
-
-- (instancetype)initWithStyle:(UITableViewCellStyle)style
-              reuseIdentifier:(NSString*)reuseIdentifier {
-  self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
-  if (self) {
-    UIView* contentView = self.contentView;
-
-    _textLabel = [[UILabel alloc] init];
-    _textLabel.translatesAutoresizingMaskIntoConstraints = NO;
-    _textLabel.text =
-        l10n_util::GetNSString(IDS_IOS_AUTOFILL_DESCRIBE_LOCAL_COPY);
-    _textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
-    _textLabel.adjustsFontForContentSizeCategory = YES;
-    _textLabel.textColor = [UIColor colorNamed:kTextPrimaryColor];
-    [_textLabel
-        setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
-                                        forAxis:
-                                            UILayoutConstraintAxisHorizontal];
-    [contentView addSubview:_textLabel];
-
-    _button = [UIButton buttonWithType:UIButtonTypeCustom];
-    [_button setTitleColor:[UIColor colorNamed:kBlueColor]
-                  forState:UIControlStateNormal];
-
-    _button.translatesAutoresizingMaskIntoConstraints = NO;
-    [_button
-        setTitle:l10n_util::GetNSString(IDS_AUTOFILL_REMOVE_LOCAL_COPY_BUTTON)
-        forState:UIControlStateNormal];
-    [contentView addSubview:_button];
-
-    // Set up the constraints.
-    [NSLayoutConstraint activateConstraints:@[
-      [_textLabel.leadingAnchor
-          constraintEqualToAnchor:contentView.leadingAnchor
-                         constant:kTableViewHorizontalSpacing],
-      [_textLabel.trailingAnchor
-          constraintLessThanOrEqualToAnchor:_button.leadingAnchor
-                                   constant:-kTableViewHorizontalSpacing],
-      [_textLabel.centerYAnchor
-          constraintEqualToAnchor:contentView.centerYAnchor],
-      [_button.trailingAnchor
-          constraintEqualToAnchor:contentView.trailingAnchor
-                         constant:-kTableViewHorizontalSpacing],
-      [_button.firstBaselineAnchor
-          constraintEqualToAnchor:_textLabel.firstBaselineAnchor],
-    ]];
-    AddOptionalVerticalPadding(contentView, _textLabel,
-                               kTableViewOneLabelCellVerticalSpacing);
-  }
-  return self;
-}
-
-- (void)prepareForReuse {
-  [super prepareForReuse];
-  [self.button removeTarget:nil
-                     action:nil
-           forControlEvents:UIControlEventAllEvents];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item_unittest.mm b/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item_unittest.mm
deleted file mode 100644
index cb9356fcf..0000000
--- a/ios/chrome/browser/ui/settings/cells/copied_to_chrome_item_unittest.mm
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h"
-
-#import "components/strings/grit/components_strings.h"
-#import "ios/chrome/grit/ios_branded_strings.h"
-#import "testing/gtest/include/gtest/gtest.h"
-#import "testing/gtest_mac.h"
-#import "testing/platform_test.h"
-#import "ui/base/l10n/l10n_util_mac.h"
-
-namespace {
-
-using CopiedToChromeItemTest = PlatformTest;
-
-// Tests that the cell created out of a CopiedToChromeItem is set up properly.
-TEST_F(CopiedToChromeItemTest, InitializeCell) {
-  CopiedToChromeItem* item = [[CopiedToChromeItem alloc] initWithType:0];
-
-  id cell = [[[item cellClass] alloc] init];
-  ASSERT_TRUE([cell isMemberOfClass:[CopiedToChromeCell class]]);
-
-  CopiedToChromeCell* copiedToChromeCell = cell;
-  EXPECT_NSEQ(l10n_util::GetNSString(IDS_IOS_AUTOFILL_DESCRIBE_LOCAL_COPY),
-              copiedToChromeCell.textLabel.text);
-
-  NSString* buttonText =
-      l10n_util::GetNSString(IDS_AUTOFILL_REMOVE_LOCAL_COPY_BUTTON);
-  EXPECT_NSEQ(buttonText,
-              [copiedToChromeCell.button titleForState:UIControlStateNormal]);
-}
-
-}  // namespace
diff --git a/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm b/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
index 12ab7a52..a72d2e59 100644
--- a/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
@@ -35,7 +35,6 @@
 #import "ios/chrome/browser/ui/autofill/cells/autofill_edit_item.h"
 #import "ios/chrome/browser/ui/settings/address_bar_preference/cells/address_bar_options_item.h"
 #import "ios/chrome/browser/ui/settings/cells/account_sign_in_item.h"
-#import "ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h"
 #import "ios/chrome/browser/ui/settings/cells/inline_promo_item.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_check_cell.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_check_item.h"
@@ -574,11 +573,6 @@
   [model setHeader:autofillHeader
       forSectionWithIdentifier:SectionIdentifierAutofill];
 
-  CopiedToChromeItem* copiedToChrome =
-      [[CopiedToChromeItem alloc] initWithType:ItemTypeAutofillData];
-  [model addItem:copiedToChrome
-      toSectionWithIdentifier:SectionIdentifierAutofill];
-
   // SectionIdentifierAccount.
   UIImage* signinPromoAvatar = ios::provider::GetSigninDefaultAvatar();
   CGSize avatarSize =
diff --git a/media/audio/audio_device_thread.cc b/media/audio/audio_device_thread.cc
index d000377..fac2d9b 100644
--- a/media/audio/audio_device_thread.cc
+++ b/media/audio/audio_device_thread.cc
@@ -63,6 +63,7 @@
 }
 
 AudioDeviceThread::~AudioDeviceThread() {
+  in_shutdown_.Set();
   socket_.Shutdown();
   if (thread_handle_.is_null())
     return;
@@ -112,6 +113,10 @@
     if (bytes_sent != sizeof(buffer_index))
       break;
   }
+
+  if (!in_shutdown_.IsSet()) {
+    callback_->OnSocketError();
+  }
 }
 
 }  // namespace media.
diff --git a/media/audio/audio_device_thread.h b/media/audio/audio_device_thread.h
index c613f2e..0e94d537 100644
--- a/media/audio/audio_device_thread.h
+++ b/media/audio/audio_device_thread.h
@@ -9,6 +9,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/sync_socket.h"
+#include "base/synchronization/atomic_flag.h"
 #include "base/threading/platform_thread.h"
 #include "base/threading/thread_checker.h"
 #include "build/buildflag.h"
@@ -46,6 +47,9 @@
     // Called whenever we receive notifications about pending input data.
     virtual void Process(uint32_t pending_data) = 0;
 
+    // Called if the socket closes outside of destruction.
+    virtual void OnSocketError() = 0;
+
     base::TimeDelta buffer_duration() const {
       return audio_parameters_.GetBufferDuration();
     }
@@ -88,6 +92,9 @@
 #endif
   void ThreadMain() final;
 
+  // Set to true in destruction, but before closing the socket.
+  base::AtomicFlag in_shutdown_;
+
   const raw_ptr<Callback> callback_;
   const char* thread_name_;
   base::CancelableSyncSocket socket_;
diff --git a/media/audio/audio_input_device.cc b/media/audio/audio_input_device.cc
index 9677510..6ebd6099b 100644
--- a/media/audio/audio_input_device.cc
+++ b/media/audio/audio_input_device.cc
@@ -82,6 +82,8 @@
   // Called whenever we receive notifications about pending data.
   void Process(uint32_t pending_data) override;
 
+  void OnSocketError() override;
+
  private:
   const bool enable_uma_;
   base::ReadOnlySharedMemoryRegion shared_memory_region_;
@@ -495,4 +497,10 @@
       "now_time (ms)", (now_time - base::TimeTicks()).InMillisecondsF());
 }
 
+void AudioInputDevice::AudioThreadCallback::OnSocketError() {
+  capture_callback_->OnCaptureError(
+      AudioCapturerSource::ErrorCode::kSocketError,
+      "Socket closed unexpectedly");
+}
+
 }  // namespace media
diff --git a/media/audio/audio_input_device_unittest.cc b/media/audio/audio_input_device_unittest.cc
index ba432d9..93726c9 100644
--- a/media/audio/audio_input_device_unittest.cc
+++ b/media/audio/audio_input_device_unittest.cc
@@ -13,6 +13,7 @@
 #include "base/sync_socket.h"
 #include "base/synchronization/waitable_event.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/test/gmock_callback_support.h"
 #include "base/test/task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -95,7 +96,63 @@
 }  // namespace.
 
 class AudioInputDeviceTest
-    : public ::testing::TestWithParam<AudioInputDevice::DeadStreamDetection> {};
+    : public ::testing::TestWithParam<AudioInputDevice::DeadStreamDetection> {
+ protected:
+  void CreateInputDevice() {
+    AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
+                           ChannelLayoutConfig::Stereo(), 48000, 480);
+
+    const uint32_t memory_size =
+        ComputeAudioInputBufferSize(params, kMemorySegmentCount);
+
+    shared_memory_ = base::ReadOnlySharedMemoryRegion::Create(memory_size);
+    ASSERT_TRUE(shared_memory_.IsValid());
+    memset(shared_memory_.mapping.memory(), 0xff, memory_size);
+
+    ASSERT_TRUE(
+        CancelableSyncSocket::CreatePair(&browser_socket_, &renderer_socket_));
+
+    MockAudioInputIPC* input_ipc = new MockAudioInputIPC();
+
+    device_ = base::MakeRefCounted<AudioInputDevice>(
+        base::WrapUnique(input_ipc), AudioInputDevice::Purpose::kUserInput,
+        AudioInputDeviceTest::GetParam());
+
+    const base::TimeTicks capture_time =
+        base::TimeTicks() + base::Microseconds(123);
+    // The AssertingCaptureCallback will check that the capture time is correct
+    // upon the call to Capture().
+    capture_callback_.emplace(capture_time);
+    device_->Initialize(params, &capture_callback_.value());
+
+    EXPECT_CALL(*input_ipc, CreateStream(_, _, _, _))
+        .WillOnce(InvokeWithoutArgs([&]() {
+          auto duplicated_shared_memory_region =
+              shared_memory_.region.Duplicate();
+          CHECK(duplicated_shared_memory_region.IsValid());
+          static_cast<AudioInputIPCDelegate*>(device_.get())
+              ->OnStreamCreated(std::move(duplicated_shared_memory_region),
+                                renderer_socket_.Take(), false);
+        }));
+    EXPECT_CALL(*input_ipc, RecordStream());
+    EXPECT_CALL(*capture_callback_, OnCaptureStarted());
+    EXPECT_CALL(*input_ipc, CloseStream());
+
+    uint8_t* ptr = static_cast<uint8_t*>(shared_memory_.mapping.memory());
+    AudioInputBuffer* buffer = reinterpret_cast<AudioInputBuffer*>(ptr);
+    buffer->params.id = 0;
+    buffer->params.capture_time_us =
+        (capture_time - base::TimeTicks()).InMicroseconds();
+    buffer->params.glitch_duration_us = 0;
+    buffer->params.glitch_count = 0;
+  }
+
+  base::MappedReadOnlyRegion shared_memory_;
+  CancelableSyncSocket browser_socket_;
+  CancelableSyncSocket renderer_socket_;
+  std::optional<AssertingCaptureCallback> capture_callback_;
+  scoped_refptr<AudioInputDevice> device_;
+};
 
 // Regular construction.
 TEST_P(AudioInputDeviceTest, Noop) {
@@ -109,7 +166,7 @@
 
 ACTION_P(ReportStateChange, device) {
   static_cast<AudioInputIPCDelegate*>(device)->OnError(
-      media::AudioCapturerSource::ErrorCode::kUnknown);
+      AudioCapturerSource::ErrorCode::kUnknown);
 }
 
 // Verify that we get an OnCaptureError() callback if CreateStream fails.
@@ -133,107 +190,52 @@
 }
 
 TEST_P(AudioInputDeviceTest, CreateStream) {
-  AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
-                         ChannelLayoutConfig::Stereo(), 48000, 480);
-  base::MappedReadOnlyRegion shared_memory;
-  CancelableSyncSocket browser_socket;
-  CancelableSyncSocket renderer_socket;
-
-  const uint32_t memory_size =
-      media::ComputeAudioInputBufferSize(params, kMemorySegmentCount);
-
-  shared_memory = base::ReadOnlySharedMemoryRegion::Create(memory_size);
-  ASSERT_TRUE(shared_memory.IsValid());
-  memset(shared_memory.mapping.memory(), 0xff, memory_size);
-
-  ASSERT_TRUE(
-      CancelableSyncSocket::CreatePair(&browser_socket, &renderer_socket));
-  base::ReadOnlySharedMemoryRegion duplicated_shared_memory_region =
-      shared_memory.region.Duplicate();
-  ASSERT_TRUE(duplicated_shared_memory_region.IsValid());
-
   base::test::TaskEnvironment ste;
-  MockCaptureCallback callback;
-  MockAudioInputIPC* input_ipc = new MockAudioInputIPC();
-  scoped_refptr<AudioInputDevice> device(new AudioInputDevice(
-      base::WrapUnique(input_ipc), AudioInputDevice::Purpose::kUserInput,
-      AudioInputDeviceTest::GetParam()));
-  device->Initialize(params, &callback);
+  CreateInputDevice();
 
-  EXPECT_CALL(*input_ipc, CreateStream(_, _, _, _))
-      .WillOnce(InvokeWithoutArgs([&]() {
-        static_cast<AudioInputIPCDelegate*>(device.get())
-            ->OnStreamCreated(std::move(duplicated_shared_memory_region),
-                              renderer_socket.Take(), false);
-      }));
-  EXPECT_CALL(*input_ipc, RecordStream());
-
-  EXPECT_CALL(callback, OnCaptureStarted());
-  device->Start();
-  EXPECT_CALL(*input_ipc, CloseStream());
-  device->Stop();
+  device_->Start();
+  device_->Stop();
 }
 
 TEST_P(AudioInputDeviceTest, CaptureCallback) {
   base::test::TaskEnvironment ste;
-  AudioParameters params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
-                         ChannelLayoutConfig::Stereo(), 48000, 480);
-  base::MappedReadOnlyRegion shared_memory;
-  CancelableSyncSocket browser_socket;
-  CancelableSyncSocket renderer_socket;
+  CreateInputDevice();
 
-  const uint32_t memory_size =
-      media::ComputeAudioInputBufferSize(params, kMemorySegmentCount);
-
-  shared_memory = base::ReadOnlySharedMemoryRegion::Create(memory_size);
-  ASSERT_TRUE(shared_memory.IsValid());
-  memset(shared_memory.mapping.memory(), 0xff, memory_size);
-
-  ASSERT_TRUE(
-      CancelableSyncSocket::CreatePair(&browser_socket, &renderer_socket));
-  base::ReadOnlySharedMemoryRegion duplicated_shared_memory_region =
-      shared_memory.region.Duplicate();
-  ASSERT_TRUE(duplicated_shared_memory_region.IsValid());
-
-  MockAudioInputIPC* input_ipc = new MockAudioInputIPC();
-  scoped_refptr<AudioInputDevice> device(new AudioInputDevice(
-      base::WrapUnique(input_ipc), AudioInputDevice::Purpose::kUserInput,
-      AudioInputDeviceTest::GetParam()));
-
-  const base::TimeTicks capture_time =
-      base::TimeTicks() + base::Microseconds(123);
-  // The AssertingCaptureCallback will check that the capture time is correct
-  // upon the call to Capture().
-  AssertingCaptureCallback callback(capture_time);
-  device->Initialize(params, &callback);
-
-  EXPECT_CALL(*input_ipc, CreateStream(_, _, _, _))
-      .WillOnce(InvokeWithoutArgs([&]() {
-        static_cast<AudioInputIPCDelegate*>(device.get())
-            ->OnStreamCreated(std::move(duplicated_shared_memory_region),
-                              renderer_socket.Take(), false);
-      }));
-  EXPECT_CALL(*input_ipc, RecordStream());
-  EXPECT_CALL(callback, OnCaptureStarted());
-
-  uint8_t* ptr = static_cast<uint8_t*>(shared_memory.mapping.memory());
-  AudioInputBuffer* buffer = reinterpret_cast<AudioInputBuffer*>(ptr);
-  buffer->params.id = 0;
-  buffer->params.capture_time_us =
-      (capture_time - base::TimeTicks()).InMicroseconds();
-  buffer->params.glitch_duration_us = 0;
-  buffer->params.glitch_count = 0;
   uint32_t buffer_index = 0;
-  browser_socket.Send(&buffer_index, sizeof(buffer_index));
+  browser_socket_.Send(&buffer_index, sizeof(buffer_index));
 
-  device->Start();
+  EXPECT_CALL(*capture_callback_, OnCaptureError(_, _)).Times(0);
+
+  device_->Start();
   ste.RunUntilIdle();
 
   // The capture occurs on another thread, wait for it.
-  callback.WaitForCapture();
+  capture_callback_->WaitForCapture();
 
-  EXPECT_CALL(*input_ipc, CloseStream());
-  device->Stop();
+  device_->Stop();
+}
+
+TEST_P(AudioInputDeviceTest, CaptureCallbackSocketError) {
+  base::test::TaskEnvironment ste;
+  CreateInputDevice();
+
+  uint32_t buffer_index = 0;
+  browser_socket_.Send(&buffer_index, sizeof(buffer_index));
+
+  EXPECT_CALL(*capture_callback_,
+              OnCaptureError(AudioCapturerSource::ErrorCode::kSocketError, _))
+      .WillOnce(base::test::RunClosure(ste.QuitClosure()));
+
+  device_->Start();
+  ste.RunUntilIdle();
+
+  // The capture occurs on another thread, wait for it.
+  capture_callback_->WaitForCapture();
+
+  browser_socket_.Close();
+  ste.RunUntilQuit();
+
+  device_->Stop();
 }
 
 INSTANTIATE_TEST_SUITE_P(
diff --git a/media/audio/audio_output_device_thread_callback.cc b/media/audio/audio_output_device_thread_callback.cc
index 98f54db..4600038 100644
--- a/media/audio/audio_output_device_thread_callback.cc
+++ b/media/audio/audio_output_device_thread_callback.cc
@@ -106,6 +106,10 @@
                    "delay (ms)", delay.InMillisecondsF());
 }
 
+void AudioOutputDeviceThreadCallback::OnSocketError() {
+  render_callback_->OnRenderError();
+}
+
 bool AudioOutputDeviceThreadCallback::CurrentThreadIsAudioDeviceThread() {
   return thread_checker_.CalledOnValidThread();
 }
diff --git a/media/audio/audio_output_device_thread_callback.h b/media/audio/audio_output_device_thread_callback.h
index 904c1fa..63594586 100644
--- a/media/audio/audio_output_device_thread_callback.h
+++ b/media/audio/audio_output_device_thread_callback.h
@@ -39,6 +39,10 @@
   // Called whenever we receive notifications about pending data.
   void Process(uint32_t control_signal) override;
 
+  // Called when the AudioDeviceThread shuts down. Unexpected calls are treated
+  // as errors.
+  void OnSocketError() override;
+
   // Returns whether the current thread is the audio device thread or not.
   // Will always return true if DCHECKs are not enabled.
   bool CurrentThreadIsAudioDeviceThread();
diff --git a/media/audio/audio_output_device_unittest.cc b/media/audio/audio_output_device_unittest.cc
index d094d6e8..11ed572 100644
--- a/media/audio/audio_output_device_unittest.cc
+++ b/media/audio/audio_output_device_unittest.cc
@@ -19,6 +19,8 @@
 #include "base/sync_socket.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/task_runner.h"
+#include "base/test/bind.h"
+#include "base/test/gmock_callback_support.h"
 #include "base/test/task_environment.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -102,6 +104,9 @@
   MOCK_METHOD1(OnDeviceInfoReceived, void(OutputDeviceInfo));
 
  protected:
+  void Render();
+  void CloseBrowserSocket();
+
   MockAudioOutputIPC* audio_output_ipc() {
     return static_cast<MockAudioOutputIPC*>(audio_device_->GetIpcForTesting());
   }
@@ -113,13 +118,12 @@
   OutputDeviceStatus device_status_;
 
  private:
-  int CalculateMemorySize();
-
   // These may need to outlive `audio_device_`.
   UnsafeSharedMemoryRegion shared_memory_region_;
   WritableSharedMemoryMapping shared_memory_mapping_;
   CancelableSyncSocket browser_socket_;
   CancelableSyncSocket renderer_socket_;
+  uint32_t counter_ = 0;
 
  protected:
   scoped_refptr<AudioOutputDevice> audio_device_;
@@ -227,6 +231,15 @@
   task_env_.FastForwardBy(base::TimeDelta());
 }
 
+void AudioOutputDeviceTest::Render() {
+  browser_socket_.Send(&counter_, sizeof(counter_));
+  ++counter_;
+}
+
+void AudioOutputDeviceTest::CloseBrowserSocket() {
+  browser_socket_.Close();
+}
+
 TEST_F(AudioOutputDeviceTest, Initialize) {
   // Tests that the object can be constructed, initialized and destructed
   // without having ever been started.
@@ -262,6 +275,45 @@
   CallOnStreamCreated();
 }
 
+TEST_F(AudioOutputDeviceTest, NoErrorForNormalShutdown) {
+  StartAudioDevice();
+  CallOnStreamCreated();
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(callback_, Render(_, _, _, _))
+      .WillOnce(DoAll(base::test::RunClosure(run_loop.QuitWhenIdleClosure()),
+                      Return(0)))
+      .WillRepeatedly(Return(0));
+
+  EXPECT_CALL(callback_, OnRenderError()).Times(0);
+
+  Render();
+  run_loop.Run();
+
+  StopAudioDevice();
+}
+
+// TODO(crbug.com/327577325) Re-enable this test
+TEST_F(AudioOutputDeviceTest, DISABLED_ErrorFiredForSocketClose) {
+  StartAudioDevice();
+  CallOnStreamCreated();
+
+  base::RunLoop run_loop;
+  EXPECT_CALL(callback_, Render(_, _, _, _))
+      .WillOnce(DoAll(base::test::RunClosure(base::BindLambdaForTesting(
+                          [&]() { CloseBrowserSocket(); })),
+                      Return(0)))
+      .WillRepeatedly(Return(0));
+
+  EXPECT_CALL(callback_, OnRenderError())
+      .WillOnce(base::test::RunClosure(run_loop.QuitWhenIdleClosure()));
+
+  Render();
+  run_loop.Run();
+
+  StopAudioDevice();
+}
+
 // Multiple start/stop with nondefault device
 TEST_F(AudioOutputDeviceTest, NonDefaultStartStopStartStop) {
   SetDevice(kNonDefaultDeviceId);
diff --git a/media/base/audio_capturer_source.h b/media/base/audio_capturer_source.h
index 3745397..0310661c 100644
--- a/media/base/audio_capturer_source.h
+++ b/media/base/audio_capturer_source.h
@@ -31,6 +31,7 @@
     kUnknown = 0,
     kSystemPermissions = 1,
     kDeviceInUse = 2,
+    kSocketError = 3,
   };
 
   class CaptureCallback {
diff --git a/media/base/audio_processing.h b/media/base/audio_processing.h
index 5b129640..1ddea83d 100644
--- a/media/base/audio_processing.h
+++ b/media/base/audio_processing.h
@@ -20,6 +20,7 @@
   bool echo_cancellation = true;
   bool noise_suppression = true;
   // Keytap removal, sometimes called "experimental noise suppression".
+  // TODO(https://webrtc.com/7494): Deprecate this setting.
   bool transient_noise_suppression = true;
   bool automatic_gain_control = true;
   bool high_pass_filter = true;
diff --git a/media/base/audio_renderer_mixer.cc b/media/base/audio_renderer_mixer.cc
index 54cad65..c177a1a 100644
--- a/media/base/audio_renderer_mixer.cc
+++ b/media/base/audio_renderer_mixer.cc
@@ -115,6 +115,11 @@
   pause_delay_ = delay;
 }
 
+bool AudioRendererMixer::HasSinkError() {
+  base::AutoLock auto_lock(lock_);
+  return sink_error_;
+}
+
 int AudioRendererMixer::Render(base::TimeDelta delay,
                                base::TimeTicks delay_timestamp,
                                const AudioGlitchInfo& glitch_info,
@@ -147,6 +152,7 @@
 void AudioRendererMixer::OnRenderError() {
   // Call each mixer input and signal an error.
   base::AutoLock auto_lock(lock_);
+  sink_error_ = true;
   for (AudioRendererMixerInput* input : error_callbacks_) {
     input->OnRenderError();
   }
diff --git a/media/base/audio_renderer_mixer.h b/media/base/audio_renderer_mixer.h
index 037e506..44f512a60 100644
--- a/media/base/audio_renderer_mixer.h
+++ b/media/base/audio_renderer_mixer.h
@@ -56,6 +56,9 @@
     return output_params_;
   }
 
+  // Return true if this mixer has ever received an error from its sink.
+  bool HasSinkError();
+
  private:
   // AudioRendererSink::RenderCallback implementation.
   int Render(base::TimeDelta delay,
@@ -99,6 +102,10 @@
   base::TimeDelta pause_delay_ GUARDED_BY(lock_);
   base::TimeTicks last_play_time_ GUARDED_BY(lock_);
   bool playing_ GUARDED_BY(lock_);
+
+  // Set if the mixer receives an error from the sink. Indicates that this
+  // mixer and sink should no longer be reused.
+  bool sink_error_ GUARDED_BY(lock_) = false;
 };
 
 }  // namespace media
diff --git a/media/base/audio_renderer_mixer_unittest.cc b/media/base/audio_renderer_mixer_unittest.cc
index b59e2700..4219fb61 100644
--- a/media/base/audio_renderer_mixer_unittest.cc
+++ b/media/base/audio_renderer_mixer_unittest.cc
@@ -503,9 +503,13 @@
     EXPECT_CALL(*fake_callbacks_[i], OnRenderError()).Times(1);
   }
 
+  EXPECT_FALSE(mixer_->HasSinkError());
+
   mixer_callback_->OnRenderError();
   for (size_t i = 0; i < mixer_inputs_.size(); ++i)
     mixer_inputs_[i]->Stop();
+
+  EXPECT_TRUE(mixer_->HasSinkError());
 }
 
 TEST_P(AudioRendererMixerBehavioralTest, OnRenderErrorPausedInput) {
diff --git a/media/capture/video/android/video_capture_device_android.cc b/media/capture/video/android/video_capture_device_android.cc
index 52a0c05..112e9dda 100644
--- a/media/capture/video/android/video_capture_device_android.cc
+++ b/media/capture/video/android/video_capture_device_android.cc
@@ -623,7 +623,7 @@
     return;
   client_->OnIncomingCapturedData(
       data, length, capture_format_, capture_color_space_, rotation,
-      false /* flip_y */, reference_time, timestamp);
+      false /* flip_y */, reference_time, timestamp, std::nullopt);
 }
 
 VideoPixelFormat VideoCaptureDeviceAndroid::GetColorspace() {
diff --git a/media/capture/video/apple/video_capture_device_apple.mm b/media/capture/video/apple/video_capture_device_apple.mm
index ba170f42..4de63e40 100644
--- a/media/capture/video/apple/video_capture_device_apple.mm
+++ b/media/capture/video/apple/video_capture_device_apple.mm
@@ -237,7 +237,7 @@
   client_->OnIncomingCapturedData(
       video_frame, video_frame_length, frame_format, color_space,
       rotation /* clockwise_rotation */, false /* flip_y */,
-      base::TimeTicks::Now(), timestamp);
+      base::TimeTicks::Now(), timestamp, std::nullopt);
 }
 
 void VideoCaptureDeviceApple::ReceiveExternalGpuMemoryBufferFrame(
@@ -251,7 +251,7 @@
     return;
   }
   client_->OnIncomingCapturedExternalBuffer(
-      std::move(frame), base::TimeTicks::Now(), timestamp,
+      std::move(frame), base::TimeTicks::Now(), timestamp, std::nullopt,
       gfx::Rect(capture_format_.frame_size));
 }
 
diff --git a/media/capture/video/chromeos/camera_device_context.cc b/media/capture/video/chromeos/camera_device_context.cc
index 10d1f6c..a6d4be8 100644
--- a/media/capture/video/chromeos/camera_device_context.cc
+++ b/media/capture/video/chromeos/camera_device_context.cc
@@ -93,7 +93,8 @@
 
   client->second->OnIncomingCapturedBufferExt(
       std::move(buffer), frame_format, gfx::ColorSpace(), reference_time,
-      timestamp, gfx::Rect(frame_format.frame_size), std::move(metadata));
+      timestamp, std::nullopt, gfx::Rect(frame_format.frame_size),
+      std::move(metadata));
 }
 
 void CameraDeviceContext::SubmitCapturedGpuMemoryBuffer(
@@ -108,9 +109,9 @@
     return;
   }
 
-  client->second->OnIncomingCapturedGfxBuffer(buffer, frame_format,
-                                              GetCameraFrameRotation(),
-                                              reference_time, timestamp);
+  client->second->OnIncomingCapturedGfxBuffer(
+      buffer, frame_format, GetCameraFrameRotation(), reference_time, timestamp,
+      std::nullopt);
 }
 
 void CameraDeviceContext::SetSensorOrientation(int sensor_orientation) {
diff --git a/media/capture/video/chromeos/mock_video_capture_client.cc b/media/capture/video/chromeos/mock_video_capture_client.cc
index 6943af7..ad1f802f 100644
--- a/media/capture/video/chromeos/mock_video_capture_client.cc
+++ b/media/capture/video/chromeos/mock_video_capture_client.cc
@@ -47,6 +47,7 @@
     bool flip_y,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_time,
     int frame_feedback_id) {
   ASSERT_GT(length, 0);
   ASSERT_TRUE(data);
@@ -60,6 +61,7 @@
     int clockwise_rotation,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_time,
     int frame_feedback_id) {
   ASSERT_TRUE(buffer);
   ASSERT_GT(buffer->GetSize().width() * buffer->GetSize().height(), 0);
@@ -71,6 +73,7 @@
     CapturedExternalVideoBuffer buffer,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_time,
     const gfx::Rect& visible_rect) {
   if (frame_cb_)
     std::move(frame_cb_).Run();
@@ -92,7 +95,8 @@
     Buffer buffer,
     const VideoCaptureFormat& format,
     base::TimeTicks reference_time,
-    base::TimeDelta timestamp) {
+    base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_time) {
   DoOnIncomingCapturedBuffer();
 }
 
@@ -102,6 +106,7 @@
     const gfx::ColorSpace& color_space,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_time,
     gfx::Rect visible_rect,
     const VideoFrameMetadata& additional_metadata) {
   DoOnIncomingCapturedVideoFrame();
diff --git a/media/capture/video/chromeos/mock_video_capture_client.h b/media/capture/video/chromeos/mock_video_capture_client.h
index 5a1d72d..e5280e25 100644
--- a/media/capture/video/chromeos/mock_video_capture_client.h
+++ b/media/capture/video/chromeos/mock_video_capture_client.h
@@ -48,33 +48,40 @@
                               bool flip_y,
                               base::TimeTicks reference_time,
                               base::TimeDelta timestamp,
+                              std::optional<base::TimeTicks> capture_begin_time,
                               int frame_feedback_id) override;
-  void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
-                                   const VideoCaptureFormat& frame_format,
-                                   int clockwise_rotation,
-                                   base::TimeTicks reference_time,
-                                   base::TimeDelta timestamp,
-                                   int frame_feedback_id) override;
+  void OnIncomingCapturedGfxBuffer(
+      gfx::GpuMemoryBuffer* buffer,
+      const VideoCaptureFormat& frame_format,
+      int clockwise_rotation,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
+      int frame_feedback_id) override;
   void OnIncomingCapturedExternalBuffer(
       CapturedExternalVideoBuffer buffer,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
       const gfx::Rect& visible_rect) override;
   // Trampoline methods to workaround GMOCK problems with std::unique_ptr<>.
   ReserveResult ReserveOutputBuffer(const gfx::Size& dimensions,
                                     VideoPixelFormat format,
                                     int frame_feedback_id,
                                     Buffer* buffer) override;
-  void OnIncomingCapturedBuffer(Buffer buffer,
-                                const VideoCaptureFormat& format,
-                                base::TimeTicks reference_time,
-                                base::TimeDelta timestamp) override;
+  void OnIncomingCapturedBuffer(
+      Buffer buffer,
+      const VideoCaptureFormat& format,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time) override;
   void OnIncomingCapturedBufferExt(
       Buffer buffer,
       const VideoCaptureFormat& format,
       const gfx::ColorSpace& color_space,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
       gfx::Rect visible_rect,
       const VideoFrameMetadata& additional_metadata) override;
 
diff --git a/media/capture/video/fake_video_capture_device.cc b/media/capture/video/fake_video_capture_device.cc
index cc1f9b9..6e74a92 100644
--- a/media/capture/video/fake_video_capture_device.cc
+++ b/media/capture/video/fake_video_capture_device.cc
@@ -806,7 +806,7 @@
       buffer_.get(), frame_size, device_state()->format,
       GetDefaultColorSpace(device_state()->format.pixel_format),
       0 /* rotation */, false /* flip_y */, now,
-      CalculateTimeSinceFirstInvocation(now));
+      CalculateTimeSinceFirstInvocation(now), std::nullopt);
 }
 
 ClientBufferFrameDeliverer::ClientBufferFrameDeliverer(
@@ -840,9 +840,9 @@
   buffer_access.reset();  // Can't outlive `capture_buffer.handle_provider'.
 
   base::TimeTicks now = base::TimeTicks::Now();
-  client()->OnIncomingCapturedBuffer(std::move(capture_buffer),
-                                     device_state()->format, now,
-                                     CalculateTimeSinceFirstInvocation(now));
+  client()->OnIncomingCapturedBuffer(
+      std::move(capture_buffer), device_state()->format, now,
+      CalculateTimeSinceFirstInvocation(now), std::nullopt);
 }
 
 JpegEncodingFrameDeliverer::JpegEncodingFrameDeliverer(
@@ -881,7 +881,7 @@
   client()->OnIncomingCapturedData(
       &jpeg_buffer_[0], frame_size, device_state()->format,
       gfx::ColorSpace::CreateJpeg(), 0 /* rotation */, false /* flip_y */, now,
-      CalculateTimeSinceFirstInvocation(now));
+      CalculateTimeSinceFirstInvocation(now), std::nullopt);
 }
 
 GpuMemoryBufferFrameDeliverer::GpuMemoryBufferFrameDeliverer(
@@ -936,9 +936,9 @@
   // When GpuMemoryBuffer is used, the frame data is opaque to the CPU for most
   // of the time.  Currently the only supported underlying format is NV12.
   modified_format.pixel_format = PIXEL_FORMAT_NV12;
-  client()->OnIncomingCapturedBuffer(std::move(capture_buffer), modified_format,
-                                     now,
-                                     CalculateTimeSinceFirstInvocation(now));
+  client()->OnIncomingCapturedBuffer(
+      std::move(capture_buffer), modified_format, now,
+      CalculateTimeSinceFirstInvocation(now), std::nullopt);
 }
 
 void FakeVideoCaptureDevice::BeepAndScheduleNextCapture(
diff --git a/media/capture/video/file_video_capture_device.cc b/media/capture/video/file_video_capture_device.cc
index 3dfb02f..dd7af09f 100644
--- a/media/capture/video/file_video_capture_device.cc
+++ b/media/capture/video/file_video_capture_device.cc
@@ -5,6 +5,7 @@
 #include "media/capture/video/file_video_capture_device.h"
 
 #include <stddef.h>
+#include <optional>
 
 #include <algorithm>
 #include <memory>
@@ -692,16 +693,16 @@
     // NV12.
     VideoCaptureFormat gmb_format = ptz_format;
     gmb_format.pixel_format = PIXEL_FORMAT_NV12;
-    client_->OnIncomingCapturedBuffer(std::move(capture_buffer), gmb_format,
-                                      current_time,
-                                      current_time - first_ref_time_);
+    client_->OnIncomingCapturedBuffer(
+        std::move(capture_buffer), gmb_format, current_time,
+        current_time - first_ref_time_, std::nullopt);
   } else {
     // Leave the color space unset for compatibility purposes but this
     // information should be retrieved from the container when possible.
     client_->OnIncomingCapturedData(
         ptz_frame.data(), ptz_frame.size(), ptz_format, gfx::ColorSpace(),
         0 /* clockwise_rotation */, false /* flip_y */, current_time,
-        current_time - first_ref_time_);
+        current_time - first_ref_time_, std::nullopt);
   }
 
   // Process waiting photo callbacks
diff --git a/media/capture/video/fuchsia/video_capture_device_fuchsia.cc b/media/capture/video/fuchsia/video_capture_device_fuchsia.cc
index 949aa56..0fcf392 100644
--- a/media/capture/video/fuchsia/video_capture_device_fuchsia.cc
+++ b/media/capture/video/fuchsia/video_capture_device_fuchsia.cc
@@ -432,7 +432,7 @@
 
   client_->OnIncomingCapturedBufferExt(
       std::move(buffer), capture_format, gfx::ColorSpace(), reference_time,
-      timestamp, gfx::Rect(visible_size), VideoFrameMetadata());
+      timestamp, std::nullopt, gfx::Rect(visible_size), VideoFrameMetadata());
 
   // Frame buffer is returned to the device by dropping the |frame_info|.
 }
diff --git a/media/capture/video/fuchsia/video_capture_device_fuchsia_test.cc b/media/capture/video/fuchsia/video_capture_device_fuchsia_test.cc
index 163857b2..a886a1c 100644
--- a/media/capture/video/fuchsia/video_capture_device_fuchsia_test.cc
+++ b/media/capture/video/fuchsia/video_capture_device_fuchsia_test.cc
@@ -128,6 +128,7 @@
       const gfx::ColorSpace& color_space,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
       gfx::Rect visible_rect,
       const VideoFrameMetadata& additional_metadata) override {
     EXPECT_TRUE(started_);
@@ -148,28 +149,34 @@
                               bool flip_y,
                               base::TimeTicks reference_time,
                               base::TimeDelta timestamp,
+                              std::optional<base::TimeTicks> capture_begin_time,
                               int frame_feedback_id) override {
     NOTREACHED_NORETURN();
   }
-  void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
-                                   const VideoCaptureFormat& frame_format,
-                                   int clockwise_rotation,
-                                   base::TimeTicks reference_time,
-                                   base::TimeDelta timestamp,
-                                   int frame_feedback_id) override {
+  void OnIncomingCapturedGfxBuffer(
+      gfx::GpuMemoryBuffer* buffer,
+      const VideoCaptureFormat& frame_format,
+      int clockwise_rotation,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
+      int frame_feedback_id) override {
     NOTREACHED_NORETURN();
   }
   void OnIncomingCapturedExternalBuffer(
       CapturedExternalVideoBuffer buffer,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
       const gfx::Rect& visible_rect) override {
     NOTREACHED_NORETURN();
   }
-  void OnIncomingCapturedBuffer(Buffer buffer,
-                                const VideoCaptureFormat& format,
-                                base::TimeTicks reference_time,
-                                base::TimeDelta timestamp) override {
+  void OnIncomingCapturedBuffer(
+      Buffer buffer,
+      const VideoCaptureFormat& format,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time) override {
     NOTREACHED_NORETURN();
   }
   void OnError(VideoCaptureError error,
diff --git a/media/capture/video/linux/v4l2_capture_delegate.cc b/media/capture/video/linux/v4l2_capture_delegate.cc
index 973d24c..70defc27 100644
--- a/media/capture/video/linux/v4l2_capture_delegate.cc
+++ b/media/capture/video/linux/v4l2_capture_delegate.cc
@@ -1161,7 +1161,7 @@
         client_->OnIncomingCapturedData(
             buffer_tracker->start(), buffer_tracker->payload_size(),
             capture_format_, gfx::ColorSpace(), rotation_, false /* flip_y */,
-            now, timestamp);
+            now, timestamp, std::nullopt);
     }
 
     while (!take_photo_callbacks_.empty()) {
diff --git a/media/capture/video/linux/v4l2_capture_delegate_gpu_helper.cc b/media/capture/video/linux/v4l2_capture_delegate_gpu_helper.cc
index dbe13cc1..acd8f5d 100644
--- a/media/capture/video/linux/v4l2_capture_delegate_gpu_helper.cc
+++ b/media/capture/video/linux/v4l2_capture_delegate_gpu_helper.cc
@@ -158,8 +158,8 @@
       std::move(capture_buffer),
       VideoCaptureFormat(dimensions, capture_format.frame_rate,
                          kTargetPixelFormat),
-      gfx::ColorSpace(), reference_time, timestamp, gfx::Rect(dimensions),
-      VideoFrameMetadata());
+      gfx::ColorSpace(), reference_time, timestamp, std::nullopt,
+      gfx::Rect(dimensions), VideoFrameMetadata());
   return status;
 }
 
diff --git a/media/capture/video/linux/v4l2_capture_delegate_gpu_helper_unittest.cc b/media/capture/video/linux/v4l2_capture_delegate_gpu_helper_unittest.cc
index a0a13662..0f8a0ae 100644
--- a/media/capture/video/linux/v4l2_capture_delegate_gpu_helper_unittest.cc
+++ b/media/capture/video/linux/v4l2_capture_delegate_gpu_helper_unittest.cc
@@ -34,19 +34,23 @@
                               bool flip_y,
                               base::TimeTicks reference_time,
                               base::TimeDelta timestamp,
+                              std::optional<base::TimeTicks> capture_begin_time,
                               int frame_feedback_id = 0) override {}
 
-  void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
-                                   const VideoCaptureFormat& frame_format,
-                                   int clockwise_rotation,
-                                   base::TimeTicks reference_time,
-                                   base::TimeDelta timestamp,
-                                   int frame_feedback_id = 0) override {}
+  void OnIncomingCapturedGfxBuffer(
+      gfx::GpuMemoryBuffer* buffer,
+      const VideoCaptureFormat& frame_format,
+      int clockwise_rotation,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
+      int frame_feedback_id = 0) override {}
 
   void OnIncomingCapturedExternalBuffer(
       CapturedExternalVideoBuffer buffer,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
       const gfx::Rect& visible_rect) override {}
 
   void OnCaptureConfigurationChanged() override {}
@@ -54,17 +58,20 @@
   MOCK_METHOD4(ReserveOutputBuffer,
                ReserveResult(const gfx::Size&, VideoPixelFormat, int, Buffer*));
 
-  void OnIncomingCapturedBuffer(Buffer buffer,
-                                const VideoCaptureFormat& format,
-                                base::TimeTicks reference_,
-                                base::TimeDelta timestamp) override {}
+  void OnIncomingCapturedBuffer(
+      Buffer buffer,
+      const VideoCaptureFormat& format,
+      base::TimeTicks reference_,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time) override {}
 
-  MOCK_METHOD7(OnIncomingCapturedBufferExt,
+  MOCK_METHOD8(OnIncomingCapturedBufferExt,
                void(Buffer,
                     const VideoCaptureFormat&,
                     const gfx::ColorSpace&,
                     base::TimeTicks,
                     base::TimeDelta,
+                    std::optional<base::TimeTicks> capture_begin_time,
                     gfx::Rect,
                     const VideoFrameMetadata&));
 
@@ -210,7 +217,7 @@
     }
   }
 
-  EXPECT_CALL(client, ReserveOutputBuffer(_, _, _, _))
+  EXPECT_CALL(client, ReserveOutputBuffer)
       .WillRepeatedly(
           Invoke([](const gfx::Size& size, VideoPixelFormat pixel_format,
                     int feedback_id,
@@ -242,7 +249,7 @@
   std::unique_ptr<std::vector<uint8_t>> sample = ReadSampleData(capture_format);
   MockV4l2GpuClient client;
 
-  EXPECT_CALL(client, ReserveOutputBuffer(_, _, _, _))
+  EXPECT_CALL(client, ReserveOutputBuffer)
       .WillRepeatedly(
           Invoke([](const gfx::Size& size, VideoPixelFormat pixel_format,
                     int feedback_id,
@@ -273,7 +280,7 @@
   std::unique_ptr<std::vector<uint8_t>> sample = ReadSampleData(capture_format);
   MockV4l2GpuClient client;
 
-  EXPECT_CALL(client, ReserveOutputBuffer(_, _, _, _))
+  EXPECT_CALL(client, ReserveOutputBuffer)
       .WillRepeatedly(
           Invoke([](const gfx::Size& size, VideoPixelFormat pixel_format,
                     int feedback_id,
@@ -307,7 +314,7 @@
   std::unique_ptr<std::vector<uint8_t>> sample = ReadSampleData(capture_format);
   MockV4l2GpuClient client;
 
-  EXPECT_CALL(client, ReserveOutputBuffer(_, _, _, _))
+  EXPECT_CALL(client, ReserveOutputBuffer)
       .WillRepeatedly(
           Invoke([](const gfx::Size& size, VideoPixelFormat pixel_format,
                     int feedback_id,
@@ -318,7 +325,7 @@
                     size, gfx::BufferFormat::YUV_420_BIPLANAR);
             return VideoCaptureDevice::Client::ReserveResult::kSucceeded;
           }));
-  EXPECT_CALL(client, OnIncomingCapturedBufferExt(_, _, _, _, _, _, _))
+  EXPECT_CALL(client, OnIncomingCapturedBufferExt)
       .WillRepeatedly(InvokeWithoutArgs([]() {}));
 
   int status = v4l2_gpu_helper_->OnIncomingCapturedData(
@@ -338,7 +345,7 @@
       ReadSampleData(capture_format);
   MockV4l2GpuClient client;
 
-  EXPECT_CALL(client, ReserveOutputBuffer(_, _, _, _))
+  EXPECT_CALL(client, ReserveOutputBuffer)
       .WillRepeatedly(
           Invoke([](const gfx::Size& size, VideoPixelFormat pixel_format,
                     int feedback_id,
@@ -349,7 +356,7 @@
                     size, gfx::BufferFormat::YUV_420_BIPLANAR);
             return VideoCaptureDevice::Client::ReserveResult::kSucceeded;
           }));
-  EXPECT_CALL(client, OnIncomingCapturedBufferExt(_, _, _, _, _, _, _))
+  EXPECT_CALL(client, OnIncomingCapturedBufferExt)
       .WillRepeatedly(InvokeWithoutArgs([]() {}));
 
   int status = v4l2_gpu_helper_->OnIncomingCapturedData(
diff --git a/media/capture/video/linux/v4l2_capture_delegate_unittest.cc b/media/capture/video/linux/v4l2_capture_delegate_unittest.cc
index eda5dbdd..a119235 100644
--- a/media/capture/video/linux/v4l2_capture_delegate_unittest.cc
+++ b/media/capture/video/linux/v4l2_capture_delegate_unittest.cc
@@ -248,19 +248,23 @@
                               bool flip_y,
                               base::TimeTicks reference_time,
                               base::TimeDelta timestamp,
+                              std::optional<base::TimeTicks> capture_begin_time,
                               int frame_feedback_id = 0) override {}
 
-  void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
-                                   const VideoCaptureFormat& frame_format,
-                                   int clockwise_rotation,
-                                   base::TimeTicks reference_time,
-                                   base::TimeDelta timestamp,
-                                   int frame_feedback_id = 0) override {}
+  void OnIncomingCapturedGfxBuffer(
+      gfx::GpuMemoryBuffer* buffer,
+      const VideoCaptureFormat& frame_format,
+      int clockwise_rotation,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
+      int frame_feedback_id = 0) override {}
 
   void OnIncomingCapturedExternalBuffer(
       CapturedExternalVideoBuffer buffer,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
       const gfx::Rect& visible_rect) override {}
 
   void OnCaptureConfigurationChanged() override {}
@@ -268,17 +272,20 @@
   MOCK_METHOD4(ReserveOutputBuffer,
                ReserveResult(const gfx::Size&, VideoPixelFormat, int, Buffer*));
 
-  void OnIncomingCapturedBuffer(Buffer buffer,
-                                const VideoCaptureFormat& format,
-                                base::TimeTicks reference_,
-                                base::TimeDelta timestamp) override {}
+  void OnIncomingCapturedBuffer(
+      Buffer buffer,
+      const VideoCaptureFormat& format,
+      base::TimeTicks reference_,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time) override {}
 
-  MOCK_METHOD7(OnIncomingCapturedBufferExt,
+  MOCK_METHOD8(OnIncomingCapturedBufferExt,
                void(Buffer,
                     const VideoCaptureFormat&,
                     const gfx::ColorSpace&,
                     base::TimeTicks,
                     base::TimeDelta,
+                    std::optional<base::TimeTicks>,
                     gfx::Rect,
                     const VideoFrameMetadata&));
 
@@ -392,7 +399,7 @@
 
     base::RunLoop run_loop;
     base::RepeatingClosure quit_closure = run_loop.QuitClosure();
-    EXPECT_CALL(*client_ptr, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+    EXPECT_CALL(*client_ptr, OnIncomingCapturedData)
         .Times(1)
         .WillOnce(RunClosure(quit_closure));
     run_loop.Run();
@@ -431,7 +438,7 @@
   std::unique_ptr<MockV4l2GpuClient> client =
       std::make_unique<MockV4l2GpuClient>();
   MockV4l2GpuClient* client_ptr = client.get();
-  EXPECT_CALL(*client_ptr, ReserveOutputBuffer(_, _, _, _))
+  EXPECT_CALL(*client_ptr, ReserveOutputBuffer)
       .WillRepeatedly(Invoke(
           [](const gfx::Size& size, VideoPixelFormat format, int feedback_id,
              VideoCaptureDevice::Client::Buffer* capture_buffer) {
@@ -443,7 +450,7 @@
           }));
 
   base::RunLoop wait_loop;
-  EXPECT_CALL(*client_ptr, OnIncomingCapturedBufferExt(_, _, _, _, _, _, _))
+  EXPECT_CALL(*client_ptr, OnIncomingCapturedBufferExt)
       .WillRepeatedly(InvokeWithoutArgs([&wait_loop, this]() {
         this->received_frame_count_++;
         if (this->received_frame_count_ == kFrameToReceive) {
diff --git a/media/capture/video/linux/video_capture_device_factory_v4l2_unittest.cc b/media/capture/video/linux/video_capture_device_factory_v4l2_unittest.cc
index d4d3e371..dd82c26 100644
--- a/media/capture/video/linux/video_capture_device_factory_v4l2_unittest.cc
+++ b/media/capture/video/linux/video_capture_device_factory_v4l2_unittest.cc
@@ -114,7 +114,7 @@
 
   base::RunLoop wait_loop;
   static const int kFrameToReceive = 3;
-  EXPECT_CALL(*client_ptr, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*client_ptr, OnIncomingCapturedData)
       .WillRepeatedly(InvokeWithoutArgs([&wait_loop]() {
         static int received_frame_count = 0;
         received_frame_count++;
diff --git a/media/capture/video/mac/video_capture_device_decklink_mac.mm b/media/capture/video/mac/video_capture_device_decklink_mac.mm
index 8d84049..80c1351 100644
--- a/media/capture/video/mac/video_capture_device_decklink_mac.mm
+++ b/media/capture/video/mac/video_capture_device_decklink_mac.mm
@@ -457,7 +457,8 @@
   if (!client_)
     return;
   client_->OnIncomingCapturedData(data, length, frame_format, color_space,
-                                  rotation, flip_y, reference_time, timestamp);
+                                  rotation, flip_y, reference_time, timestamp,
+                                  std::nullopt);
 }
 
 void VideoCaptureDeviceDeckLinkMac::SendErrorString(
diff --git a/media/capture/video/mock_device.cc b/media/capture/video/mock_device.cc
index 73f32b01..8089ab4 100644
--- a/media/capture/video/mock_device.cc
+++ b/media/capture/video/mock_device.cc
@@ -22,7 +22,7 @@
       static_cast<int>(media::VideoFrame::AllocationSize(
           stub_frame->format(), stub_frame->coded_size())),
       format, gfx::ColorSpace(), rotation, false /* flip_y */,
-      base::TimeTicks(), base::TimeDelta(), frame_feedback_id);
+      base::TimeTicks(), base::TimeDelta(), std::nullopt, frame_feedback_id);
 }
 
 void MockDevice::SendOnStarted() {
diff --git a/media/capture/video/mock_video_capture_device_client.cc b/media/capture/video/mock_video_capture_device_client.cc
index a66af33..165280ca 100644
--- a/media/capture/video/mock_video_capture_device_client.cc
+++ b/media/capture/video/mock_video_capture_device_client.cc
@@ -7,10 +7,12 @@
 #include <utility>
 
 #include "base/memory/raw_ptr.h"
+#include "base/time/time.h"
 #include "media/base/video_frame.h"
 
 using testing::_;
 using testing::Invoke;
+using testing::WithArgs;
 
 namespace media {
 
@@ -87,7 +89,8 @@
     Buffer buffer,
     const media::VideoCaptureFormat& format,
     base::TimeTicks reference_time,
-    base::TimeDelta timestamp) {
+    base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_time) {
   DoOnIncomingCapturedBuffer(buffer, format, reference_time, timestamp);
 }
 void MockVideoCaptureDeviceClient::OnIncomingCapturedBufferExt(
@@ -96,6 +99,7 @@
     const gfx::ColorSpace& color_space,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_time,
     gfx::Rect visible_rect,
     const media::VideoFrameMetadata& additional_metadata) {
   DoOnIncomingCapturedBufferExt(buffer, format, color_space, reference_time,
@@ -110,7 +114,7 @@
   result->fake_frame_captured_callback_ = std::move(frame_captured_callback);
 
   auto* raw_result_ptr = result.get();
-  ON_CALL(*result, ReserveOutputBuffer(_, _, _, _))
+  ON_CALL(*result, ReserveOutputBuffer)
       .WillByDefault(
           Invoke([](const gfx::Size& dimensions, VideoPixelFormat format, int,
                     VideoCaptureDevice::Client::Buffer* buffer) {
@@ -121,37 +125,26 @@
                                               frame_format.frame_size));
             return VideoCaptureDevice::Client::ReserveResult::kSucceeded;
           }));
-  ON_CALL(*result, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
-      .WillByDefault(
-          Invoke([raw_result_ptr](const uint8_t*, int,
-                                  const media::VideoCaptureFormat& frame_format,
-                                  const gfx::ColorSpace&, int, bool,
-                                  base::TimeTicks, base::TimeDelta, int) {
+  ON_CALL(*result, OnIncomingCapturedData)
+      .WillByDefault(WithArgs<2>(Invoke(
+          [raw_result_ptr](const media::VideoCaptureFormat& frame_format) {
             raw_result_ptr->fake_frame_captured_callback_.Run(frame_format);
-          }));
-  ON_CALL(*result, OnIncomingCapturedGfxBuffer(_, _, _, _, _, _))
-      .WillByDefault(
-          Invoke([raw_result_ptr](gfx::GpuMemoryBuffer*,
-                                  const media::VideoCaptureFormat& frame_format,
-                                  int, base::TimeTicks, base::TimeDelta, int) {
+          })));
+  ON_CALL(*result, OnIncomingCapturedGfxBuffer)
+      .WillByDefault(WithArgs<1>(Invoke(
+          [raw_result_ptr](const media::VideoCaptureFormat& frame_format) {
             raw_result_ptr->fake_frame_captured_callback_.Run(frame_format);
-          }));
-  ON_CALL(*result, DoOnIncomingCapturedBuffer(_, _, _, _))
-      .WillByDefault(
-          Invoke([raw_result_ptr](media::VideoCaptureDevice::Client::Buffer&,
-                                  const media::VideoCaptureFormat& frame_format,
-                                  base::TimeTicks, base::TimeDelta) {
+          })));
+  ON_CALL(*result, DoOnIncomingCapturedBuffer)
+      .WillByDefault(WithArgs<1>(Invoke(
+          [raw_result_ptr](const media::VideoCaptureFormat& frame_format) {
             raw_result_ptr->fake_frame_captured_callback_.Run(frame_format);
-          }));
-  ON_CALL(*result, DoOnIncomingCapturedBufferExt(_, _, _, _, _, _, _))
-      .WillByDefault(
-          Invoke([raw_result_ptr](media::VideoCaptureDevice::Client::Buffer&,
-                                  const media::VideoCaptureFormat& frame_format,
-                                  const gfx::ColorSpace&, base::TimeTicks,
-                                  base::TimeDelta, gfx::Rect,
-                                  const media::VideoFrameMetadata&) {
+          })));
+  ON_CALL(*result, DoOnIncomingCapturedBufferExt)
+      .WillByDefault(WithArgs<1>(Invoke(
+          [raw_result_ptr](const media::VideoCaptureFormat& frame_format) {
             raw_result_ptr->fake_frame_captured_callback_.Run(frame_format);
-          }));
+          })));
   return result;
 }
 
diff --git a/media/capture/video/mock_video_capture_device_client.h b/media/capture/video/mock_video_capture_device_client.h
index 9aa5760..9544e05 100644
--- a/media/capture/video/mock_video_capture_device_client.h
+++ b/media/capture/video/mock_video_capture_device_client.h
@@ -20,65 +20,86 @@
   MockVideoCaptureDeviceClient();
   ~MockVideoCaptureDeviceClient() override;
 
-  MOCK_METHOD0(OnCaptureConfigurationChanged, void(void));
-  MOCK_METHOD9(OnIncomingCapturedData,
-               void(const uint8_t* data,
-                    int length,
-                    const VideoCaptureFormat& frame_format,
-                    const gfx::ColorSpace& color_space,
-                    int rotation,
-                    bool flip_y,
-                    base::TimeTicks reference_time,
-                    base::TimeDelta timestamp,
-                    int frame_feedback_id));
-  MOCK_METHOD6(OnIncomingCapturedGfxBuffer,
-               void(gfx::GpuMemoryBuffer* buffer,
-                    const VideoCaptureFormat& frame_format,
-                    int clockwise_rotation,
-                    base::TimeTicks reference_time,
-                    base::TimeDelta timestamp,
-                    int frame_feedback_id));
-  MOCK_METHOD4(OnIncomingCapturedExternalBuffer,
-               void(CapturedExternalVideoBuffer buffer,
-                    base::TimeTicks reference_time,
-                    base::TimeDelta timestamp,
-                    const gfx::Rect& visible_rect));
-  MOCK_METHOD4(ReserveOutputBuffer,
-               ReserveResult(const gfx::Size&, VideoPixelFormat, int, Buffer*));
-  MOCK_METHOD3(OnError,
-               void(media::VideoCaptureError error,
-                    const base::Location& from_here,
-                    const std::string& reason));
-  MOCK_METHOD1(OnFrameDropped, void(VideoCaptureFrameDropReason reason));
-  MOCK_METHOD0(OnStarted, void(void));
-  MOCK_CONST_METHOD0(GetBufferPoolUtilization, double(void));
+  MOCK_METHOD(void, OnCaptureConfigurationChanged, (), (override));
+  MOCK_METHOD(void,
+              OnIncomingCapturedData,
+              (const uint8_t* data,
+               int length,
+               const VideoCaptureFormat& frame_format,
+               const gfx::ColorSpace& color_space,
+               int rotation,
+               bool flip_y,
+               base::TimeTicks reference_time,
+               base::TimeDelta timestamp,
+               std::optional<base::TimeTicks> capture_begin_time,
+               int frame_feedback_id),
+              (override));
+  MOCK_METHOD(void,
+              OnIncomingCapturedGfxBuffer,
+              (gfx::GpuMemoryBuffer * buffer,
+               const VideoCaptureFormat& frame_format,
+               int clockwise_rotation,
+               base::TimeTicks reference_time,
+               base::TimeDelta timestamp,
+               std::optional<base::TimeTicks> capture_begin_time,
+               int frame_feedback_id),
+              (override));
+  MOCK_METHOD(void,
+              OnIncomingCapturedExternalBuffer,
+              (CapturedExternalVideoBuffer buffer,
+               base::TimeTicks reference_time,
+               base::TimeDelta timestamp,
+               std::optional<base::TimeTicks> capture_begin_time,
+               const gfx::Rect& visible_rect),
+              (override));
+  MOCK_METHOD(ReserveResult,
+              ReserveOutputBuffer,
+              (const gfx::Size&, VideoPixelFormat, int, Buffer*),
+              (override));
+  MOCK_METHOD(void,
+              OnError,
+              (media::VideoCaptureError error,
+               const base::Location& from_here,
+               const std::string& reason),
+              (override));
+  MOCK_METHOD(void,
+              OnFrameDropped,
+              (VideoCaptureFrameDropReason reason),
+              (override));
+  MOCK_METHOD(void, OnStarted, (), (override));
+  MOCK_METHOD(double, GetBufferPoolUtilization, (), (const override));
 
-  void OnIncomingCapturedBuffer(Buffer buffer,
-                                const VideoCaptureFormat& format,
-                                base::TimeTicks reference_time,
-                                base::TimeDelta timestamp) override;
+  void OnIncomingCapturedBuffer(
+      Buffer buffer,
+      const VideoCaptureFormat& format,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time) override;
   void OnIncomingCapturedBufferExt(
       Buffer buffer,
       const VideoCaptureFormat& format,
       const gfx::ColorSpace& color_space,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
       gfx::Rect visible_rect,
       const VideoFrameMetadata& additional_metadata) override;
 
-  MOCK_METHOD4(DoOnIncomingCapturedBuffer,
-               void(Buffer&,
-                    const VideoCaptureFormat&,
-                    base::TimeTicks,
-                    base::TimeDelta));
-  MOCK_METHOD7(DoOnIncomingCapturedBufferExt,
-               void(Buffer& buffer,
-                    const VideoCaptureFormat& format,
-                    const gfx::ColorSpace& color_space,
-                    base::TimeTicks reference_time,
-                    base::TimeDelta timestamp,
-                    gfx::Rect visible_rect,
-                    const VideoFrameMetadata& additional_metadata));
+  MOCK_METHOD(
+      void,
+      DoOnIncomingCapturedBuffer,
+      (Buffer&, const VideoCaptureFormat&, base::TimeTicks, base::TimeDelta),
+      ());
+  MOCK_METHOD(void,
+              DoOnIncomingCapturedBufferExt,
+              (Buffer & buffer,
+               const VideoCaptureFormat& format,
+               const gfx::ColorSpace& color_space,
+               base::TimeTicks reference_time,
+               base::TimeDelta timestamp,
+               gfx::Rect visible_rect,
+               const VideoFrameMetadata& additional_metadata),
+              ());
 
   static std::unique_ptr<MockVideoCaptureDeviceClient>
   CreateMockClientWithBufferAllocator(
diff --git a/media/capture/video/mock_video_frame_receiver.h b/media/capture/video/mock_video_frame_receiver.h
index c52d4e3..0538383e 100644
--- a/media/capture/video/mock_video_frame_receiver.h
+++ b/media/capture/video/mock_video_frame_receiver.h
@@ -16,13 +16,7 @@
   ~MockVideoFrameReceiver() override;
 
   MOCK_METHOD1(MockOnNewBufferHandle, void(int buffer_id));
-  MOCK_METHOD3(
-      MockOnFrameReadyInBuffer,
-      void(int buffer_id,
-           std::unique_ptr<
-               VideoCaptureDevice::Client::Buffer::ScopedAccessPermission>*
-               buffer_read_permission,
-           const gfx::Size&));
+  MOCK_METHOD1(MockOnFrameReadyInBuffer, void(ReadyFrameInBuffer frame));
   MOCK_METHOD0(OnCaptureConfigurationChanged, void());
   MOCK_METHOD1(OnError, void(media::VideoCaptureError error));
   MOCK_METHOD1(OnFrameDropped, void(media::VideoCaptureFrameDropReason reason));
@@ -40,8 +34,7 @@
   }
 
   void OnFrameReadyInBuffer(ReadyFrameInBuffer frame) override {
-    MockOnFrameReadyInBuffer(frame.buffer_id, &frame.buffer_read_permission,
-                             frame.frame_info->coded_size);
+    MockOnFrameReadyInBuffer(std::move(frame));
   }
 };
 
diff --git a/media/capture/video/video_capture_device.cc b/media/capture/video/video_capture_device.cc
index 214dd600..ef875f2 100644
--- a/media/capture/video/video_capture_device.cc
+++ b/media/capture/video/video_capture_device.cc
@@ -87,9 +87,11 @@
     int clockwise_rotation,
     bool flip_y,
     base::TimeTicks reference_time,
-    base::TimeDelta timestamp) {
+    base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp) {
   OnIncomingCapturedData(data, length, frame_format, color_space,
                          clockwise_rotation, flip_y, reference_time, timestamp,
+                         capture_begin_timestamp,
                          /*frame_feedback_id=*/0);
 }
 
@@ -98,9 +100,11 @@
     const VideoCaptureFormat& frame_format,
     int clockwise_rotation,
     base::TimeTicks reference_time,
-    base::TimeDelta timestamp) {
+    base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp) {
   OnIncomingCapturedGfxBuffer(buffer, frame_format, clockwise_rotation,
                               reference_time, timestamp,
+                              capture_begin_timestamp,
                               /*frame_feedback_id=*/0);
 }
 
diff --git a/media/capture/video/video_capture_device.h b/media/capture/video/video_capture_device.h
index bb89eea..607d240f 100644
--- a/media/capture/video/video_capture_device.h
+++ b/media/capture/video/video_capture_device.h
@@ -185,24 +185,28 @@
     // OnConsumerReportingUtilization(). This identifier is needed because
     // frames are consumed asynchronously and multiple frames can be "in flight"
     // at the same time.
-    virtual void OnIncomingCapturedData(const uint8_t* data,
-                                        int length,
-                                        const VideoCaptureFormat& frame_format,
-                                        const gfx::ColorSpace& color_space,
-                                        int clockwise_rotation,
-                                        bool flip_y,
-                                        base::TimeTicks reference_time,
-                                        base::TimeDelta timestamp,
-                                        int frame_feedback_id) = 0;
+    virtual void OnIncomingCapturedData(
+        const uint8_t* data,
+        int length,
+        const VideoCaptureFormat& frame_format,
+        const gfx::ColorSpace& color_space,
+        int clockwise_rotation,
+        bool flip_y,
+        base::TimeTicks reference_time,
+        base::TimeDelta timestamp,
+        std::optional<base::TimeTicks> capture_begin_timestamp,
+        int frame_feedback_id) = 0;
     // Convenience wrapper that passes in 0 as |frame_feedback_id|.
-    void OnIncomingCapturedData(const uint8_t* data,
-                                int length,
-                                const VideoCaptureFormat& frame_format,
-                                const gfx::ColorSpace& color_space,
-                                int clockwise_rotation,
-                                bool flip_y,
-                                base::TimeTicks reference_time,
-                                base::TimeDelta timestamp);
+    void OnIncomingCapturedData(
+        const uint8_t* data,
+        int length,
+        const VideoCaptureFormat& frame_format,
+        const gfx::ColorSpace& color_space,
+        int clockwise_rotation,
+        bool flip_y,
+        base::TimeTicks reference_time,
+        base::TimeDelta timestamp,
+        std::optional<base::TimeTicks> capture_begin_timestamp);
 
     // Captured a new video frame, data for which is stored in the
     // GpuMemoryBuffer pointed to by |buffer|.  The format of the frame is
@@ -218,13 +222,16 @@
         int clockwise_rotation,
         base::TimeTicks reference_time,
         base::TimeDelta timestamp,
+        std::optional<base::TimeTicks> capture_begin_timestamp,
         int frame_feedback_id) = 0;
     // Convenience wrapper that passes in 0 as |frame_feedback_id|.
-    void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
-                                     const VideoCaptureFormat& frame_format,
-                                     int clockwise_rotation,
-                                     base::TimeTicks reference_time,
-                                     base::TimeDelta timestamp);
+    void OnIncomingCapturedGfxBuffer(
+        gfx::GpuMemoryBuffer* buffer,
+        const VideoCaptureFormat& frame_format,
+        int clockwise_rotation,
+        base::TimeTicks reference_time,
+        base::TimeDelta timestamp,
+        std::optional<base::TimeTicks> capture_begin_timestamp);
 
     // Captured a new video frame. The data for this frame is in
     // |buffer.handle|, which is owned by the platform-specific capture device.
@@ -239,6 +246,7 @@
         CapturedExternalVideoBuffer buffer,
         base::TimeTicks reference_time,
         base::TimeDelta timestamp,
+        std::optional<base::TimeTicks> capture_begin_timestamp,
         const gfx::Rect& visible_rect) = 0;
 
     // Reserve an output buffer into which contents can be captured directly.
@@ -262,10 +270,12 @@
     // ReserveOutputBuffer().
     // See OnIncomingCapturedData for details of |reference_time| and
     // |timestamp|.
-    virtual void OnIncomingCapturedBuffer(Buffer buffer,
-                                          const VideoCaptureFormat& format,
-                                          base::TimeTicks reference_time,
-                                          base::TimeDelta timestamp) = 0;
+    virtual void OnIncomingCapturedBuffer(
+        Buffer buffer,
+        const VideoCaptureFormat& format,
+        base::TimeTicks reference_time,
+        base::TimeDelta timestamp,
+        std::optional<base::TimeTicks> capture_begin_timestamp) = 0;
 
     // Extended version of OnIncomingCapturedBuffer() allowing clients to
     // pass a custom |visible_rect| and |additional_metadata|.
@@ -275,6 +285,7 @@
         const gfx::ColorSpace& color_space,
         base::TimeTicks reference_time,
         base::TimeDelta timestamp,
+        std::optional<base::TimeTicks> capture_begin_timestamp,
         gfx::Rect visible_rect,
         const VideoFrameMetadata& additional_metadata) = 0;
 
diff --git a/media/capture/video/video_capture_device_client.cc b/media/capture/video/video_capture_device_client.cc
index 75567523..689127b2 100644
--- a/media/capture/video/video_capture_device_client.cc
+++ b/media/capture/video/video_capture_device_client.cc
@@ -15,6 +15,7 @@
 #include "base/location.h"
 #include "base/ranges/algorithm.h"
 #include "base/strings/stringprintf.h"
+#include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
@@ -295,6 +296,7 @@
     bool flip_y,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp,
     int frame_feedback_id) {
   DFAKE_SCOPED_RECURSIVE_LOCK(call_from_producer_);
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
@@ -329,7 +331,8 @@
 
   if (format.pixel_format == PIXEL_FORMAT_Y16) {
     return OnIncomingCapturedY16Data(data, length, format, reference_time,
-                                     timestamp, frame_feedback_id);
+                                     timestamp, capture_begin_timestamp,
+                                     frame_feedback_id);
   }
 
   // |new_unrotated_{width,height}| are the dimensions of the output buffer that
@@ -406,9 +409,9 @@
 
   const VideoCaptureFormat output_format =
       VideoCaptureFormat(dimensions, format.frame_rate, PIXEL_FORMAT_I420);
-  OnIncomingCapturedBufferExt(std::move(buffer), output_format, color_space,
-                              reference_time, timestamp, gfx::Rect(dimensions),
-                              VideoFrameMetadata());
+  OnIncomingCapturedBufferExt(
+      std::move(buffer), output_format, color_space, reference_time, timestamp,
+      capture_begin_timestamp, gfx::Rect(dimensions), VideoFrameMetadata());
 }
 
 void VideoCaptureDeviceClient::OnIncomingCapturedGfxBuffer(
@@ -417,6 +420,7 @@
     int clockwise_rotation,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp,
     int frame_feedback_id) {
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
                "VideoCaptureDeviceClient::OnIncomingCapturedGfxBuffer");
@@ -495,18 +499,19 @@
   const VideoCaptureFormat output_format = VideoCaptureFormat(
       dimensions, frame_format.frame_rate, PIXEL_FORMAT_I420);
   OnIncomingCapturedBuffer(std::move(output_buffer), output_format,
-                           reference_time, timestamp);
+                           reference_time, timestamp, capture_begin_timestamp);
 }
 
 void VideoCaptureDeviceClient::OnIncomingCapturedExternalBuffer(
     CapturedExternalVideoBuffer buffer,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp,
     const gfx::Rect& visible_rect) {
   ReadyFrameInBuffer ready_frame;
   if (CreateReadyFrameFromExternalBuffer(
-          std::move(buffer), reference_time, timestamp, visible_rect,
-          &ready_frame) != ReserveResult::kSucceeded) {
+          std::move(buffer), reference_time, timestamp, capture_begin_timestamp,
+          visible_rect, &ready_frame) != ReserveResult::kSucceeded) {
     DVLOG(2) << __func__
              << " CreateReadyFrameFromExternalBuffer failed: reservation "
                 "trakcer failed.";
@@ -520,6 +525,7 @@
     CapturedExternalVideoBuffer buffer,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp,
     const gfx::Rect& visible_rect,
     ReadyFrameInBuffer* ready_buffer) {
   // Reserve an ID for this buffer that will not conflict with any of the IDs
@@ -577,6 +583,7 @@
   info->visible_rect = visible_rect;
   info->metadata.frame_rate = buffer.format.frame_rate;
   info->metadata.reference_time = reference_time;
+  info->metadata.capture_begin_time = capture_begin_timestamp;
 
   buffer_pool_->HoldForConsumers(buffer_id, 1);
   buffer_pool_->RelinquishProducerReservation(buffer_id);
@@ -651,11 +658,13 @@
     Buffer buffer,
     const VideoCaptureFormat& format,
     base::TimeTicks reference_time,
-    base::TimeDelta timestamp) {
+    base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp) {
   DFAKE_SCOPED_RECURSIVE_LOCK(call_from_producer_);
   OnIncomingCapturedBufferExt(
       std::move(buffer), format, gfx::ColorSpace(), reference_time, timestamp,
-      gfx::Rect(format.frame_size), VideoFrameMetadata());
+      capture_begin_timestamp, gfx::Rect(format.frame_size),
+      VideoFrameMetadata());
 }
 
 void VideoCaptureDeviceClient::OnIncomingCapturedBufferExt(
@@ -664,6 +673,7 @@
     const gfx::ColorSpace& color_space,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp,
     gfx::Rect visible_rect,
     const VideoFrameMetadata& additional_metadata) {
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("video_and_image_capture"),
@@ -673,6 +683,7 @@
   VideoFrameMetadata metadata = additional_metadata;
   metadata.frame_rate = format.frame_rate;
   metadata.reference_time = reference_time;
+  metadata.capture_begin_time = capture_begin_timestamp;
 
   mojom::VideoFrameInfoPtr info = mojom::VideoFrameInfo::New();
   info->timestamp = timestamp;
@@ -727,6 +738,7 @@
     const VideoCaptureFormat& format,
     base::TimeTicks reference_time,
     base::TimeDelta timestamp,
+    std::optional<base::TimeTicks> capture_begin_timestamp,
     int frame_feedback_id) {
   Buffer buffer;
   const auto reservation_result_code = ReserveOutputBuffer(
@@ -747,6 +759,6 @@
   const VideoCaptureFormat output_format = VideoCaptureFormat(
       format.frame_size, format.frame_rate, PIXEL_FORMAT_Y16);
   OnIncomingCapturedBuffer(std::move(buffer), output_format, reference_time,
-                           timestamp);
+                           timestamp, capture_begin_timestamp);
 }
 }  // namespace media
diff --git a/media/capture/video/video_capture_device_client.h b/media/capture/video/video_capture_device_client.h
index abf0cba..9ebdad2 100644
--- a/media/capture/video/video_capture_device_client.h
+++ b/media/capture/video/video_capture_device_client.h
@@ -76,40 +76,48 @@
 
   // VideoCaptureDevice::Client implementation.
   void OnCaptureConfigurationChanged() override;
-  void OnIncomingCapturedData(const uint8_t* data,
-                              int length,
-                              const VideoCaptureFormat& frame_format,
-                              const gfx::ColorSpace& color_space,
-                              int clockwise_rotation,
-                              bool flip_y,
-                              base::TimeTicks reference_time,
-                              base::TimeDelta timestamp,
-                              int frame_feedback_id) override;
-  void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
-                                   const VideoCaptureFormat& frame_format,
-                                   int clockwise_rotation,
-                                   base::TimeTicks reference_time,
-                                   base::TimeDelta timestamp,
-                                   int frame_feedback_id) override;
+  void OnIncomingCapturedData(
+      const uint8_t* data,
+      int length,
+      const VideoCaptureFormat& frame_format,
+      const gfx::ColorSpace& color_space,
+      int clockwise_rotation,
+      bool flip_y,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_timestamp,
+      int frame_feedback_id) override;
+  void OnIncomingCapturedGfxBuffer(
+      gfx::GpuMemoryBuffer* buffer,
+      const VideoCaptureFormat& frame_format,
+      int clockwise_rotation,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_timestamp,
+      int frame_feedback_id) override;
   void OnIncomingCapturedExternalBuffer(
       CapturedExternalVideoBuffer buffer,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_timestamp,
       const gfx::Rect& visible_rect) override;
   ReserveResult ReserveOutputBuffer(const gfx::Size& dimensions,
                                     VideoPixelFormat format,
                                     int frame_feedback_id,
                                     Buffer* buffer) override;
-  void OnIncomingCapturedBuffer(Buffer buffer,
-                                const VideoCaptureFormat& format,
-                                base::TimeTicks reference_time,
-                                base::TimeDelta timestamp) override;
+  void OnIncomingCapturedBuffer(
+      Buffer buffer,
+      const VideoCaptureFormat& format,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_timestamp) override;
   void OnIncomingCapturedBufferExt(
       Buffer buffer,
       const VideoCaptureFormat& format,
       const gfx::ColorSpace& color_space,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_timestamp,
       gfx::Rect visible_rect,
       const VideoFrameMetadata& additional_metadata) override;
   void OnError(VideoCaptureError error,
@@ -125,16 +133,19 @@
       CapturedExternalVideoBuffer buffer,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_timestamp,
       const gfx::Rect& visible_rect,
       ReadyFrameInBuffer* ready_buffer);
 
   // A branch of OnIncomingCapturedData for Y16 frame_format.pixel_format.
-  void OnIncomingCapturedY16Data(const uint8_t* data,
-                                 int length,
-                                 const VideoCaptureFormat& frame_format,
-                                 base::TimeTicks reference_time,
-                                 base::TimeDelta timestamp,
-                                 int frame_feedback_id);
+  void OnIncomingCapturedY16Data(
+      const uint8_t* data,
+      int length,
+      const VideoCaptureFormat& frame_format,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_timestamp,
+      int frame_feedback_id);
 
   // The receiver to which we post events.
   const std::unique_ptr<VideoFrameReceiver> receiver_;
diff --git a/media/capture/video/video_capture_device_client_unittest.cc b/media/capture/video/video_capture_device_client_unittest.cc
index f2f3611..55e1804 100644
--- a/media/capture/video/video_capture_device_client_unittest.cc
+++ b/media/capture/video/video_capture_device_client_unittest.cc
@@ -12,13 +12,18 @@
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/test/task_environment.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "media/base/limits.h"
 #include "media/base/video_frame.h"
+#include "media/base/video_types.h"
+#include "media/capture/mojom/video_capture_buffer.mojom.h"
 #include "media/capture/video/mock_gpu_memory_buffer_manager.h"
 #include "media/capture/video/mock_video_frame_receiver.h"
 #include "media/capture/video/video_capture_buffer_pool_impl.h"
+#include "media/capture/video/video_capture_buffer_tracker.h"
+#include "media/capture/video/video_capture_buffer_tracker_factory.h"
 #include "media/capture/video/video_capture_buffer_tracker_factory_impl.h"
 #include "media/capture/video/video_frame_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -31,10 +36,13 @@
 
 using ::testing::_;
 using ::testing::AtLeast;
+using ::testing::Field;
 using ::testing::InSequence;
 using ::testing::Invoke;
 using ::testing::Mock;
 using ::testing::NiceMock;
+using ::testing::Optional;
+using ::testing::Pointee;
 using ::testing::SaveArg;
 
 namespace media {
@@ -58,6 +66,54 @@
                        observer) override {}
 };
 
+class FakeVideoCaptureBufferHandle : public VideoCaptureBufferHandle {
+ public:
+  size_t mapped_size() const override { return 1024; }
+  uint8_t* data() const override { return nullptr; }
+  const uint8_t* const_data() const override { return nullptr; }
+};
+
+class FakeVideoCaptureBufferTracker : public VideoCaptureBufferTracker {
+ public:
+  bool Init(const gfx::Size& dimensions,
+            VideoPixelFormat format,
+            const mojom::PlaneStridesPtr& strides) override {
+    return true;
+  }
+  bool IsReusableForFormat(const gfx::Size& dimensions,
+                           VideoPixelFormat format,
+                           const mojom::PlaneStridesPtr& strides) override {
+    return true;
+  }
+  uint32_t GetMemorySizeInBytes() override { return 1024; }
+  std::unique_ptr<VideoCaptureBufferHandle> GetMemoryMappedAccess() override {
+    return std::make_unique<FakeVideoCaptureBufferHandle>();
+  }
+  base::UnsafeSharedMemoryRegion DuplicateAsUnsafeRegion() override {
+    return base::UnsafeSharedMemoryRegion();
+  }
+  gfx::GpuMemoryBufferHandle GetGpuMemoryBufferHandle() override {
+    return gfx::GpuMemoryBufferHandle();
+  }
+  VideoCaptureBufferType GetBufferType() override {
+    return VideoCaptureBufferType::kGpuMemoryBuffer;
+  }
+};
+
+class FakeVideoCaptureBufferTrackerFactory
+    : public VideoCaptureBufferTrackerFactory {
+ public:
+  std::unique_ptr<VideoCaptureBufferTracker> CreateTracker(
+      VideoCaptureBufferType buffer_type) override {
+    return std::make_unique<FakeVideoCaptureBufferTracker>();
+  }
+  std::unique_ptr<VideoCaptureBufferTracker>
+  CreateTrackerForExternalGpuMemoryBuffer(
+      gfx::GpuMemoryBufferHandle handle) override {
+    return std::make_unique<FakeVideoCaptureBufferTracker>();
+  }
+};
+
 }  // namespace
 
 // Test fixture for testing a unit consisting of an instance of
@@ -67,10 +123,35 @@
 // production.
 class VideoCaptureDeviceClientTest : public ::testing::Test {
  public:
-  VideoCaptureDeviceClientTest() {
+  void InitWithSharedMemoryBufferPool() {
     scoped_refptr<VideoCaptureBufferPoolImpl> buffer_pool(
         new VideoCaptureBufferPoolImpl(VideoCaptureBufferType::kSharedMemory,
                                        2));
+    Init(std::move(buffer_pool));
+  }
+
+  void InitWithGmbBufferPool() {
+    scoped_refptr<VideoCaptureBufferPoolImpl> buffer_pool(
+        new VideoCaptureBufferPoolImpl(
+            VideoCaptureBufferType::kSharedMemory, 2,
+            std::make_unique<FakeVideoCaptureBufferTrackerFactory>()));
+    Init(std::move(buffer_pool));
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<unittest_internal::MockGpuMemoryBufferManager>
+      gpu_memory_buffer_manager_;
+  FakeVideoEffectsManagerImpl fake_video_effects_manager_;
+  mojo::Receiver<video_capture::mojom::VideoEffectsManager>
+      video_effects_manager_receiver_{&fake_video_effects_manager_};
+
+  // Must outlive `receiver_`.
+  std::unique_ptr<VideoCaptureDeviceClient> device_client_;
+  raw_ptr<NiceMock<MockVideoFrameReceiver>> receiver_;
+
+ private:
+  void Init(scoped_refptr<VideoCaptureBufferPoolImpl> buffer_pool) {
     auto controller = std::make_unique<NiceMock<MockVideoFrameReceiver>>();
     receiver_ = controller.get();
     gpu_memory_buffer_manager_ =
@@ -85,29 +166,12 @@
         video_effects_manager_receiver_.BindNewPipeAndPassRemote());
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   }
-
-  VideoCaptureDeviceClientTest(const VideoCaptureDeviceClientTest&) = delete;
-  VideoCaptureDeviceClientTest& operator=(const VideoCaptureDeviceClientTest&) =
-      delete;
-
-  ~VideoCaptureDeviceClientTest() override = default;
-
- protected:
-  base::test::TaskEnvironment task_environment_;
-  std::unique_ptr<unittest_internal::MockGpuMemoryBufferManager>
-      gpu_memory_buffer_manager_;
-  FakeVideoEffectsManagerImpl fake_video_effects_manager_;
-  mojo::Receiver<video_capture::mojom::VideoEffectsManager>
-      video_effects_manager_receiver_{&fake_video_effects_manager_};
-
-  // Must outlive `receiver_`.
-  std::unique_ptr<VideoCaptureDeviceClient> device_client_;
-  raw_ptr<NiceMock<MockVideoFrameReceiver>> receiver_;
 };
 
 // A small test for reference and to verify VideoCaptureDeviceClient is
 // minimally functional.
 TEST_F(VideoCaptureDeviceClientTest, Minimal) {
+  InitWithSharedMemoryBufferPool();
   const size_t kScratchpadSizeInBytes = 400;
   unsigned char data[kScratchpadSizeInBytes] = {};
   const VideoCaptureFormat kFrameFormat(gfx::Size(10, 10), 30.0f /*frame_rate*/,
@@ -119,12 +183,14 @@
     const int expected_buffer_id = 0;
     EXPECT_CALL(*receiver_, OnLog(_));
     EXPECT_CALL(*receiver_, MockOnNewBufferHandle(expected_buffer_id));
-    EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer(expected_buffer_id, _, _));
+    EXPECT_CALL(*receiver_,
+                MockOnFrameReadyInBuffer(
+                    Field(&ReadyFrameInBuffer::buffer_id, expected_buffer_id)));
   }
   device_client_->VideoCaptureDevice::Client::OnIncomingCapturedData(
       data, kScratchpadSizeInBytes, kFrameFormat, kColorSpace,
       0 /* clockwise rotation */, false /* flip_y */, base::TimeTicks(),
-      base::TimeDelta());
+      base::TimeDelta(), std::nullopt);
 
   const gfx::Size kBufferDimensions(10, 10);
   const VideoCaptureFormat kFrameFormatNV12(
@@ -138,20 +204,85 @@
     InSequence s;
     const int expected_buffer_id = 0;
     EXPECT_CALL(*receiver_, OnLog(_));
-    EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer(expected_buffer_id, _, _));
+    EXPECT_CALL(*receiver_,
+                MockOnFrameReadyInBuffer(
+                    Field(&ReadyFrameInBuffer::buffer_id, expected_buffer_id)));
     EXPECT_CALL(*receiver_, OnBufferRetired(expected_buffer_id));
   }
   device_client_->VideoCaptureDevice::Client::OnIncomingCapturedGfxBuffer(
       buffer.get(), kFrameFormatNV12, 0 /*clockwise rotation*/,
-      base::TimeTicks(), base::TimeDelta());
+      base::TimeTicks(), base::TimeDelta(), std::nullopt);
 
   // Releasing |device_client_| will also release |receiver_|.
   receiver_ = nullptr;  // Avoid dangling reference.
   device_client_.reset();
 }
 
+TEST_F(VideoCaptureDeviceClientTest,
+       ProgressesCaptureBeginTimestampsForOnIncomingCapturedData) {
+  InitWithSharedMemoryBufferPool();
+  auto expected_timestamp = base::TimeTicks() + base::Seconds(66);
+  EXPECT_CALL(
+      *receiver_,
+      MockOnFrameReadyInBuffer(Field(
+          &ReadyFrameInBuffer::frame_info,
+          Pointee(Field(&mojom::VideoFrameInfo::metadata,
+                        Field(&media::VideoFrameMetadata::capture_begin_time,
+                              Optional(expected_timestamp)))))));
+  constexpr size_t kScratchpadSizeInBytes = 400;
+  unsigned char data[kScratchpadSizeInBytes] = {};
+  device_client_->VideoCaptureDevice::Client::OnIncomingCapturedData(
+      data, kScratchpadSizeInBytes,
+      VideoCaptureFormat(gfx::Size(10, 10), 30.0f, PIXEL_FORMAT_I420),
+      gfx::ColorSpace::CreateREC601(), 0, false, base::TimeTicks(),
+      base::TimeDelta(), expected_timestamp);
+}
+
+TEST_F(VideoCaptureDeviceClientTest,
+       ProgressesCaptureBeginTimestampsForOnIncomingCapturedGfxBuffer) {
+  InitWithSharedMemoryBufferPool();
+  auto expected_timestamp = base::TimeTicks() + base::Seconds(77);
+  EXPECT_CALL(
+      *receiver_,
+      MockOnFrameReadyInBuffer(Field(
+          &ReadyFrameInBuffer::frame_info,
+          Pointee(Field(&mojom::VideoFrameInfo::metadata,
+                        Field(&media::VideoFrameMetadata::capture_begin_time,
+                              Optional(expected_timestamp)))))));
+  auto resolution = gfx::Size(32, 32);
+  auto buffer = gpu_memory_buffer_manager_->CreateFakeGpuMemoryBuffer(
+      resolution, gfx::BufferFormat::YUV_420_BIPLANAR,
+      gfx::BufferUsage::SCANOUT_CAMERA_READ_WRITE, gpu::kNullSurfaceHandle,
+      nullptr);
+  device_client_->VideoCaptureDevice::Client::OnIncomingCapturedGfxBuffer(
+      buffer.get(), VideoCaptureFormat(resolution, 30.0f, PIXEL_FORMAT_NV12), 0,
+      base::TimeTicks(), base::TimeDelta(), expected_timestamp);
+}
+
+TEST_F(VideoCaptureDeviceClientTest,
+       ProgressesCaptureBeginTimestampsForOnIncomingCapturedExternalBuffer) {
+  InitWithGmbBufferPool();
+  auto expected_timestamp = base::TimeTicks() + base::Seconds(88);
+  EXPECT_CALL(
+      *receiver_,
+      MockOnFrameReadyInBuffer(Field(
+          &ReadyFrameInBuffer::frame_info,
+          Pointee(Field(&mojom::VideoFrameInfo::metadata,
+                        Field(&media::VideoFrameMetadata::capture_begin_time,
+                              Optional(expected_timestamp)))))));
+  auto resolution = gfx::Size(32, 32);
+  device_client_->OnIncomingCapturedExternalBuffer(
+      CapturedExternalVideoBuffer(
+          gfx::GpuMemoryBufferHandle(),
+          VideoCaptureFormat(resolution, 30, PIXEL_FORMAT_NV12),
+          gfx::ColorSpace::CreateREC601()),
+      base::TimeTicks(), base::TimeDelta(), expected_timestamp,
+      gfx::Rect(resolution));
+}
+
 // Tests that we fail silently if no available buffers to use.
 TEST_F(VideoCaptureDeviceClientTest, DropsFrameIfNoBuffer) {
+  InitWithSharedMemoryBufferPool();
   const size_t kScratchpadSizeInBytes = 400;
   unsigned char data[kScratchpadSizeInBytes] = {};
   const VideoCaptureFormat kFrameFormat(gfx::Size(10, 10), 30.0f /*frame_rate*/,
@@ -165,35 +296,30 @@
   std::vector<std::unique_ptr<
       VideoCaptureDevice::Client::Buffer::ScopedAccessPermission>>
       read_permission;
-  EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer(_, _, _))
+  EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer)
       .Times(2)
-      .WillRepeatedly(Invoke(
-          [&read_permission](
-              int buffer_id,
-              std::unique_ptr<
-                  VideoCaptureDevice::Client::Buffer::ScopedAccessPermission>*
-                  buffer_read_permission,
-              const gfx::Size&) {
-            read_permission.push_back(std::move(*buffer_read_permission));
-          }));
+      .WillRepeatedly(Invoke([&read_permission](ReadyFrameInBuffer frame) {
+        read_permission.push_back(std::move(frame.buffer_read_permission));
+      }));
   // Pass three frames. The third will be dropped.
   device_client_->VideoCaptureDevice::Client::OnIncomingCapturedData(
       data, kScratchpadSizeInBytes, kFrameFormat, kColorSpace,
       0 /* clockwise rotation */, false /* flip_y */, base::TimeTicks(),
-      base::TimeDelta());
+      base::TimeDelta(), std::nullopt);
   device_client_->VideoCaptureDevice::Client::OnIncomingCapturedData(
       data, kScratchpadSizeInBytes, kFrameFormat, kColorSpace,
       0 /* clockwise rotation */, false /* flip_y */, base::TimeTicks(),
-      base::TimeDelta());
+      base::TimeDelta(), std::nullopt);
   device_client_->VideoCaptureDevice::Client::OnIncomingCapturedData(
       data, kScratchpadSizeInBytes, kFrameFormat, kColorSpace,
       0 /* clockwise rotation */, false /* flip_y */, base::TimeTicks(),
-      base::TimeDelta());
+      base::TimeDelta(), std::nullopt);
   Mock::VerifyAndClearExpectations(receiver_);
 }
 
 // Tests that buffer-based capture API accepts some memory-backed pixel formats.
 TEST_F(VideoCaptureDeviceClientTest, DataCaptureGoodPixelFormats) {
+  InitWithSharedMemoryBufferPool();
   // The usual ReserveOutputBuffer() -> OnIncomingCapturedVideoFrame() cannot
   // be used since it does not accept all pixel formats. The memory backed
   // buffer OnIncomingCapturedData() is used instead, with a dummy scratchpad
@@ -229,13 +355,13 @@
     params.requested_format.pixel_format = format;
 
     EXPECT_CALL(*receiver_, OnLog(_)).Times(1);
-    EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer(_, _, _)).Times(1);
+    EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer).Times(1);
     device_client_->VideoCaptureDevice::Client::OnIncomingCapturedData(
         data,
         media::VideoFrame::AllocationSize(params.requested_format.pixel_format,
                                           params.requested_format.frame_size),
         params.requested_format, kColorSpace, 0 /* clockwise_rotation */,
-        false /* flip_y */, base::TimeTicks(), base::TimeDelta());
+        false /* flip_y */, base::TimeTicks(), base::TimeDelta(), std::nullopt);
     Mock::VerifyAndClearExpectations(receiver_);
   }
 }
@@ -243,6 +369,7 @@
 // Test that we receive the expected resolution for a given captured frame
 // resolution and rotation. Odd resolutions are also cropped.
 TEST_F(VideoCaptureDeviceClientTest, CheckRotationsAndCrops) {
+  InitWithSharedMemoryBufferPool();
   const struct SizeAndRotation {
     gfx::Size input_resolution;
     int rotation;
@@ -269,15 +396,17 @@
     params.requested_format = VideoCaptureFormat(
         size_and_rotation.input_resolution, 30.0f, PIXEL_FORMAT_ARGB);
     gfx::Size coded_size;
-    EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer(_, _, _))
+    EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer)
         .Times(1)
-        .WillOnce(SaveArg<2>(&coded_size));
+        .WillOnce(Invoke([&coded_size](ReadyFrameInBuffer frame) {
+          coded_size = frame.frame_info->coded_size;
+        }));
     device_client_->VideoCaptureDevice::Client::OnIncomingCapturedData(
         data,
         media::VideoFrame::AllocationSize(params.requested_format.pixel_format,
                                           params.requested_format.frame_size),
         params.requested_format, gfx::ColorSpace(), size_and_rotation.rotation,
-        false /* flip_y */, base::TimeTicks(), base::TimeDelta());
+        false /* flip_y */, base::TimeTicks(), base::TimeDelta(), std::nullopt);
 
     EXPECT_EQ(coded_size.width(), size_and_rotation.output_resolution.width());
     EXPECT_EQ(coded_size.height(),
@@ -303,12 +432,14 @@
             gpu::kNullSurfaceHandle, nullptr);
 
     gfx::Size coded_size;
-    EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer(_, _, _))
+    EXPECT_CALL(*receiver_, MockOnFrameReadyInBuffer)
         .Times(1)
-        .WillOnce(SaveArg<2>(&coded_size));
+        .WillOnce(Invoke([&coded_size](ReadyFrameInBuffer frame) {
+          coded_size = frame.frame_info->coded_size;
+        }));
     device_client_->VideoCaptureDevice::Client::OnIncomingCapturedGfxBuffer(
         buffer.get(), params.requested_format, size_and_rotation.rotation,
-        base::TimeTicks(), base::TimeDelta());
+        base::TimeTicks(), base::TimeDelta(), std::nullopt);
 
     EXPECT_EQ(coded_size.width(), size_and_rotation.output_resolution.width());
     EXPECT_EQ(coded_size.height(),
@@ -322,6 +453,7 @@
 // Tests that the VideoEffectsManager remote is closed on the correct task
 // runner. Destruction on the wrong task runner will cause a crash.
 TEST_F(VideoCaptureDeviceClientTest, DestructionClosesVideoEffectsManager) {
+  InitWithSharedMemoryBufferPool();
   base::RunLoop run_loop;
   video_effects_manager_receiver_.set_disconnect_handler(
       run_loop.QuitClosure());
diff --git a/media/capture/video/video_capture_device_unittest.cc b/media/capture/video/video_capture_device_unittest.cc
index 6113f79..01298898 100644
--- a/media/capture/video/video_capture_device_unittest.cc
+++ b/media/capture/video/video_capture_device_unittest.cc
@@ -150,6 +150,7 @@
 using ::testing::Invoke;
 using ::testing::Return;
 using ::testing::SaveArg;
+using ::testing::WithArgs;
 
 namespace media {
 namespace {
@@ -337,36 +338,33 @@
 
   std::unique_ptr<MockVideoCaptureDeviceClient> CreateDeviceClient() {
     auto result = std::make_unique<NiceMockVideoCaptureDeviceClient>();
-    ON_CALL(*result, OnError(_, _, _)).WillByDefault(Invoke(DumpError));
-    EXPECT_CALL(*result, ReserveOutputBuffer(_, _, _, _)).Times(0);
-    EXPECT_CALL(*result, DoOnIncomingCapturedBuffer(_, _, _, _)).Times(0);
-    EXPECT_CALL(*result, DoOnIncomingCapturedBufferExt(_, _, _, _, _, _, _))
-        .Times(0);
-    ON_CALL(*result, OnIncomingCapturedData(_, _, _, _, _, _, _, _, _))
-        .WillByDefault(
+    ON_CALL(*result, OnError).WillByDefault(Invoke(DumpError));
+    EXPECT_CALL(*result, ReserveOutputBuffer).Times(0);
+    EXPECT_CALL(*result, DoOnIncomingCapturedBuffer).Times(0);
+    EXPECT_CALL(*result, DoOnIncomingCapturedBufferExt).Times(0);
+    ON_CALL(*result, OnIncomingCapturedData)
+        .WillByDefault(WithArgs<0, 1, 2>(
             Invoke([this](const uint8_t* data, int length,
-                          const media::VideoCaptureFormat& frame_format,
-                          const gfx::ColorSpace&, int, bool, base::TimeTicks,
-                          base::TimeDelta, int) {
+                          const media::VideoCaptureFormat& frame_format) {
               ASSERT_GT(length, 0);
               ASSERT_TRUE(data);
               main_thread_task_runner_->PostTask(
                   FROM_HERE,
                   base::BindOnce(&VideoCaptureDeviceTest::OnFrameCaptured,
                                  base::Unretained(this), frame_format));
-            }));
-    ON_CALL(*result, OnIncomingCapturedGfxBuffer(_, _, _, _, _, _))
-        .WillByDefault(Invoke([this](
-                                  gfx::GpuMemoryBuffer* buffer,
-                                  const media::VideoCaptureFormat& frame_format,
-                                  int, base::TimeTicks, base::TimeDelta, int) {
-          ASSERT_TRUE(buffer);
-          ASSERT_GT(buffer->GetSize().width() * buffer->GetSize().height(), 0);
-          main_thread_task_runner_->PostTask(
-              FROM_HERE,
-              base::BindOnce(&VideoCaptureDeviceTest::OnFrameCaptured,
-                             base::Unretained(this), frame_format));
-        }));
+            })));
+    ON_CALL(*result, OnIncomingCapturedGfxBuffer)
+        .WillByDefault(WithArgs<0, 1>(
+            Invoke([this](gfx::GpuMemoryBuffer* buffer,
+                          const media::VideoCaptureFormat& frame_format) {
+              ASSERT_TRUE(buffer);
+              ASSERT_GT(buffer->GetSize().width() * buffer->GetSize().height(),
+                        0);
+              main_thread_task_runner_->PostTask(
+                  FROM_HERE,
+                  base::BindOnce(&VideoCaptureDeviceTest::OnFrameCaptured,
+                                 base::Unretained(this), frame_format));
+            })));
     return result;
   }
 
diff --git a/media/capture/video/win/video_capture_device_mf_win.cc b/media/capture/video/win/video_capture_device_mf_win.cc
index 1ac52e6..5db187cc 100644
--- a/media/capture/video/win/video_capture_device_mf_win.cc
+++ b/media/capture/video/win/video_capture_device_mf_win.cc
@@ -2275,8 +2275,8 @@
       VideoCaptureFormat(
           texture_size, selected_video_capability_->supported_format.frame_rate,
           pixel_format),
-      color_space_, reference_time, timestamp, gfx::Rect(texture_size),
-      frame_metadata);
+      color_space_, reference_time, timestamp, std::nullopt,
+      gfx::Rect(texture_size), frame_metadata);
 
   return hr;
 }
@@ -2344,9 +2344,9 @@
               selected_video_capability_->supported_format.frame_rate,
               pixel_format),
           gfx::ColorSpace());
-  client_->OnIncomingCapturedExternalBuffer(std::move(external_buffer),
-                                            reference_time, timestamp,
-                                            gfx::Rect(texture_size));
+  client_->OnIncomingCapturedExternalBuffer(
+      std::move(external_buffer), reference_time, timestamp, std::nullopt,
+      gfx::Rect(texture_size));
   return hr;
 }
 
@@ -2409,8 +2409,8 @@
     client_->OnIncomingCapturedData(
         locked_buffer.data(), locked_buffer.length(),
         selected_video_capability_->supported_format, color_space_,
-        camera_rotation_.value(), false /* flip_y */, reference_time,
-        timestamp);
+        camera_rotation_.value(), false /* flip_y */, reference_time, timestamp,
+        std::nullopt);
   }
 
   while (!video_stream_take_photo_callbacks_.empty()) {
diff --git a/media/capture/video/win/video_capture_device_mf_win_unittest.cc b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
index 64c3233..64fefd7d 100644
--- a/media/capture/video/win/video_capture_device_mf_win_unittest.cc
+++ b/media/capture/video/win/video_capture_device_mf_win_unittest.cc
@@ -74,35 +74,42 @@
                               bool flip_y,
                               base::TimeTicks reference_time,
                               base::TimeDelta timestamp,
+                              std::optional<base::TimeTicks> capture_begin_time,
                               int frame_feedback_id) override {}
 
-  void OnIncomingCapturedGfxBuffer(gfx::GpuMemoryBuffer* buffer,
-                                   const VideoCaptureFormat& frame_format,
-                                   int clockwise_rotation,
-                                   base::TimeTicks reference_time,
-                                   base::TimeDelta timestamp,
-                                   int frame_feedback_id) override {}
+  void OnIncomingCapturedGfxBuffer(
+      gfx::GpuMemoryBuffer* buffer,
+      const VideoCaptureFormat& frame_format,
+      int clockwise_rotation,
+      base::TimeTicks reference_time,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
+      int frame_feedback_id) override {}
 
   void OnIncomingCapturedExternalBuffer(
       CapturedExternalVideoBuffer buffer,
       base::TimeTicks reference_time,
       base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time,
       const gfx::Rect& visible_rect) override {}
 
   MOCK_METHOD4(ReserveOutputBuffer,
                ReserveResult(const gfx::Size&, VideoPixelFormat, int, Buffer*));
 
-  void OnIncomingCapturedBuffer(Buffer buffer,
-                                const VideoCaptureFormat& format,
-                                base::TimeTicks reference_,
-                                base::TimeDelta timestamp) override {}
+  void OnIncomingCapturedBuffer(
+      Buffer buffer,
+      const VideoCaptureFormat& format,
+      base::TimeTicks reference_,
+      base::TimeDelta timestamp,
+      std::optional<base::TimeTicks> capture_begin_time) override {}
 
-  MOCK_METHOD7(OnIncomingCapturedBufferExt,
+  MOCK_METHOD8(OnIncomingCapturedBufferExt,
                void(Buffer,
                     const VideoCaptureFormat&,
                     const gfx::ColorSpace&,
                     base::TimeTicks,
                     base::TimeDelta,
+                    std::optional<base::TimeTicks>,
                     gfx::Rect,
                     const VideoFrameMetadata&));
 
@@ -2251,7 +2258,8 @@
   EXPECT_CALL(*client_, OnIncomingCapturedBufferExt)
       .WillOnce(Invoke([](VideoCaptureDevice::Client::Buffer buffer,
                           const VideoCaptureFormat&, const gfx::ColorSpace&,
-                          base::TimeTicks, base::TimeDelta, gfx::Rect,
+                          base::TimeTicks, base::TimeDelta,
+                          std::optional<base::TimeTicks>, gfx::Rect,
                           const VideoFrameMetadata&) {
         gfx::GpuMemoryBufferHandle gmb_handle =
             buffer.handle_provider->GetGpuMemoryBufferHandle();
diff --git a/media/capture/video/win/video_capture_device_win.cc b/media/capture/video/win/video_capture_device_win.cc
index 81ceb425..74fa8ce 100644
--- a/media/capture/video/win/video_capture_device_win.cc
+++ b/media/capture/video/win/video_capture_device_win.cc
@@ -891,9 +891,9 @@
     // DXVA_ExtendedFormat. Then use its fields DXVA_VideoPrimaries,
     // DXVA_VideoTransferMatrix, DXVA_VideoTransferFunction and
     // DXVA_NominalRangeto build a gfx::ColorSpace. See http://crbug.com/959992.
-    client_->OnIncomingCapturedData(buffer, length, format, gfx::ColorSpace(),
-                                    camera_rotation_.value(), flip_y,
-                                    base::TimeTicks::Now(), timestamp);
+    client_->OnIncomingCapturedData(
+        buffer, length, format, gfx::ColorSpace(), camera_rotation_.value(),
+        flip_y, base::TimeTicks::Now(), timestamp, std::nullopt);
   }
 
   while (!take_photo_callbacks_.empty()) {
diff --git a/media/webrtc/audio_processor_test.cc b/media/webrtc/audio_processor_test.cc
index 0ba3512..55838a2b4 100644
--- a/media/webrtc/audio_processor_test.cc
+++ b/media/webrtc/audio_processor_test.cc
@@ -180,18 +180,11 @@
     EXPECT_EQ(config.noise_suppression.level,
               webrtc::AudioProcessing::Config::NoiseSuppression::kHigh);
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
-    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
-    EXPECT_FALSE(config.echo_canceller.mobile_mode);
-    EXPECT_FALSE(config.transient_suppression.enabled);
-#elif BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
-    // Android and iOS use echo cancellation optimized for mobiles, and does not
-    // support keytap suppression.
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+    // Android and iOS use echo cancellation optimized for mobiles.
     EXPECT_TRUE(config.echo_canceller.mobile_mode);
-    EXPECT_FALSE(config.transient_suppression.enabled);
 #else
     EXPECT_FALSE(config.echo_canceller.mobile_mode);
-    EXPECT_TRUE(config.transient_suppression.enabled);
 #endif
   }
 
diff --git a/media/webrtc/helpers.cc b/media/webrtc/helpers.cc
index 4cca3ea..e8ef7ce 100644
--- a/media/webrtc/helpers.cc
+++ b/media/webrtc/helpers.cc
@@ -159,12 +159,6 @@
 #else
   apm_config.echo_canceller.mobile_mode = false;
 #endif
-#if !(BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
-      BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) ||               \
-      BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS))
-  apm_config.transient_suppression.enabled =
-      settings.transient_noise_suppression;
-#endif
   ConfigAutomaticGainControl(settings, apm_config);
   return ap_builder.SetConfig(apm_config).Create();
 }
diff --git a/media/webrtc/helpers_unittests.cc b/media/webrtc/helpers_unittests.cc
index 472ae8a..2e2d3b02 100644
--- a/media/webrtc/helpers_unittests.cc
+++ b/media/webrtc/helpers_unittests.cc
@@ -57,17 +57,11 @@
   EXPECT_EQ(config.noise_suppression.level,
             webrtc::AudioProcessing::Config::NoiseSuppression::kHigh);
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
-    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
-  EXPECT_FALSE(config.transient_suppression.enabled);
-#elif BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
-  // Android and iOS use echo cancellation optimized for mobiles, and does not
-  // support keytap suppression.
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+  // Android and iOS use echo cancellation optimized for mobiles.
   EXPECT_TRUE(config.echo_canceller.mobile_mode);
-  EXPECT_FALSE(config.transient_suppression.enabled);
 #else
   EXPECT_FALSE(config.echo_canceller.mobile_mode);
-  EXPECT_TRUE(config.transient_suppression.enabled);
 #endif
 }
 
@@ -189,23 +183,5 @@
   }
 }
 
-TEST(CreateWebRtcAudioProcessingModuleTest, ToggleTransientSuppression) {
-  for (bool transient_suppression_enabled : {true, false}) {
-    SCOPED_TRACE(transient_suppression_enabled);
-    auto config = CreateApmGetConfig(/*settings=*/{
-        .transient_noise_suppression = transient_suppression_enabled});
-
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
-    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA) ||               \
-    BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
-    // Transient suppression is not supported (nor useful) on mobile platforms.
-    EXPECT_FALSE(config.transient_suppression.enabled);
-#else
-    EXPECT_EQ(config.transient_suppression.enabled,
-              transient_suppression_enabled);
-#endif
-  }
-}
-
 }  // namespace
 }  // namespace media
diff --git a/net/third_party/quiche/src b/net/third_party/quiche/src
index 42dab6b..6fed964 160000
--- a/net/third_party/quiche/src
+++ b/net/third_party/quiche/src
@@ -1 +1 @@
-Subproject commit 42dab6be059fbe2a3d98367e1d5ad19d887156d3
+Subproject commit 6fed96483ed2f0fd9567211f50df8beef43ac140
diff --git a/services/network/cors/cors_url_loader.cc b/services/network/cors/cors_url_loader.cc
index c2eb732d..0f8eb451 100644
--- a/services/network/cors/cors_url_loader.cc
+++ b/services/network/cors/cors_url_loader.cc
@@ -723,6 +723,7 @@
 
   if (request_.redirect_mode == mojom::RedirectMode::kManual) {
     CheckTainted(redirect_info);
+    redirect_info_ = redirect_info;
     deferred_redirect_url_ = std::make_unique<GURL>(redirect_info.new_url);
     forwarding_client_->OnReceiveRedirect(redirect_info,
                                           std::move(response_head));
diff --git a/services/network/ip_protection/ip_protection_token_cache_manager_impl.cc b/services/network/ip_protection/ip_protection_token_cache_manager_impl.cc
index 24887f1c..286f364 100644
--- a/services/network/ip_protection/ip_protection_token_cache_manager_impl.cc
+++ b/services/network/ip_protection/ip_protection_token_cache_manager_impl.cc
@@ -84,8 +84,10 @@
     VLOG(2) << "IPPATC::MaybeRefillCache calling TryGetAuthTokens";
     config_getter_->get()->TryGetAuthTokens(
         batch_size_, proxy_layer_,
-        base::BindOnce(&IpProtectionTokenCacheManagerImpl::OnGotAuthTokens,
-                       weak_ptr_factory_.GetWeakPtr()));
+        base::BindOnce(
+            &IpProtectionTokenCacheManagerImpl::OnGotAuthTokens,
+            weak_ptr_factory_.GetWeakPtr(),
+            /*attempt_start_time_for_metrics=*/base::TimeTicks::Now()));
   }
 
   ScheduleMaybeRefillCache();
@@ -133,6 +135,7 @@
 }
 
 void IpProtectionTokenCacheManagerImpl::OnGotAuthTokens(
+    const base::TimeTicks attempt_start_time_for_metrics,
     std::optional<std::vector<network::mojom::BlindSignedAuthTokenPtr>> tokens,
     std::optional<base::Time> try_again_after) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -160,6 +163,10 @@
                  network::mojom::BlindSignedAuthTokenPtr& b) {
                 return a->expiration < b->expiration;
               });
+
+    base::UmaHistogramMediumTimes(
+        "NetworkService.IpProtection.TokenBatchGenerationTime",
+        base::TimeTicks::Now() - attempt_start_time_for_metrics);
   } else {
     VLOG(2) << "IPPATC::OnGotAuthTokens back off until " << *try_again_after;
     try_get_auth_tokens_after_ = *try_again_after;
@@ -266,8 +273,10 @@
   CHECK(on_try_get_auth_tokens_completed_for_testing_);
   config_getter_->get()->TryGetAuthTokens(
       batch_size_, proxy_layer_,
-      base::BindOnce(&IpProtectionTokenCacheManagerImpl::OnGotAuthTokens,
-                     weak_ptr_factory_.GetWeakPtr()));
+      base::BindOnce(
+          &IpProtectionTokenCacheManagerImpl::OnGotAuthTokens,
+          weak_ptr_factory_.GetWeakPtr(),
+          /*attempt_start_time_for_metrics=*/base::TimeTicks::Now()));
 }
 
 }  // namespace network
diff --git a/services/network/ip_protection/ip_protection_token_cache_manager_impl.h b/services/network/ip_protection/ip_protection_token_cache_manager_impl.h
index 9d227fc..4c1696d 100644
--- a/services/network/ip_protection/ip_protection_token_cache_manager_impl.h
+++ b/services/network/ip_protection/ip_protection_token_cache_manager_impl.h
@@ -73,6 +73,7 @@
 
  private:
   void OnGotAuthTokens(
+      base::TimeTicks attempt_start_time_for_metrics,
       std::optional<std::vector<network::mojom::BlindSignedAuthTokenPtr>>
           tokens,
       std::optional<base::Time> try_again_after);
diff --git a/services/network/ip_protection/ip_protection_token_cache_manager_impl_unittest.cc b/services/network/ip_protection/ip_protection_token_cache_manager_impl_unittest.cc
index d409e15..3872d85 100644
--- a/services/network/ip_protection/ip_protection_token_cache_manager_impl_unittest.cc
+++ b/services/network/ip_protection/ip_protection_token_cache_manager_impl_unittest.cc
@@ -32,6 +32,8 @@
     "NetworkService.IpProtection.ProxyB.TokenSpendRate";
 constexpr char kProxyBTokenExpirationRateHistogram[] =
     "NetworkService.IpProtection.ProxyB.TokenExpirationRate";
+constexpr char kTokenBatchGenerationTimeHistogram[] =
+    "NetworkService.IpProtection.TokenBatchGenerationTime";
 const base::TimeDelta kTokenRateMeasurementInterval = base::Minutes(5);
 
 struct ExpectedTryGetAuthTokensCall {
@@ -103,10 +105,12 @@
 }  // namespace
 
 struct HistogramState {
-  // Number of successful requests (true).
+  // Number of successful calls to GetAuthToken (true).
   int success;
-  // Number of failed requests (false).
+  // Number of failed calls to GetAuthToken (false).
   int failure;
+  // Number of successful token batch generations.
+  int generated;
 };
 
 class IpProtectionTokenCacheManagerImplTest : public testing::Test {
@@ -138,6 +142,8 @@
                                         state.success);
     histogram_tester_.ExpectBucketCount(kGetAuthTokenResultHistogram, false,
                                         state.failure);
+    histogram_tester_.ExpectTotalCount(kTokenBatchGenerationTimeHistogram,
+                                       state.generated);
   }
 
   // Create a batch of tokens.
@@ -251,7 +257,8 @@
   ASSERT_TRUE(token);
   EXPECT_EQ((*token)->token, "token-0");
   EXPECT_EQ((*token)->expiration, kFutureExpiration);
-  ExpectHistogramState(HistogramState{.success = 1, .failure = 0});
+  ExpectHistogramState(
+      HistogramState{.success = 1, .failure = 0, .generated = 1});
 }
 
 // `GetAuthToken()` returns nullopt on a cache containing expired tokens.
@@ -261,7 +268,8 @@
   CallTryGetAuthTokensAndWait(network::mojom::IpProtectionProxyLayer::kProxyA);
   ASSERT_TRUE(mock_.GotAllExpectedMockCalls());
   EXPECT_FALSE(ipp_proxy_a_token_cache_manager_->GetAuthToken());
-  ExpectHistogramState(HistogramState{.success = 0, .failure = 1});
+  ExpectHistogramState(
+      HistogramState{.success = 0, .failure = 1, .generated = 1});
 }
 
 // If `TryGetAuthTokens()` returns an empty batch, the cache remains empty.
@@ -273,7 +281,8 @@
 
   ASSERT_FALSE(ipp_proxy_a_token_cache_manager_->IsAuthTokenAvailable());
   ASSERT_FALSE(ipp_proxy_a_token_cache_manager_->GetAuthToken());
-  ExpectHistogramState(HistogramState{.success = 0, .failure = 1});
+  ExpectHistogramState(
+      HistogramState{.success = 0, .failure = 1, .generated = 1});
 }
 
 // If `TryGetAuthTokens()` returns an backoff due to an error, the cache remains
@@ -287,7 +296,8 @@
 
   ASSERT_FALSE(ipp_proxy_a_token_cache_manager_->IsAuthTokenAvailable());
   ASSERT_FALSE(ipp_proxy_a_token_cache_manager_->GetAuthToken());
-  ExpectHistogramState(HistogramState{.success = 0, .failure = 1});
+  ExpectHistogramState(
+      HistogramState{.success = 0, .failure = 1, .generated = 0});
 }
 
 // `GetAuthToken()` skips expired tokens and returns a non-expired token,
@@ -304,7 +314,8 @@
   auto got_token = ipp_proxy_a_token_cache_manager_->GetAuthToken();
   EXPECT_EQ(got_token.value()->token, "good-token");
   EXPECT_EQ(got_token.value()->expiration, kFutureExpiration);
-  ExpectHistogramState(HistogramState{.success = 1, .failure = 0});
+  ExpectHistogramState(
+      HistogramState{.success = 1, .failure = 0, .generated = 1});
 }
 
 TEST_F(IpProtectionTokenCacheManagerImplTest, TokenExpirationFuzzed) {
@@ -332,7 +343,8 @@
   EXPECT_FALSE(ipp_proxy_a_token_cache_manager_->IsAuthTokenAvailable());
   auto token = ipp_token_cache_manager.GetAuthToken();
   ASSERT_FALSE(token);
-  ExpectHistogramState(HistogramState{.success = 0, .failure = 1});
+  ExpectHistogramState(
+      HistogramState{.success = 0, .failure = 1, .generated = 0});
 }
 
 // Verify that the token spend rate for ProxyA is measured correctly.
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index dd83db8..1086e19 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5377,9 +5377,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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5389,8 +5389,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
@@ -5533,9 +5533,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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5545,8 +5545,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 1cb8702..d0a4f8aa 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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.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.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.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.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index be1c5e4b..4b52eb7 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -39981,9 +39981,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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -39992,8 +39992,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
@@ -40131,9 +40131,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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -40142,8 +40142,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
@@ -41480,9 +41480,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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -41492,8 +41492,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
@@ -41636,9 +41636,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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -41648,8 +41648,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
@@ -42961,9 +42961,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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42972,8 +42972,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
@@ -43111,9 +43111,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.6328.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 124.0.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43122,8 +43122,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v124.0.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index b6b67a60..aaedcd2fe 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.6328.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.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.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.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.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.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.6328.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.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.6328.0",
+        "description": "Run with ash-chrome version 124.0.6329.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.6328.0",
-              "revision": "version:124.0.6328.0"
+              "location": "lacros_version_skew_tests_v124.0.6329.0",
+              "revision": "version:124.0.6329.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index ac7dc153..89c62e4 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.6328.0',
+    'description': 'Run with ash-chrome version 124.0.6329.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6328.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v124.0.6329.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.6328.0',
-          'revision': 'version:124.0.6328.0',
+          'location': 'lacros_version_skew_tests_v124.0.6329.0',
+          'revision': 'version:124.0.6329.0',
         },
       ],
     },
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index f0e2d00..42f16ff2 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -6713,6 +6713,21 @@
             ]
         }
     ],
+    "EnableWifiQos": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "EnableWifiQos"
+                    ]
+                }
+            ]
+        }
+    ],
     "EndOfLifeIncentive": [
         {
             "platforms": [
@@ -19166,27 +19181,6 @@
             ]
         }
     ],
-    "UsernameFirstFlowWithIntermediateValues": [
-        {
-            "platforms": [
-                "android",
-                "chromeos",
-                "chromeos_lacros",
-                "ios",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "UsernameFirstFlowWithIntermediateValues"
-                    ]
-                }
-            ]
-        }
-    ],
     "UsernameFirstFlowWithIntermediateValuesPredictions": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index a971e5b4..19e725e 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit a971e5b42e1e8e61ccb0d8e4b3f2245aaa4f2d4d
+Subproject commit 19e725e49c7d23f810ebd709b79d91323af921c1
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index c4abecb..c853bfe 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -1375,6 +1375,15 @@
 
 BASE_FEATURE(kLinkPreview, "LinkPreview", base::FEATURE_DISABLED_BY_DEFAULT);
 
+constexpr base::FeatureParam<LinkPreviewTriggerType>::Option
+    link_preview_trigger_type_options[] = {
+        {LinkPreviewTriggerType::kAltClick, "alt_click"},
+        {LinkPreviewTriggerType::kAltHover, "alt_hover"},
+        {LinkPreviewTriggerType::kLongPress, "long_press"}};
+const base::FeatureParam<LinkPreviewTriggerType> kLinkPreviewTriggerType{
+    &kLinkPreview, "trigger_type", LinkPreviewTriggerType::kAltHover,
+    &link_preview_trigger_type_options};
+
 // A feature to control whether the loading phase should be extended beyond
 // First Meaningful Paint by a configurable buffer.
 BASE_FEATURE(kLoadingPhaseBufferTimeAfterFirstMeaningfulPaint,
@@ -2536,6 +2545,11 @@
          base::FeatureList::IsEnabled(kFetchLaterAPI);
 }
 
+bool IsLinkPreviewTriggerTypeEnabled(LinkPreviewTriggerType type) {
+  return base::FeatureList::IsEnabled(blink::features::kLinkPreview) &&
+         type == blink::features::kLinkPreviewTriggerType.Get();
+}
+
 BASE_FEATURE(kExpandCompositedCullRect,
              "ExpandCompositedCullRect",
              base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/third_party/blink/common/loader/throttling_url_loader.cc b/third_party/blink/common/loader/throttling_url_loader.cc
index 5098dbd..e549048 100644
--- a/third_party/blink/common/loader/throttling_url_loader.cc
+++ b/third_party/blink/common/loader/throttling_url_loader.cc
@@ -286,6 +286,8 @@
 }
 
 ThrottlingURLLoader::~ThrottlingURLLoader() {
+  TRACE_EVENT_WITH_FLOW0("loading", "ThrottlingURLLoader::~ThrottlingURLLoader",
+                         TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN);
   if (inside_delegate_calls_ > 0) {
     // A throttle is calling into this object. In this case, delay destruction
     // of the throttles, so that throttles don't need to worry about any
@@ -408,6 +410,8 @@
     network::mojom::URLLoaderClient* client,
     const net::NetworkTrafficAnnotationTag& traffic_annotation)
     : forwarding_client_(client), traffic_annotation_(traffic_annotation) {
+  TRACE_EVENT_WITH_FLOW0("loading", "ThrottlingURLLoader::ThrottlingURLLoader",
+                         TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_OUT);
   throttles_.reserve(throttles.size());
   for (auto& throttle : throttles)
     throttles_.emplace_back(this, std::move(throttle));
@@ -420,6 +424,9 @@
     network::ResourceRequest* url_request,
     scoped_refptr<base::SequencedTaskRunner> task_runner,
     std::optional<std::vector<std::string>> cors_exempt_header_list) {
+  TRACE_EVENT_WITH_FLOW0("loading", "ThrottlingURLLoader::Start",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   DCHECK_EQ(DEFERRED_NONE, deferred_stage_);
   DCHECK(!loader_completed_);
 
@@ -486,6 +493,9 @@
 }
 
 void ThrottlingURLLoader::StartNow() {
+  TRACE_EVENT_WITH_FLOW0("loading", "ThrottlingURLLoader::StartNow",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   DCHECK(start_info_);
   if (!throttle_will_start_redirect_url_.is_empty()) {
     auto first_party_url_policy =
@@ -620,8 +630,10 @@
   DCHECK_EQ(DEFERRED_NONE, deferred_stage_);
   DCHECK(!loader_completed_);
   DCHECK(deferring_throttles_.empty());
-  TRACE_EVENT1("loading", "ThrottlingURLLoader::OnReceiveResponse", "url",
-               response_url_.possibly_invalid_spec());
+  TRACE_EVENT_WITH_FLOW1("loading", "ThrottlingURLLoader::OnReceiveResponse",
+                         TRACE_ID_LOCAL(this),
+                         TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
+                         "url", response_url_.possibly_invalid_spec());
   if (start_info_ && start_info_->url_request.keepalive) {
     base::UmaHistogramBoolean("FetchKeepAlive.Renderer.Total.ReceivedResponse",
                               true);
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index a6591c2..91072938 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -330,6 +330,7 @@
     "web/web_label_element.h",
     "web/web_language_detection_details.h",
     "web/web_lifecycle_update.h",
+    "web/web_link_preview_triggerer.h",
     "web/web_local_frame.h",
     "web/web_local_frame_client.h",
     "web/web_local_frame_observer.h",
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index be0bff2d..537c7ef 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -830,6 +830,18 @@
 // Tracking bug: go/launch/4269184
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kLinkPreview);
 
+enum class LinkPreviewTriggerType {
+  // Alt + left click
+  kAltClick,
+  // Alt + mousehover
+  kAltHover,
+  // Long left click down
+  kLongPress,
+};
+
+BLINK_COMMON_EXPORT extern const base::FeatureParam<LinkPreviewTriggerType>
+    kLinkPreviewTriggerType;
+
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(kLoadingTasksUnfreezable);
 
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
@@ -1620,6 +1632,10 @@
 // specific to one of them should not rely on this function.
 BLINK_COMMON_EXPORT bool IsKeepAliveURLLoaderServiceEnabled();
 
+// Returns true if Link Preview and the given trigger type is enabled.
+BLINK_COMMON_EXPORT bool IsLinkPreviewTriggerTypeEnabled(
+    LinkPreviewTriggerType type);
+
 // Kill-switch for removing Authorization header upon cross origin redirects.
 BLINK_COMMON_EXPORT BASE_DECLARE_FEATURE(
     kRemoveAuthroizationOnCrossOriginRedirect);
diff --git a/third_party/blink/public/mojom/BUILD.gn b/third_party/blink/public/mojom/BUILD.gn
index 68454e1..739be81 100644
--- a/third_party/blink/public/mojom/BUILD.gn
+++ b/third_party/blink/public/mojom/BUILD.gn
@@ -76,13 +76,13 @@
     "fetch/fetch_api_response.mojom",
     "file/file_utilities.mojom",
     "file_system_access/file_system_access_access_handle_host.mojom",
-    "file_system_access/file_system_access_capacity_allocation_host.mojom",
     "file_system_access/file_system_access_cloud_identifier.mojom",
     "file_system_access/file_system_access_data_transfer_token.mojom",
     "file_system_access/file_system_access_directory_handle.mojom",
     "file_system_access/file_system_access_error.mojom",
     "file_system_access/file_system_access_file_delegate_host.mojom",
     "file_system_access/file_system_access_file_handle.mojom",
+    "file_system_access/file_system_access_file_modification_host.mojom",
     "file_system_access/file_system_access_file_writer.mojom",
     "file_system_access/file_system_access_manager.mojom",
     "file_system_access/file_system_access_observer.mojom",
diff --git a/third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom b/third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom
index 663a3957..66516f31 100644
--- a/third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom
+++ b/third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom
@@ -9,7 +9,7 @@
 import "third_party/blink/public/mojom/blob/blob.mojom";
 import "third_party/blink/public/mojom/blob/serialized_blob.mojom";
 import "third_party/blink/public/mojom/file_system_access/file_system_access_access_handle_host.mojom";
-import "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom";
+import "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom";
 import "third_party/blink/public/mojom/file_system_access/file_system_access_cloud_identifier.mojom";
 import "third_party/blink/public/mojom/file_system_access/file_system_access_error.mojom";
 import "third_party/blink/public/mojom/file_system_access/file_system_access_file_delegate_host.mojom";
@@ -20,7 +20,7 @@
 // Representation of a regular file for an AccessHandle.
 // `os_file` is an OS-level file which provides direct read/write access.
 // `file_size` is the file size of `os_file` (in bytes).
-// `capacity_allocation_host` is a mojo pipe to manage capacity allocations for
+// `file_modification_host` is a mojo pipe to manage capacity allocations for
 // this file.
 //
 // Each file has an associated capacity, which is the number of bytes of
@@ -30,7 +30,7 @@
 // file's size on disk, such as truncate() create capacity.
 //
 // Each file starts out with zero (0) capacity, and may call
-// RequestCapacityChange() on the capacity_allocation_host to obtain capacity
+// RequestCapacityChange() on the file_modification_host to obtain capacity
 // from the quota system. Capacity counts as quota usage by the storage key.
 // When the instance's mojo pipe is closed, any available capacity is
 // automatically returned to the quota system.
@@ -40,7 +40,7 @@
 struct FileSystemAccessRegularFile {
   mojo_base.mojom.File os_file;
   int64 file_size;
-  pending_remote<FileSystemAccessCapacityAllocationHost> capacity_allocation_host;
+  pending_remote<FileSystemAccessFileModificationHost> file_modification_host;
 };
 
 // Representation of a file for an AccessHandle.
diff --git a/third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom b/third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom
similarity index 77%
rename from third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom
rename to third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom
index 4c2d5d88..c0f5b61 100644
--- a/third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom
+++ b/third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom
@@ -4,15 +4,12 @@
 
 module blink.mojom;
 
-// Manages storge capacity allocations for a non-incognito file handle held by
+// Manages storage file modifications for a non-incognito file handle held by
 // an AccessHandle of FileSystemAccess.
 //
-// Capacity allocations are used for non-incognito files only. In incognito
-// mode, quota is fully managed in the browser process.
-//
-// TODO(https://crbug.com/1490686): Consider renaming this to be more generic,
-// since it now does more than just handle quota.
-interface FileSystemAccessCapacityAllocationHost {
+// This interface is used for non-incognito files only. In incognito mode, quota
+// is fully managed in the browser process.
+interface FileSystemAccessFileModificationHost {
   // Requests or releases storage capacity.
   //
   // This interface's consumer must only expand the file handle associated with
@@ -29,7 +26,7 @@
   // to succeed. In other words, if `capacity_delta` < 0,
   // `granted_capacity_delta` is guaranteed to == `capacity_delta`.
   //
-  // The synchronous interface is needed as File System Access Access Handles
+  // The synchronous method is needed as File System Access Access Handles
   // implement synchronous write operations.
   [Sync]
   RequestCapacityChange(int64 capacity_delta) =>
diff --git a/third_party/blink/public/web/web_document.h b/third_party/blink/public/web/web_document.h
index b377ae4..a52f557 100644
--- a/third_party/blink/public/web/web_document.h
+++ b/third_party/blink/public/web/web_document.h
@@ -185,6 +185,11 @@
   // Returns the referrer for this document.
   WebString OutgoingReferrer() const;
 
+  // (Experimental) Initiates Link Preview for `url`.
+  //
+  // It is intended to be used in WebLinkPreviewTriggerer.
+  void InitiatePreview(const WebURL& url);
+
 #if INSIDE_BLINK
   WebDocument(Document*);
   WebDocument& operator=(Document*);
diff --git a/third_party/blink/public/web/web_element.h b/third_party/blink/public/web/web_element.h
index 9cd08e8..103cbab7 100644
--- a/third_party/blink/public/web/web_element.h
+++ b/third_party/blink/public/web/web_element.h
@@ -95,6 +95,12 @@
   // Otherwise returns the empty string.
   WebString SelectedText() const;
 
+  // Selects the text in this element.
+  // If `select_all`, then the entire contents of the element is selected.
+  // If `!select_all`, then selects only the empty range at the end of the
+  // element
+  void SelectText(bool select_all);
+
   // Simulates a paste of `text` event into `this` element.
   //
   // There are three different behaviors depending on `replace_all` and which
diff --git a/third_party/blink/public/web/web_link_preview_triggerer.h b/third_party/blink/public/web/web_link_preview_triggerer.h
new file mode 100644
index 0000000..7a8e283
--- /dev/null
+++ b/third_party/blink/public/web/web_link_preview_triggerer.h
@@ -0,0 +1,63 @@
+// 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 THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_LINK_PREVIEW_TRIGGERER_H_
+#define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_LINK_PREVIEW_TRIGGERER_H_
+
+#include "base/functional/callback.h"
+#include "third_party/blink/public/common/input/web_mouse_event.h"
+#include "third_party/blink/public/platform/web_url.h"
+#include "third_party/blink/public/web/web_document.h"
+#include "third_party/blink/public/web/web_element.h"
+
+namespace blink {
+
+// Experimental
+// Observes signals of user actions and determines to trigger Link Preview.
+//
+// It will be instantiated per LocalFrame. Notified events are bare and intended
+// to be filtered by implementations.
+//
+// For concrete triggers, see the implementations.
+class WebLinkPreviewTriggerer {
+ public:
+  virtual ~WebLinkPreviewTriggerer() = default;
+
+  // Events to track current modifier state.
+  //
+  // This is called for every key event and mouse leave event. For mouse leave
+  // event, kNoModifier blink::WebInputEvent::kNoModifiers is passed as an
+  // argument.
+  //
+  // The argument `modifiers` is a bit mask consisting of
+  // `blink::WebInputEvent::Modifiers`.
+  //
+  // Note that we don't still have a right way to track modifiers state (But
+  // it's enough for Link Preview because it can trigger preview only if a mouse
+  // is on anchor element.) because
+  // 1. we can't get changes of modifiers outside the window, and 2. we don't
+  // have a reliable way to `KeyobardEventManager::GetCurrentModifierState`
+  // except for macOS. For example, consider the following cases:
+  //
+  // A.  A user presses and holds Alt button, leaves the window, releases Alt,
+  //     enters to the window.
+  // A'. Same for subframe -> parent frame -> subframe.
+  // B.  A user presses and holds Alt button, leaves the window, releases Alt,
+  //     presses Alt, enters to the window.
+  //
+  // In A and A' (resp. B), the ideal `GetCurrentModifierState` should return
+  // `kNoModifiers` (resp `kAltKey`), but we don't know how to correctly get to
+  // know it. So, for safety, mouseleave event emits kNoModifiers.
+  virtual void MaybeChangedKeyEventModifier(int modifiers) {}
+  // Called when the hover element changed.
+  virtual void DidChangeHoverElement(blink::WebElement element) {}
+  // Called when an anchor element with valid link received a mouse event.
+  virtual void DidAnchorElementReceiveMouseEvent(
+      blink::WebElement anchor_element,
+      blink::WebMouseEvent mouse_event) {}
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_LINK_PREVIEW_TRIGGERER_H_
diff --git a/third_party/blink/public/web/web_local_frame_client.h b/third_party/blink/public/web/web_local_frame_client.h
index 58612b9..1e475f6 100644
--- a/third_party/blink/public/web/web_local_frame_client.h
+++ b/third_party/blink/public/web/web_local_frame_client.h
@@ -140,6 +140,7 @@
 class WebURLRequest;
 class WebURLResponse;
 class WebView;
+class WebLinkPreviewTriggerer;
 struct FramePolicy;
 struct Impression;
 struct JavaScriptFrameworkDetectionResult;
@@ -862,6 +863,11 @@
       const WebURL& base_url) {
     return nullptr;
   }
+
+  virtual std::unique_ptr<WebLinkPreviewTriggerer> CreateLinkPreviewTriggerer();
+
+  virtual void SetLinkPreviewTriggererForTesting(
+      std::unique_ptr<WebLinkPreviewTriggerer> trigger);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/accessibility/ax_object_cache.h b/third_party/blink/renderer/core/accessibility/ax_object_cache.h
index c446212..61e95857 100644
--- a/third_party/blink/renderer/core/accessibility/ax_object_cache.h
+++ b/third_party/blink/renderer/core/accessibility/ax_object_cache.h
@@ -196,18 +196,6 @@
 
   virtual void OnTouchAccessibilityHover(const gfx::Point&) = 0;
 
-  // Gets the AXID of the given Node. If there is not yet an associated
-  // AXObject, this method will create one (along with its parent chain) and
-  // return the id.
-  virtual AXID GetAXID(Node*) = 0;
-
-  // Crash dumps have shown there are times where AXID's are queried
-  // 'out-of-band' where we may not be in a state where creating AXObjects and
-  // repairing parent chains is possible. This will look for the current
-  // AXObject associated with the given node and return its id without creating
-  // or recomputing any state.
-  virtual AXID GetExistingAXID(Node*) = 0;
-
   virtual AXObject* ObjectFromAXID(AXID) const = 0;
 
   virtual AXObject* Root() = 0;
diff --git a/third_party/blink/renderer/core/accessibility/axid.h b/third_party/blink/renderer/core/accessibility/axid.h
index 4b805d8..e116ab0c 100644
--- a/third_party/blink/renderer/core/accessibility/axid.h
+++ b/third_party/blink/renderer/core/accessibility/axid.h
@@ -6,7 +6,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_AXID_H_
 
 namespace blink {
-using AXID = unsigned;
+using AXID = int;
 }
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_ACCESSIBILITY_AXID_H_
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.cc b/third_party/blink/renderer/core/animation/css/css_animations.cc
index c0e563e0..93a51f8 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -1871,6 +1871,10 @@
   return effect.Affects(PropertyHandle(GetCSSPropertyBackgroundColor()));
 }
 
+bool AffectsClipPath(const AnimationEffect& effect) {
+  return effect.Affects(PropertyHandle(GetCSSPropertyClipPath()));
+}
+
 void UpdateAnimationFlagsForEffect(const AnimationEffect& effect,
                                    ComputedStyleBuilder& builder) {
   if (effect.Affects(PropertyHandle(GetCSSPropertyOpacity())))
@@ -1889,15 +1893,18 @@
     builder.SetHasCurrentBackdropFilterAnimation(true);
   if (AffectsBackgroundColor(effect))
     builder.SetHasCurrentBackgroundColorAnimation(true);
-  if (effect.Affects(PropertyHandle(GetCSSPropertyClipPath())))
+  if (AffectsClipPath(effect)) {
     builder.SetHasCurrentClipPathAnimation(true);
+  }
 }
 
 void SetCompositablePaintAnimationChangedIfAffected(
     const AnimationEffect& effect,
     ComputedStyleBuilder& builder) {
-  if (RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled() &&
-      AffectsBackgroundColor(effect)) {
+  if ((RuntimeEnabledFeatures::CompositeBGColorAnimationEnabled() &&
+       AffectsBackgroundColor(effect)) ||
+      (RuntimeEnabledFeatures::CompositeClipPathAnimationEnabled() &&
+       AffectsClipPath(effect))) {
     builder.SetCompositablePaintAnimationChanged(true);
   }
 }
diff --git a/third_party/blink/renderer/core/aom/computed_accessible_node.cc b/third_party/blink/renderer/core/aom/computed_accessible_node.cc
index 10d4a65..9b8cc2d7 100644
--- a/third_party/blink/renderer/core/aom/computed_accessible_node.cc
+++ b/third_party/blink/renderer/core/aom/computed_accessible_node.cc
@@ -114,7 +114,7 @@
   ax_context_->GetDocument()->View()->UpdateAllLifecyclePhasesExceptPaint(
       DocumentUpdateReason::kAccessibility);
   AXObjectCache& cache = ax_context_->GetAXObjectCache();
-  AXID ax_id = ax_id_ ? ax_id_ : cache.GetAXID(element_);
+  AXID ax_id = ax_id_ ? ax_id_ : element_->GetDomNodeId();
   if (!ax_id || !cache.ObjectFromAXID(ax_id)) {
     resolver_->Resolve();  // No AXObject exists for this element.
     return;
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.cc b/third_party/blink/renderer/core/css/element_rule_collector.cc
index ae1fede0..c74b0efa 100644
--- a/third_party/blink/renderer/core/css/element_rule_collector.cc
+++ b/third_party/blink/renderer/core/css/element_rule_collector.cc
@@ -64,12 +64,39 @@
 
 namespace blink {
 namespace {
+
 struct CumulativeRulePerfKey {
   String selector;
   String style_sheet_id;
   CumulativeRulePerfKey(const String& selector, const String& style_sheet_id)
       : selector(selector), style_sheet_id(style_sheet_id) {}
 };
+
+struct ContextWithStyleScopeFrame {
+  STACK_ALLOCATED();
+
+ public:
+  ContextWithStyleScopeFrame(Element* element,
+                             const MatchRequest& match_request,
+                             StyleRequest* pseudo_style_request,
+                             StyleScopeFrame* parent_frame)
+      : style_scope_frame(*element, parent_frame), context(element) {
+    context.style_scope_frame =
+        &style_scope_frame.GetParentFrameOrThis(*element);
+    context.scope = match_request.Scope();
+    context.pseudo_id = pseudo_style_request->pseudo_id;
+    context.pseudo_argument = &pseudo_style_request->pseudo_argument;
+    context.vtt_originating_element = match_request.VTTOriginatingElement();
+  }
+
+  // This StyleScopeFrame is effectively ignored if the StyleRecalcContext
+  // provides StyleScopeFrame already (see call to GetParentFrameOrThis above).
+  // This happens e.g. when we need to collect matching rules for inspector
+  // purposes.
+  StyleScopeFrame style_scope_frame;
+  SelectorChecker::SelectorCheckingContext context;
+};
+
 }  // namespace
 }  // namespace blink
 
@@ -412,22 +439,8 @@
     const RuleSet* rule_set,
     int style_sheet_index,
     const SelectorChecker& checker,
+    SelectorChecker::SelectorCheckingContext& context,
     PartRequest* part_request) {
-  // This StyleScopeFrame is effectively ignored if the StyleRecalcContext
-  // provides StyleScopeFrame already (see call to GetParentFrameOrThis below).
-  // This happens e.g. when we need to collect matching rules for inspector
-  // purposes.
-  StyleScopeFrame style_scope_frame(context_.GetElement(),
-                                    style_recalc_context_.style_scope_frame);
-
-  SelectorChecker::SelectorCheckingContext context(&context_.GetElement());
-  context.scope = match_request.Scope();
-  context.pseudo_id = pseudo_style_request_.pseudo_id;
-  context.pseudo_argument = &pseudo_style_request_.pseudo_argument;
-  context.vtt_originating_element = match_request.VTTOriginatingElement();
-  context.style_scope_frame =
-      &style_scope_frame.GetParentFrameOrThis(context_.GetElement());
-
   // If we are _not_ in initial style, or we are just collecting rules,
   // we must skip all rules marked with @starting-style.
   bool reject_starting_styles = style_recalc_context_.is_ensuring_style ||
@@ -481,7 +494,6 @@
     }
 
     SelectorChecker::MatchResult result;
-    context.style_scope = scope_seeker.Seek(rule_data.GetPosition());
     if (context.vtt_originating_element == nullptr &&
         rule_data.IsEntirelyCoveredByBucketing()) {
       // Just by seeing this rule, we know that its selector
@@ -490,21 +502,27 @@
       if (pseudo_style_request_.pseudo_id != kPseudoIdNone) {
         continue;
       }
+#if DCHECK_IS_ON()
+      context.style_scope = scope_seeker.Seek(rule_data.GetPosition());
       DCHECK(!context.style_scope);
       DCHECK(SlowMatchWithNoResultFlags(checker, context, selector, rule_data,
                                         suppress_visited_, result.proximity));
+#endif
     } else if (context.vtt_originating_element == nullptr &&
                rule_data.SelectorIsEasy()) {
       if (pseudo_style_request_.pseudo_id != kPseudoIdNone) {
         continue;
       }
       bool easy_match = EasySelectorChecker::Match(&selector, context.element);
+#if DCHECK_IS_ON()
+      context.style_scope = scope_seeker.Seek(rule_data.GetPosition());
       DCHECK(!context.style_scope);
       DCHECK_EQ(easy_match, SlowMatchWithNoResultFlags(
                                 checker, context, selector, rule_data,
                                 suppress_visited_, result.proximity))
           << "Mismatch for selector " << selector.SelectorText()
           << " on element " << context.element;
+#endif
       if (!easy_match) {
         continue;
       }
@@ -513,6 +531,7 @@
       context.match_visited =
           !suppress_visited_ &&
           rule_data.LinkMatchType() == CSSSelector::kMatchVisited;
+      context.style_scope = scope_seeker.Seek(rule_data.GetPosition());
       bool match = checker.Match(context, result);
       result_.AddFlags(result.flags);
       if (!match) {
@@ -620,6 +639,7 @@
     const RuleSet* rule_set,
     int style_sheet_index,
     const SelectorChecker& checker,
+    SelectorChecker::SelectorCheckingContext& context,
     PartRequest* part_request) {
   // This is a very common case for many style sheets, and by putting it here
   // instead of inside CollectMatchingRulesForListInternal(), we're usually
@@ -634,11 +654,11 @@
   // when tracing is not enabled.
   if (!*g_selector_stats_tracing_enabled) {
     return CollectMatchingRulesForListInternal<stop_at_first_match, false>(
-        rules, match_request, rule_set, style_sheet_index, checker,
+        rules, match_request, rule_set, style_sheet_index, checker, context,
         part_request);
   } else {
     return CollectMatchingRulesForListInternal<stop_at_first_match, true>(
-        rules, match_request, rule_set, style_sheet_index, checker,
+        rules, match_request, rule_set, style_sheet_index, checker, context,
         part_request);
   }
 }
@@ -688,6 +708,10 @@
   SelectorChecker checker(nullptr, pseudo_style_request_, mode_,
                           matching_ua_rules_);
 
+  ContextWithStyleScopeFrame context(&context_.GetElement(), match_request,
+                                     &pseudo_style_request_,
+                                     style_recalc_context_.style_scope_frame);
+
   Element& element = context_.GetElement();
   const AtomicString& pseudo_id = element.ShadowPseudoId();
   if (!pseudo_id.empty()) {
@@ -695,8 +719,8 @@
     for (const auto bundle : match_request.AllRuleSets()) {
       if (CollectMatchingRulesForList<stop_at_first_match>(
               bundle.rule_set->UAShadowPseudoElementRules(pseudo_id),
-              match_request, bundle.rule_set, bundle.style_sheet_index,
-              checker) &&
+              match_request, bundle.rule_set, bundle.style_sheet_index, checker,
+              context.context) &&
           stop_at_first_match) {
         return true;
       }
@@ -707,7 +731,7 @@
     for (const auto bundle : match_request.AllRuleSets()) {
       if (CollectMatchingRulesForList<stop_at_first_match>(
               bundle.rule_set->CuePseudoRules(), match_request, bundle.rule_set,
-              bundle.style_sheet_index, checker) &&
+              bundle.style_sheet_index, checker, context.context) &&
           stop_at_first_match) {
         return true;
       }
@@ -730,8 +754,8 @@
     for (const auto bundle : match_request.AllRuleSets()) {
       if (CollectMatchingRulesForList<stop_at_first_match>(
               bundle.rule_set->IdRules(element.IdForStyleResolution()),
-              match_request, bundle.rule_set, bundle.style_sheet_index,
-              checker) &&
+              match_request, bundle.rule_set, bundle.style_sheet_index, checker,
+              context.context) &&
           stop_at_first_match) {
         return true;
       }
@@ -742,7 +766,8 @@
       for (const auto bundle : match_request.AllRuleSets()) {
         if (CollectMatchingRulesForList<stop_at_first_match>(
                 bundle.rule_set->ClassRules(class_name), match_request,
-                bundle.rule_set, bundle.style_sheet_index, checker) &&
+                bundle.rule_set, bundle.style_sheet_index, checker,
+                context.context) &&
             stop_at_first_match) {
           return true;
         }
@@ -819,7 +844,8 @@
         }
         if (CollectMatchingRulesForList<stop_at_first_match>(
                 bundle.rule_set->AttrRules(lower_name), match_request,
-                bundle.rule_set, bundle.style_sheet_index, checker) &&
+                bundle.rule_set, bundle.style_sheet_index, checker,
+                context.context) &&
             stop_at_first_match) {
           return true;
         }
@@ -834,7 +860,8 @@
     for (const auto bundle : match_request.AllRuleSets()) {
       if (CollectMatchingRulesForList<stop_at_first_match>(
               bundle.rule_set->LinkPseudoClassRules(), match_request,
-              bundle.rule_set, bundle.style_sheet_index, checker) &&
+              bundle.rule_set, bundle.style_sheet_index, checker,
+              context.context) &&
           stop_at_first_match) {
         return true;
       }
@@ -844,7 +871,8 @@
     for (const auto bundle : match_request.AllRuleSets()) {
       if (CollectMatchingRulesForList<stop_at_first_match>(
               bundle.rule_set->FocusPseudoClassRules(), match_request,
-              bundle.rule_set, bundle.style_sheet_index, checker) &&
+              bundle.rule_set, bundle.style_sheet_index, checker,
+              context.context) &&
           stop_at_first_match) {
         return true;
       }
@@ -854,7 +882,8 @@
     for (const auto bundle : match_request.AllRuleSets()) {
       if (CollectMatchingRulesForList<stop_at_first_match>(
               bundle.rule_set->SelectorFragmentAnchorRules(), match_request,
-              bundle.rule_set, bundle.style_sheet_index, checker) &&
+              bundle.rule_set, bundle.style_sheet_index, checker,
+              context.context) &&
           stop_at_first_match) {
         return true;
       }
@@ -864,7 +893,8 @@
     for (const auto bundle : match_request.AllRuleSets()) {
       if (CollectMatchingRulesForList<stop_at_first_match>(
               bundle.rule_set->FocusVisiblePseudoClassRules(), match_request,
-              bundle.rule_set, bundle.style_sheet_index, checker) &&
+              bundle.rule_set, bundle.style_sheet_index, checker,
+              context.context) &&
           stop_at_first_match) {
         return true;
       }
@@ -874,7 +904,8 @@
     for (const auto bundle : match_request.AllRuleSets()) {
       if (CollectMatchingRulesForList<stop_at_first_match>(
               bundle.rule_set->RootElementRules(), match_request,
-              bundle.rule_set, bundle.style_sheet_index, checker) &&
+              bundle.rule_set, bundle.style_sheet_index, checker,
+              context.context) &&
           stop_at_first_match) {
         return true;
       }
@@ -886,7 +917,8 @@
   for (const auto bundle : match_request.AllRuleSets()) {
     if (CollectMatchingRulesForList<stop_at_first_match>(
             bundle.rule_set->TagRules(element_name), match_request,
-            bundle.rule_set, bundle.style_sheet_index, checker) &&
+            bundle.rule_set, bundle.style_sheet_index, checker,
+            context.context) &&
         stop_at_first_match) {
       return true;
     }
@@ -894,7 +926,7 @@
   for (const auto bundle : match_request.AllRuleSets()) {
     if (CollectMatchingRulesForList<stop_at_first_match>(
             bundle.rule_set->UniversalRules(), match_request, bundle.rule_set,
-            bundle.style_sheet_index, checker) &&
+            bundle.style_sheet_index, checker, context.context) &&
         stop_at_first_match) {
       return true;
     }
@@ -907,14 +939,18 @@
   SelectorChecker checker(nullptr, pseudo_style_request_, mode_,
                           matching_ua_rules_);
 
+  ContextWithStyleScopeFrame context(&context_.GetElement(), match_request,
+                                     &pseudo_style_request_,
+                                     style_recalc_context_.style_scope_frame);
+
   for (const auto bundle : match_request.AllRuleSets()) {
     CollectMatchingRulesForList</*stop_at_first_match=*/false>(
         bundle.rule_set->ShadowHostRules(), match_request, bundle.rule_set,
-        bundle.style_sheet_index, checker);
+        bundle.style_sheet_index, checker, context.context);
     if (bundle.rule_set->MayHaveScopeInUniversalBucket()) {
       CollectMatchingRulesForList</*stop_at_first_match=*/false>(
           bundle.rule_set->UniversalRules(), match_request, bundle.rule_set,
-          bundle.style_sheet_index, checker);
+          bundle.style_sheet_index, checker, context.context);
     }
   }
 }
@@ -924,16 +960,20 @@
   SelectorChecker checker(nullptr, pseudo_style_request_, mode_,
                           matching_ua_rules_);
 
+  ContextWithStyleScopeFrame context(&context_.GetElement(), match_request,
+                                     &pseudo_style_request_,
+                                     style_recalc_context_.style_scope_frame);
+
   for (const auto bundle : match_request.AllRuleSets()) {
     if (CollectMatchingRulesForList</*stop_at_first_match=*/true>(
             bundle.rule_set->ShadowHostRules(), match_request, bundle.rule_set,
-            bundle.style_sheet_index, checker)) {
+            bundle.style_sheet_index, checker, context.context)) {
       return true;
     }
     if (bundle.rule_set->MayHaveScopeInUniversalBucket()) {
       if (CollectMatchingRulesForList</*stop_at_first_match=*/true>(
               bundle.rule_set->UniversalRules(), match_request, bundle.rule_set,
-              bundle.style_sheet_index, checker)) {
+              bundle.style_sheet_index, checker, context.context)) {
         return true;
       }
     }
@@ -945,11 +985,14 @@
     const MatchRequest& match_request) {
   SelectorChecker checker(nullptr, pseudo_style_request_, mode_,
                           matching_ua_rules_);
+  ContextWithStyleScopeFrame context(&context_.GetElement(), match_request,
+                                     &pseudo_style_request_,
+                                     style_recalc_context_.style_scope_frame);
 
   for (const auto bundle : match_request.AllRuleSets()) {
     CollectMatchingRulesForList</*stop_at_first_match=*/false>(
         bundle.rule_set->SlottedPseudoElementRules(), match_request,
-        bundle.rule_set, bundle.style_sheet_index, checker);
+        bundle.rule_set, bundle.style_sheet_index, checker, context.context);
   }
 }
 
@@ -961,10 +1004,14 @@
   SelectorChecker checker(&part_names, pseudo_style_request_, mode_,
                           matching_ua_rules_);
 
+  ContextWithStyleScopeFrame context(&context_.GetElement(), match_request,
+                                     &pseudo_style_request_,
+                                     style_recalc_context_.style_scope_frame);
+
   for (const auto bundle : match_request.AllRuleSets()) {
     CollectMatchingRulesForList</*stop_at_first_match=*/false>(
         bundle.rule_set->PartPseudoRules(), match_request, bundle.rule_set,
-        bundle.style_sheet_index, checker, &request);
+        bundle.style_sheet_index, checker, context.context, &request);
   }
 }
 
diff --git a/third_party/blink/renderer/core/css/element_rule_collector.h b/third_party/blink/renderer/core/css/element_rule_collector.h
index cbefec34..5e30b81 100644
--- a/third_party/blink/renderer/core/css/element_rule_collector.h
+++ b/third_party/blink/renderer/core/css/element_rule_collector.h
@@ -263,12 +263,14 @@
   bool CollectMatchingRulesInternal(const MatchRequest&);
 
   template <bool stop_at_first_match, bool perf_trace_enabled>
-  bool CollectMatchingRulesForListInternal(base::span<const RuleData>,
-                                           const MatchRequest&,
-                                           const RuleSet*,
-                                           int,
-                                           const SelectorChecker&,
-                                           PartRequest* = nullptr);
+  bool CollectMatchingRulesForListInternal(
+      base::span<const RuleData>,
+      const MatchRequest&,
+      const RuleSet*,
+      int,
+      const SelectorChecker&,
+      SelectorChecker::SelectorCheckingContext&,
+      PartRequest* = nullptr);
 
   template <bool stop_at_first_match>
   bool CollectMatchingRulesForList(base::span<const RuleData>,
@@ -276,6 +278,7 @@
                                    const RuleSet*,
                                    int,
                                    const SelectorChecker&,
+                                   SelectorChecker::SelectorCheckingContext&,
                                    PartRequest* = nullptr);
 
   bool Match(SelectorChecker&,
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index fc65223..f07d78a 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -75,6 +75,7 @@
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/public/platform/web_content_settings_client.h"
 #include "third_party/blink/public/platform/web_theme_engine.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/public/web/web_print_page_description.h"
 #include "third_party/blink/renderer/bindings/core/v8/frozen_array.h"
 #include "third_party/blink/renderer/bindings/core/v8/isolated_world_csp.h"
@@ -3201,6 +3202,7 @@
 
   GetStyleEngine().DidDetach();
 
+  GetFrame()->DocumentDetached();
   GetFrame()->GetEventHandlerRegistry().DocumentDetached(*this);
 
   // Signal destruction to mutation observers.
@@ -5322,8 +5324,26 @@
   GetStyleEngine().MarkViewportUnitDirty(ViewportUnitFlag::kDynamic);
 }
 
+void EmitDidChangeHoverElement(Document& document, Element* new_hover_element) {
+  LocalFrame* local_frame = document.GetFrame();
+  if (!local_frame) {
+    return;
+  }
+
+  WebLinkPreviewTriggerer* triggerer =
+      local_frame->GetOrCreateLinkPreviewTriggerer();
+  if (!triggerer) {
+    return;
+  }
+
+  WebElement web_element = WebElement(DynamicTo<Element>(new_hover_element));
+  triggerer->DidChangeHoverElement(web_element);
+}
+
 void Document::SetHoverElement(Element* new_hover_element) {
   HTMLElement::HoveredElementChanged(hover_element_, new_hover_element);
+  EmitDidChangeHoverElement(*this, new_hover_element);
+
   hover_element_ = new_hover_element;
 }
 
diff --git a/third_party/blink/renderer/core/dom/dom_node_ids.cc b/third_party/blink/renderer/core/dom/dom_node_ids.cc
index 5de3c55db..3325b0f 100644
--- a/third_party/blink/renderer/core/dom/dom_node_ids.cc
+++ b/third_party/blink/renderer/core/dom/dom_node_ids.cc
@@ -17,6 +17,11 @@
 }
 
 // static
+DOMNodeId DOMNodeIds::ExistingIdForNode(const Node* node) {
+  return ExistingIdForNode(const_cast<Node*>(node));
+}
+
+// static
 DOMNodeId DOMNodeIds::IdForNode(Node* node) {
   return node ? WeakIdentifierMap<Node, DOMNodeId>::Identifier(node)
               : kInvalidDOMNodeId;
diff --git a/third_party/blink/renderer/core/dom/dom_node_ids.h b/third_party/blink/renderer/core/dom/dom_node_ids.h
index 7f31040..2e3bd46 100644
--- a/third_party/blink/renderer/core/dom/dom_node_ids.h
+++ b/third_party/blink/renderer/core/dom/dom_node_ids.h
@@ -22,6 +22,9 @@
   // Return a DOMNodeID or 0 if one hasn't been assigned.
   static DOMNodeId ExistingIdForNode(Node*);
 
+  // Return a DOMNodeID or 0 if one hasn't been assigned.
+  static DOMNodeId ExistingIdForNode(const Node*);
+
   // Return the existing DOMNodeID if it has already been assigned, otherwise,
   // assign a new DOMNodeID and return that.
   static DOMNodeId IdForNode(Node*);
diff --git a/third_party/blink/renderer/core/editing/finder/text_finder.cc b/third_party/blink/renderer/core/editing/finder/text_finder.cc
index 7cb3c84..8ced6fd6 100644
--- a/third_party/blink/renderer/core/editing/finder/text_finder.cc
+++ b/third_party/blink/renderer/core/editing/finder/text_finder.cc
@@ -483,8 +483,8 @@
   Node* end_node = active_match_->endContainer();
   ax_object_cache->HandleTextMarkerDataAdded(start_node, end_node);
 
-  int32_t start_id = ax_object_cache->GetAXID(start_node);
-  int32_t end_id = ax_object_cache->GetAXID(end_node);
+  int32_t start_id = start_node->GetDomNodeId();
+  int32_t end_id = end_node->GetDomNodeId();
 
   auto params = mojom::blink::FindInPageResultAXParams::New(
       identifier, active_match_index_ + 1, start_id,
diff --git a/third_party/blink/renderer/core/exported/build.gni b/third_party/blink/renderer/core/exported/build.gni
index 745c1e9..c3fb023 100644
--- a/third_party/blink/renderer/core/exported/build.gni
+++ b/third_party/blink/renderer/core/exported/build.gni
@@ -86,6 +86,7 @@
   "web_frame_serializer_test_helper.h",
   "web_image_test.cc",
   "web_meaningful_layouts_test.cc",
+  "web_link_preview_triggerer_test.cc",
   "web_node_test.cc",
   "web_plugin_container_test.cc",
   "web_range_test.cc",
diff --git a/third_party/blink/renderer/core/exported/web_document.cc b/third_party/blink/renderer/core/exported/web_document.cc
index cc236fea..c063db4 100644
--- a/third_party/blink/renderer/core/exported/web_document.cc
+++ b/third_party/blink/renderer/core/exported/web_document.cc
@@ -67,6 +67,7 @@
 #include "third_party/blink/renderer/core/html/plugin_document.h"
 #include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/speculation_rules/document_speculation_rules.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
@@ -368,4 +369,18 @@
   return WebString(ConstUnwrap<Document>()->domWindow()->OutgoingReferrer());
 }
 
+void WebDocument::InitiatePreview(const WebURL& url) {
+  if (!url.IsValid()) {
+    return;
+  }
+
+  Document* document = blink::To<Document>(private_.Get());
+  if (!document) {
+    return;
+  }
+
+  KURL kurl(url);
+  DocumentSpeculationRules::From(*document).InitiatePreview(kurl);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/exported/web_element.cc b/third_party/blink/renderer/core/exported/web_element.cc
index 5cc36b7..3c71a93 100644
--- a/third_party/blink/renderer/core/exported/web_element.cc
+++ b/third_party/blink/renderer/core/exported/web_element.cc
@@ -169,6 +169,37 @@
                         .Build());
 }
 
+void WebElement::SelectText(bool select_all) {
+  auto* element = Unwrap<Element>();
+  LocalFrame* frame = element->GetDocument().GetFrame();
+  if (!frame) {
+    return;
+  }
+
+  // Makes sure the selection is inside `element`: if `select_all`, selects
+  // all inside `element`; otherwise, selects an empty range at the end.
+  if (auto* text_control_element =
+          blink::DynamicTo<TextControlElement>(element)) {
+    if (select_all) {
+      text_control_element->select();
+    } else {
+      text_control_element->Focus(FocusParams(SelectionBehaviorOnFocus::kNone,
+                                              mojom::blink::FocusType::kScript,
+                                              nullptr, FocusOptions::Create()));
+      text_control_element->setSelectionStart(std::numeric_limits<int>::max());
+    }
+  } else {
+    Position base = FirstPositionInOrBeforeNode(*element);
+    Position extent = LastPositionInOrAfterNode(*element);
+    if (!select_all) {
+      base = extent;
+    }
+    frame->Selection().SetSelection(
+        SelectionInDOMTree::Builder().SetBaseAndExtent(base, extent).Build(),
+        SetSelectionOptions());
+  }
+}
+
 void WebElement::PasteText(const WebString& text, bool replace_all) {
   if (!IsEditable()) {
     return;
@@ -185,29 +216,7 @@
   };
 
   if (replace_all || !ContainsFrameSelection()) {
-    // Makes sure the selection is inside `element`: if `replace_all`, selects
-    // all inside `element`; otherwise, selects an empty range at the end.
-    if (auto* text_control_element =
-            blink::DynamicTo<TextControlElement>(element)) {
-      if (replace_all) {
-        text_control_element->select();
-      } else {
-        text_control_element->Focus(FocusParams(
-            SelectionBehaviorOnFocus::kNone, mojom::blink::FocusType::kScript,
-            nullptr, FocusOptions::Create()));
-        text_control_element->setSelectionStart(
-            std::numeric_limits<int>::max());
-      }
-    } else {
-      Position base = FirstPositionInOrBeforeNode(*element);
-      Position extent = LastPositionInOrAfterNode(*element);
-      if (!replace_all) {
-        base = extent;
-      }
-      frame->Selection().SetSelection(
-          SelectionInDOMTree::Builder().SetBaseAndExtent(base, extent).Build(),
-          SetSelectionOptions());
-    }
+    SelectText(replace_all);
     // JavaScript handlers may have destroyed the frame or moved the selection.
     if (is_destroyed(*frame) || !ContainsFrameSelection()) {
       return;
diff --git a/third_party/blink/renderer/core/exported/web_element_test.cc b/third_party/blink/renderer/core/exported/web_element_test.cc
index 9822fc9d..02a8d8b 100644
--- a/third_party/blink/renderer/core/exported/web_element_test.cc
+++ b/third_party/blink/renderer/core/exported/web_element_test.cc
@@ -180,6 +180,34 @@
   EXPECT_EQ(test_element.SelectedText().Utf8(), "");
 }
 
+// Tests SelectText() with a textarea.
+TEST_F(WebElementTest, SelectTextOfTextArea) {
+  InsertHTML(
+      R"(<div>Foo</div>
+      <textarea id=testElement>Some plain text here.</textarea>
+      <div>Bar</div>)");
+
+  TestElement().SelectText(/*select_all=*/false);
+  EXPECT_EQ(Selection().SelectedText(), "");
+
+  TestElement().SelectText(/*select_all=*/true);
+  EXPECT_EQ(Selection().SelectedText(), "Some plain text here.");
+}
+
+// Tests SelectText() with a contenteditable.
+TEST_F(WebElementTest, SelectTextOfContentEditable) {
+  InsertHTML(
+      R"(<div>Foo</div>
+      <div id=testElement contenteditable>Some <b>rich text</b> here.</div>
+      <textarea>Some plain text here.</textarea>)");
+
+  TestElement().SelectText(/*select_all=*/false);
+  EXPECT_EQ(Selection().SelectedText(), "");
+
+  TestElement().SelectText(/*select_all=*/true);
+  EXPECT_EQ(Selection().SelectedText(), "Some rich text here.");
+}
+
 TEST_F(WebElementTest, PasteTextIntoContentEditable) {
   InsertHTML(
       "<div id=testElement contenteditable>Some <b>rich text</b> here.</div>"
diff --git a/third_party/blink/renderer/core/exported/web_link_preview_triggerer_test.cc b/third_party/blink/renderer/core/exported/web_link_preview_triggerer_test.cc
new file mode 100644
index 0000000..b77531de
--- /dev/null
+++ b/third_party/blink/renderer/core/exported/web_link_preview_triggerer_test.cc
@@ -0,0 +1,203 @@
+// 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 "third_party/blink/public/web/web_link_preview_triggerer.h"
+
+#include <memory>
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/input/web_keyboard_event.h"
+#include "third_party/blink/public/web/web_element.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/exported/web_view_impl.h"
+#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
+#include "third_party/blink/renderer/core/input/event_handler.h"
+#include "third_party/blink/renderer/core/page/page.h"
+#include "third_party/blink/renderer/core/testing/page_test_base.h"
+#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
+#include "ui/events/keycodes/dom/dom_code.h"
+
+namespace blink {
+
+class MockWebLinkPreviewTriggerer : public WebLinkPreviewTriggerer {
+ public:
+  MockWebLinkPreviewTriggerer() = default;
+  ~MockWebLinkPreviewTriggerer() override = default;
+
+  int LastKeyEventModifiers() const { return last_key_event_modifiers_; }
+
+  const WebElement& HoverElement() const { return hover_element_; }
+
+  const WebElement& Element() const { return element_; }
+
+  const std::optional<WebMouseEvent>& MouseEvent() const {
+    return mouse_event_;
+  }
+
+  void MaybeChangedKeyEventModifier(int modifiers) override {
+    last_key_event_modifiers_ = modifiers;
+  }
+
+  void DidChangeHoverElement(blink::WebElement element) override {
+    hover_element_ = element;
+  }
+
+  void DidAnchorElementReceiveMouseEvent(
+      blink::WebElement anchor_element,
+      blink::WebMouseEvent mouse_event) override {
+    element_ = anchor_element;
+    mouse_event_ = mouse_event;
+  }
+
+ private:
+  int last_key_event_modifiers_ = blink::WebInputEvent::kNoModifiers;
+  WebElement hover_element_;
+  WebElement element_;
+  std::optional<WebMouseEvent> mouse_event_ = std::nullopt;
+};
+
+class WebLinkPreviewTriggererTest : public PageTestBase {
+ protected:
+  void Initialize() {
+    LocalFrame* local_frame = GetDocument().GetFrame();
+    CHECK(local_frame);
+
+    local_frame->SetLinkPreviewTriggererForTesting(
+        std::make_unique<MockWebLinkPreviewTriggerer>());
+  }
+
+  void SetInnerHTML(const String& html) {
+    GetDocument().documentElement()->setInnerHTML(html);
+  }
+};
+
+TEST_F(WebLinkPreviewTriggererTest, MaybeChangedKeyEventModifierCalled) {
+  Initialize();
+  SetHtmlInnerHTML("<div></div>");
+  MockWebLinkPreviewTriggerer* triggerer =
+      static_cast<MockWebLinkPreviewTriggerer*>(
+          GetDocument().GetFrame()->GetOrCreateLinkPreviewTriggerer());
+
+  EXPECT_EQ(WebInputEvent::kNoModifiers, triggerer->LastKeyEventModifiers());
+
+  WebKeyboardEvent e0{WebInputEvent::Type::kRawKeyDown, WebInputEvent::kAltKey,
+                      WebInputEvent::GetStaticTimeStampForTests()};
+  e0.dom_code = static_cast<int>(ui::DomCode::ALT_LEFT);
+  GetDocument().GetFrame()->GetEventHandler().KeyEvent(e0);
+
+  EXPECT_EQ(WebInputEvent::kAltKey, triggerer->LastKeyEventModifiers());
+
+  WebKeyboardEvent e1{WebInputEvent::Type::kKeyUp, WebInputEvent::kNoModifiers,
+                      WebInputEvent::GetStaticTimeStampForTests()};
+  e1.dom_code = static_cast<int>(ui::DomCode::ALT_LEFT);
+  GetDocument().GetFrame()->GetEventHandler().KeyEvent(e1);
+
+  EXPECT_EQ(WebInputEvent::kNoModifiers, triggerer->LastKeyEventModifiers());
+}
+
+TEST_F(WebLinkPreviewTriggererTest,
+       MaybeChangedKeyEventModifierCalledWithNoModifiersOnMouseLeave) {
+  Initialize();
+  SetHtmlInnerHTML("<div></div>");
+  MockWebLinkPreviewTriggerer* triggerer =
+      static_cast<MockWebLinkPreviewTriggerer*>(
+          GetDocument().GetFrame()->GetOrCreateLinkPreviewTriggerer());
+
+  EXPECT_EQ(WebInputEvent::kNoModifiers, triggerer->LastKeyEventModifiers());
+
+  WebKeyboardEvent e0{WebInputEvent::Type::kRawKeyDown, WebInputEvent::kAltKey,
+                      WebInputEvent::GetStaticTimeStampForTests()};
+  e0.dom_code = static_cast<int>(ui::DomCode::ALT_LEFT);
+  GetDocument().GetFrame()->GetEventHandler().KeyEvent(e0);
+
+  EXPECT_EQ(WebInputEvent::kAltKey, triggerer->LastKeyEventModifiers());
+
+  WebMouseEvent e1(WebMouseEvent::Type::kMouseLeave, gfx::PointF(262, 29),
+                   gfx::PointF(329, 67),
+                   WebPointerProperties::Button::kNoButton, 1,
+                   WebInputEvent::Modifiers::kNoModifiers,
+                   WebInputEvent::GetStaticTimeStampForTests());
+  GetDocument().GetFrame()->GetEventHandler().HandleMouseLeaveEvent(e1);
+
+  EXPECT_EQ(WebInputEvent::kNoModifiers, triggerer->LastKeyEventModifiers());
+}
+
+TEST_F(WebLinkPreviewTriggererTest, DidChangeHoverElementCalledOnHoverChanged) {
+  Initialize();
+  SetHtmlInnerHTML(
+      "<style>"
+      "  body { margin:0px; }"
+      "  a { display:block; width:100px; height:100px; }"
+      "</style>"
+      "<body>"
+      "  <a href=\"https://example.com\">anchor</a>"
+      "</body>");
+  MockWebLinkPreviewTriggerer* triggerer =
+      static_cast<MockWebLinkPreviewTriggerer*>(
+          GetDocument().GetFrame()->GetOrCreateLinkPreviewTriggerer());
+
+  {
+    gfx::PointF point(50, 50);
+    WebMouseEvent mouse_move_event(WebInputEvent::Type::kMouseMove, point,
+                                   point,
+                                   WebPointerProperties::Button::kNoButton, 0,
+                                   WebInputEvent::Modifiers::kNoModifiers,
+                                   WebInputEvent::GetStaticTimeStampForTests());
+
+    GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
+        mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
+
+    EXPECT_FALSE(triggerer->HoverElement().IsNull());
+    EXPECT_EQ("A", triggerer->HoverElement().TagName());
+    EXPECT_EQ("https://example.com",
+              triggerer->HoverElement().GetAttribute("href"));
+  }
+
+  {
+    gfx::PointF point(200, 200);
+    WebMouseEvent mouse_move_event(WebInputEvent::Type::kMouseMove, point,
+                                   point,
+                                   WebPointerProperties::Button::kNoButton, 0,
+                                   WebInputEvent::Modifiers::kNoModifiers,
+                                   WebInputEvent::GetStaticTimeStampForTests());
+
+    GetDocument().GetFrame()->GetEventHandler().HandleMouseMoveEvent(
+        mouse_move_event, Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
+
+    EXPECT_FALSE(triggerer->Element().IsNull());
+    EXPECT_EQ("HTML", triggerer->HoverElement().TagName());
+  }
+}
+
+TEST_F(WebLinkPreviewTriggererTest,
+       DidAnchorElementReceiveMouseEventCalledOnMousePress) {
+  Initialize();
+  SetHtmlInnerHTML(
+      "<style>"
+      "  body { margin:0px; }"
+      "  a { display:block; width:100px; height:100px; }"
+      "</style>"
+      "<body>"
+      "  <a href=\"https://example.com\">anchor</a>"
+      "</body>");
+  MockWebLinkPreviewTriggerer* triggerer =
+      static_cast<MockWebLinkPreviewTriggerer*>(
+          GetDocument().GetFrame()->GetOrCreateLinkPreviewTriggerer());
+
+  gfx::PointF point(50, 50);
+  WebMouseEvent mouse_down_event(WebInputEvent::Type::kMouseDown, point, point,
+                                 WebPointerProperties::Button::kLeft, 1,
+                                 WebInputEvent::Modifiers::kNoModifiers,
+                                 WebInputEvent::GetStaticTimeStampForTests());
+
+  GetDocument().GetFrame()->GetEventHandler().HandleMousePressEvent(
+      mouse_down_event);
+
+  EXPECT_FALSE(triggerer->Element().IsNull());
+  EXPECT_EQ("https://example.com", triggerer->Element().GetAttribute("href"));
+  EXPECT_TRUE(triggerer->MouseEvent());
+  EXPECT_EQ(WebInputEvent::Type::kMouseDown,
+            triggerer->MouseEvent()->GetType());
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 4f33aff..d7fc8c4b 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -82,6 +82,7 @@
 #include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/public/web/web_content_capture_client.h"
 #include "third_party/blink/public/web/web_frame.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/public/web/web_local_frame_client.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_controller.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
@@ -1015,6 +1016,13 @@
   return DomWindow() ? DomWindow()->document() : nullptr;
 }
 
+void LocalFrame::DocumentDetached() {
+  // Resets WebLinkPreviewTrigerer when the document detached as
+  // WebLinkPreviewInitiator depends on document.
+  is_link_preivew_triggerer_initialized_ = false;
+  link_preview_triggerer_.reset();
+}
+
 void LocalFrame::SetPagePopupOwner(Element& owner) {
   page_popup_owner_ = &owner;
 }
@@ -3884,4 +3892,32 @@
   return print_params_;
 }
 
+WebLinkPreviewTriggerer* LocalFrame::GetOrCreateLinkPreviewTriggerer() {
+  EnsureLinkPreviewTriggererInitialized();
+  return link_preview_triggerer_.get();
+}
+
+void LocalFrame::EnsureLinkPreviewTriggererInitialized() {
+  if (is_link_preivew_triggerer_initialized_) {
+    return;
+  }
+
+  CHECK(!link_preview_triggerer_);
+
+  WebLocalFrameImpl* web_local_frame = WebLocalFrameImpl::FromFrame(this);
+  if (!web_local_frame) {
+    return;
+  }
+
+  link_preview_triggerer_ =
+      web_local_frame->Client()->CreateLinkPreviewTriggerer();
+  is_link_preivew_triggerer_initialized_ = true;
+}
+
+void LocalFrame::SetLinkPreviewTriggererForTesting(
+    std::unique_ptr<WebLinkPreviewTriggerer> trigger) {
+  link_preview_triggerer_ = std::move(trigger);
+  is_link_preivew_triggerer_initialized_ = true;
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index d3faff9..1f659eb 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -149,6 +149,7 @@
 class Node;
 class NodeTraversal;
 class PerformanceMonitor;
+class WebLinkPreviewTriggerer;
 class PluginData;
 class PolicyContainer;
 class ScrollSnapshotClient;
@@ -289,6 +290,7 @@
   void SetDOMWindow(LocalDOMWindow*);
   LocalFrameView* View() const override;
   Document* GetDocument() const;
+  void DocumentDetached();
   void SetPagePopupOwner(Element&);
   Element* PagePopupOwner() const { return page_popup_owner_.Get(); }
   bool HasPagePopupOwner() const { return page_popup_owner_ != nullptr; }
@@ -932,6 +934,10 @@
 
   const WebPrintParams& GetPrintParams() const;
 
+  WebLinkPreviewTriggerer* GetOrCreateLinkPreviewTriggerer();
+  void SetLinkPreviewTriggererForTesting(
+      std::unique_ptr<WebLinkPreviewTriggerer> trigger);
+
  private:
   friend class FrameNavigationDisabler;
   // LocalFrameMojoHandler is a part of LocalFrame.
@@ -1015,6 +1021,8 @@
   void MaybeUpdateWindowControlsOverlayWithNewZoomLevel();
 #endif
 
+  void EnsureLinkPreviewTriggererInitialized();
+
   std::unique_ptr<FrameScheduler> frame_scheduler_;
 
   // Holds all PauseSubresourceLoadingHandles allowing either |this| to delete
@@ -1197,6 +1205,12 @@
       feature_handle_for_scheduler_;
 
   WebPrintParams print_params_;
+
+  // Holds WebLinkPreviewTriggerer instance if content renderer client wants to
+  // inject it. Note that `link_preview_triggerer_` may be nullptr after
+  // initialization.
+  bool is_link_preivew_triggerer_initialized_ = false;
+  std::unique_ptr<WebLinkPreviewTriggerer> link_preview_triggerer_;
 };
 
 inline FrameLoader& LocalFrame::Loader() const {
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_client.cc b/third_party/blink/renderer/core/frame/web_local_frame_client.cc
index 3adcd7c..bae55d3 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_client.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_client.cc
@@ -7,6 +7,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/renderer/platform/loader/fetch/url_loader/url_loader.h"
 
 namespace blink {
@@ -31,4 +32,12 @@
   return nullptr;
 }
 
+std::unique_ptr<WebLinkPreviewTriggerer>
+WebLocalFrameClient::CreateLinkPreviewTriggerer() {
+  return nullptr;
+}
+
+void WebLocalFrameClient::SetLinkPreviewTriggererForTesting(
+    std::unique_ptr<WebLinkPreviewTriggerer> trigger) {}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
index b7fb563..d45816a 100644
--- a/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_form_control_element.cc
@@ -648,24 +648,13 @@
 
 int32_t HTMLFormControlElement::GetAxId() const {
   Document& document = GetDocument();
-  if (!document.IsActive() || !document.View())
+  if (!document.IsActive() || !document.View()) {
     return 0;
-  // TODO(accessibility) Simplify this once AXIDs use DOMNodeIds. At that
-  // point it will be safe to get the AXID at any time.
-  if (AXObjectCache* cache = document.ExistingAXObjectCache()) {
-    LocalFrameView* local_frame_view = document.View();
-    if (local_frame_view->IsUpdatingLifecycle()) {
-      // Autofill (the caller of this code) can end up making calls to get AXIDs
-      // of form elements during, e.g. resize observer callbacks, which are
-      // in the middle up updating the document lifecycle. In these cases, just
-      // return the existing AXID of the element.
-      return cache->GetExistingAXID(const_cast<HTMLFormControlElement*>(this));
-    }
-
-    return cache->GetAXID(const_cast<HTMLFormControlElement*>(this));
   }
-
-  return 0;
+  // The AXId is the same as the DOM node id.
+  int32_t result = DOMNodeIds::ExistingIdForNode(this);
+  CHECK(result) << "May need to call GetDomNodeId() from a non-const function";
+  return result;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_anchor_element.cc b/third_party/blink/renderer/core/html/html_anchor_element.cc
index e69d12b..bee4233 100644
--- a/third_party/blink/renderer/core/html/html_anchor_element.cc
+++ b/third_party/blink/renderer/core/html/html_anchor_element.cc
@@ -35,10 +35,12 @@
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
 #include "third_party/blink/public/mojom/permissions_policy/permissions_policy_feature.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/renderer/core/editing/editing_utilities.h"
 #include "third_party/blink/renderer/core/events/keyboard_event.h"
 #include "third_party/blink/renderer/core/events/mouse_event.h"
 #include "third_party/blink/renderer/core/events/pointer_event.h"
+#include "third_party/blink/renderer/core/events/web_input_event_conversion.h"
 #include "third_party/blink/renderer/core/frame/ad_tracker.h"
 #include "third_party/blink/renderer/core/frame/attribution_src_loader.h"
 #include "third_party/blink/renderer/core/frame/deprecation/deprecation.h"
@@ -209,8 +211,33 @@
   url.AppendNumber(clamped_point.y());
 }
 
+void EmitDidAnchorElementReceiveMouseEvent(HTMLAnchorElement& anchor_element,
+                                           Event& event) {
+  LocalFrame* local_frame = anchor_element.GetDocument().GetFrame();
+  if (!local_frame) {
+    return;
+  }
+
+  WebLinkPreviewTriggerer* triggerer =
+      local_frame->GetOrCreateLinkPreviewTriggerer();
+  if (!triggerer) {
+    return;
+  }
+
+  auto* mev = DynamicTo<MouseEvent>(event);
+  if (!mev) {
+    return;
+  }
+
+  WebElement web_element = WebElement(DynamicTo<Element>(&anchor_element));
+  WebMouseEventBuilder web_mouse_event(anchor_element.GetLayoutObject(), *mev);
+  triggerer->DidAnchorElementReceiveMouseEvent(web_element, web_mouse_event);
+}
+
 void HTMLAnchorElement::DefaultEventHandler(Event& event) {
   if (IsLink()) {
+    EmitDidAnchorElementReceiveMouseEvent(*this, event);
+
     if (isConnected() && base::FeatureList::IsEnabled(
                              features::kSpeculativeServiceWorkerWarmUp)) {
       Document& top_document = GetDocument().TopDocument();
diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc
index d29f7f63..4fdb000f 100644
--- a/third_party/blink/renderer/core/input/event_handler.cc
+++ b/third_party/blink/renderer/core/input/event_handler.cc
@@ -38,6 +38,7 @@
 #include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom-blink.h"
 #include "third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom-blink.h"
 #include "third_party/blink/public/platform/task_type.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/renderer/core/clipboard/data_transfer.h"
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/document.h"
@@ -1058,6 +1059,13 @@
   Page* page = frame_->GetPage();
   if (page)
     page->GetChromeClient().ClearToolTip(*frame_);
+
+  WebLinkPreviewTriggerer* triggerer =
+      frame_->GetOrCreateLinkPreviewTriggerer();
+  if (triggerer) {
+    triggerer->MaybeChangedKeyEventModifier(WebInputEvent::kNoModifiers);
+  }
+
   HandleMouseMoveOrLeaveEvent(event, Vector<WebMouseEvent>(),
                               Vector<WebMouseEvent>());
   pointer_event_manager_->RemoveLastMousePosition();
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.cc b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
index e4709d1..7d92e590 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.cc
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.cc
@@ -13,6 +13,7 @@
 #include "third_party/blink/public/mojom/frame/user_activation_notification_type.mojom-blink.h"
 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/public/web/web_link_preview_triggerer.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/events/simulated_click_options.h"
 #include "third_party/blink/renderer/core/dom/focus_params.h"
@@ -209,6 +210,8 @@
   if (initial_key_event.windows_key_code == VK_CAPITAL)
     CapsLockStateMayHaveChanged();
 
+  KeyEventModifierMayHaveChanged(initial_key_event.GetModifiers());
+
   if (scroll_manager_->MiddleClickAutoscrollInProgress()) {
     DCHECK(RuntimeEnabledFeatures::MiddleClickAutoscrollEnabled());
     // If a key is pressed while the middleClickAutoscroll is in progress then
@@ -405,6 +408,16 @@
   }
 }
 
+void KeyboardEventManager::KeyEventModifierMayHaveChanged(int modifiers) {
+  WebLinkPreviewTriggerer* triggerer =
+      frame_->GetOrCreateLinkPreviewTriggerer();
+  if (!triggerer) {
+    return;
+  }
+
+  triggerer->MaybeChangedKeyEventModifier(modifiers);
+}
+
 void KeyboardEventManager::DefaultKeyboardEventHandler(
     KeyboardEvent* event,
     Node* possible_focused_node) {
diff --git a/third_party/blink/renderer/core/input/keyboard_event_manager.h b/third_party/blink/renderer/core/input/keyboard_event_manager.h
index 15d7f8e4..cb10de5 100644
--- a/third_party/blink/renderer/core/input/keyboard_event_manager.h
+++ b/third_party/blink/renderer/core/input/keyboard_event_manager.h
@@ -58,6 +58,8 @@
   bool is_handling_key_event() const { return is_handling_key_event_; }
 
  private:
+  void KeyEventModifierMayHaveChanged(int modifiers);
+
   friend class Internals;
   // Allows overriding the current caps lock state for testing purposes.
   static void SetCurrentCapsLockState(OverrideCapsLockState);
diff --git a/third_party/blink/renderer/core/inspector/inspector_audits_agent.h b/third_party/blink/renderer/core/inspector/inspector_audits_agent.h
index 892f854..18a8069aa 100644
--- a/third_party/blink/renderer/core/inspector/inspector_audits_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_audits_agent.h
@@ -12,14 +12,12 @@
 #include "third_party/blink/renderer/core/inspector/inspector_contrast.h"
 #include "third_party/blink/renderer/core/inspector/protocol/audits.h"
 
-namespace protocol {
-namespace Audits {
-class InspectorIssue;
-}  // namespace Audits
-}  // namespace protocol
-
 namespace blink {
 
+namespace protocol::Audits {
+class InspectorIssue;
+}  // namespace protocol::Audits
+
 class InspectorIssueStorage;
 class WebAutofillClient;
 
diff --git a/third_party/blink/renderer/core/layout/inline/inline_items_builder.cc b/third_party/blink/renderer/core/layout/inline/inline_items_builder.cc
index 239acfe8..eddba51 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_items_builder.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_items_builder.cc
@@ -16,6 +16,7 @@
 #include "third_party/blink/renderer/core/layout/inline/transformed_string.h"
 #include "third_party/blink/renderer/core/layout/layout_inline.h"
 #include "third_party/blink/renderer/core/layout/layout_text.h"
+#include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_view.h"
 #include "third_party/blink/renderer/platform/wtf/text/text_offset_map.h"
@@ -525,17 +526,15 @@
     AppendText(TransformedString(layout_text->TransformedText()), *layout_text);
     return;
   }
-  String original = layout_text->OriginalText();
-  TextOffsetMap offset_map;
-  String transformed =
-      layout_text->TransformAndSecureText(original, offset_map);
-  if (layout_text->TransformedText().length() != transformed.length()) {
-    NOTREACHED() << "Mismatch; class=" << layout_text->GetName()
-                 << " stored=" << layout_text->TransformedText()
-                 << " live=" << transformed;
-  }
+  // Do not use LayoutText::OriginalText() here.  This code is used when
+  // OriginalText() was updated but TransformedText() is not updated yet, and we
+  // need to use TransformedText() in that case.  It is required to make
+  // InlineNode::SetTextWithOffset() workable.
+  auto [original_length, offset_map] =
+      layout_text->GetVariableLengthTransformResult();
+  String transformed = layout_text->TransformedText();
   const Vector<unsigned> length_map = TransformedString::CreateLengthMap(
-      original.length(), transformed.length(), offset_map);
+      original_length, transformed.length(), offset_map);
   CHECK(transformed.length() == length_map.size() || length_map.size() == 0);
   AppendText(
       TransformedString(transformed, {length_map.data(), length_map.size()}),
diff --git a/third_party/blink/renderer/core/layout/inline/inline_node.cc b/third_party/blink/renderer/core/layout/inline/inline_node.cc
index 2f23b88..895e242 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_node.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_node.cc
@@ -934,7 +934,7 @@
     return false;
   }
   layout_text->SetTextInternal(new_text);
-  layout_text->SetHasVariableLengthTransform(false);
+  layout_text->ClearHasVariableLengthTransform();
 
   InlineNode node(editor.GetLayoutBlockFlow());
   InlineNodeData* data = node.MutableData();
diff --git a/third_party/blink/renderer/core/layout/inline/inline_node_test.cc b/third_party/blink/renderer/core/layout/inline/inline_node_test.cc
index 0ace3fd..d69d6be 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_node_test.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_node_test.cc
@@ -672,6 +672,20 @@
   EXPECT_FALSE(next->GetLayoutObject()->NeedsCollectInlines());
 }
 
+// crbug.com/325306591
+// We had a crash in OffsetMapping building during SetTextWithOffset().
+TEST_F(InlineNodeTest, SetTextWithOffsetWithTextTransform) {
+  SetBodyInnerHTML(R"HTML(
+    <div id="container" style="text-transform:uppercase">&#xdf;X</div>)HTML");
+
+  Element* container = GetElementById("container");
+  auto* text = To<Text>(container->firstChild());
+
+  text->deleteData(1, 1, ASSERT_NO_EXCEPTION);
+  UpdateAllLifecyclePhasesForTest();
+  // Pass if no crash in InlineItemsBuilder.
+}
+
 struct StyleChangeData {
   const char* css;
   enum ChangedElements {
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 789acc94..7925c6cc 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -222,6 +222,36 @@
          ElementAnimations::CompositedPaintStatus::kComposited;
 }
 
+// If there's a composited clip path animation, and it doesn't have a cached
+// value for CompositedClipPathStatus, that means we need to regenerate the
+// paint properties, as the composited clip path status is calculated then. See
+// HasCompositeClipPathAnimation in clip_path_clipper.cc
+bool ShouldRefreshPaintPropertiesForClipPath(Node* node,
+                                             const ComputedStyle* style) {
+  if (!RuntimeEnabledFeatures::CompositeClipPathAnimationEnabled()) {
+    return false;
+  }
+
+  // We don't care what the composited clip path status is if there's no
+  // composited clip path animation.
+  if (!style->HasCurrentClipPathAnimation()) {
+    return false;
+  }
+
+  Element* element = DynamicTo<Element>(node);
+  if (!element) {
+    return false;
+  }
+
+  ElementAnimations* element_animations = element->GetElementAnimations();
+  if (!element_animations) {
+    return false;
+  }
+
+  return element_animations->CompositedClipPathStatus() ==
+         ElementAnimations::CompositedPaintStatus::kNeedsRepaintOrNoAnimation;
+}
+
 StyleDifference AdjustForCompositableAnimationPaint(
     const ComputedStyle* old_style,
     const ComputedStyle* new_style,
@@ -2784,10 +2814,9 @@
 
   // Clip Path animations need a property update when they're composited, as it
   // changes between mask based and path based clip.
-  if (diff.NeedsNormalPaintInvalidation() && old_style &&
-      (!old_style->ClipPathDataEquivalent(*style_) ||
-       (old_style->HasCurrentClipPathAnimation() &&
-        !style_->HasCurrentClipPathAnimation()))) {
+  if ((diff.NeedsNormalPaintInvalidation() && old_style &&
+       !old_style->ClipPathDataEquivalent(*style_)) ||
+      ShouldRefreshPaintPropertiesForClipPath(GetNode(), style_)) {
     SetNeedsPaintPropertyUpdate();
     PaintingLayer()->SetNeedsCompositingInputsUpdate();
   }
diff --git a/third_party/blink/renderer/core/layout/layout_text.cc b/third_party/blink/renderer/core/layout/layout_text.cc
index 8610bb0..d0609c5 100644
--- a/third_party/blink/renderer/core/layout/layout_text.cc
+++ b/third_party/blink/renderer/core/layout/layout_text.cc
@@ -989,6 +989,31 @@
   return std::make_pair(masked, TextOffsetMap());
 }
 
+void LayoutText::SetVariableLengthTransformResult(
+    wtf_size_t original_length,
+    const TextOffsetMap& offset_map) {
+  if (offset_map.IsEmpty()) {
+    ClearHasVariableLengthTransform();
+    return;
+  }
+  has_variable_length_transform_ = true;
+  View()->RegisterVariableLengthTransformResult(*this,
+                                                {original_length, offset_map});
+}
+
+VariableLengthTransformResult LayoutText::GetVariableLengthTransformResult()
+    const {
+  return View()->GetVariableLengthTransformResult(*this);
+}
+
+void LayoutText::ClearHasVariableLengthTransform() {
+  NOT_DESTROYED();
+  if (has_variable_length_transform_) {
+    View()->UnregisterVariableLengthTransformResult(*this);
+  }
+  has_variable_length_transform_ = false;
+}
+
 void LayoutText::SetTextIfNeeded(String text) {
   NOT_DESTROYED();
   DCHECK(text);
@@ -1037,8 +1062,9 @@
 void LayoutText::TextDidChangeWithoutInvalidation() {
   NOT_DESTROYED();
   TextOffsetMap offset_map;
+  wtf_size_t original_length = text_.length();
   text_ = TransformAndSecureText(text_, offset_map);
-  has_variable_length_transform_ = !offset_map.IsEmpty();
+  SetVariableLengthTransformResult(original_length, offset_map);
   if (auto* secure_text_timer = SecureTextTimer::ActiveInstanceFor(this)) {
     // text_ may be updated later before timer fires. We invalidate the
     // last_typed_character_offset_ to avoid inconsistency.
diff --git a/third_party/blink/renderer/core/layout/layout_text.h b/third_party/blink/renderer/core/layout/layout_text.h
index bef8349..d7012d7f 100644
--- a/third_party/blink/renderer/core/layout/layout_text.h
+++ b/third_party/blink/renderer/core/layout/layout_text.h
@@ -45,6 +45,7 @@
 struct InlineItemsData;
 struct InlineItemSpan;
 struct TextDiffRange;
+struct VariableLengthTransformResult;
 
 // LayoutText is the root class for anything that represents
 // a text node (see core/dom/text.h).
@@ -134,10 +135,8 @@
     NOT_DESTROYED();
     return has_variable_length_transform_;
   }
-  void SetHasVariableLengthTransform(bool flag) {
-    NOT_DESTROYED();
-    has_variable_length_transform_ = flag;
-  }
+  VariableLengthTransformResult GetVariableLengthTransformResult() const;
+  void ClearHasVariableLengthTransform();
 
   // Returns first letter part of |LayoutTextFragment|.
   virtual LayoutText* GetFirstLetterPart() const {
@@ -403,6 +402,8 @@
 
   std::pair<String, TextOffsetMap> SecureText(const String& plain,
                                               UChar mask) const;
+  void SetVariableLengthTransformResult(wtf_size_t original_length,
+                                        const TextOffsetMap& offset_map);
 
   bool IsText() const final {
     NOT_DESTROYED();
diff --git a/third_party/blink/renderer/core/layout/layout_view.cc b/third_party/blink/renderer/core/layout/layout_view.cc
index 803df05..ab56812 100644
--- a/third_party/blink/renderer/core/layout/layout_view.cc
+++ b/third_party/blink/renderer/core/layout/layout_view.cc
@@ -141,6 +141,7 @@
 void LayoutView::Trace(Visitor* visitor) const {
   visitor->Trace(frame_view_);
   visitor->Trace(svg_text_descendants_);
+  visitor->Trace(text_to_variable_length_transform_result_);
   visitor->Trace(hit_test_cache_);
   visitor->Trace(initial_containing_block_resize_handled_list_);
   LayoutNGBlockFlow::Trace(visitor);
@@ -414,6 +415,24 @@
   return *svg_text_descendants_;
 }
 
+void LayoutView::RegisterVariableLengthTransformResult(
+    const LayoutText& text,
+    const VariableLengthTransformResult& result) {
+  CHECK(text.HasVariableLengthTransform());
+  text_to_variable_length_transform_result_.Set(&text, result);
+}
+
+void LayoutView::UnregisterVariableLengthTransformResult(
+    const LayoutText& text) {
+  text_to_variable_length_transform_result_.erase(&text);
+}
+
+VariableLengthTransformResult LayoutView::GetVariableLengthTransformResult(
+    const LayoutText& text) {
+  CHECK(text.HasVariableLengthTransform());
+  return text_to_variable_length_transform_result_.at(&text);
+}
+
 LayoutViewTransitionRoot* LayoutView::GetViewTransitionRoot() const {
   // Returns nullptr if LastChild isn't a ViewTransitionRoot.
   return DynamicTo<LayoutViewTransitionRoot>(LastChild());
diff --git a/third_party/blink/renderer/core/layout/layout_view.h b/third_party/blink/renderer/core/layout/layout_view.h
index 2f4fdb38..359ae58 100644
--- a/third_party/blink/renderer/core/layout/layout_view.h
+++ b/third_party/blink/renderer/core/layout/layout_view.h
@@ -33,15 +33,22 @@
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_hash_set.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
+#include "third_party/blink/renderer/platform/wtf/text/text_offset_map.h"
 
 namespace blink {
 
 class HitTestCache;
 class HitTestLocation;
 class HitTestResult;
+class LayoutText;
 class LayoutViewTransitionRoot;
 class LocalFrameView;
 
+struct VariableLengthTransformResult {
+  wtf_size_t original_length;
+  TextOffsetMap offset_map;
+};
+
 // LayoutView is the root of the layout tree and the Document's LayoutObject.
 //
 // It corresponds to the CSS concept of 'initial containing block' (or ICB).
@@ -334,6 +341,14 @@
 
   TrackedDescendantsMap& SvgTextDescendantsMap();
 
+  // Manage rare data of LayoutText.
+  void RegisterVariableLengthTransformResult(
+      const LayoutText& text,
+      const VariableLengthTransformResult& result);
+  void UnregisterVariableLengthTransformResult(const LayoutText& text);
+  VariableLengthTransformResult GetVariableLengthTransformResult(
+      const LayoutText& text);
+
   LayoutViewTransitionRoot* GetViewTransitionRoot() const;
 
  private:
@@ -387,6 +402,9 @@
   // computed with ancestor transforms.
   Member<TrackedDescendantsMap> svg_text_descendants_;
 
+  HeapHashMap<WeakMember<const LayoutText>, VariableLengthTransformResult>
+      text_to_variable_length_transform_result_;
+
   unsigned hit_test_count_;
   unsigned hit_test_cache_hits_;
   Member<HitTestCache> hit_test_cache_;
diff --git a/third_party/blink/renderer/core/loader/navigation_policy.cc b/third_party/blink/renderer/core/loader/navigation_policy.cc
index 01e86be..90f6f32 100644
--- a/third_party/blink/renderer/core/loader/navigation_policy.cc
+++ b/third_party/blink/renderer/core/loader/navigation_policy.cc
@@ -140,8 +140,8 @@
 
 NavigationPolicy NavigationPolicyFromEvent(const Event* event) {
   // TODO(b:298160400): Add a setting to disable Link Preview.
-  bool is_link_preview_enabled =
-      base::FeatureList::IsEnabled(features::kLinkPreview);
+  bool is_link_preview_enabled = IsLinkPreviewTriggerTypeEnabled(
+      features::LinkPreviewTriggerType::kAltClick);
 
   NavigationPolicy event_policy =
       NavigationPolicyFromEventInternal(event, is_link_preview_enabled);
diff --git a/third_party/blink/renderer/core/loader/navigation_policy_test.cc b/third_party/blink/renderer/core/loader/navigation_policy_test.cc
index ffff6dbc..5cf24b39 100644
--- a/third_party/blink/renderer/core/loader/navigation_policy_test.cc
+++ b/third_party/blink/renderer/core/loader/navigation_policy_test.cc
@@ -104,7 +104,8 @@
 class NavigationPolicyWithLinkPreviewEnabledTest : public NavigationPolicyTest {
  protected:
   void SetUp() override {
-    scoped_feature_list_.InitAndEnableFeature(features::kLinkPreview);
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kLinkPreview, {{"trigger_type", "alt_click"}});
   }
 };
 
diff --git a/third_party/blink/renderer/core/svg/animation/smil_time_container.cc b/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
index 8c63166..3244dba4 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
+++ b/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
@@ -31,6 +31,7 @@
 #include "third_party/blink/renderer/core/animation/document_timeline.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element_traversal.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/frame/web_feature.h"
@@ -635,6 +636,27 @@
   }
 }
 
+namespace {
+
+bool CanThrottleTarget(const SVGElement& target) {
+  // Don't throttle if the target is in the layout tree.
+  if (target.GetLayoutObject()) {
+    return false;
+  }
+  // Don't throttle if the target has computed style (for example <stop>
+  // elements).
+  if (ComputedStyle::NullifyEnsured(target.GetComputedStyle())) {
+    return false;
+  }
+  // Don't throttle if the target has use instances.
+  if (!target.InstancesForElement().empty()) {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
 bool SMILTimeContainer::ApplyTimedEffects(SMILTime elapsed) {
   if (document_order_indexes_dirty_)
     UpdateDocumentOrderIndexes();
@@ -647,8 +669,7 @@
     if (animations && animations->Apply(elapsed)) {
       did_apply_effects = true;
 
-      if (!disable_throttling && (entry.key->GetLayoutObject() ||
-                                  !entry.key->InstancesForElement().empty())) {
+      if (!disable_throttling && !CanThrottleTarget(*entry.key)) {
         disable_throttling = true;
       }
     }
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 21cc8fa9..8d23f1a 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -7876,8 +7876,6 @@
     string_builder = string_builder + " axid#" + String::Number(AXObjectID());
     // Add useful HTML element info, like <div.myClass#myId>.
     if (GetNode()) {
-      string_builder =
-          string_builder + " node#" + String::Number(GetNode()->GetDomNodeId());
       string_builder = string_builder + " " + GetNodeString(GetNode());
       if (IsRoot()) {
         string_builder = string_builder + " isRoot";
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index 7580ffd..9d1c260 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -1008,12 +1008,15 @@
   }
 #endif
 
-  auto iter = node_object_mapping_.find(node);
-  if (iter == node_object_mapping_.end()) {
+  AXID node_id = static_cast<AXID>(DOMNodeIds::ExistingIdForNode(node));
+  if (!node_id) {
+    // An ID hasn't yet been generated for this DOM node, but ::CreateAndInit()
+    // will ensure a DOMNodeID is generated by using node->GetDomNodeId().
+    // Therefore if an id doesn't exist for a DOM node, it means that it can't
+    // have an associated AXObject.
     return nullptr;
   }
 
-  AXID node_id = iter->value;
   auto it_result = objects_.find(node_id);
   if (it_result == objects_.end()) {
     return nullptr;
@@ -1041,9 +1044,9 @@
   auto it_ax = inline_text_box_object_mapping_.find(inline_text_box);
   AXID ax_id =
       it_ax != inline_text_box_object_mapping_.end() ? it_ax->value : 0;
-  DCHECK(!WTF::IsHashTraitsDeletedValue<HashTraits<AXID>>(ax_id));
   if (!ax_id)
     return nullptr;
+  DCHECK(!WTF::IsHashTraitsEmptyOrDeletedValue<HashTraits<AXID>>(ax_id));
 
   auto it_result = objects_.find(ax_id);
   AXObject* result = it_result != objects_.end() ? it_result->value : nullptr;
@@ -1058,22 +1061,6 @@
   return result;
 }
 
-AXID AXObjectCacheImpl::GetAXID(Node* node) {
-  AXID existing_axid = GetExistingAXID(node);
-  if (existing_axid != ui::AXNodeData::kInvalidAXID) {
-    return existing_axid;
-  }
-  UpdateAXForAllDocuments();
-  return GetExistingAXID(node);
-}
-
-AXID AXObjectCacheImpl::GetExistingAXID(Node* node) {
-  AXObject* ax_object = Get(node);
-  if (!ax_object)
-    return ui::AXNodeData::kInvalidAXID;
-  return ax_object->AXObjectID();
-}
-
 AXObject* AXObjectCacheImpl::Get(AccessibleNode* accessible_node) {
   if (!accessible_node)
     return nullptr;
@@ -1090,9 +1077,9 @@
 
   auto it_ax = accessible_node_mapping_.find(accessible_node);
   AXID ax_id = it_ax != accessible_node_mapping_.end() ? it_ax->value : 0;
-  DCHECK(!WTF::IsHashTraitsDeletedValue<HashTraits<AXID>>(ax_id));
   if (!ax_id)
     return nullptr;
+  DCHECK(!WTF::IsHashTraitsEmptyOrDeletedValue<HashTraits<AXID>>(ax_id));
 
   auto it_result = objects_.find(ax_id);
   AXObject* result = it_result != objects_.end() ? it_result->value : nullptr;
@@ -1587,24 +1574,31 @@
     return nullptr;
   }
 
-  AXID axid = GenerateAXID();
-  DCHECK(!base::Contains(objects_, axid));
-
+  // If there is a DOM node, use its dom_node_id, otherwise, generate an AXID.
+  // The dom_node_id can be used even if there is also a layout object.
+  AXID axid;
   if (node) {
-    DCHECK(!node_object_mapping_.Contains(node))
-        << "Already have an AXObject for " << node;
-    node_object_mapping_.Set(node, axid);
+    axid = static_cast<AXID>(node->GetDomNodeId());
+    if (ax_tree_serializer_) {
+      // In the case where axid is being reused, because a previous AXObject
+      // existed for the same node, ensure that the serializer sees it as new.
+      ax_tree_serializer_->MarkNodeDirty(axid);
+    }
   } else {
-    DCHECK(!layout_object_mapping_.Contains(layout_object))
-        << "Already have an AXObject for " << layout_object;
-    layout_object_mapping_.Set(layout_object, axid);
+    axid = GenerateAXID();
   }
+  DCHECK(!base::Contains(objects_, axid));
 
   // Create the new AXObject.
   AXObject* new_obj = nullptr;
   if (ax_type == kAXLayoutObject) {
     // Prefer to create from renderer if there is a layout object because
     // AXLayoutObjects can provide information about bounding boxes.
+    if (!node) {
+      DCHECK(!layout_object_mapping_.Contains(layout_object))
+          << "Already have an AXObject for " << layout_object;
+      layout_object_mapping_.Set(layout_object, axid);
+    }
     new_obj = CreateFromRenderer(layout_object);
   } else {
     new_obj = CreateFromNode(node);
@@ -1714,7 +1708,7 @@
 
   AXObject* new_obj = CreateFromInlineTextBox(inline_text_box);
 
-  const AXID axid = AssociateAXID(new_obj);
+  AXID axid = AssociateAXID(new_obj);
 
   inline_text_box_object_mapping_.Set(inline_text_box, axid);
   new_obj->Init(parent);
@@ -1797,10 +1791,12 @@
     }
   }
 
-  obj->Detach();
-
+  // Remove references to AXID before detaching, so that nothing will retrieve a
+  // detached object, which is illegal.
   RemoveReferencesToAXID(ax_id);
 
+  obj->Detach();
+
   // Remove the object.
   // TODO(accessibility) We don't use the return value, can we use .erase()
   // and it will still make sure that the object is cleaned up?
@@ -1896,26 +1892,21 @@
 }
 
 void AXObjectCacheImpl::Remove(Node* node, bool notify_parent) {
-  if (!node)
-    return;
+  DCHECK(node);
+  LayoutObject* layout_object = node->GetLayoutObject();
+  DCHECK(!layout_object || layout_object_mapping_.find(layout_object) ==
+                               layout_object_mapping_.end())
+      << "AXObject cannot be backed by both a layout object and node.";
 
-  whitespace_ignored_map_.erase(node->GetDomNodeId());
+  AXID axid = node->GetDomNodeId();
+  whitespace_ignored_map_.erase(axid);
 
   if (node == active_aria_modal_dialog_) {
     UpdateActiveAriaModalDialog(FocusedNode());
   }
 
-  auto iter = node_object_mapping_.find(node);
-  if (iter != node_object_mapping_.end()) {
-    LayoutObject* layout_object = node->GetLayoutObject();
-    DCHECK(!layout_object || layout_object_mapping_.find(layout_object) ==
-                                 layout_object_mapping_.end())
-        << "AXObject cannot be backed by both a layout object and node.";
-    AXID ax_id = iter->value;
-    DCHECK(ax_id);
-    node_object_mapping_.erase(iter);
-    Remove(ax_id, notify_parent);
-  }
+  DCHECK_GE(axid, 1);
+  Remove(axid, notify_parent);
 }
 
 void AXObjectCacheImpl::RemovePopup(Document* popup_document) {
@@ -2087,15 +2078,30 @@
   }
 }
 
+// All generated AXIDs are negative, ranging from kFirstGeneratedRendererNodeID
+// to kLastGeneratedRendererNodeID, in order to avoid conflict with the ids
+// reused from dom_node_ids, which are positive, and generated IDs on the
+// browser side, which are negative, starting at -1.
 AXID AXObjectCacheImpl::GenerateAXID() const {
-  static AXID last_used_id = 0;
+  // The first id is close to INT_MIN/2, leaving plenty of room for negative
+  // generated IDs both here and on the browser side, but starting at an even
+  // number makes it easier to read when debugging.
+  static AXID last_used_id = ui::kFirstGeneratedRendererNodeID;
 
   // Generate a new ID.
   AXID obj_id = last_used_id;
   do {
-    ++obj_id;
-  } while (!obj_id || WTF::IsHashTraitsDeletedValue<HashTraits<AXID>>(obj_id) ||
-           objects_.Contains(obj_id));
+    if (--obj_id == ui::kLastGeneratedRendererNodeID) {
+      // This is very unlikely to happen, but if we find that it happens, we
+      // could gracefully turn off a11y instead of crashing the renderer.
+      CHECK(!has_axid_generator_looped_)
+          << "Not enough room more generated accessibility objects.";
+      has_axid_generator_looped_ = true;
+      obj_id = ui::kFirstGeneratedRendererNodeID;
+    }
+  } while (has_axid_generator_looped_ && objects_.Contains(obj_id));
+
+  DCHECK(!WTF::IsHashTraitsEmptyOrDeletedValue<HashTraits<AXID>>(obj_id));
 
   last_used_id = obj_id;
 
@@ -2112,7 +2118,12 @@
   // Check for already-assigned ID.
   DCHECK(!obj->AXObjectID()) << "Object should not already have an AXID";
 
-  const AXID new_axid = use_axid ? use_axid : GenerateAXID();
+  AXID new_axid = use_axid ? use_axid : GenerateAXID();
+
+  bool should_have_node_id = obj->IsAXNodeObject() && obj->GetNode();
+  DCHECK_EQ(should_have_node_id, IsDOMNodeID(new_axid))
+      << "An AXID is also a DOMNodeID (positive integer) if any only if the "
+         "AXObject is an AXNodeObject with a DOM node.";
 
   obj->SetAXObjectID(new_axid);
   objects_.Set(new_axid, obj);
@@ -2125,15 +2136,28 @@
 
   // Clear AXIDs from maps. Note: do not need to erase id from
   // changed_bounds_ids_, a set which is cleared each time
-  // SerializeLocationChanges() is finished.
-  autofill_suggestion_availability_map_.erase(obj_id);
-  fixed_or_sticky_node_ids_.erase(obj_id);
-  cached_bounding_boxes_.erase(obj_id);
-  computed_node_mapping_.erase(obj_id);
+  // SerializeLocationChanges() is finished. Also, do not need to erase id from
+  // invalidated_ids_main_ or invalidated_ids_popup_, which are cleared each
+  // time ProcessInvalidatedObjects() finishes, and having extra ids in those
+  // sets is not harmful.
 
-  // Clear id from relation cache.
-  if (relation_cache_) {
-    relation_cache_->RemoveAXID(obj_id);
+  cached_bounding_boxes_.erase(obj_id);
+
+  if (IsDOMNodeID(obj_id)) {
+    // Optimization: these maps only contain ids for AXObjects with a DOM node.
+    fixed_or_sticky_node_ids_.erase(obj_id);
+    // Only objects with a DOM node can be in the relation cache.
+    if (relation_cache_) {
+      relation_cache_->RemoveAXID(obj_id);
+    }
+    // Allow the new AXObject for the same node to be serialized correctly.
+    nodes_with_pending_children_changed_.erase(obj_id);
+    computed_node_mapping_.erase(obj_id);
+  } else {
+    // Non-DOM ids should never find their way into these maps.
+    DCHECK(!fixed_or_sticky_node_ids_.Contains(obj_id));
+    DCHECK(!computed_node_mapping_.Contains(obj_id));
+    DCHECK(!nodes_with_pending_children_changed_.Contains(obj_id));
   }
 }
 
@@ -2336,8 +2360,11 @@
     return;
 
   // A text changed event is redundant with children changed on the same node.
-  if (base::Contains(nodes_with_pending_children_changed_, node)) {
-    return;
+  if (AXID node_id = static_cast<AXID>(node->GetDomNodeId())) {
+    if (nodes_with_pending_children_changed_.find(node_id) !=
+        nodes_with_pending_children_changed_.end()) {
+      return;
+    }
   }
 
   DeferTreeUpdate(TreeUpdateReason::kTextChangedOnNode, node);
@@ -2365,9 +2392,11 @@
     // If the text changed in a pseudo element, rebuild the entire subtree.
     if (node->IsPseudoElement()) {
       RemoveAXObjectsInLayoutSubtree(node->GetLayoutObject());
-    } else if (base::Contains(nodes_with_pending_children_changed_, node)) {
+    } else if (AXID node_id = static_cast<AXID>(node->GetDomNodeId())) {
       // Text changed is redundant with children changed on the same node.
-      return;
+      if (base::Contains(nodes_with_pending_children_changed_, node_id)) {
+        return;
+      }
     }
 
     DeferTreeUpdate(TreeUpdateReason::kTextChangedOnClosestNodeForLayoutObject,
@@ -2581,7 +2610,8 @@
 }
 
 void AXObjectCacheImpl::NodeIsAttached(Node* node) {
-  DCHECK(node);
+  CHECK(node);
+  CHECK(node->isConnected());
   SCOPED_DISALLOW_LIFECYCLE_TRANSITION();
 
   // It normally is not necessary to process text nodes here, because we'll
@@ -2657,8 +2687,27 @@
   }
 
   AXObject* obj = Get(node);
+  CHECK(obj);
+  CHECK(obj->CachedParentObject());
+
   MaybeNewRelationTarget(*node, obj);
 
+  // If there is a previous AXObject, it is being reattached with a new
+  // LayoutObject, in which case we should ensureinvalidation of its subtree.
+  // TODO(accessibility): Try to remove this by finding the specific situations
+  // where it is necessary and handling proactively for those.
+  NotifySubtreeDirty(obj);
+
+  // Even if the node or parent are ignored, an ancestor may need to include
+  // descendants of the attached node, thus ChildrenChangedWithCleanLayout()
+  // must be called. It handles ignored logic, ensuring that the first ancestor
+  // that should have this as a child will be updated.
+  ChildrenChangedWithCleanLayout(obj->CachedParentObject());
+
+  if (IsA<HTMLAreaElement>(node)) {
+    ChildrenChangedWithCleanLayout(obj);
+  }
+
   // Rare edge case: if an image is added, it could have changed the order of
   // images with the same usemap in the document. Only the first image for a
   // given <map> should have the <area> children. Therefore, get the current
@@ -2730,7 +2779,7 @@
   if (AXObject* ax_ancestor_for_notification = InvalidateChildren(obj)) {
     if (ax_ancestor_for_notification->GetNode() &&
         nodes_with_pending_children_changed_.Contains(
-            ax_ancestor_for_notification->GetNode())) {
+            ax_ancestor_for_notification->GetNode()->GetDomNodeId())) {
       return;
     }
     ChildrenChangedWithCleanLayout(ax_ancestor_for_notification->GetNode(),
@@ -2748,7 +2797,7 @@
     CHECK(!IsFrozen());
     if (ax_ancestor_for_notification->GetNode() &&
         !nodes_with_pending_children_changed_
-             .insert(ax_ancestor_for_notification->GetNode())
+             .insert(ax_ancestor_for_notification->GetNode()->GetDomNodeId())
              .is_new_entry) {
       return nullptr;
     }
@@ -4105,18 +4154,20 @@
   if (AXObject* obj = GetOrCreate(node)) {
     ChildrenChangedOnAncestorOf(obj);
 
-    // When the role of `obj` is changed, its AXObject needs to be destroyed and
-    // a new one needs to be created in its place.
-    if (RolePresentationPropagates(node)) {
-      // If role changes on a table, menu, or list invalidate the subtree of
-      // objects that may require a specific parent role in order to keep their
-      // role. For example, rows and cells require a table ancestor, and list
-      // items require a parent list (must be direct DOM parent).
-      RemoveSubtreeWithFlatTraversal(node, /* remove_root */ true,
-                                     /* notify_parent */ false);
-    } else {
-      // The children of this thing need to detach from parent.
-      Remove(obj, /* notify_parent */ false);
+    if (!obj->IsDetached()) {
+      // When the role of `obj` is changed, its AXObject needs to be destroyed
+      // and a new one needs to be created in its place.
+      if (RolePresentationPropagates(node)) {
+        // If role changes on a table, menu, or list invalidate the subtree of
+        // objects that may require a specific parent role in order to keep
+        // their role. For example, rows and cells require a table ancestor, and
+        // list items require a parent list (must be direct DOM parent).
+        RemoveSubtreeWithFlatTraversal(node, /* remove_root */ true,
+                                       /* notify_parent */ false);
+      } else {
+        // The children of this thing need to detach from parent.
+        Remove(obj, /* notify_parent */ false);
+      }
     }
 
     // Calling GetOrCreate(node) will not only create a new object with the
@@ -4126,6 +4177,10 @@
     if (AXObject* new_object = GetOrCreate(node)) {
       relation_cache_->UpdateAriaOwnsWithCleanLayout(new_object, true);
       new_object->UpdateChildrenIfNecessary();
+      // Need to mark dirty because the dom_node_id-based ID remains the same,
+      // and therefore the serializer may not automatically serialize this node
+      // from the children changed on the parent.
+      MarkAXSubtreeDirtyWithCleanLayout(new_object);
     }
   }
 }
@@ -5489,10 +5544,13 @@
       !marker_controller.MarkersFor(*text_node, spelling_and_grammar_markers)
            .empty();
   if (has_spelling_or_grammar_markers) {
-    if (nodes_with_spelling_or_grammar_markers_.insert(node).is_new_entry)
+    if (nodes_with_spelling_or_grammar_markers_.insert(node->GetDomNodeId())
+            .is_new_entry) {
       ChildrenChangedWithCleanLayout(node);
+    }
   } else {
-    const auto& iter = nodes_with_spelling_or_grammar_markers_.find(node);
+    const auto& iter =
+        nodes_with_spelling_or_grammar_markers_.find(node->GetDomNodeId());
     if (iter != nodes_with_spelling_or_grammar_markers_.end()) {
       nodes_with_spelling_or_grammar_markers_.erase(iter);
       ChildrenChangedWithCleanLayout(node);
@@ -5795,7 +5853,6 @@
   visitor->Trace(last_selected_from_active_descendant_);
   visitor->Trace(accessible_node_mapping_);
   visitor->Trace(layout_object_mapping_);
-  visitor->Trace(node_object_mapping_);
   visitor->Trace(inline_text_box_object_mapping_);
   visitor->Trace(active_aria_modal_dialog_);
 
@@ -5806,8 +5863,6 @@
   visitor->Trace(permission_observer_receiver_);
   visitor->Trace(tree_update_callback_queue_main_);
   visitor->Trace(tree_update_callback_queue_popup_);
-  visitor->Trace(nodes_with_pending_children_changed_);
-  visitor->Trace(nodes_with_spelling_or_grammar_markers_);
   visitor->Trace(nodes_for_subtree_removal_);
   visitor->Trace(render_accessibility_host_);
   visitor->Trace(ax_tree_source_);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
index df6bce4..478956d 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
@@ -74,15 +74,10 @@
 class HTMLAreaElement;
 class WebLocalFrameClient;
 
-// Describes a decicion on whether to create an AXNodeObject, an AXLayoutObject,
+// Describes a decision on whether to create an AXNodeObject, an AXLayoutObject,
 // or nothing (which will cause the AX subtree to be pruned at that point).
-// Currently this also mirrors the decision on whether to back the object by a
-// node or a layout object. When the AXObject is backed by a node, it's
-// AXID can be looked up in node_object_mapping_, and when the AXObject is
-// backed by layout, it's AXID can be looked up in layout_object_mapping_.
-// TODO(accessibility) Split the decision of what to use for backing from what
-// type of object to create, and use a node whenever possible, in order to
-// enable more stable IDs for most objects.
+// Not that AXLayoutObjects may be backed by a node, if it has one, and most do.
+// Only pseudo element descendants are missing DOM nodes.
 enum AXObjectType { kPruneSubtree = 0, kAXNodeObject, kAXLayoutObject };
 
 struct TextChangedOperation {
@@ -358,10 +353,6 @@
   // the AXObject for |child|.
   AXObject* RepairChildrenOfIncludedParent(Node* child);
 
-  AXID GetAXID(Node*) override;
-
-  AXID GetExistingAXID(Node*) override;
-
   // Return an AXObject for the AccessibleNode. If the AccessibleNode is
   // attached to an element, will return the AXObject for that element instead.
   AXObject* Get(AccessibleNode*);
@@ -699,7 +690,6 @@
   // Helpers for CreateAndInit().
   AXObject* CreateFromRenderer(LayoutObject*);
   AXObject* CreateFromNode(Node*);
-
   AXObject* CreateFromInlineTextBox(AbstractInlineTextBox*);
 
   // Removes AXObject backed by passed-in object, if there is one.
@@ -730,6 +720,10 @@
   bool IsPopupDocumentDirty() const;
   void ProcessSubtreeRemoval(Node*, bool remove_root);
 
+  // Returns true if the AXID is for a DOM node.
+  // All other AXIDs are generated.
+  bool IsDOMNodeID(AXID axid) { return axid > 0; }
+
   HeapHashSet<WeakMember<InspectorAccessibilityAgent>> agents_;
 
   struct AXEventParams final : public GarbageCollected<AXEventParams> {
@@ -872,10 +866,14 @@
 
   ui::AXMode ax_mode_;
 
+  // AXIDs for AXNodeObjects reuse the int ids in dom_node_id, all other AXIDs
+  // are negative in order to avoid a conflict.
   HeapHashMap<AXID, Member<AXObject>> objects_;
   HeapHashMap<Member<AccessibleNode>, AXID> accessible_node_mapping_;
+  // When the AXObject is backed by layout, its AXID can be looked up in
+  // layout_object_mapping_. When the AXObject is backed by a node, its
+  // AXID can be looked up via node->GetDomNodeId().
   HeapHashMap<Member<const LayoutObject>, AXID> layout_object_mapping_;
-  HeapHashMap<Member<const Node>, AXID> node_object_mapping_;
   HeapHashMap<Member<AbstractInlineTextBox>, AXID>
       inline_text_box_object_mapping_;
 #if DCHECK_IS_ON()
@@ -1067,11 +1065,11 @@
   TreeUpdateCallbackQueue tree_update_callback_queue_popup_;
 
   // Help de-dupe processing of repetitive events.
-  HeapHashSet<WeakMember<Node>> nodes_with_pending_children_changed_;
+  HashSet<AXID> nodes_with_pending_children_changed_;
   HashSet<AXID> nodes_with_pending_location_changed_;
 
   // Nodes with document markers that have received accessibility updates.
-  HeapHashSet<WeakMember<Node>> nodes_with_spelling_or_grammar_markers_;
+  HashSet<AXID> nodes_with_spelling_or_grammar_markers_;
 
   // Nodes renoved from flat tree.
   HeapVector<std::pair<Member<Node>, bool>> nodes_for_subtree_removal_;
@@ -1172,6 +1170,8 @@
   // Make sure the next serialization sends everything.
   bool mark_all_dirty_ = false;
 
+  mutable bool has_axid_generator_looped_ = false;
+
   FRIEND_TEST_ALL_PREFIXES(AccessibilityTest, PauseUpdatesAfterMaxNumberQueued);
   FRIEND_TEST_ALL_PREFIXES(AccessibilityTest,
                            UpdateAXForAllDocumentsAfterPausedUpdates);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_test.cc b/third_party/blink/renderer/modules/accessibility/ax_object_test.cc
index 7040512..ad1f51a 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_test.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_test.cc
@@ -1046,7 +1046,7 @@
   ASSERT_NE(nullptr, timeline_node);
   AXObjectCache* cache = timeline_node->GetDocument().ExistingAXObjectCache();
   ASSERT_NE(nullptr, cache);
-  AXObject* video_slider = cache->ObjectFromAXID(cache->GetAXID(timeline_node));
+  AXObject* video_slider = cache->ObjectFromAXID(timeline_node->GetDomNodeId());
 
   ASSERT_NE(nullptr, video_slider);
   ASSERT_EQ(video_slider->RoleValue(), ax::mojom::blink::Role::kSlider);
diff --git a/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition_test.cc b/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition_test.cc
index af939de..6ba3106 100644
--- a/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition_test.cc
+++ b/third_party/blink/renderer/modules/csspaint/nativepaint/clip_path_paint_definition_test.cc
@@ -57,7 +57,7 @@
   }
 };
 
-// Test the case where there is a background-color animation with two simple
+// Test the case where there is a clip-path animation with two simple
 // keyframes that will not fall back to main.
 TEST_F(ClipPathPaintDefinitionTest, SimpleClipPathAnimationNotFallback) {
   ScopedCompositeClipPathAnimationForTest composite_clip_path_animation(true);
@@ -111,6 +111,93 @@
             animation);
 }
 
+// Test the case where a 2nd composited clip path animation causes a fallback to
+// the main thread. In this case, the paint properties should update to avoid
+// any crashes or paint worklets existing beyond their validity.
+TEST_F(ClipPathPaintDefinitionTest, FallbackOnNonCompositableSecondAnimation) {
+  ScopedCompositeClipPathAnimationForTest composite_clip_path_animation(true);
+  SetBodyInnerHTML(R"HTML(
+    <div id ="target" style="width: 100px; height: 100px">
+    </div>
+  )HTML");
+
+  Timing timing;
+  timing.iteration_duration = ANIMATION_TIME_DELTA_FROM_SECONDS(30);
+
+  CSSPropertyID property_id = CSSPropertyID::kClipPath;
+  Persistent<StringKeyframe> start_keyframe =
+      MakeGarbageCollected<StringKeyframe>();
+  start_keyframe->SetCSSPropertyValue(property_id, "circle(50% at 50% 50%)",
+                                      SecureContextMode::kInsecureContext,
+                                      nullptr);
+  Persistent<StringKeyframe> end_keyframe =
+      MakeGarbageCollected<StringKeyframe>();
+  end_keyframe->SetCSSPropertyValue(property_id, "circle(30% at 30% 30%)",
+                                    SecureContextMode::kInsecureContext,
+                                    nullptr);
+
+  StringKeyframeVector keyframes;
+  keyframes.push_back(start_keyframe);
+  keyframes.push_back(end_keyframe);
+
+  auto* model = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
+  model->SetComposite(EffectModel::kCompositeReplace);
+
+  Element* element = GetElementById("target");
+  LayoutObject* lo = element->GetLayoutObject();
+  NonThrowableExceptionState exception_state;
+  DocumentTimeline* timeline =
+      MakeGarbageCollected<DocumentTimeline>(&GetDocument());
+  Animation* animation = Animation::Create(
+      MakeGarbageCollected<KeyframeEffect>(element, model, timing), timeline,
+      exception_state);
+  animation->play();
+
+  GetDocument().View()->UpdateLifecycleToCompositingInputsClean(
+      DocumentUpdateReason::kTest);
+  EXPECT_TRUE(lo->NeedsPaintPropertyUpdate());
+  UpdateAllLifecyclePhasesForTest();
+
+  // After adding a single animation, all should be well.
+  EXPECT_TRUE(lo->FirstFragment().PaintProperties()->ClipPathMask());
+  EXPECT_TRUE(element->GetElementAnimations());
+  EXPECT_EQ(element->GetElementAnimations()->CompositedClipPathStatus(),
+            CompositedPaintStatus::kComposited);
+  EXPECT_EQ(element->GetElementAnimations()->Animations().size(), 1u);
+  EXPECT_EQ(ClipPathPaintDefinition::GetAnimationIfCompositable(element),
+            animation);
+
+  Timing timing2;
+  timing2.iteration_duration = ANIMATION_TIME_DELTA_FROM_SECONDS(30);
+  timing2.start_delay = Timing::Delay(ANIMATION_TIME_DELTA_FROM_SECONDS(5));
+
+  Animation* animation2 = Animation::Create(
+      MakeGarbageCollected<KeyframeEffect>(element, model, timing2), timeline,
+      exception_state);
+  animation2->play();
+
+  EXPECT_EQ(element->GetElementAnimations()->Animations().size(), 2u);
+  // If support for delayed animations is added, this check will fail. This test
+  // should be updated to create a non compositible animation through other
+  // means in this case.
+  EXPECT_EQ(ClipPathPaintDefinition::GetAnimationIfCompositable(element),
+            nullptr);
+
+  // After adding a second animation with a delay, we gracefully fallback.
+  GetDocument().View()->UpdateLifecycleToCompositingInputsClean(
+      DocumentUpdateReason::kTest);
+  EXPECT_TRUE(lo->NeedsPaintPropertyUpdate());
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_FALSE(lo->FirstFragment().PaintProperties()->ClipPathMask());
+
+  // Further frames shouldn't cause more property updates than necessary.
+  GetDocument().View()->UpdateLifecycleToCompositingInputsClean(
+      DocumentUpdateReason::kTest);
+  EXPECT_FALSE(lo->NeedsPaintPropertyUpdate());
+  UpdateAllLifecyclePhasesForTest();
+  EXPECT_FALSE(lo->FirstFragment().PaintProperties()->ClipPathMask());
+}
+
 TEST_F(ClipPathPaintDefinitionTest, ClipBoundingBoxEncompassesAnimation) {
   ScopedCompositeClipPathAnimationForTest composite_clip_path_animation(true);
   SetBodyInnerHTML(R"HTML(
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.cc b/third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.cc
index f0b8c39e..3dd64536 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.cc
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.cc
@@ -9,7 +9,7 @@
 #include "base/sequence_checker.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/types/pass_key.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom-blink.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom-blink.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
@@ -27,16 +27,16 @@
 
 FileSystemAccessCapacityTracker::FileSystemAccessCapacityTracker(
     ExecutionContext* context,
-    mojo::PendingRemote<mojom::blink::FileSystemAccessCapacityAllocationHost>
-        capacity_allocation_host_remote,
+    mojo::PendingRemote<mojom::blink::FileSystemAccessFileModificationHost>
+        file_modification_host_remote,
     int64_t file_size,
     base::PassKey<FileSystemAccessRegularFileDelegate>)
-    : capacity_allocation_host_(context),
+    : file_modification_host_(context),
       file_size_(file_size),
       file_capacity_(file_size) {
-  capacity_allocation_host_.Bind(std::move(capacity_allocation_host_remote),
-                                 context->GetTaskRunner(TaskType::kStorage));
-  DCHECK(capacity_allocation_host_.is_bound());
+  file_modification_host_.Bind(std::move(file_modification_host_remote),
+                               context->GetTaskRunner(TaskType::kStorage));
+  DCHECK(file_modification_host_.is_bound());
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
@@ -62,7 +62,7 @@
     std::move(callback).Run(true);
     return;
   }
-  capacity_allocation_host_->RequestCapacityChange(
+  file_modification_host_->RequestCapacityChange(
       capacity_delta,
       WTF::BindOnce(&FileSystemAccessCapacityTracker::DidRequestCapacityChange,
                     WrapPersistent(this), required_capacity,
@@ -91,7 +91,7 @@
 
   int64_t granted_capacity;
   // Request the necessary capacity from the browser process.
-  bool call_succeeded = capacity_allocation_host_->RequestCapacityChange(
+  bool call_succeeded = file_modification_host_->RequestCapacityChange(
       capacity_delta, &granted_capacity);
   DCHECK(call_succeeded) << "Mojo call failed";
 
@@ -112,7 +112,7 @@
 
   file_size_ = new_size;
 
-  capacity_allocation_host_->OnContentsModified();
+  file_modification_host_->OnContentsModified();
 }
 
 void FileSystemAccessCapacityTracker::DidRequestCapacityChange(
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.h b/third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.h
index 534d150f..8299be1 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.h
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.h
@@ -8,7 +8,7 @@
 #include "base/functional/callback.h"
 #include "base/types/pass_key.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom-blink.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom-blink.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 
@@ -28,8 +28,8 @@
  public:
   explicit FileSystemAccessCapacityTracker(
       ExecutionContext* context,
-      mojo::PendingRemote<mojom::blink::FileSystemAccessCapacityAllocationHost>
-          capacity_allocation_host_remote,
+      mojo::PendingRemote<mojom::blink::FileSystemAccessFileModificationHost>
+          file_modification_host_remote,
       int64_t file_size,
       base::PassKey<FileSystemAccessRegularFileDelegate>);
 
@@ -64,7 +64,7 @@
 
   // GarbageCollected
   void Trace(Visitor* visitor) const {
-    visitor->Trace(capacity_allocation_host_);
+    visitor->Trace(file_modification_host_);
   }
 
  private:
@@ -81,8 +81,8 @@
   SEQUENCE_CHECKER(sequence_checker_);
 
   // Used to route capacity allocation requests to the browser.
-  HeapMojoRemote<mojom::blink::FileSystemAccessCapacityAllocationHost>
-      capacity_allocation_host_;
+  HeapMojoRemote<mojom::blink::FileSystemAccessFileModificationHost>
+      file_modification_host_;
 
   // Size of the file represented by the FileSystemAccessRegularFileDelegate
   // owning this.
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
index a024273..1380e233 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.cc
@@ -10,8 +10,8 @@
 #include "base/numerics/checked_math.h"
 #include "base/task/sequenced_task_runner.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom-blink.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_handle.mojom-blink.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom-blink.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -23,12 +23,12 @@
     mojom::blink::FileSystemAccessRegularFilePtr regular_file) {
   base::File backing_file = std::move(regular_file->os_file);
   int64_t backing_file_size = regular_file->file_size;
-  mojo::PendingRemote<mojom::blink::FileSystemAccessCapacityAllocationHost>
-      capacity_allocation_host_remote =
-          std::move(regular_file->capacity_allocation_host);
+  mojo::PendingRemote<mojom::blink::FileSystemAccessFileModificationHost>
+      file_modification_host_remote =
+          std::move(regular_file->file_modification_host);
   return MakeGarbageCollected<FileSystemAccessRegularFileDelegate>(
       context, std::move(backing_file), backing_file_size,
-      std::move(capacity_allocation_host_remote),
+      std::move(file_modification_host_remote),
       base::PassKey<FileSystemAccessFileDelegate>());
 }
 
@@ -36,13 +36,13 @@
     ExecutionContext* context,
     base::File backing_file,
     int64_t backing_file_size,
-    mojo::PendingRemote<mojom::blink::FileSystemAccessCapacityAllocationHost>
-        capacity_allocation_host_remote,
+    mojo::PendingRemote<mojom::blink::FileSystemAccessFileModificationHost>
+        file_modification_host_remote,
     base::PassKey<FileSystemAccessFileDelegate>)
     : backing_file_(std::move(backing_file)),
       capacity_tracker_(MakeGarbageCollected<FileSystemAccessCapacityTracker>(
           context,
-          std::move(capacity_allocation_host_remote),
+          std::move(file_modification_host_remote),
           backing_file_size,
           base::PassKey<FileSystemAccessRegularFileDelegate>())),
       task_runner_(context->GetTaskRunner(TaskType::kStorage)) {}
diff --git a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h
index 16c50e9..cb4f18d 100644
--- a/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h
+++ b/third_party/blink/renderer/modules/file_system_access/file_system_access_regular_file_delegate.h
@@ -10,7 +10,7 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/types/pass_key.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
-#include "third_party/blink/public/mojom/file_system_access/file_system_access_capacity_allocation_host.mojom-blink.h"
+#include "third_party/blink/public/mojom/file_system_access/file_system_access_file_modification_host.mojom-blink.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/file_system_access/file_system_access_capacity_tracker.h"
 #include "third_party/blink/renderer/modules/file_system_access/file_system_access_file_delegate.h"
@@ -29,8 +29,8 @@
       ExecutionContext* context,
       base::File backing_file,
       int64_t backing_file_size,
-      mojo::PendingRemote<mojom::blink::FileSystemAccessCapacityAllocationHost>
-          capacity_allocation_host_remote,
+      mojo::PendingRemote<mojom::blink::FileSystemAccessFileModificationHost>
+          file_modification_host_remote,
       base::PassKey<FileSystemAccessFileDelegate>);
 
   FileSystemAccessRegularFileDelegate(
diff --git a/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.cc b/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.cc
index c7a6bf2..0e0229c 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.cc
+++ b/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.cc
@@ -146,7 +146,7 @@
   base::AutoLock auto_lock(mixers_lock_);
 
   auto it = mixers_.find(key);
-  if (it != mixers_.end()) {
+  if (it != mixers_.end() && !it->second.mixer->HasSinkError()) {
     auto new_count = ++it->second.ref_count;
     CHECK(new_count != std::numeric_limits<decltype(new_count)>::max());
 
@@ -161,6 +161,12 @@
     sink->Stop();
 
     return it->second.mixer;
+  } else if (it != mixers_.end() && it->second.mixer->HasSinkError()) {
+    DVLOG(1) << "Not reusing mixer with errors: " << it->second.mixer;
+
+    // Move bad mixers out of the reuse map.
+    dead_mixers_[key] = it->second;
+    mixers_.erase(it);
   }
 
   const media::AudioParameters& mixer_output_params =
@@ -205,12 +211,31 @@
       [](const std::pair<MixerKey, AudioRendererMixerReference>& val) {
         return val.second.mixer;
       });
-  DCHECK(it != mixers_.end());
+
+  // If a mixer isn't in the normal map, check the map for mixers w/ errors.
+  bool dead_mixer = false;
+  if (it == mixers_.end()) {
+    it = base::ranges::find(
+        dead_mixers_, mixer,
+        [](const std::pair<MixerKey, AudioRendererMixerReference>& val) {
+          return val.second.mixer;
+        });
+    DCHECK(it != dead_mixers_.end());
+    dead_mixer = true;
+  }
 
   // Only remove the mixer if AudioRendererMixerManager is the last owner.
   it->second.ref_count--;
   if (it->second.ref_count == 0) {
     delete it->second.mixer;
+    if (dead_mixer) {
+      dead_mixers_.erase(it);
+    } else {
+      mixers_.erase(it);
+    }
+  } else if (!dead_mixer && it->second.mixer->HasSinkError()) {
+    // Move bad mixers out of the reuse map.
+    dead_mixers_[it->first] = it->second;
     mixers_.erase(it);
   }
 }
diff --git a/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.h b/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.h
index d7e5ebc..cc3fc45 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.h
+++ b/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager.h
@@ -180,6 +180,11 @@
 
   // Active mixers.
   AudioRendererMixerMap mixers_;
+
+  // Mixers which encountered errors, but can't yet be destroyed since they are
+  // still owned by an input.
+  AudioRendererMixerMap dead_mixers_;
+
   base::Lock mixers_lock_;
 };
 
diff --git a/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager_test.cc b/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager_test.cc
index 3c6a324..6a22f21 100644
--- a/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager_test.cc
+++ b/third_party/blink/renderer/modules/media/audio/audio_renderer_mixer_manager_test.cc
@@ -118,6 +118,7 @@
 
   // Number of instantiated mixers.
   size_t mixer_count() { return manager_->mixers_.size(); }
+  size_t dead_mixer_count() { return manager_->dead_mixers_.size(); }
 
  protected:
   scoped_refptr<media::MockAudioRendererSink> GetSink(
@@ -203,6 +204,55 @@
   EXPECT_EQ(0u, mixer_count());
 }
 
+TEST_F(AudioRendererMixerManagerTest, ReturnMixerWithError) {
+  mock_sink_ = CreateNormalSink();
+  auto* local_sink = mock_sink_.get();
+
+  // There should be no mixers outstanding to start with.
+  EXPECT_EQ(0u, mixer_count());
+
+  media::AudioParameters params1(
+      media::AudioParameters::AUDIO_PCM_LINEAR,
+      media::ChannelLayoutConfig::FromLayout<kChannelLayout>(), kSampleRate,
+      kBufferSize);
+
+  media::AudioRendererMixer* mixer1 =
+      GetMixer(kFrameToken, params1, AudioLatency::Type::kPlayback,
+               kDefaultDeviceId, SinkUseState::kNewSink);
+  ASSERT_TRUE(mixer1);
+  EXPECT_EQ(1u, mixer_count());
+
+  // The same parameters should return the same mixer1.
+  EXPECT_EQ(mixer1,
+            GetMixer(kFrameToken, params1, AudioLatency::Type::kPlayback,
+                     kDefaultDeviceId, SinkUseState::kExistingSink));
+  EXPECT_EQ(1u, mixer_count());
+
+  // Trigger an error in mixer1.
+  local_sink->callback()->OnRenderError();
+
+  // Return the extra mixer we just acquired, it should not be deleted, but put
+  // into the dead mixer map.
+  ReturnMixer(mixer1);
+  EXPECT_EQ(0u, mixer_count());
+  EXPECT_EQ(1u, dead_mixer_count());
+
+  // Using the same params should create a new mixer due to the error.
+  media::AudioRendererMixer* mixer2 =
+      GetMixer(kFrameToken, params1, AudioLatency::Type::kPlayback,
+               kDefaultDeviceId, SinkUseState::kNewSink);
+  ASSERT_TRUE(mixer2);
+  EXPECT_EQ(1u, mixer_count());
+  EXPECT_EQ(1u, dead_mixer_count());
+  EXPECT_NE(mixer1, mixer2);
+
+  // Return both outstanding mixers.
+  ReturnMixer(mixer1);
+  EXPECT_EQ(0u, dead_mixer_count());
+  ReturnMixer(mixer2);
+  EXPECT_EQ(0u, mixer_count());
+}
+
 // Verify GetMixer() correctly deduplicates mixer with irrelevant AudioParameter
 // differences.
 TEST_F(AudioRendererMixerManagerTest, MixerReuse) {
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
index ae1dda5..f35d38ed 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_audio_processor_test.cc
@@ -168,19 +168,13 @@
     EXPECT_TRUE(config.noise_suppression.enabled);
     EXPECT_EQ(config.noise_suppression.level,
               webrtc::AudioProcessing::Config::NoiseSuppression::kHigh);
+    EXPECT_FALSE(config.transient_suppression.enabled);
 
-#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX) || \
-    BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_FUCHSIA)
-    EXPECT_FALSE(config.echo_canceller.mobile_mode);
-    EXPECT_FALSE(config.transient_suppression.enabled);
-#elif BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
-    // Android uses echo cancellation optimized for mobiles, and does not
-    // support keytap suppression.
+#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
+    // Android uses echo cancellation optimized for mobiles.
     EXPECT_TRUE(config.echo_canceller.mobile_mode);
-    EXPECT_FALSE(config.transient_suppression.enabled);
 #else
     EXPECT_FALSE(config.echo_canceller.mobile_mode);
-    EXPECT_TRUE(config.transient_suppression.enabled);
 #endif
   }
 
diff --git a/third_party/blink/renderer/platform/wtf/dtoa.cc b/third_party/blink/renderer/platform/wtf/dtoa.cc
index 6c0dc25..24b6a52 100644
--- a/third_party/blink/renderer/platform/wtf/dtoa.cc
+++ b/third_party/blink/renderer/platform/wtf/dtoa.cc
@@ -43,6 +43,29 @@
 
 namespace WTF {
 
+namespace {
+
+double ParseDoubleFromLongString(const UChar* string,
+                                 size_t length,
+                                 size_t& parsed_length) {
+  wtf_size_t conversion_length = base::checked_cast<wtf_size_t>(length);
+  auto conversion_buffer = std::make_unique<LChar[]>(conversion_length);
+  for (wtf_size_t i = 0; i < conversion_length; ++i) {
+    conversion_buffer[i] = IsASCII(string[i]) ? string[i] : 0;
+  }
+  return ParseDouble(conversion_buffer.get(), length, parsed_length);
+}
+
+const double_conversion::StringToDoubleConverter& GetDoubleConverter() {
+  static double_conversion::StringToDoubleConverter converter(
+      double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES |
+          double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK,
+      0.0, 0, nullptr, nullptr);
+  return converter;
+}
+
+}  // namespace
+
 const char* NumberToString(double d, NumberToStringBuffer buffer) {
   double_conversion::StringBuilder builder(buffer, kNumberToStringBufferLength);
   const double_conversion::DoubleToStringConverter& converter =
@@ -136,31 +159,34 @@
 
 double ParseDouble(const LChar* string, size_t length, size_t& parsed_length) {
   int int_parsed_length = 0;
-  double d = internal::GetDoubleConverter().StringToDouble(
+  double d = GetDoubleConverter().StringToDouble(
       reinterpret_cast<const char*>(string), base::saturated_cast<int>(length),
       &int_parsed_length);
   parsed_length = int_parsed_length;
   return d;
 }
 
-namespace internal {
-
-double ParseDoubleFromLongString(const UChar* string,
-                                 size_t length,
-                                 size_t& parsed_length) {
-  wtf_size_t conversion_length = base::checked_cast<wtf_size_t>(length);
-  auto conversion_buffer = std::make_unique<LChar[]>(conversion_length);
-  for (wtf_size_t i = 0; i < conversion_length; ++i)
-    conversion_buffer[i] = IsASCII(string[i]) ? string[i] : 0;
-  return ParseDouble(conversion_buffer.get(), length, parsed_length);
+double ParseDouble(const UChar* string, size_t length, size_t& parsed_length) {
+  const size_t kConversionBufferSize = 64;
+  if (length > kConversionBufferSize) {
+    return ParseDoubleFromLongString(string, length, parsed_length);
+  }
+  LChar conversion_buffer[kConversionBufferSize];
+  for (size_t i = 0; i < length; ++i) {
+    conversion_buffer[i] =
+        IsASCII(string[i]) ? static_cast<LChar>(string[i]) : 0;
+  }
+  return ParseDouble(conversion_buffer, length, parsed_length);
 }
 
-const double_conversion::StringToDoubleConverter& GetDoubleConverter() {
-  static double_conversion::StringToDoubleConverter converter(
-      double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES |
-          double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK,
-      0.0, 0, nullptr, nullptr);
-  return converter;
+namespace internal {
+
+void InitializeDoubleConverter() {
+  // Force initialization of static DoubleToStringConverter converter variable
+  // inside EcmaScriptConverter function while we are in single thread mode.
+  double_conversion::DoubleToStringConverter::EcmaScriptConverter();
+
+  GetDoubleConverter();
 }
 
 }  // namespace internal
diff --git a/third_party/blink/renderer/platform/wtf/dtoa.h b/third_party/blink/renderer/platform/wtf/dtoa.h
index a53cd33..777fca6 100644
--- a/third_party/blink/renderer/platform/wtf/dtoa.h
+++ b/third_party/blink/renderer/platform/wtf/dtoa.h
@@ -26,10 +26,6 @@
 #include "third_party/blink/renderer/platform/wtf/text/wtf_uchar.h"
 #include "third_party/blink/renderer/platform/wtf/wtf_export.h"
 
-namespace double_conversion {
-class StringToDoubleConverter;
-}  // namespace double_conversion
-
 namespace WTF {
 
 // Size = 80 for sizeof(DtoaBuffer) + some sign bits, decimal point, 'e',
@@ -54,24 +50,10 @@
                               size_t& parsed_length);
 
 namespace internal {
-double ParseDoubleFromLongString(const UChar* string,
-                                 size_t length,
-                                 size_t& parsed_length);
-const double_conversion::StringToDoubleConverter& GetDoubleConverter();
-}  // namespace internal
 
-inline double ParseDouble(const UChar* string,
-                          size_t length,
-                          size_t& parsed_length) {
-  const size_t kConversionBufferSize = 64;
-  if (length > kConversionBufferSize)
-    return internal::ParseDoubleFromLongString(string, length, parsed_length);
-  LChar conversion_buffer[kConversionBufferSize];
-  for (size_t i = 0; i < length; ++i)
-    conversion_buffer[i] =
-        IsASCII(string[i]) ? static_cast<LChar>(string[i]) : 0;
-  return ParseDouble(conversion_buffer, length, parsed_length);
-}
+void InitializeDoubleConverter();
+
+}  // namespace internal
 
 }  // namespace WTF
 
diff --git a/third_party/blink/renderer/platform/wtf/wtf.cc b/third_party/blink/renderer/platform/wtf/wtf.cc
index 9424893..e72fbf4 100644
--- a/third_party/blink/renderer/platform/wtf/wtf.cc
+++ b/third_party/blink/renderer/platform/wtf/wtf.cc
@@ -30,7 +30,6 @@
 
 #include "third_party/blink/renderer/platform/wtf/wtf.h"
 
-#include "base/third_party/double_conversion/double-conversion/double-conversion.h"
 #include "build/build_config.h"
 #include "third_party/abseil-cpp/absl/base/attributes.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
@@ -83,10 +82,7 @@
 
   Threading::Initialize();
 
-  // Force initialization of static DoubleToStringConverter converter variable
-  // inside EcmaScriptConverter function while we are in single thread mode.
-  double_conversion::DoubleToStringConverter::EcmaScriptConverter();
-  internal::GetDoubleConverter();
+  internal::InitializeDoubleConverter();
 
   internal::InitializeMainThreadStackEstimate();
   AtomicString::Init();
diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
index adeecda..b3ca045 100755
--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
+++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py
@@ -709,6 +709,9 @@
             'ui::AXTreeUpdate',
             'ui::AXTreeID',
             'ui::AXTreeIDUnknown',
+            'ui::kInvalidAXNodeID',
+            'ui::kFirstGeneratedRendererNodeID',
+            'ui::kLastGeneratedRendererNodeID',
             'ui::kAXModeBasic',
             'ui::kAXModeComplete',
             'ui::ToString',
diff --git a/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html b/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html
index b9c4b8a..665ed69 100644
--- a/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html
+++ b/third_party/blink/web_tests/accessibility/aom-computed-accessible-node.html
@@ -52,13 +52,12 @@
     assert_equals(button2CAXNode.name, "axButton");
     assert_equals(button2CAXNode.role, "button");
 
-    // As button1 has no node in the accessibility tree anymore, assert that the
-    // its previously retrieved computed accessible node has had its attributes
-    // nullified.
+    // As button1 still has a node in the accessibility tree, but its layout has
+    // been removed,and therefore the name is now null.
     assert_equals(button1CAXNode.name, null);
-    assert_equals(button1CAXNode.role, null);
+    assert_equals(button1CAXNode.role, "button");
 
-}, "Deleting nodes from the accessibility tree will not cause a crash, and properties on any references to a deleted computed accessible node have been nullified.");
+}, "Deleting layout from the accessibility tree will not cause a crash.");
 
 </script>
 
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 6b3b71cf..633cdec8 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -343013,6 +343013,14 @@
       []
      ],
      "insertion-removing-steps": {
+      "blur-event.window-expected.txt": [
+       "7c476a91ba6d9ebbca436a25cccd90144bd3e23f",
+       []
+      ],
+      "insertion-removing-steps-iframe.window-expected.txt": [
+       "a52434e4389db97ce71166f2f9975406e5b0ed7c",
+       []
+      ],
       "insertion-removing-steps-script.window-expected.txt": [
        "420e94308f537d87004ad920c6fb3ea0c758ee88",
        []
@@ -478467,8 +478475,15 @@
       ]
      ],
      "insertion-removing-steps": {
+      "blur-event.window.js": [
+       "4c8cd85cbf5483c552047fe9698576f201a30a5f",
+       [
+        "dom/nodes/insertion-removing-steps/blur-event.window.html",
+        {}
+       ]
+      ],
       "insertion-removing-steps-iframe.window.js": [
-       "a10610f4677b159b8373ead2ff9f6fd1f49dabf7",
+       "60c2bec0c8aa213b1bfa1d75c99ca1745cb9871c",
        [
         "dom/nodes/insertion-removing-steps/insertion-removing-steps-iframe.window.html",
         {}
@@ -583917,7 +583932,7 @@
       ]
      ],
      "test_serialize.html": [
-      "88a9ce5221dcf90ad7c064b6e135ba5e1b37da4f",
+      "59226db6e72b731cf0973a3c2c0fd75d950aefc3",
       [
        null,
        {}
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/anchor.tentative.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/anchor.tentative.https.window.js
index 4e860ad..f547386 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/anchor.tentative.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/anchor.tentative.https.window.js
@@ -149,6 +149,44 @@
   expected: NavigationTestResult.SUCCESS,
 }), "public to public: no preflight required.");
 
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => anchorTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.noCorsHeader(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.FAILURE,
+    }),
+    'public to public redirected to private: missing CORS headers.');
+
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => anchorTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.navigation(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.SUCCESS,
+    }),
+    'public to public to private: success.');
+
 // The following tests verify that `CSP: treat-as-public-address` makes
 // documents behave as if they had been served from a public IP address.
 
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/anchor.tentative.https.window_include=from-public-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/anchor.tentative.https.window_include=from-public-expected.txt
index 083cb0d..19aec266 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/anchor.tentative.https.window_include=from-public-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/anchor.tentative.https.window_include=from-public-expected.txt
@@ -11,5 +11,7 @@
   assert_equals: expected "timeout" but got "success"
 [FAIL] public to private: missing PNA header.
   assert_equals: expected "timeout" but got "success"
+[FAIL] public to public redirected to private: missing CORS headers.
+  assert_equals: expected "timeout" but got "success"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/service-worker-fetch-all.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/service-worker-fetch-all.js
new file mode 100644
index 0000000..78ac8d15
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/service-worker-fetch-all.js
@@ -0,0 +1,20 @@
+self.addEventListener("install", () => {
+    // Skip waiting before replacing the previously-active service worker, if any.
+    // This allows the bridge script to notice the controller change and query
+    // the install time via fetch.
+    self.skipWaiting();
+});
+
+self.addEventListener("activate", (event) => {
+    // Claim all clients so that the bridge script notices the activation.
+    event.waitUntil(self.clients.claim());
+});
+
+self.addEventListener("fetch", (event) => {
+  const url = new URL(event.request.url).searchParams.get("proxied-url");
+  if (url) {
+    event.respondWith(fetch(url));
+  } else {
+    event.respondWith(fetch(event.request));
+  }
+});
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/support.sub.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/support.sub.js
index 46a9d9e07..1cb432b 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/support.sub.js
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/resources/support.sub.js
@@ -480,6 +480,13 @@
 };
 
 async function windowOpenTest(t, { source, target, expected }) {
+  if (target.behavior && target.behavior.redirect) {
+    target.behavior.redirect.searchParams.set('file', 'openee.html');
+    target.behavior.redirect.searchParams.set(
+        'file-if-no-preflight-received',
+        'no-preflight-received.html',
+    );
+  }
   const targetUrl = preflightUrl(target);
   targetUrl.searchParams.set("file", "openee.html");
   targetUrl.searchParams.set(
@@ -507,6 +514,13 @@
 }
 
 async function windowOpenExistingTest(t, { source, target, expected }) {
+  if (target.behavior && target.behavior.redirect) {
+    target.behavior.redirect.searchParams.set('file', 'openee.html');
+    target.behavior.redirect.searchParams.set(
+        'file-if-no-preflight-received',
+        'no-preflight-received.html',
+    );
+  }
   const targetUrl = preflightUrl(target);
   targetUrl.searchParams.set("file", "openee.html");
   targetUrl.searchParams.set(
@@ -535,6 +549,13 @@
 }
 
 async function anchorTest(t, { source, target, expected }) {
+  if (target.behavior && target.behavior.redirect) {
+    target.behavior.redirect.searchParams.set('file', 'openee.html');
+    target.behavior.redirect.searchParams.set(
+        'file-if-no-preflight-received',
+        'no-preflight-received.html',
+    );
+  }
   const targetUrl = preflightUrl(target);
   targetUrl.searchParams.set("file", "openee.html");
   targetUrl.searchParams.set(
@@ -855,3 +876,66 @@
   assert_equals(status, expected.status, "response status");
   assert_equals(body, expected.body, "response body");
 }
+
+async function makeServiceWorkerTest(t, { source, target, expected, fetch_document=false }) {
+  const bridgeUrl = resolveUrl(
+      "resources/service-worker-bridge.html",
+      sourceResolveOptions({ server: source.server }));
+
+  const scriptUrl = fetch_document?
+      resolveUrl("resources/service-worker-fetch-all.js", sourceResolveOptions(source)):
+      resolveUrl("resources/service-worker.js", sourceResolveOptions(source));
+
+  const realTargetUrl = preflightUrl(target);
+
+  // Fetch a URL within the service worker's scope, but tell it which URL to
+  // really fetch.
+  const targetUrl = new URL("service-worker-proxy", scriptUrl);
+  targetUrl.searchParams.append("proxied-url", realTargetUrl.href);
+
+  const iframe = await appendIframe(t, document, bridgeUrl);
+
+  const request = (message) => {
+    const reply = futureMessage();
+    iframe.contentWindow.postMessage(message, "*");
+    return reply;
+  };
+
+  {
+    const { error, loaded } = await request({
+      action: "register",
+      url: scriptUrl.href,
+    });
+
+    assert_equals(error, undefined, "register error");
+    assert_true(loaded, "response loaded");
+  }
+
+  try {
+    const { controlled, numControllerChanges } = await request({
+      action: "wait",
+      numControllerChanges: 1,
+    });
+
+    assert_equals(numControllerChanges, 1, "controller change");
+    assert_true(controlled, "bridge script is controlled");
+
+    const { error, ok, body } = await request({
+      action: "fetch",
+      url: targetUrl.href,
+    });
+
+    assert_equals(error, expected.error, "fetch error");
+    assert_equals(ok, expected.ok, "response ok");
+    assert_equals(body, expected.body, "response body");
+  } finally {
+    // Always unregister the service worker.
+    const { error, unregistered } = await request({
+      action: "unregister",
+      scope: new URL("./", scriptUrl).href,
+    });
+
+    assert_equals(error, undefined, "unregister error");
+    assert_true(unregistered, "unregistered");
+  }
+}
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt
new file mode 100644
index 0000000..c9433e6f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt
@@ -0,0 +1,7 @@
+This is a testharness.js-based test.
+[FAIL] treat-as-public to local: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+[FAIL] treat-as-public to private: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window.js
new file mode 100644
index 0000000..6fc29ce4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window.js
@@ -0,0 +1,101 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `ServiceWorker` scripts are
+// subject to Private Network Access checks, just like fetches from within
+// documents.
+
+// Results that may be expected in tests.
+const TestResult = {
+    SUCCESS: { ok: true, body: "success" },
+    FAILURE: { error: "TypeError" },
+};
+
+promise_test(t => makeServiceWorkerTest(t, {
+    source: {
+      server: Server.HTTPS_LOCAL,
+      treatAsPublic: true,
+    },
+    target: {
+      server: Server.OTHER_HTTPS_LOCAL,
+      behavior: {
+        preflight: PreflightBehavior.failure(),
+        response: ResponseBehavior.allowCrossOrigin()
+      },
+    },
+    expected: TestResult.FAILURE,
+    fetch_document: true,
+}), "treat-as-public to local: failed preflight.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+    source: {
+      server: Server.HTTPS_LOCAL,
+      treatAsPublic: true,
+    },
+    target: {
+      server: Server.OTHER_HTTPS_LOCAL,
+      behavior: {
+        preflight: PreflightBehavior.success(token()),
+        response: ResponseBehavior.allowCrossOrigin(),
+      },
+    },
+    expected: TestResult.SUCCESS,
+    fetch_document: true,
+}), "treat-as-public to local: success.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+    source: {
+      server: Server.HTTPS_LOCAL,
+      treatAsPublic: true,
+    },
+    target: { server: Server.HTTPS_LOCAL },
+    expected: TestResult.SUCCESS,
+    fetch_document: true,
+}), "treat-as-public to local (same-origin): no preflight required.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+    source: {
+      server: Server.HTTPS_LOCAL,
+      treatAsPublic: true,
+    },
+    target: {
+      server: Server.HTTPS_PRIVATE,
+      behavior: {
+        preflight: PreflightBehavior.failure(),
+        response: ResponseBehavior.allowCrossOrigin()
+      },
+    },
+    expected: TestResult.FAILURE,
+    fetch_document: true,
+}), "treat-as-public to private: failed preflight.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+    source: {
+      server: Server.HTTPS_LOCAL,
+      treatAsPublic: true,
+    },
+    target: {
+      server: Server.HTTPS_PRIVATE,
+      behavior: {
+        preflight: PreflightBehavior.success(token()),
+        response: ResponseBehavior.allowCrossOrigin(),
+      },
+    },
+    expected: TestResult.SUCCESS,
+    fetch_document: true,
+}), "treat-as-public to private: success.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+    source: {
+      server: Server.HTTPS_LOCAL,
+      treatAsPublic: true,
+    },
+    target: {
+      server: Server.HTTPS_PUBLIC,
+      behavior: { response: ResponseBehavior.allowCrossOrigin() },
+    },
+    expected: TestResult.SUCCESS,
+    fetch_document: true,
+}), "treat-as-public to public: success.");
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt
new file mode 100644
index 0000000..3f3485f4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt
@@ -0,0 +1,9 @@
+This is a testharness.js-based test.
+[FAIL] private to local: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+[FAIL] public to local: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+[FAIL] public to private: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window.js
new file mode 100644
index 0000000..ec38055
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window.js
@@ -0,0 +1,114 @@
+// META: script=/common/utils.js
+// META: script=resources/support.sub.js
+//
+// Spec: https://wicg.github.io/private-network-access/#integration-fetch
+//
+// These tests check that fetches from within `ServiceWorker` scripts are
+// subject to Private Network Access checks, just like fetches from within
+// documents.
+
+// Results that may be expected in tests.
+const TestResult = {
+    SUCCESS: { ok: true, body: "success" },
+    FAILURE: { error: "TypeError" },
+};
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_LOCAL },
+  target: { server: Server.HTTPS_LOCAL },
+  expected: TestResult.SUCCESS,
+  fetch_document: true,
+}), "local to local: success.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_PRIVATE },
+  target: {
+    server: Server.HTTPS_LOCAL,
+    behavior: {
+      preflight: PreflightBehavior.failure(),
+      response: ResponseBehavior.allowCrossOrigin()
+    },
+  },
+  expected: TestResult.FAILURE,
+  fetch_document: true,
+}), "private to local: failed preflight.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_PRIVATE },
+  target: {
+    server: Server.HTTPS_LOCAL,
+    behavior: {
+      preflight: PreflightBehavior.success(token()),
+      response: ResponseBehavior.allowCrossOrigin(),
+    },
+  },
+  expected: TestResult.SUCCESS,
+  fetch_document: true,
+}), "private to local: success.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_PRIVATE },
+  target: { server: Server.HTTPS_PRIVATE },
+  expected: TestResult.SUCCESS,
+  fetch_document: true,
+}), "private to private: success.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_PUBLIC },
+  target: {
+    server: Server.HTTPS_LOCAL,
+    behavior: {
+      preflight: PreflightBehavior.failure(),
+      response: ResponseBehavior.allowCrossOrigin()
+    },
+  },
+  expected: TestResult.FAILURE,
+  fetch_document: true,
+}), "public to local: failed preflight.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_PUBLIC },
+  target: {
+    server: Server.HTTPS_LOCAL,
+    behavior: {
+      preflight: PreflightBehavior.success(token()),
+      response: ResponseBehavior.allowCrossOrigin(),
+    },
+  },
+  expected: TestResult.SUCCESS,
+  fetch_document: true,
+}), "public to local: success.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_PUBLIC },
+  target: {
+    server: Server.HTTPS_PRIVATE,
+    behavior: {
+      preflight: PreflightBehavior.failure(),
+      response: ResponseBehavior.allowCrossOrigin()
+    },
+  },
+  expected: TestResult.FAILURE,
+  fetch_document: true,
+}), "public to private: failed preflight.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_PUBLIC },
+  target: {
+    server: Server.HTTPS_PRIVATE,
+    behavior: {
+      preflight: PreflightBehavior.success(token()),
+      response: ResponseBehavior.allowCrossOrigin(),
+    },
+  },
+  expected: TestResult.SUCCESS,
+  fetch_document: true,
+}), "public to private: success.");
+
+promise_test(t => makeServiceWorkerTest(t, {
+  source: { server: Server.HTTPS_PUBLIC },
+  target: { server: Server.HTTPS_PUBLIC },
+  expected: TestResult.SUCCESS,
+  fetch_document: true,
+}), "public to public: success.");
+
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch.tentative.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch.tentative.https.window.js
index cb6d1f79..5fc5800 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch.tentative.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/service-worker-fetch.tentative.https.window.js
@@ -16,84 +16,25 @@
   FAILURE: { error: "TypeError" },
 };
 
-async function makeTest(t, { source, target, expected }) {
-  const bridgeUrl = resolveUrl(
-      "resources/service-worker-bridge.html",
-      sourceResolveOptions({ server: source.server }));
-
-  const scriptUrl =
-      resolveUrl("resources/service-worker.js", sourceResolveOptions(source));
-
-  const realTargetUrl = preflightUrl(target);
-
-  // Fetch a URL within the service worker's scope, but tell it which URL to
-  // really fetch.
-  const targetUrl = new URL("service-worker-proxy", scriptUrl);
-  targetUrl.searchParams.append("proxied-url", realTargetUrl.href);
-
-  const iframe = await appendIframe(t, document, bridgeUrl);
-
-  const request = (message) => {
-    const reply = futureMessage();
-    iframe.contentWindow.postMessage(message, "*");
-    return reply;
-  };
-
-  {
-    const { error, loaded } = await request({
-      action: "register",
-      url: scriptUrl.href,
-    });
-
-    assert_equals(error, undefined, "register error");
-    assert_true(loaded, "response loaded");
-  }
-
-  try {
-    const { controlled, numControllerChanges } = await request({
-      action: "wait",
-      numControllerChanges: 1,
-    });
-
-    assert_equals(numControllerChanges, 1, "controller change");
-    assert_true(controlled, "bridge script is controlled");
-
-    const { error, ok, body } = await request({
-      action: "fetch",
-      url: targetUrl.href,
-    });
-
-    assert_equals(error, expected.error, "fetch error");
-    assert_equals(ok, expected.ok, "response ok");
-    assert_equals(body, expected.body, "response body");
-  } finally {
-    // Always unregister the service worker.
-    const { error, unregistered } = await request({
-      action: "unregister",
-      scope: new URL("./", scriptUrl).href,
-    });
-
-    assert_equals(error, undefined, "unregister error");
-    assert_true(unregistered, "unregistered");
-  }
-}
-
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_LOCAL },
   target: { server: Server.HTTPS_LOCAL },
   expected: TestResult.SUCCESS,
 }), "local to local: success.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_PRIVATE },
   target: {
     server: Server.HTTPS_LOCAL,
-    behavior: { response: ResponseBehavior.allowCrossOrigin() },
+    behavior: {
+      preflight: PreflightBehavior.failure(),
+      response: ResponseBehavior.allowCrossOrigin()
+    },
   },
   expected: TestResult.FAILURE,
 }), "private to local: failed preflight.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_PRIVATE },
   target: {
     server: Server.HTTPS_LOCAL,
@@ -105,22 +46,25 @@
   expected: TestResult.SUCCESS,
 }), "private to local: success.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_PRIVATE },
   target: { server: Server.HTTPS_PRIVATE },
   expected: TestResult.SUCCESS,
 }), "private to private: success.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_PUBLIC },
   target: {
     server: Server.HTTPS_LOCAL,
-    behavior: { response: ResponseBehavior.allowCrossOrigin() },
+    behavior: {
+      preflight: PreflightBehavior.failure(),
+      response: ResponseBehavior.allowCrossOrigin()
+    },
   },
   expected: TestResult.FAILURE,
 }), "public to local: failed preflight.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_PUBLIC },
   target: {
     server: Server.HTTPS_LOCAL,
@@ -132,16 +76,19 @@
   expected: TestResult.SUCCESS,
 }), "public to local: success.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_PUBLIC },
   target: {
     server: Server.HTTPS_PRIVATE,
-    behavior: { response: ResponseBehavior.allowCrossOrigin() },
+    behavior: {
+      preflight: PreflightBehavior.failure(),
+      response: ResponseBehavior.allowCrossOrigin()
+    },
   },
   expected: TestResult.FAILURE,
 }), "public to private: failed preflight.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_PUBLIC },
   target: {
     server: Server.HTTPS_PRIVATE,
@@ -153,25 +100,28 @@
   expected: TestResult.SUCCESS,
 }), "public to private: success.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: { server: Server.HTTPS_PUBLIC },
   target: { server: Server.HTTPS_PUBLIC },
   expected: TestResult.SUCCESS,
 }), "public to public: success.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: {
     server: Server.HTTPS_LOCAL,
     treatAsPublic: true,
   },
   target: {
     server: Server.OTHER_HTTPS_LOCAL,
-    behavior: { response: ResponseBehavior.allowCrossOrigin() },
+    behavior: {
+      preflight: PreflightBehavior.failure(),
+      response: ResponseBehavior.allowCrossOrigin()
+    },
   },
   expected: TestResult.FAILURE,
 }), "treat-as-public to local: failed preflight.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: {
     server: Server.HTTPS_LOCAL,
     treatAsPublic: true,
@@ -186,7 +136,7 @@
   expected: TestResult.SUCCESS,
 }), "treat-as-public to local: success.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: {
     server: Server.HTTPS_LOCAL,
     treatAsPublic: true,
@@ -195,19 +145,22 @@
   expected: TestResult.SUCCESS,
 }), "treat-as-public to local (same-origin): no preflight required.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: {
     server: Server.HTTPS_LOCAL,
     treatAsPublic: true,
   },
   target: {
     server: Server.HTTPS_PRIVATE,
-    behavior: { response: ResponseBehavior.allowCrossOrigin() },
+    behavior: {
+      preflight: PreflightBehavior.failure(),
+      response: ResponseBehavior.allowCrossOrigin()
+    },
   },
   expected: TestResult.FAILURE,
 }), "treat-as-public to private: failed preflight.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: {
     server: Server.HTTPS_LOCAL,
     treatAsPublic: true,
@@ -222,7 +175,7 @@
   expected: TestResult.SUCCESS,
 }), "treat-as-public to private: success.");
 
-subsetTest(promise_test, t => makeTest(t, {
+subsetTest(promise_test, t => makeServiceWorkerTest(t, {
   source: {
     server: Server.HTTPS_LOCAL,
     treatAsPublic: true,
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window.js
index 6a2a624f..565a2117 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window.js
@@ -167,6 +167,44 @@
     }),
     'public to public: no preflight required.');
 
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => windowOpenExistingTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.noCorsHeader(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.FAILURE,
+    }),
+    'public to public redirected to private: missing CORS headers.');
+
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => windowOpenExistingTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.navigation(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.SUCCESS,
+    }),
+    'public to public to private: success.');
+
 // The following tests verify that `CSP: treat-as-public-address` makes
 // documents behave as if they had been served from a public IP address.
 
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window_include=from-public-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window_include=from-public-expected.txt
index 083cb0d..19aec266 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window_include=from-public-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window_include=from-public-expected.txt
@@ -11,5 +11,7 @@
   assert_equals: expected "timeout" but got "success"
 [FAIL] public to private: missing PNA header.
   assert_equals: expected "timeout" but got "success"
+[FAIL] public to public redirected to private: missing CORS headers.
+  assert_equals: expected "timeout" but got "success"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open.tentative.https.window.js b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open.tentative.https.window.js
index 6793d1f3..42d70af 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open.tentative.https.window.js
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open.tentative.https.window.js
@@ -149,6 +149,44 @@
   expected: NavigationTestResult.SUCCESS,
 }), "public to public: no preflight required.");
 
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => windowOpenTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.noCorsHeader(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.FAILURE,
+    }),
+    'public to public redirected to private: missing CORS headers.');
+
+subsetTestByKey(
+    'from-public', promise_test_parallel,
+    t => windowOpenTest(t, {
+      source: {server: Server.HTTPS_PUBLIC},
+      target: {
+        server: Server.HTTPS_PUBLIC,
+        behavior: {
+          redirect: preflightUrl({
+            server: Server.HTTPS_PRIVATE,
+            behavior: {
+              preflight: PreflightBehavior.navigation(token()),
+            }
+          }),
+        }
+      },
+      expected: NavigationTestResult.SUCCESS,
+    }),
+    'public to public to private: success.');
+
 // The following tests verify that `CSP: treat-as-public-address` makes
 // documents behave as if they had been served from a public IP address.
 
diff --git a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open.tentative.https.window_include=from-public-expected.txt b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open.tentative.https.window_include=from-public-expected.txt
index 083cb0d..19aec266 100644
--- a/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open.tentative.https.window_include=from-public-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/fetch/private-network-access/window-open.tentative.https.window_include=from-public-expected.txt
@@ -11,5 +11,7 @@
   assert_equals: expected "timeout" but got "success"
 [FAIL] public to private: missing PNA header.
   assert_equals: expected "timeout" but got "success"
+[FAIL] public to public redirected to private: missing CORS headers.
+  assert_equals: expected "timeout" but got "success"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/channels/test_serialize.html b/third_party/blink/web_tests/external/wpt/infrastructure/channels/test_serialize.html
index 88a9ce5..59226db6 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/channels/test_serialize.html
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/channels/test_serialize.html
@@ -27,7 +27,7 @@
 promise_test(async t => {
     let remoteValue = RemoteObject.from(document.head);
     let result = await remote.call(inputValue => {
-        if (!inputValue instanceof RemoteObject) {
+        if (!(inputValue instanceof RemoteObject)) {
             throw new AssertionError(`Expected RemoteObject`);
         }
         return inputValue;
diff --git a/third_party/blink/web_tests/external/wpt/svg/animations/stop-animation-01.html b/third_party/blink/web_tests/external/wpt/svg/animations/stop-animation-01.html
new file mode 100644
index 0000000..d240c51
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/svg/animations/stop-animation-01.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Animate a &lt;stop> element</title>
+<link rel="match" href="../struct/reftests/reference/green-100x100.html">
+<script src="/common/reftest-wait.js"></script>
+<script src="/common/rendering-utils.js"></script>
+<script>
+function test() {
+  waitForAtLeastOneFrame().then(takeScreenshot);
+}
+</script>
+<svg>
+  <linearGradient id="g">
+    <stop stop-color="red">
+      <animate attributeName="stop-color" values="red; green" dur="1s"
+               keyTimes="0; 0.01" fill="freeze" calcMode="discrete"
+               onbegin="test()"/>
+    </stop>
+  </linearGradient>
+  <rect width="100" height="100" fill="url(#g)"/>
+</svg>
diff --git a/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/anchor.tentative.https.window_include=from-public-expected.txt b/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/anchor.tentative.https.window_include=from-public-expected.txt
index d14171b..631d8815 100644
--- a/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/anchor.tentative.https.window_include=from-public-expected.txt
+++ b/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/anchor.tentative.https.window_include=from-public-expected.txt
@@ -15,5 +15,9 @@
   assert_equals: expected "timeout" but got "no preflight received"
 [FAIL] public to private: success.
   assert_equals: expected "success" but got "no preflight received"
+[FAIL] public to public redirected to private: missing CORS headers.
+  assert_equals: expected "timeout" but got "no preflight received"
+[FAIL] public to public to private: success.
+  assert_equals: expected "success" but got "no preflight received"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window_include=from-public-expected.txt b/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window_include=from-public-expected.txt
index d14171b..631d8815 100644
--- a/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window_include=from-public-expected.txt
+++ b/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/window-open-existing.tentative.https.window_include=from-public-expected.txt
@@ -15,5 +15,9 @@
   assert_equals: expected "timeout" but got "no preflight received"
 [FAIL] public to private: success.
   assert_equals: expected "success" but got "no preflight received"
+[FAIL] public to public redirected to private: missing CORS headers.
+  assert_equals: expected "timeout" but got "no preflight received"
+[FAIL] public to public to private: success.
+  assert_equals: expected "success" but got "no preflight received"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/window-open.tentative.https.window_include=from-public-expected.txt b/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/window-open.tentative.https.window_include=from-public-expected.txt
index d14171b..631d8815 100644
--- a/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/window-open.tentative.https.window_include=from-public-expected.txt
+++ b/third_party/blink/web_tests/virtual/pna-navigations-disabled/external/wpt/fetch/private-network-access/window-open.tentative.https.window_include=from-public-expected.txt
@@ -15,5 +15,9 @@
   assert_equals: expected "timeout" but got "no preflight received"
 [FAIL] public to private: success.
   assert_equals: expected "success" but got "no preflight received"
+[FAIL] public to public redirected to private: missing CORS headers.
+  assert_equals: expected "timeout" but got "no preflight received"
+[FAIL] public to public to private: success.
+  assert_equals: expected "success" but got "no preflight received"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/virtual/pna-workers-disabled/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt b/third_party/blink/web_tests/virtual/pna-workers-disabled/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt
new file mode 100644
index 0000000..69a47aa
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/pna-workers-disabled/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt
@@ -0,0 +1,11 @@
+This is a testharness.js-based test.
+[FAIL] treat-as-public to local: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+[FAIL] treat-as-public to local: success.
+  assert_equals: fetch error expected (undefined) undefined but got (string) "TypeError"
+[FAIL] treat-as-public to private: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+[FAIL] treat-as-public to private: success.
+  assert_equals: fetch error expected (undefined) undefined but got (string) "TypeError"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/pna-workers-disabled/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt b/third_party/blink/web_tests/virtual/pna-workers-disabled/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt
new file mode 100644
index 0000000..d459de6b
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/pna-workers-disabled/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt
@@ -0,0 +1,15 @@
+This is a testharness.js-based test.
+[FAIL] private to local: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+[FAIL] private to local: success.
+  assert_equals: fetch error expected (undefined) undefined but got (string) "TypeError"
+[FAIL] public to local: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+[FAIL] public to local: success.
+  assert_equals: fetch error expected (undefined) undefined but got (string) "TypeError"
+[FAIL] public to private: failed preflight.
+  assert_equals: fetch error expected (string) "TypeError" but got (undefined) undefined
+[FAIL] public to private: success.
+  assert_equals: fetch error expected (undefined) undefined but got (string) "TypeError"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt b/third_party/blink/web_tests/virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt
new file mode 100644
index 0000000..d2490db
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/service-worker-fetch-document-treat-as-public.tentative.https.window-expected.txt
@@ -0,0 +1,3 @@
+This is a testharness.js-based test.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt b/third_party/blink/web_tests/virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt
new file mode 100644
index 0000000..d2490db
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/pna-workers-enabled/external/wpt/fetch/private-network-access/service-worker-fetch-document.tentative.https.window-expected.txt
@@ -0,0 +1,3 @@
+This is a testharness.js-based test.
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/wpt_internal/dom/abort/resources/run-async-gc.js b/third_party/blink/web_tests/wpt_internal/dom/abort/resources/run-async-gc.js
index a86e6e92..548645da 100644
--- a/third_party/blink/web_tests/wpt_internal/dom/abort/resources/run-async-gc.js
+++ b/third_party/blink/web_tests/wpt_internal/dom/abort/resources/run-async-gc.js
@@ -1,9 +1,11 @@
-async function runAsyncGC() {
+async function runAsyncGC(args = {}) {
   // Run gc in a loop to ensure anything needing more than one cycle can be
   // collected, e.g. due to dependencies. Note this is similar to
   // ThreadState::CollectAllGarbageForTesting, but async and with 2 less
   // iterations.
   for (let i = 0; i < 3; i++) {
-    await gc({type: 'major', execution: 'async', flavor: 'last-resort'});
+    // crbug.com/1474629: invoking gc({execution: 'async'}) trips leak
+    // detection, so use postTask and run sync gc() to do async GC.
+    await scheduler.postTask(() => { gc(); }, args);
   }
 }
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 1abc236..32e2e5c 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 1abc236bbd98af59cac64b471b3ea6ab900350db
+Subproject commit 32e2e5c8d0343434cf154f2b928775c2d95ad8c5
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index b92185a..acc4bb2 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit b92185a4c9aba77a6cbbc8d8aa65820e8be5dae7
+Subproject commit acc4bb2e6ba2063e1171c81951abd331aa9cf068
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 5ccdd75..e13f347 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 5ccdd75fa57a758fa15db394dd6ad6092963cfed
+Subproject commit e13f34726b11afcb77bebca0e6a79c08ba3fd2c6
diff --git a/third_party/perfetto b/third_party/perfetto
index 609cb8e..1553701 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit 609cb8ef0288f4e1667b1034dc53ad319d43ca99
+Subproject commit 1553701a9f0a47be12af882f0d7801df5b674122
diff --git a/third_party/re2/src b/third_party/re2/src
index f9550c3f..2d866a3 160000
--- a/third_party/re2/src
+++ b/third_party/re2/src
@@ -1 +1 @@
-Subproject commit f9550c3f7207f946a45bbccd1814b12b136aae72
+Subproject commit 2d866a3d0753f4f4fce93cccc6c59c4b052d7db4
diff --git a/third_party/skia b/third_party/skia
index ef2511b..081ba94 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit ef2511b0a6f21b4bb43414ac0571d755791ae22a
+Subproject commit 081ba94858f6ee93b518e1a4107f81cbf7a224cb
diff --git a/third_party/webrtc b/third_party/webrtc
index c935bb2..16ac10d 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit c935bb2141e2e616e1f9e1df4568d976ad1828c0
+Subproject commit 16ac10d9f75cde959f00df062f544c49941882da
diff --git a/tools/codeql/queries/bad_message_no_return.ql b/tools/codeql/queries/bad_message_no_return.ql
index eb859046..168c776 100644
--- a/tools/codeql/queries/bad_message_no_return.ql
+++ b/tools/codeql/queries/bad_message_no_return.ql
@@ -4,6 +4,7 @@
 
 import cpp
 import lib.Chromium
+import lib.CommonPatterns
 
 /**
  * @name potential ReceivedBadMessage call without return.
@@ -25,27 +26,9 @@
   }
 }
 
-from BadMessageCall call, Function f
+from BadMessageCall call
 where
   Chromium::isChromiumCode(call) and
-
-  call.getEnclosingFunction() = f and
-
-  // Ignore any calls with a returnStatement in the same enclosing block.
-  not exists(ReturnStmt returnStmt |
-    call.getEnclosingBlock() = returnStmt.getEnclosingBlock()
-  )  and
-
-  // Ignore any calls with a returnStatement immediately after in the block
-  exists(Stmt stmtAfterCall |
-    stmtAfterCall.getEnclosingFunction() = f and
-    stmtAfterCall.getLocation().getStartLine() > call.getLocation().getStartLine() and
-    not stmtAfterCall instanceof ReturnStmt and
-    not exists(ReturnStmt returnBetween |
-      returnBetween.getEnclosingFunction() = f and
-      returnBetween.getLocation().getStartLine() > call.getLocation().getStartLine() and
-      returnBetween.getLocation().getStartLine() < stmtAfterCall.getLocation().getStartLine()
-    )
-  )
+  CommonPatterns::isCallNotFollowedByReturn(call)
 select call,
   call.getLocation().getFile().getRelativePath() + ":" + call.getLocation().getStartLine().toString()
diff --git a/tools/codeql/queries/blink_exception_no_return.ql b/tools/codeql/queries/blink_exception_no_return.ql
index 05c739f6..052b789 100644
--- a/tools/codeql/queries/blink_exception_no_return.ql
+++ b/tools/codeql/queries/blink_exception_no_return.ql
@@ -4,6 +4,7 @@
 
 import cpp
 import lib.Chromium
+import lib.CommonPatterns
 
 /**
  * @name Throw*Exception call without return.
@@ -26,24 +27,6 @@
 from ThrowExceptionCall call, Function f
 where
   Chromium::isBlinkCode(call) and
-
-  call.getEnclosingFunction() = f and
-
-  // Ignore any calls with a returnStatement in the same enclosing block.
-  not exists(ReturnStmt returnStmt |
-    call.getEnclosingBlock() = returnStmt.getEnclosingBlock()
-  )  and
-
-  // Ignore any calls with a returnStatement immediately after in the block.
-  exists(Stmt stmtAfterCall |
-    stmtAfterCall.getEnclosingFunction() = f and
-    stmtAfterCall.getLocation().getStartLine() > call.getLocation().getStartLine() and
-    not stmtAfterCall instanceof ReturnStmt and
-    not exists(ReturnStmt returnBetween |
-      returnBetween.getEnclosingFunction() = f and
-      returnBetween.getLocation().getStartLine() > call.getLocation().getStartLine() and
-      returnBetween.getLocation().getStartLine() < stmtAfterCall.getLocation().getStartLine()
-    )
-  )
+  CommonPatterns::isCallNotFollowedByReturn(call)
 select call,
   call.getLocation().getFile().getRelativePath() + ":" + call.getLocation().getStartLine().toString()
diff --git a/tools/codeql/queries/lib/CommonPatterns.qll b/tools/codeql/queries/lib/CommonPatterns.qll
new file mode 100644
index 0000000..4a91081
--- /dev/null
+++ b/tools/codeql/queries/lib/CommonPatterns.qll
@@ -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 cpp
+
+module CommonPatterns {
+  /**
+  * Predicate to check if a function call is not followed by a return statement
+  * within the same or immediately after in any block.
+  */
+  pragma[inline]
+  predicate isCallNotFollowedByReturn(FunctionCall call) {
+    exists(Function f |
+      call.getEnclosingFunction() = f and
+
+      // Ignore any calls with a returnStatement in the same enclosing block.
+      not exists(ReturnStmt returnStmt |
+        call.getEnclosingBlock() = returnStmt.getEnclosingBlock()
+      ) and
+
+      // Ignore any calls with a returnStatement immediately after in the block.
+      exists(Stmt stmtAfterCall |
+        stmtAfterCall.getEnclosingFunction() = f and
+        stmtAfterCall.getLocation().getStartLine() > call.getLocation().getStartLine() and
+        not stmtAfterCall instanceof ReturnStmt and
+        not exists(ReturnStmt returnBetween |
+          returnBetween.getEnclosingFunction() = f and
+          returnBetween.getLocation().getStartLine() > call.getLocation().getStartLine() and
+          returnBetween.getLocation().getStartLine() < stmtAfterCall.getLocation().getStartLine()
+        )
+      )
+    )
+  }
+}
diff --git a/tools/codeql/queries/mojo_reportbadmessage_no_return.ql b/tools/codeql/queries/mojo_reportbadmessage_no_return.ql
index 2c924e6..1eb6ad7 100644
--- a/tools/codeql/queries/mojo_reportbadmessage_no_return.ql
+++ b/tools/codeql/queries/mojo_reportbadmessage_no_return.ql
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 import cpp
 import lib.Chromium
+import lib.CommonPatterns
 
 /**
  * @name potential ReportBadMessage call without return.
@@ -21,23 +22,7 @@
 from ReportBadMessageCall call, Function f
 where
   Chromium::isChromiumCode(call) and
-
-  call.getEnclosingFunction() = f and
-
-  // Ignore any calls with a returnStatement in the same enclosing block.
-  not exists(ReturnStmt returnStmt | call.getEnclosingBlock() = returnStmt.getEnclosingBlock()) and
-
-  // Ignore any calls with a returnStatement immediately after in the block
-  exists(Stmt stmtAfterCall |
-    stmtAfterCall.getEnclosingFunction() = f and
-    stmtAfterCall.getLocation().getStartLine() > call.getLocation().getStartLine() and
-    not stmtAfterCall instanceof ReturnStmt and
-    not exists(ReturnStmt returnBetween |
-      returnBetween.getEnclosingFunction() = f and
-      returnBetween.getLocation().getStartLine() > call.getLocation().getStartLine() and
-      returnBetween.getLocation().getStartLine() < stmtAfterCall.getLocation().getStartLine()
-    )
-  )
+  CommonPatterns::isCallNotFollowedByReturn(call)
 select call,
   call.getLocation().getFile().getRelativePath() + ":" +
     call.getLocation().getStartLine().toString()
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index c433cb72..bbae028 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -219,19 +219,14 @@
   "chrome/browser/resources/chromeos/app_icon/app_icon_resources.grd": {
     "structures": [2800],
   },
-  "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/login/oobe_conditional_resources.grd": {
-    "META": {"sizes": {"includes": [150], "structures": [300]}},
+  "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/login/resources.grd": {
+    "META": {"sizes": {"includes": [300],}},
     "includes": [2820],
-    "structures": [2840],
   },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/lock_screen_reauth/resources.grd": {
     "META": {"sizes": {"includes": [30]}},
     "includes": [2860],
   },
-  "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/login/oobe_unconditional_resources.grd": {
-    "META": {"sizes": {"includes": [350]}},
-    "includes": [2880],
-  },
   "<(SHARED_INTERMEDIATE_DIR)/chrome/browser/resources/chromeos/multidevice_internals/resources.grd": {
     "META": {"sizes": {"includes": [35]}},
     "includes": [2900],
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 1141c5d..7dcc10d 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -21055,6 +21055,23 @@
   </description>
 </action>
 
+<action name="MobileFullscreenEntered">
+  <owner>ajuma@chromium.org</owner>
+  <owner>alionadangla@chromium.org</owner>
+  <description>
+    Reported when user scrolled to enter in fullscreen mode. iOS only.
+  </description>
+</action>
+
+<action name="MobileFullscreenExited">
+  <owner>ajuma@chromium.org</owner>
+  <owner>alionadangla@chromium.org</owner>
+  <description>
+    Reported when user scrolled or tapped on the toolbars to exit fullscreen
+    mode. iOS only.
+  </description>
+</action>
+
 <action name="MobileFullscreenExitedManually">
   <owner>ajuma@chromium.org</owner>
   <owner>alionadangla@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index 00f18f3..649ecc1 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -2457,9 +2457,9 @@
 
     This metric is only recorded if a suggestion for the {AutofillFormType} was
     shown, accepted, and the suggestion was filled successfully. This is
-    recorded once per form. It is reported as &quot;User chose to fill&quot;
-    once. It does not matter if the user clears the filled values nor if the
-    user accepts a different suggestion later.
+    recorded once per navigation. It is reported as &quot;User chose to
+    fill&quot; once. It does not matter if the user clears the filled values nor
+    if the user accepts a different suggestion later.
 
     If the accepted suggestion is not filled (e.g., if it required
     authentication and the user cancelled or failed that step), then this metric
@@ -2492,6 +2492,30 @@
   <token key="AutofillFormType" variants="AutofillFormType"/>
 </histogram>
 
+<histogram
+    name="Autofill.Funnel.NotClassifiedAsTargetFilling.FillAfterSuggestion{AutofillFormType}"
+    enum="BooleanAutofillFillAfterSuggestion" expires_after="2025-01-20">
+  <owner>brunobraga@google.com</owner>
+  <owner>chrome-autofill-team@google.com</owner>
+  <summary>
+    Counts whether users accepted any autofill suggestion that was shown to them
+    for a given form. This metric is conditioned to those suggestions that were
+    triggered via the context menu on a field that does not match the target
+    filling product.
+
+    This metric is only recorded if a suggestion for the {AutofillFormType} was
+    shown, accepted, and the suggestion was filled successfully. This is
+    recorded once per navigation. It is reported as &quot;User chose to
+    fill&quot; once. It does not matter if the user clears the filled values nor
+    if the user accepts a different suggestion later.
+
+    If the accepted suggestion is not filled (e.g., if it required
+    authentication and the user cancelled or failed that step), then this metric
+    is not recorded.
+  </summary>
+  <token key="AutofillFormType" variants="AutofillFormType"/>
+</histogram>
+
 <histogram name="Autofill.Funnel.ParsedAsType{AutofillFormType}"
     enum="BooleanAutofillParsedAsType" expires_after="2024-12-12">
   <owner>battre@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/network/histograms.xml b/tools/metrics/histograms/metadata/network/histograms.xml
index aafcc04..507aeaf7 100644
--- a/tools/metrics/histograms/metadata/network/histograms.xml
+++ b/tools/metrics/histograms/metadata/network/histograms.xml
@@ -4586,6 +4586,32 @@
   </summary>
 </histogram>
 
+<histogram name="NetworkService.IpProtection.TokenBatchGenerationTime"
+    units="ms" expires_after="2024-08-22">
+  <owner>ashleynewson@chromium.org</owner>
+  <owner>src/android_webview/OWNERS</owner>
+  <owner>src/chrome/browser/ip_protection/OWNERS</owner>
+  <summary>
+    Records the time taken for a successful attempt to generate auth tokens.
+    This only measures the time across a single attempt (not across retries,
+    which may be delayed by a variable backoff).
+
+    This measures the whole token batch generation process, from an
+    IpProtectionTokenCacheManagerImpl's perspective, from just before calling
+    IpProtectionConfigGetter::TryGetAuthTokens until OnGotAuthTokens. Note that
+    if OnGotAuthTokens receives a non-nullopt but empty vector of tokens this is
+    considered a success by this metric.
+
+    Note that if multiple token caches exist (one for each proxy layer), the
+    attempts in each token cache are timed independently, but they will all feed
+    into the same histogram.
+
+    This histogram will only be emitted if the MaskedDomainList and
+    EnableIpPrivacyProxy features are enabled. Some kind of platform-dependent
+    signin is also required.
+  </summary>
+</histogram>
+
 <histogram name="NetworkService.IpProtection.TokenBatchRequestTime" units="ms"
     expires_after="2024-07-28">
   <owner>djmitche@chromium.org</owner>
@@ -4593,6 +4619,11 @@
   <summary>
     Records the elapsed time for successful requests by IpProtectionConfigGetter
     for blind-signed tokens from BSA.
+
+    This metric only measures part of the Chrome-specific blind-signing
+    implementation and does not encompass the full token batch generation
+    process. See NetworkService.IpProtection.TokenBatchGenerationTime for a
+    generic measurement of the full token batch generation process.
   </summary>
 </histogram>
 
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 9b183c7..029cc2cf 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -392,7 +392,7 @@
 </histogram>
 
 <histogram name="PasswordGeneration.SubmissionAvailableEvent"
-    enum="PasswordSubmissionEvent" expires_after="M125">
+    enum="PasswordSubmissionEvent" expires_after="M131">
   <owner>kazinova@google.com</owner>
   <owner>shaikhitdin@google.com</owner>
   <owner>chrome-password-manager-metrics-alerts@google.com</owner>
@@ -902,7 +902,7 @@
 </histogram>
 
 <histogram name="PasswordManager.BubbleSuppression.AccountsInStatisticsTable2"
-    units="accounts" expires_after="M125">
+    units="accounts" expires_after="M131">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -1526,7 +1526,7 @@
 </histogram>
 
 <histogram name="PasswordManager.HttpPasswordMigrationMode2"
-    enum="HttpPasswordMigrationMode" expires_after="2024-03-31">
+    enum="HttpPasswordMigrationMode" expires_after="2024-09-30">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -1790,7 +1790,7 @@
 </histogram>
 
 <histogram name="PasswordManager.JavaScriptOnlyValueInSubmittedForm"
-    enum="JavaScriptOnlyValueInPasswordForm" expires_after="2024-03-31">
+    enum="JavaScriptOnlyValueInPasswordForm" expires_after="2024-09-30">
   <owner>kazinova@google.com</owner>
   <owner>battre@chromium.org</owner>
   <summary>
@@ -2703,7 +2703,7 @@
 </histogram>
 
 <histogram name="PasswordManager.PasswordStore.TimesAttemptedToReenrollInUPM"
-    units="Times" expires_after="2024-03-31">
+    units="Times" expires_after="2024-09-30">
   <owner>kazinova@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -3513,7 +3513,7 @@
 </histogram>
 
 <histogram name="PasswordManager.StoreDecryptionResult"
-    enum="PasswordDecryptionResult" expires_after="2024-03-30">
+    enum="PasswordDecryptionResult" expires_after="2024-09-30">
   <owner>mamir@chromium.org</owner>
   <owner>kazinova@google.com</owner>
   <summary>
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
index 365f822..26dbc16 100644
--- a/ui/accessibility/ax_event_generator.cc
+++ b/ui/accessibility/ax_event_generator.cc
@@ -324,7 +324,8 @@
                                      ax::mojom::Role old_role,
                                      ax::mojom::Role new_role) {
   DCHECK_EQ(tree_, tree);
-  AddEvent(node, Event::ROLE_CHANGED);
+  AddEvent(node, new_role == ax::mojom::Role::kAlert ? Event::ALERT
+                                                     : Event::ROLE_CHANGED);
 }
 
 void AXEventGenerator::OnIgnoredChanged(AXTree* tree,
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index 868cb75..e5b15504 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -1482,7 +1482,7 @@
 }
 
 bool AXNode::IsGenerated() const {
-  bool is_generated_node = id() < 0;
+  bool is_generated_node = id() < 0 && id() > kInitialEmptyDocumentRootNodeID;
 #if DCHECK_IS_ON()
   // Currently, the only generated nodes are columns and table header
   // containers, and when those roles occur, they are always extra mac nodes.
diff --git a/ui/accessibility/ax_node_id_forward.h b/ui/accessibility/ax_node_id_forward.h
index 0bca0b1d3..10966350 100644
--- a/ui/accessibility/ax_node_id_forward.h
+++ b/ui/accessibility/ax_node_id_forward.h
@@ -5,6 +5,7 @@
 #ifndef UI_ACCESSIBILITY_AX_NODE_ID_FORWARD_H_
 #define UI_ACCESSIBILITY_AX_NODE_ID_FORWARD_H_
 
+#include <limits.h>
 #include <stdint.h>
 
 namespace ui {
@@ -14,7 +15,20 @@
 
 // If a node is not yet or no longer valid, its ID should have a value of
 // kInvalidAXNodeID.
+// Note: positive AXNodeIDs match DOM node ids generated by Blin. They are used
+// for objects that contain that matching DOM node.
 static constexpr AXNodeID kInvalidAXNodeID = 0;
+// The browser needs to generate an AXNodeID for objects that it inserts in
+// the hierarchy, such as "extra mac nodes"
+static constexpr AXNodeID kFirstGeneratedBrowserNodeID = -1;
+static constexpr AXNodeID kLastGeneratedBrowserNodeID = -999999999;
+// A new AXTree is constructed with an empty document. The root must have a
+// unique AXNodeID that will not match any other id.
+static constexpr AXNodeID kInitialEmptyDocumentRootNodeID = -1000000000;
+// The renderer needs to generate an AXNodeID for objects that don't have a DOM
+// node. These start with this value and subsequent ids each subtract 1.
+static constexpr AXNodeID kFirstGeneratedRendererNodeID = -1000000001;
+static constexpr AXNodeID kLastGeneratedRendererNodeID = INT_MIN;
 
 }  // namespace ui
 
diff --git a/ui/accessibility/ax_tree.cc b/ui/accessibility/ax_tree.cc
index e6c4557..d60ca025 100644
--- a/ui/accessibility/ax_tree.cc
+++ b/ui/accessibility/ax_tree.cc
@@ -1983,7 +1983,8 @@
   for (AXTreeObserver& observer : observers_)
     observer.OnNodeDataChanged(this, old_data, new_data);
 
-  if (old_data.role != new_data.role) {
+  if (old_data.role != new_data.role && !old_data.IsInvisibleOrIgnored() &&
+      !new_data.IsInvisibleOrIgnored()) {
     for (AXTreeObserver& observer : observers_)
       observer.OnRoleChanged(this, node, old_data.role, new_data.role);
   }
diff --git a/ui/accessibility/ax_tree_serializer.h b/ui/accessibility/ax_tree_serializer.h
index 8882088..a4a4894 100644
--- a/ui/accessibility/ax_tree_serializer.h
+++ b/ui/accessibility/ax_tree_serializer.h
@@ -111,7 +111,10 @@
   // Invalidate the subtree rooted at this node, ensuring that the entire
   // subtree is re-serialized the next time any of those nodes end up
   // being serialized.
-  void MarkSubtreeDirty(AXNodeID node);
+  void MarkSubtreeDirty(AXNodeID id);
+
+  // Invalidate a single node, ensuring that it is reserialized.
+  void MarkNodeDirty(AXNodeID id);
 
   // Return whether or not this node is in the client tree. If you call
   // this immediately after serializing, this indicates whether a given
@@ -603,11 +606,19 @@
 }
 
 template <typename AXSourceNode, typename AXSourceNodeVectorType>
+void AXTreeSerializer<AXSourceNode, AXSourceNodeVectorType>::MarkNodeDirty(
+    AXNodeID id) {
+  if (ClientTreeNode* client_node = ClientTreeNodeById(id)) {
+    client_node->is_dirty = true;
+  }
+}
+
+template <typename AXSourceNode, typename AXSourceNodeVectorType>
 void AXTreeSerializer<AXSourceNode, AXSourceNodeVectorType>::MarkSubtreeDirty(
     AXNodeID id) {
-  ClientTreeNode* client_node = ClientTreeNodeById(id);
-  if (client_node)
+  if (ClientTreeNode* client_node = ClientTreeNodeById(id)) {
     MarkClientSubtreeDirty(client_node);
+  }
 }
 
 template <typename AXSourceNode, typename AXSourceNodeVectorType>
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index bffdc2b7..f32d79c 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -2458,12 +2458,19 @@
 }
 
 void AXPlatformNodeAuraLinux::SetDocumentParentOnFrameIfNecessary() {
-  if (GetAtkRole() != ATK_ROLE_DOCUMENT_WEB)
+  if (GetRole() != ax::mojom::Role::kRootWebArea) {
     return;
+  }
 
   if (!GetDelegate()->IsWebContent())
     return;
 
+  // If there is a parent, then this is not the root document.
+  if (GetDelegate()->node()->GetUnignoredParent()) {
+    return;
+  }
+
+  // Get the ATK parent, which will cross over into the UI hierarchy.
   AtkObject* parent_atk_object = GetParent();
   AXPlatformNodeAuraLinux* parent =
       AXPlatformNodeAuraLinux::FromAtkObject(parent_atk_object);
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 8f010d95..46f557f 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -2017,6 +2017,7 @@
   // convention here and when we fire events via
   // ::NotifyWinEvent().
   *id = -GetUniqueId();
+  DCHECK(*id < 0);
   return S_OK;
 }
 
diff --git a/ui/accessibility/platform/ax_unique_id.h b/ui/accessibility/platform/ax_unique_id.h
index 285de33..b9c9736 100644
--- a/ui/accessibility/platform/ax_unique_id.h
+++ b/ui/accessibility/platform/ax_unique_id.h
@@ -21,6 +21,8 @@
 //
 // These ids must not be conflated with the int id, that comes with web node
 // data, which are only unique within their source frame.
+// TODO(accessibility) We should be able to get rid of this, because node IDs
+// are actually unique within their own OS-level window.
 class COMPONENT_EXPORT(AX_PLATFORM) AXUniqueId {
  public:
   AXUniqueId();
diff --git a/ui/shell_dialogs/selected_file_info.cc b/ui/shell_dialogs/selected_file_info.cc
index 060aac6..fd59d72b 100644
--- a/ui/shell_dialogs/selected_file_info.cc
+++ b/ui/shell_dialogs/selected_file_info.cc
@@ -4,6 +4,7 @@
 
 #include "ui/shell_dialogs/selected_file_info.h"
 
+#include "base/containers/to_vector.h"
 #include "base/ranges/algorithm.h"
 
 namespace ui {
@@ -38,19 +39,13 @@
 
 std::vector<SelectedFileInfo> FilePathListToSelectedFileInfoList(
     const std::vector<base::FilePath>& paths) {
-  std::vector<SelectedFileInfo> selected_files;
-  for (const auto& path : paths) {
-    selected_files.emplace_back(path);
-  }
-  return selected_files;
+  return base::ToVector(
+      paths, [](const auto& path) { return SelectedFileInfo(path); });
 }
 
 std::vector<base::FilePath> SelectedFileInfoListToFilePathList(
     const std::vector<SelectedFileInfo>& files) {
-  std::vector<base::FilePath> paths;
-  base::ranges::transform(files, std::back_inserter(paths),
-                          &SelectedFileInfo::path);
-  return paths;
+  return base::ToVector(files, &SelectedFileInfo::path);
 }
 
 }  // namespace ui
diff --git a/v8 b/v8
index ea012d2..2c63d7b 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit ea012d232c66229b2d4f3f2a268aa999d668972b
+Subproject commit 2c63d7b6b700b0a2d257d4f4f19d2948c41e9b88