diff --git a/.eslintrc.js b/.eslintrc.js
index 78007a5d..d13be07 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -24,6 +24,7 @@
     'new-parens': 'error',
     'no-array-constructor': 'error',
     'no-console': ['error', {allow: ['info', 'warn', 'error', 'assert']}],
+    'no-debugger': 'error',
     'no-extra-boolean-cast': 'error',
     'no-extra-semi': 'error',
     'no-new-wrappers': 'error',
diff --git a/DEPS b/DEPS
index d444851..3af41afc 100644
--- a/DEPS
+++ b/DEPS
@@ -308,11 +308,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'src_internal_revision': '6365d276cb6ee8b2d6992bcc8699f3e1eff7fe3b',
+  'src_internal_revision': '5fb766e4a38b8e77fc63d347b829a1903c23a919',
   # 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': 'b159229f2174800f6655f7b7dbba01d7bd3d5d48',
+  'skia_revision': '50ac1117f1597d57faf9ae5360ad95029c515f97',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -320,7 +320,7 @@
   # 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': '49e434dba2f98a749fe36d44137cee2b8f039800',
+  'angle_revision': '6557da03c85eee30448e1fefc2d89bdc348c580d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # 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': '8e6c915f32a6c98fe6968eace7a2a9a66589a892',
+  'devtools_frontend_revision': '4e11c8801f7a4e8e9f7b94f6acd4155cbe0a5e57',
   # 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.
@@ -427,7 +427,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '413dfc05c2ba4c5a5d8629b440b17ab9b3ed5c5d',
+  'dawn_revision': 'b5d89266d090eb586b756294ea09e4beb0c06bcb',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -827,7 +827,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '7fd047c41ca4c77672258cc2644b116844100171',
+    '8b6ec119f001eb5ad89d77c153fa132bd65801c0',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -982,7 +982,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'IMaWheUQ-GmELoiuWqPaUhQt_A4P2dHCf_Wzxa-ULxoC',
+          'version': '2mvgH2wtdOI49Je4ZitkDbIq6HHd25YrHrJ-RqzrS0cC',
       },
     ],
     'condition': 'checkout_android',
@@ -1198,7 +1198,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' + '@' + 'b4ea518eb65f83a0f5c4e8f9625b099ec18c07d8',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + '120296784c3ee38e176618378479e2d63ab4deaf',
     'condition': 'checkout_src_internal',
   },
 
@@ -1811,7 +1811,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@35d6b77d10f523580acf18702b28efc2709bc1d2',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@ea0fb515f594700d7fc4cbfff49268f596de4984',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21',
@@ -1848,10 +1848,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'f4bf599a8b575df685c31d9c4729a70a04e377ed',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '11efd3b4ad23b66ed7aa88e84193833dbe5a7150',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '3f94329188723ae92fc1bdefbcacd659fed2aa8b',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '181dbebba43f9023754d1f4766e7e4a2516c53a4',
+    Var('webrtc_git') + '/src.git' + '@' + 'd86c0cdbde7261deefc5771f41a14186bac9fd09',
 
   # 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.
@@ -1963,7 +1963,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/eche_app/app',
-        'version': 'adlsj1Xru68irZ1xlmnbIEtZ4jaFfqbhmuDXMhGNpCQC',
+        'version': '58FAaGZeYdOt_nscq4CI8EYcnleAGq_UoFn7C_FWgZUC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1985,7 +1985,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/media_app/app',
-        'version': '-E_2mIUEbbKyJWZzHVRIuanUBTuj_qtcDBs4y87tagkC',
+        'version': 'W7KDA1fDp311XqHX9sUTPggIOaay2dmUlX3p3W8YNVIC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4125,7 +4125,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        'f758ded9acac8b0e8eb6f1638b8fb945c15da69d',
+        'ce28903ff84c8484e00400f18816c7dac205a29b',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 65d88bc..709bcf5 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2864,8 +2864,6 @@
     "wm/splitview/split_view_controller.h",
     "wm/splitview/split_view_divider.cc",
     "wm/splitview/split_view_divider.h",
-    "wm/splitview/split_view_divider_handler_view.cc",
-    "wm/splitview/split_view_divider_handler_view.h",
     "wm/splitview/split_view_divider_view.cc",
     "wm/splitview/split_view_divider_view.h",
     "wm/splitview/split_view_drag_indicators.cc",
diff --git a/ash/accelerators/debug_commands.cc b/ash/accelerators/debug_commands.cc
index 7625b5c..ce64251 100644
--- a/ash/accelerators/debug_commands.cc
+++ b/ash/accelerators/debug_commands.cc
@@ -252,10 +252,8 @@
 }
 
 void HandleShowInformedRestore() {
-  if (features::IsForestFeatureEnabled()) {
-    Shell::Get()
-        ->pine_controller()
-        ->MaybeStartPineOverviewSessionDevAccelerator();
+  if (auto* pine_controller = Shell::Get()->pine_controller()) {
+    pine_controller->MaybeStartPineOverviewSessionDevAccelerator();
   }
 }
 
diff --git a/ash/capture_mode/capture_mode_pixeltest.cc b/ash/capture_mode/capture_mode_pixeltest.cc
index 001e458f..8746fa4 100644
--- a/ash/capture_mode/capture_mode_pixeltest.cc
+++ b/ash/capture_mode/capture_mode_pixeltest.cc
@@ -132,7 +132,7 @@
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       base::StrCat({"screen_capture_popup_notification_",
                     GetDisplayTypeName(GetDisplayType())}),
-      /*revision_number=*/0,
+      /*revision_number=*/1,
       test_api()->GetPopupViewForId(kScreenCaptureNotificationId)));
 }
 
@@ -164,7 +164,7 @@
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       base::StrCat({"video_capture_notification_popup_",
                     GetDisplayTypeName(GetDisplayType())}),
-      /*revision_number=*/3, notification_popup_view));
+      /*revision_number=*/4, notification_popup_view));
 
   test_api()->ToggleBubble();
   auto* notification_view =
@@ -172,7 +172,7 @@
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       base::StrCat({"video_capture_notification_view_",
                     GetDisplayTypeName(GetDisplayType())}),
-      /*revision_number=*/3, notification_view));
+      /*revision_number=*/4, notification_view));
 }
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index 07c1925..d0232b13 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -4450,11 +4450,11 @@
     controller->PerformCapture();
     waiter.Wait();
   }
-  // Click on the notification body. This should take us to the files app.
+  // Click on the notification body. This should open the default handler.
   ClickOnNotification(std::nullopt);
   EXPECT_FALSE(GetPreviewNotification());
   histogram_tester.ExpectBucketCount(kQuickActionHistogramName,
-                                     CaptureQuickAction::kFiles, 1);
+                                     CaptureQuickAction::kOpenDefault, 1);
 
   controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                    CaptureModeType::kImage);
diff --git a/ash/components/arc/appfuse/OWNERS b/ash/components/arc/appfuse/OWNERS
index e1627725..4228552 100644
--- a/ash/components/arc/appfuse/OWNERS
+++ b/ash/components/arc/appfuse/OWNERS
@@ -1 +1,2 @@
-hashimoto@chromium.org
+youkichihosoi@chromium.org
+momohatt@chromium.org
diff --git a/ash/components/arc/disk_quota/OWNERS b/ash/components/arc/disk_quota/OWNERS
index e1627725..631b9d62 100644
--- a/ash/components/arc/disk_quota/OWNERS
+++ b/ash/components/arc/disk_quota/OWNERS
@@ -1 +1,2 @@
-hashimoto@chromium.org
+momohatt@chromium.org
+youkichihosoi@chromium.org
diff --git a/ash/components/arc/obb_mounter/OWNERS b/ash/components/arc/obb_mounter/OWNERS
index e1627725..4228552 100644
--- a/ash/components/arc/obb_mounter/OWNERS
+++ b/ash/components/arc/obb_mounter/OWNERS
@@ -1 +1,2 @@
-hashimoto@chromium.org
+youkichihosoi@chromium.org
+momohatt@chromium.org
diff --git a/ash/components/arc/volume_mounter/OWNERS b/ash/components/arc/volume_mounter/OWNERS
index 09e9f02..cfe73aa31 100644
--- a/ash/components/arc/volume_mounter/OWNERS
+++ b/ash/components/arc/volume_mounter/OWNERS
@@ -1,7 +1,5 @@
 youkichihosoi@chromium.org
-
-# Backup reviewers:
-hashimoto@chromium.org
+momohatt@chromium.org
 
 per-file *_mojom_traits*.*=set noparent
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index a0d5d891..a859a5a 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -1150,7 +1150,7 @@
 // Enable the new notifications for downloaded files and screen captures.
 BASE_FEATURE(kFileNotificationRevamp,
              "kFileNotificationRevamp",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 // Enables experimental UI features in Files app.
 BASE_FEATURE(kFilesAppExperimental,
@@ -3577,11 +3577,6 @@
   return base::FeatureList::IsEnabled(kForceReSyncDrive);
 }
 
-bool IsForestFeatureEnabled() {
-  return base::FeatureList::IsEnabled(kForestFeature) &&
-         switches::IsForestSecretKeyMatched();
-}
-
 bool IsFullscreenAfterUnlockAllowed() {
   return base::FeatureList::IsEnabled(kFullscreenAfterUnlockAllowed);
 }
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index 66d18b4..fc2fe108 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -1046,7 +1046,6 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool ShouldForceEnableServerSideSpeechRecognitionForDev();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsForceReSyncDriveEnabled();
-COMPONENT_EXPORT(ASH_CONSTANTS) bool IsForestFeatureEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheLauncherEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsEcheLauncherListViewEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
diff --git a/ash/picker/views/picker_image_item_view.cc b/ash/picker/views/picker_image_item_view.cc
index 07d0fa15..f7cbfee2 100644
--- a/ash/picker/views/picker_image_item_view.cc
+++ b/ash/picker/views/picker_image_item_view.cc
@@ -23,7 +23,8 @@
 PickerImageItemView::PickerImageItemView(
     SelectItemCallback select_item_callback,
     std::unique_ptr<views::ImageView> image)
-    : PickerItemView(std::move(select_item_callback)) {
+    : PickerItemView(std::move(select_item_callback),
+                     FocusIndicatorStyle::kFocusRingWithInsetGap) {
   SetUseDefaultFillLayout(true);
   SetCornerRadius(kPickerImageItemCornerRadius);
 
diff --git a/ash/picker/views/picker_item_view.cc b/ash/picker/views/picker_item_view.cc
index 21b2709..7ae99e6 100644
--- a/ash/picker/views/picker_item_view.cc
+++ b/ash/picker/views/picker_item_view.cc
@@ -19,9 +19,11 @@
 #include "ui/color/color_provider.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/point.h"
+#include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_host.h"
+#include "ui/gfx/geometry/skia_conversions.h"
 #include "ui/views/background.h"
 #include "ui/views/controls/button/button.h"
 #include "ui/views/controls/focus_ring.h"
@@ -30,6 +32,13 @@
 namespace ash {
 namespace {
 
+// How much to clip the focused PickerItemView by. Inset by at least the default
+// focus ring inset so the clipping is actually visible, then clip by the actual
+// desired amount. It would be better to absolute value the halo inset here, but
+// to keep this constexpr, also multiply by -1 to keep it positive.
+constexpr float kPseudoFocusClipInset =
+    views::FocusRing::kDefaultHaloInset * -1.0f + 2.0f;
+
 constexpr auto kPickerItemFocusIndicatorMargins = gfx::Insets::VH(6, 0);
 
 std::unique_ptr<views::Background> GetPickerItemBackground(
@@ -58,6 +67,8 @@
       base::TimeDelta());
 
   switch (focus_indicator_style_) {
+    case FocusIndicatorStyle::kFocusRingWithInsetGap:
+      [[fallthrough]];
     case FocusIndicatorStyle::kFocusRing:
       views::FocusRing::Get(this)->SetHasFocusPredicate(
           base::BindRepeating([](const View* view) {
@@ -118,6 +129,10 @@
   }
 }
 
+void PickerItemView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
+  UpdateClipPathForFocusRingWithInsetGap();
+}
+
 void PickerItemView::SetCornerRadius(int corner_radius) {
   if (corner_radius_ == corner_radius) {
     return;
@@ -141,6 +156,9 @@
   item_state_ = item_state;
   SetBackground(GetPickerItemBackground(item_state_, corner_radius_));
   switch (focus_indicator_style_) {
+    case FocusIndicatorStyle::kFocusRingWithInsetGap:
+      UpdateClipPathForFocusRingWithInsetGap();
+      [[fallthrough]];
     case FocusIndicatorStyle::kFocusRing:
       views::FocusRing::Get(this)->SchedulePaint();
       break;
@@ -150,6 +168,22 @@
   }
 }
 
+void PickerItemView::UpdateClipPathForFocusRingWithInsetGap() {
+  if (focus_indicator_style_ != FocusIndicatorStyle::kFocusRingWithInsetGap) {
+    return;
+  }
+
+  SkPath clip_path;
+  if (item_state_ == ItemState::kPseudoFocused) {
+    gfx::RectF inset_bounds(GetLocalBounds());
+    const SkScalar radius =
+        SkIntToScalar(corner_radius_ - kPseudoFocusClipInset);
+    inset_bounds.Inset(kPseudoFocusClipInset);
+    clip_path.addRoundRect(gfx::RectFToSkRect(inset_bounds), radius, radius);
+  }
+  SetClipPath(clip_path);
+}
+
 BEGIN_METADATA(PickerItemView)
 END_METADATA
 
diff --git a/ash/picker/views/picker_item_view.h b/ash/picker/views/picker_item_view.h
index 7fa21fb..8d525dd 100644
--- a/ash/picker/views/picker_item_view.h
+++ b/ash/picker/views/picker_item_view.h
@@ -36,6 +36,9 @@
   enum class FocusIndicatorStyle {
     // Indicate focus using a rounded rectangular ring around the item.
     kFocusRing,
+    // Similar to `kFocusRing`, but clips the PickerItemView with a 1dp border
+    // as well as adding a rounded rectangular ring.
+    kFocusRingWithInsetGap,
     // Indicate focus using a vertical bar with half rounded corners at the left
     // edge of the item.
     kFocusBar,
@@ -56,6 +59,7 @@
   void PaintButtonContents(gfx::Canvas* canvas) override;
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;
+  void OnBoundsChanged(const gfx::Rect& previous_bounds) override;
 
   void SelectItem();
 
@@ -65,6 +69,8 @@
   void SetItemState(ItemState item_state);
 
  private:
+  void UpdateClipPathForFocusRingWithInsetGap();
+
   SelectItemCallback select_item_callback_;
 
   ItemState item_state_ = ItemState::kNormal;
diff --git a/ash/public/cpp/resources/ash_public_unscaled_resources.grd b/ash/public/cpp/resources/ash_public_unscaled_resources.grd
index bd7c6e1..230b359d 100644
--- a/ash/public/cpp/resources/ash_public_unscaled_resources.grd
+++ b/ash/public/cpp/resources/ash_public_unscaled_resources.grd
@@ -59,7 +59,7 @@
       <structure type="lottie" name="IDR_PINE_NUDGE_IMAGE_LM" file="unscaled_resources/pine_nudge_lm.json" compress="gzip" />
       <structure type="lottie" name="IDR_PINE_ONBOARDING_IMAGE" file="unscaled_resources/pine_onboarding.json" compress="gzip" />
       <!-- Focus Mode -->
-      <structure type="lottie" name="IDR_FOCUS_MODE_EQUALIZER_LIGHT_ANIMATION" file="unscaled_resources/equalizer_light.json" compress="gzip" />
+      <structure type="lottie" name="IDR_FOCUS_MODE_EQUALIZER_ANIMATION" file="unscaled_resources/equalizer.json" compress="gzip" />
       <!-- Birch -->
       <structure type="lottie" name="IDR_BIRCH_RELEASE_NOTES_ICON" file="unscaled_resources/birch_release_notes_icon.json" compress="gzip" />
       <!-- Mahi -->
diff --git a/ash/public/cpp/resources/unscaled_resources/equalizer.json b/ash/public/cpp/resources/unscaled_resources/equalizer.json
new file mode 100644
index 0000000..b529c44
--- /dev/null
+++ b/ash/public/cpp/resources/unscaled_resources/equalizer.json
@@ -0,0 +1 @@
+{"v":"5.9.3","fr":60,"ip":709,"op":771,"w":20,"h":20,"nm":"equalizer light","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Adjustment Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[10,10.062,0],"ix":2,"l":2},"a":{"a":0,"k":[7,7,0],"ix":1,"l":2},"s":{"a":0,"k":[28,28,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":771,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"4","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.124,10.25,0],"ix":2,"l":2},"a":{"a":0,"k":[19.812,7.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":714,"s":[200,200,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":717,"s":[200,580,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":720,"s":[200,550,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":726,"s":[200,80,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":729,"s":[200,226,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,0.111,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":735,"s":[200,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":743,"s":[200,200,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":749,"s":[200,160,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,0,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":762,"s":[200,100,100]},{"t":771,"s":[200,200,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.828,0],[0,0],[0,-0.828],[0,0],[0.828,0],[0,0.828],[0,0]],"o":[[0,0],[0.828,0],[0,0],[0,0.828],[-0.828,0],[0,0],[0,-0.828]],"v":[[0.004,-2.542],[0.011,-2.543],[1.504,-1.042],[1.5,1.41],[0,2.91],[-1.5,1.41],[-1.496,-1.042]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"_CrOS_SecondaryColor","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[19.75,7.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1769,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"3","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[13.376,10.25,0],"ix":2,"l":2},"a":{"a":0,"k":[13.938,7.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":711,"s":[200,200,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":714,"s":[200,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":717,"s":[200,80,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":723,"s":[200,240,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":728,"s":[200,112,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,7.209,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":734,"s":[200,230,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":743,"s":[200,200,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":746,"s":[200,232,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":752,"s":[200,200,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":758,"s":[200,96,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,-24.869,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":761,"s":[200,192,100]},{"t":771,"s":[200,200,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.828,0],[0,0],[0,-0.828],[0,0],[0.828,0],[0,0.828],[0,0]],"o":[[0,0],[0.828,0],[0,0],[0,0.828],[-0.828,0],[0,0],[0,-0.828]],"v":[[0,-7],[0,-7],[1.5,-5.5],[1.5,5.5],[0,7],[-1.5,5.5],[-1.5,-5.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"_CrOS_SecondaryColor","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[13.75,7.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1769,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"2","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1.188,10.25,0],"ix":2,"l":2},"a":{"a":0,"k":[7.844,7.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":711,"s":[200,200,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,-18.66,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":734,"s":[200,190,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":743,"s":[200,200,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":746,"s":[200,300,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":755,"s":[200,80,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,-1.664,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":761,"s":[200,118,100]},{"t":771,"s":[200,200,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.828,0],[0,0],[0,-0.828],[0,0],[0.828,0],[0,0.828],[0,0]],"o":[[0,0],[0.828,0],[0,0],[0,0.828],[-0.828,0],[0,0],[0,-0.828]],"v":[[0,-4],[0,-4],[1.5,-2.5],[1.5,2.5],[0,4],[-1.5,2.5],[-1.5,-2.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"_CrOS_SecondaryColor","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[7.75,7.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1769,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"1","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[-10.812,10.25,0],"ix":2,"l":2},"a":{"a":0,"k":[1.844,7.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":709,"s":[200,200,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":712,"s":[200,160,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,0,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":734,"s":[200,66,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":743,"s":[200,200,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":752,"s":[200,80,100]},{"i":{"x":[0.833,0.833,0.833],"y":[1,0.833,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0.167,0]},"t":756,"s":[200,200,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,0.1,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":762,"s":[200,100,100]},{"t":771,"s":[200,200,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.828,0],[0,0],[0,-0.828],[0,0],[0.828,0],[0,0.828],[0,0]],"o":[[0,0],[0.828,0],[0,0],[0,0.828],[-0.828,0],[0,0],[0,-0.828]],"v":[[0,-6],[0,-6],[1.5,-4.5],[1.5,4.5],[0,6],[-1.5,4.5],[-1.5,-4.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"_CrOS_SecondaryColor","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[1.75,7.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1769,"st":0,"ct":1,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/ash/public/cpp/resources/unscaled_resources/equalizer_light.json b/ash/public/cpp/resources/unscaled_resources/equalizer_light.json
deleted file mode 100644
index cd28af2..0000000
--- a/ash/public/cpp/resources/unscaled_resources/equalizer_light.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.8.1","fr":60,"ip":709,"op":771,"w":21,"h":21,"nm":"equalizer light","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[19.562,12.125,0],"ix":2,"l":2},"a":{"a":0,"k":[19.812,7.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":714,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":717,"s":[100,290,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":720,"s":[100,275,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":726,"s":[100,40,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":729,"s":[100,113,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,0.111,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":735,"s":[100,50,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":743,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":749,"s":[100,80,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,0,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":762,"s":[100,50,100]},{"t":771,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.828,0],[0,0],[0,-0.828],[0,0],[0.828,0],[0,0.828],[0,0]],"o":[[0,0],[0.828,0],[0,0],[0,0.828],[-0.828,0],[0,0],[0,-0.828]],"v":[[0.004,-2.542],[0.011,-2.543],[1.504,-1.042],[1.5,1.41],[0,2.91],[-1.5,1.41],[-1.496,-1.042]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803986549,0.917647123337,0.929411828518,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"_CrOS_SecondaryColor","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[19.75,7.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1769,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[13.688,12.125,0],"ix":2,"l":2},"a":{"a":0,"k":[13.938,7.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":711,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":714,"s":[100,50,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":717,"s":[100,40,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":723,"s":[100,120,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":728,"s":[100,56,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,7.209,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":734,"s":[100,115,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":743,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":746,"s":[100,116,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":752,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":758,"s":[100,48,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,-24.869,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":761,"s":[100,96,100]},{"t":771,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.828,0],[0,0],[0,-0.828],[0,0],[0.828,0],[0,0.828],[0,0]],"o":[[0,0],[0.828,0],[0,0],[0,0.828],[-0.828,0],[0,0],[0,-0.828]],"v":[[0,-7],[0,-7],[1.5,-5.5],[1.5,5.5],[0,7],[-1.5,5.5],[-1.5,-5.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.917647063732,0.929411768913,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"_CrOS_SecondaryColor","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[13.75,7.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1769,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[7.594,12.125,0],"ix":2,"l":2},"a":{"a":0,"k":[7.844,7.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":711,"s":[100,100,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,-18.66,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":734,"s":[100,95,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":743,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":746,"s":[100,150,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":755,"s":[100,40,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,-1.664,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":761,"s":[100,59,100]},{"t":771,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.828,0],[0,0],[0,-0.828],[0,0],[0.828,0],[0,0.828],[0,0]],"o":[[0,0],[0.828,0],[0,0],[0,0.828],[-0.828,0],[0,0],[0,-0.828]],"v":[[0,-4],[0,-4],[1.5,-2.5],[1.5,2.5],[0,4],[-1.5,2.5],[-1.5,-2.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.917647063732,0.929411768913,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"_CrOS_SecondaryColor","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[7.75,7.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1769,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1.594,12.125,0],"ix":2,"l":2},"a":{"a":0,"k":[1.844,7.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":709,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":712,"s":[100,80,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,0,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":734,"s":[100,33,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":743,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":752,"s":[100,40,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":756,"s":[100,100,100]},{"i":{"x":[0.86,0.86,0.86],"y":[1,0.1,1]},"o":{"x":[0.66,0.66,0.66],"y":[0,0,0]},"t":762,"s":[100,50,100]},{"t":771,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.828,0],[0,0],[0,-0.828],[0,0],[0.828,0],[0,0.828],[0,0]],"o":[[0,0],[0.828,0],[0,0],[0,0.828],[-0.828,0],[0,0],[0,-0.828]],"v":[[0,-6],[0,-6],[1.5,-4.5],[1.5,4.5],[0,6],[-1.5,4.5],[-1.5,-4.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.909803926945,0.917647063732,0.929411768913,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"_CrOS_SecondaryColor","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[1.75,7.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1769,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc
index af0c4f34..bb09d1f 100644
--- a/ash/root_window_controller.cc
+++ b/ash/root_window_controller.cc
@@ -49,6 +49,7 @@
 #include "ash/touch/touch_hud_debug.h"
 #include "ash/touch/touch_hud_projection.h"
 #include "ash/touch/touch_observer_hud.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wallpaper/views/wallpaper_widget_controller.h"
 #include "ash/wm/always_on_top_controller.h"
 #include "ash/wm/bounds_tracker/window_bounds_tracker.h"
@@ -846,7 +847,7 @@
                                            ui::MenuSourceType source_type) {
   // Show birch bar context menu for the primary user in clamshell mode Overview
   // without a partial split screen.
-  if (features::IsForestFeatureEnabled() &&
+  if (IsForestFeatureEnabled() &&
       Shell::Get()->session_controller()->IsUserPrimary() &&
       OverviewController::Get()->InOverviewSession() &&
       !split_view_overview_session_) {
@@ -1214,7 +1215,7 @@
                   non_lock_screen_containers);
 
   aura::Window* shutdown_screenshot_container = non_lock_screen_containers;
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureFlagEnabled()) {
     shutdown_screenshot_container = CreateContainer(
         kShellWindowId_ShutdownScreenshotContainer,
         "ShutdownScreenshotContainer", non_lock_screen_containers);
diff --git a/ash/shelf/hotseat_widget.cc b/ash/shelf/hotseat_widget.cc
index 571d783b..bf1d8a9 100644
--- a/ash/shelf/hotseat_widget.cc
+++ b/ash/shelf/hotseat_widget.cc
@@ -499,6 +499,12 @@
 
   // The type of highlight border.
   views::HighlightBorder::Type border_type_;
+
+  // Tracks whether the forest flag was enabled when entering overview.
+  // TODO(sammiequon): This is temporary while the secret key exists. After the
+  // secret key is removed, entering/exiting overview should never need to
+  // remove/readd blur.
+  bool was_forest_on_overview_enter_ = false;
 };
 
 HotseatWidget::DelegateView::~DelegateView() {
@@ -716,7 +722,8 @@
 
 void HotseatWidget::DelegateView::OnOverviewModeWillStart() {
   // Forest uses background blur in overview.
-  if (IsForestFeatureEnabled()) {
+  was_forest_on_overview_enter_ = IsForestFeatureEnabled();
+  if (was_forest_on_overview_enter_) {
     return;
   }
   DCHECK_LE(blur_lock_, 2);
@@ -728,7 +735,8 @@
 void HotseatWidget::DelegateView::OnOverviewModeEndingAnimationComplete(
     bool canceled) {
   // Forest uses background blur in overview.
-  if (IsForestFeatureEnabled()) {
+  if (was_forest_on_overview_enter_) {
+    was_forest_on_overview_enter_ = false;
     return;
   }
   DCHECK_GT(blur_lock_, 0);
diff --git a/ash/shelf/shelf_widget.cc b/ash/shelf/shelf_widget.cc
index 7518c62e..2274146 100644
--- a/ash/shelf/shelf_widget.cc
+++ b/ash/shelf/shelf_widget.cc
@@ -34,6 +34,7 @@
 #include "ash/style/ash_color_id.h"
 #include "ash/style/style_util.h"
 #include "ash/system/status_area_widget.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ash/wm/work_area_insets.h"
 #include "base/command_line.h"
@@ -489,8 +490,7 @@
   const bool in_app = ShelfConfig::Get()->is_in_app();
 
   const bool in_overview_mode = ShelfConfig::Get()->in_overview_mode();
-  const bool in_oak_session =
-      features::IsForestFeatureEnabled() && in_overview_mode;
+  const bool in_oak_session = IsForestFeatureEnabled() && in_overview_mode;
   const bool split_view = ShelfConfig::Get()->in_split_view_with_overview();
   bool show_opaque_background =
       (!in_oak_session) && (!tablet_mode || in_app || split_view);
diff --git a/ash/shell.cc b/ash/shell.cc
index 549c34b..8bede10 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -197,6 +197,7 @@
 #include "ash/tray_action/tray_action.h"
 #include "ash/user_education/user_education_controller.h"
 #include "ash/user_education/user_education_delegate.h"
+#include "ash/utility/forest_util.h"
 #include "ash/utility/occlusion_tracker_pauser.h"
 #include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "ash/wm/ash_focus_rules.h"
@@ -1592,7 +1593,7 @@
   // used in its constructor.
   app_list_controller_ = std::make_unique<AppListControllerImpl>();
 
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureFlagEnabled()) {
     birch_model_ = std::make_unique<BirchModel>();
   }
 
@@ -1769,7 +1770,7 @@
   projector_controller_ = std::make_unique<ProjectorControllerImpl>();
 
   float_controller_ = std::make_unique<FloatController>();
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureFlagEnabled()) {
     pine_controller_ = std::make_unique<PineController>();
   }
   pip_controller_ = std::make_unique<PipController>();
diff --git a/ash/system/focus_mode/sounds/playlist_image_button.cc b/ash/system/focus_mode/sounds/playlist_image_button.cc
index 27a30d9..1742c83 100644
--- a/ash/system/focus_mode/sounds/playlist_image_button.cc
+++ b/ash/system/focus_mode/sounds/playlist_image_button.cc
@@ -23,7 +23,7 @@
 std::unique_ptr<lottie::Animation> GetEqualizerAnimation() {
   std::optional<std::vector<uint8_t>> lottie_data =
       ui::ResourceBundle::GetSharedInstance().GetLottieData(
-          IDR_FOCUS_MODE_EQUALIZER_LIGHT_ANIMATION);
+          IDR_FOCUS_MODE_EQUALIZER_ANIMATION);
   CHECK(lottie_data.has_value());
 
   return std::make_unique<lottie::Animation>(
diff --git a/ash/utility/forest_util.cc b/ash/utility/forest_util.cc
index ab4f8f4..d3fff78 100644
--- a/ash/utility/forest_util.cc
+++ b/ash/utility/forest_util.cc
@@ -12,16 +12,23 @@
 
 namespace ash {
 
+bool IsForestFeatureFlagEnabled() {
+  return base::FeatureList::IsEnabled(features::kForestFeature);
+}
+
 bool IsForestFeatureEnabled() {
-  if (!base::FeatureList::IsEnabled(features::kForestFeature)) {
+  if (!IsForestFeatureFlagEnabled()) {
     return false;
   }
 
-  // TODO(http://b/333952534): Remove the google api DEPS changes.
-  if (gaia::IsGoogleInternalAccountEmail(Shell::Get()
-                                             ->session_controller()
-                                             ->GetActiveAccountId()
-                                             .GetUserEmail())) {
+  // The shell may not be created in some unit tests.
+  Shell* shell = Shell::HasInstance() ? Shell::Get() : nullptr;
+
+  // TODO(http://b/333952534): Remove the google api DEPS changes, and move this
+  // function back to ash/constants/ash_features.
+  if (shell &&
+      gaia::IsGoogleInternalAccountEmail(
+          shell->session_controller()->GetActiveAccountId().GetUserEmail())) {
     return true;
   }
 
diff --git a/ash/utility/forest_util.h b/ash/utility/forest_util.h
index 80ca4176..0e63d34e 100644
--- a/ash/utility/forest_util.h
+++ b/ash/utility/forest_util.h
@@ -9,6 +9,8 @@
 
 namespace ash {
 
+ASH_EXPORT bool IsForestFeatureFlagEnabled();
+
 // Checks for the forest feature. This needs a secret key, unless the active
 // user is a google account.
 ASH_EXPORT bool IsForestFeatureEnabled();
diff --git a/ash/wallpaper/views/wallpaper_widget_controller.cc b/ash/wallpaper/views/wallpaper_widget_controller.cc
index dc06905..109906b 100644
--- a/ash/wallpaper/views/wallpaper_widget_controller.cc
+++ b/ash/wallpaper/views/wallpaper_widget_controller.cc
@@ -4,11 +4,11 @@
 
 #include "ash/wallpaper/views/wallpaper_widget_controller.h"
 
-#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
 #include "ash/shell.h"
 #include "ash/style/color_util.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wallpaper/views/wallpaper_view.h"
 #include "ui/aura/window.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
@@ -140,7 +140,7 @@
 }
 
 void WallpaperWidgetController::CreateWallpaperUnderlayLayer() {
-  if (!features::IsForestFeatureEnabled()) {
+  if (!IsForestFeatureFlagEnabled()) {
     return;
   }
 
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 8b9d4d1..4787592 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
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 import {assert, assertExists, assertNotReached} from '../assert.js';
+import {Flag} from '../flag.js';
 import {Point} from '../geometry.js';
+import * as loadTimeData from '../models/load_time_data.js';
 import {DeviceOperator} from '../mojo/device_operator.js';
 import * as state from '../state.js';
 import {CropRegionRect, Resolution} from '../type.js';
@@ -373,6 +375,7 @@
     const deviceOperator = assertExists(DeviceOperator.getInstance());
     await deviceOperator.resetCropRegion(this.deviceId);
     this.ptzSettings = DIGITAL_ZOOM_DEFAULT_SETTINGS;
+    state.set(state.State.SUPER_RES_ZOOM, false);
   }
 
   async pan(value: number): Promise<void> {
@@ -409,6 +412,12 @@
     const cropRegion = calculateCropRegion(baseSettings, this.fullCropRegion);
     await deviceOperator.setCropRegion(this.deviceId, cropRegion);
     this.ptzSettings = baseSettings;
+
+    state.set(state.State.SUPER_RES_ZOOM, this.isSuperResZoom());
+  }
+
+  private isSuperResZoom(): boolean {
+    return loadTimeData.getChromeFlag(Flag.SUPER_RES);
   }
 
   private isFullFrame({zoom}: PTZSettings): boolean {
diff --git a/ash/webui/camera_app_ui/resources/js/flag.ts b/ash/webui/camera_app_ui/resources/js/flag.ts
index 04e6725..b3b09c3 100644
--- a/ash/webui/camera_app_ui/resources/js/flag.ts
+++ b/ash/webui/camera_app_ui/resources/js/flag.ts
@@ -8,4 +8,5 @@
 export enum Flag {
   AUTO_QR = 'auto_qr',
   DIGITAL_ZOOM = 'digital_zoom',
+  SUPER_RES = 'super_res',
 }
diff --git a/ash/webui/camera_app_ui/resources/js/js.gni b/ash/webui/camera_app_ui/resources/js/js.gni
index bc6a903..aaacb62 100644
--- a/ash/webui/camera_app_ui/resources/js/js.gni
+++ b/ash/webui/camera_app_ui/resources/js/js.gni
@@ -47,6 +47,7 @@
   "lit/components/index.ts",
   "lit/components/mode-selector.ts",
   "lit/components/record-time-chip.ts",
+  "lit/components/super-res-loading-indicator.ts",
   "lit/components/svg-wrapper.ts",
   "lit/components/switch-device-button.ts",
   "lit/components/text-tooltip.ts",
diff --git a/ash/webui/camera_app_ui/resources/js/lit/components/gallery-button.ts b/ash/webui/camera_app_ui/resources/js/lit/components/gallery-button.ts
index 62f51ac..903658b1 100644
--- a/ash/webui/camera_app_ui/resources/js/lit/components/gallery-button.ts
+++ b/ash/webui/camera_app_ui/resources/js/lit/components/gallery-button.ts
@@ -2,18 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './super-res-loading-indicator.js';
+
 import {
   classMap,
   css,
   html,
   LitElement,
   PropertyDeclarations,
+  styleMap,
 } from 'chrome://resources/mwc/lit/index.js';
 
 import {CoverPhoto} from '../../cover_photo.js';
 import {I18nString} from '../../i18n_string.js';
 import {getI18nMessage} from '../../models/load_time_data.js';
+import {State} from '../../state.js';
+import {PerfEvent} from '../../type.js';
 import {withTooltip} from '../directives/with_tooltip.js';
+import {StateObserverController} from '../state_observer_controller.js';
 import {DEFAULT_STYLE} from '../styles.js';
 
 export class GalleryButton extends LitElement {
@@ -41,6 +47,12 @@
         display: none;
       }
 
+      #loading-indicator {
+        height: var(--big-icon);
+        position: absolute;
+        width: var(--big-icon);
+      }
+
       img {
         height: 100%;
         object-fit: cover;
@@ -63,6 +75,12 @@
     return this.shadowRoot?.querySelector('img')?.getAttribute('src') ?? '';
   }
 
+  private readonly superResZoomState =
+      new StateObserverController(this, State.SUPER_RES_ZOOM);
+
+  private readonly photoTakingState =
+      new StateObserverController(this, PerfEvent.PHOTO_TAKING);
+
   override render(): RenderResult {
     const buttonClasses = {
       hidden: this.cover === null,
@@ -71,6 +89,15 @@
       draggable: this.cover?.draggable ?? false,
     };
     return html`
+    <div>
+      <div id="loading-indicator"
+        style=${styleMap({
+          visibility: this.superResZoomState.value ? 'visible' : 'hidden',
+        })}>
+        <super-res-loading-indicator .photoProcessing=${
+          this.photoTakingState.value}>
+        </super-res-loading-indicator>
+      </div>
       <button
           aria-label=${getI18nMessage(I18nString.GALLERY_BUTTON)}
           ${withTooltip()}
@@ -79,6 +106,7 @@
             class=${classMap(imgClasses)}
             src=${this.cover?.url ?? ''}>
       </button>
+    </div>
     `;
   }
 }
diff --git a/ash/webui/camera_app_ui/resources/js/lit/components/super-res-loading-indicator.ts b/ash/webui/camera_app_ui/resources/js/lit/components/super-res-loading-indicator.ts
new file mode 100644
index 0000000..454dd270
--- /dev/null
+++ b/ash/webui/camera_app_ui/resources/js/lit/components/super-res-loading-indicator.ts
@@ -0,0 +1,94 @@
+// 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 {
+  css,
+  html,
+  LitElement,
+  PropertyDeclarations,
+} from 'chrome://resources/mwc/lit/index.js';
+
+import {DEFAULT_STYLE} from '../styles.js';
+
+export class SuperResLoadingIndicator extends LitElement {
+  static override styles = [
+    DEFAULT_STYLE,
+    css`
+      .spin-start circle {
+        animation: loading 0.5s ease-out forwards;
+      }
+
+      @keyframes loading {
+        from {
+          stroke-dashoffset: 315;
+        }
+        to {
+          stroke-dashoffset: 95;
+        }
+      }
+
+      .spin-complete circle {
+        animation: finish 2s ease-out forwards;
+      }
+
+      @keyframes finish {
+        0% {
+          stroke-dashoffset: 95;
+        }
+        20% {
+          stroke-dashoffset: 0;
+        }
+        25% {
+          stroke-opacity: 1;
+        }
+        40% {
+          stroke-opacity: 0;
+        }
+        55% {
+          stroke-opacity: 1;
+        }
+        70% {
+          stroke-opacity: 0;
+        }
+        85% {
+          stroke-opacity: 1;
+        }
+        100% {
+          stroke-opacity: 0;
+        }
+      }
+    `,
+  ];
+
+  static override properties: PropertyDeclarations = {
+    photoProcessing: {type: Boolean},
+  };
+
+  /**
+   * Whether the photo processing is ongoing.
+   */
+  photoProcessing = false;
+
+  override render(): RenderResult {
+    return html`
+    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"
+        class=${this.photoProcessing ? 'spin-start' : 'spin-complete'}>
+      <circle stroke-linecap="round" cx="50" cy="50" r="48"
+          stroke="var(--cros-sys-on_primary_container)" stroke-width="5"
+          fill="none" stroke-dasharray="315"
+          transform="rotate(-90,50,50)" />
+    </svg>
+    `;
+  }
+}
+
+window.customElements.define(
+    'super-res-loading-indicator', SuperResLoadingIndicator);
+
+declare global {
+  interface HTMLElementTagNameMap {
+    /* eslint-disable-next-line @typescript-eslint/naming-convention */
+    'super-res-loading-indicator': SuperResLoadingIndicator;
+  }
+}
diff --git a/ash/webui/camera_app_ui/resources/js/state.ts b/ash/webui/camera_app_ui/resources/js/state.ts
index 842b6ba..5b088ae 100644
--- a/ash/webui/camera_app_ui/resources/js/state.ts
+++ b/ash/webui/camera_app_ui/resources/js/state.ts
@@ -55,6 +55,7 @@
   SHOULD_HANDLE_INTENT_RESULT = 'should-handle-intent-result',
   SNAPSHOTTING = 'snapshotting',
   STREAMING = 'streaming',
+  SUPER_RES_ZOOM = 'super-res-zoom',
   SUSPEND = 'suspend',
   TABLET = 'tablet',
   TABLET_LANDSCAPE = 'tablet-landscape',
diff --git a/ash/webui/camera_app_ui/resources/utils/cca/commands/dev.py b/ash/webui/camera_app_ui/resources/utils/cca/commands/dev.py
index 15ce59a8..f6ab85e 100644
--- a/ash/webui/camera_app_ui/resources/utils/cca/commands/dev.py
+++ b/ash/webui/camera_app_ui/resources/utils/cca/commands/dev.py
@@ -92,6 +92,7 @@
             "timeLapse": True,
             "auto_qr": True,
             "digital_zoom": True,
+            "super_res": True,
         }
         load_time_data.update(self._load_grd_strings())
         relative_path = _get_root_relative_path(request_path)
diff --git a/ash/wm/desks/desk_bar_view_base.cc b/ash/wm/desks/desk_bar_view_base.cc
index f07fd837..d051db9d 100644
--- a/ash/wm/desks/desk_bar_view_base.cc
+++ b/ash/wm/desks/desk_bar_view_base.cc
@@ -21,6 +21,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_id.h"
 #include "ash/style/typography.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desk_action_button.h"
 #include "ash/wm/desks/desk_action_view.h"
@@ -125,7 +126,7 @@
   auto* layer = view->layer();
   layer->SetFillsBoundsOpaquely(false);
 
-  if (features::IsForestFeatureEnabled() && !type_is_desk_button) {
+  if (IsForestFeatureEnabled() && !type_is_desk_button) {
     // Forests feature needs a transparent desks bar background. Still needs the
     // view layer to perform animations.
     return;
@@ -623,9 +624,8 @@
         height = kDeskBarZeroStateHeight;
       } else {
         height = DeskPreviewView::GetHeight(root) +
-                 (features::IsForestFeatureEnabled()
-                      ? kExpandedDeskBarHeightWithOak
-                      : kDeskBarNonPreviewAllocatedHeight);
+                 (IsForestFeatureEnabled() ? kExpandedDeskBarHeightWithOak
+                                           : kDeskBarNonPreviewAllocatedHeight);
       }
       break;
   }
diff --git a/ash/wm/lock_state_controller.cc b/ash/wm/lock_state_controller.cc
index e85e60f..563b664 100644
--- a/ash/wm/lock_state_controller.cc
+++ b/ash/wm/lock_state_controller.cc
@@ -12,7 +12,6 @@
 #include "ash/app_list/app_list_controller_impl.h"
 #include "ash/cancel_mode.h"
 #include "ash/capture_mode/capture_mode_controller.h"
-#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "ash/public/cpp/saved_desk_delegate.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -21,6 +20,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
+#include "ash/utility/forest_util.h"
 #include "ash/utility/occlusion_tracker_pauser.h"
 #include "ash/wallpaper/views/wallpaper_view.h"
 #include "ash/wallpaper/views/wallpaper_widget_controller.h"
@@ -459,7 +459,7 @@
 void LockStateController::RequestRestart(
     power_manager::RequestRestartReason reason,
     const std::string& description) {
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     restart_reason_ = reason;
     restart_description_ = description;
     HideAndMaybeLockCursor(/*lock=*/false);
@@ -813,7 +813,7 @@
 }
 
 void LockStateController::ShutdownOnPine(bool with_pre_animation) {
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     TakePineImageAndShutdown(with_pre_animation);
   } else {
     StartShutdownProcess(with_pre_animation);
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index bd2b53aa..5477b124 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -32,6 +32,7 @@
 #include "ash/style/rounded_label_widget.h"
 #include "ash/style/typography.h"
 #include "ash/system/toast/toast_manager_impl.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "ash/wm/desks/default_desk_button.h"
 #include "ash/wm/desks/desk_bar_view_base.h"
@@ -537,7 +538,7 @@
   // disabled by users. We don't need to worry about showing/hiding the bar
   // dynamically on primary/secondary user switch because we exit overview when
   // we switch users.
-  return features::IsForestFeatureEnabled() &&
+  return IsForestFeatureEnabled() &&
          Shell::Get()->session_controller()->IsUserPrimary() &&
          BirchBarController::Get()->GetShowBirchSuggestions() &&
          !SplitViewController::Get(root_window)->InSplitViewMode();
@@ -545,7 +546,7 @@
 
 bool ShouldShowPineDialog(aura::Window* root_window) {
   return root_window == Shell::GetPrimaryRootWindow() &&
-         features::IsForestFeatureEnabled() &&
+         IsForestFeatureEnabled() &&
          !!Shell::Get()->pine_controller()->pine_contents_data();
 }
 
@@ -682,7 +683,7 @@
 
   MaybeInitBirchBarWidget();
 
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     scoped_overview_wallpaper_clipper_ =
         std::make_unique<ScopedOverviewWallpaperClipper>(this);
   }
@@ -792,7 +793,7 @@
 
   // Create a feedback button that shows even when no items are present (e.g.,
   // for Pine).
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     UpdateFeedbackButton();
   }
 
@@ -1659,7 +1660,7 @@
 }
 
 gfx::Insets OverviewGrid::GetGridHorizontalPaddings() const {
-  if (!features::IsForestFeatureEnabled()) {
+  if (!IsForestFeatureEnabled()) {
     return gfx::Insets();
   }
 
@@ -1690,7 +1691,7 @@
 }
 
 gfx::Insets OverviewGrid::GetGridVerticalPaddings() const {
-  const bool forest_enabled = features::IsForestFeatureEnabled();
+  const bool forest_enabled = IsForestFeatureEnabled();
 
   // Use compact paddings for partial overview.
   if (forest_enabled &&
@@ -2296,7 +2297,7 @@
     scoped_overview_wallpaper_clipper_->RefreshWallpaperClipBounds();
   }
 
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     UpdateFeedbackButton();
   }
 }
@@ -3006,7 +3007,7 @@
 void OverviewGrid::MaybeCenterOverviewItems(
     std::vector<gfx::RectF>& out_window_rects,
     const base::flat_set<OverviewItemBase*>& ignored_items) {
-  if (!features::IsForestFeatureEnabled() || out_window_rects.empty()) {
+  if (!IsForestFeatureEnabled() || out_window_rects.empty()) {
     return;
   }
 
@@ -3211,7 +3212,7 @@
 }
 
 void OverviewGrid::RefreshDesksWidgets(bool visible) {
-  if (!features::IsForestFeatureEnabled()) {
+  if (!IsForestFeatureEnabled()) {
     return;
   }
 
@@ -3384,7 +3385,7 @@
 }
 
 void OverviewGrid::UpdateFeedbackButton() {
-  CHECK(features::IsForestFeatureEnabled());
+  CHECK(IsForestFeatureEnabled());
 
   if (SplitViewController::Get(root_window_)->InSplitViewMode()) {
     feedback_widget_.reset();
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index 6ada017..6b572ae 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -8,7 +8,6 @@
 
 #include "ash/accessibility/accessibility_controller.h"
 #include "ash/app_list/app_list_controller_impl.h"
-#include "ash/constants/ash_features.h"
 #include "ash/frame_throttler/frame_throttling_controller.h"
 #include "ash/metrics/user_metrics_recorder.h"
 #include "ash/public/cpp/window_properties.h"
@@ -17,6 +16,7 @@
 #include "ash/screen_util.h"
 #include "ash/shell.h"
 #include "ash/style/rounded_label_widget.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/desks/legacy_desk_bar_view.h"
@@ -204,7 +204,7 @@
   }
 
   // Create this before the birch bar widget.
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     birch_bar_controller_ = std::make_unique<BirchBarController>(
         /*from_pine_service=*/enter_exit_overview_type_ ==
         OverviewEnterExitType::kPine);
@@ -1588,7 +1588,7 @@
 
   // Entering or exiting splitview is unexpected behavior in a pine overview
   // session.
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     CHECK(!Shell::Get()->pine_controller()->pine_contents_data());
   }
 
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index 0b4f1ae..f193b6b 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -41,6 +41,7 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/test/raster_scale_change_tracker.h"
 #include "ash/test/test_window_builder.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wallpaper/views/wallpaper_view.h"
 #include "ash/wallpaper/views/wallpaper_widget_controller.h"
 #include "ash/wm/desks/desk.h"
@@ -5401,7 +5402,7 @@
   // it is not true on any of the root windows.
   bool IsFloatContainerNormalStacked() const {
     for (aura::Window* root : Shell::GetAllRootWindows()) {
-      if (features::IsForestFeatureEnabled()) {
+      if (IsForestFeatureEnabled()) {
         // The float container should be the top-most child of the
         // `ShutdownScreenshotContainer` when the feature `ForestFeature` is
         // enabled.
diff --git a/ash/wm/overview/overview_utils.cc b/ash/wm/overview/overview_utils.cc
index 8433f06..d2116f1 100644
--- a/ash/wm/overview/overview_utils.cc
+++ b/ash/wm/overview/overview_utils.cc
@@ -7,7 +7,6 @@
 #include <utility>
 
 #include "ash/accessibility/accessibility_controller.h"
-#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/root_window_controller.h"
@@ -15,6 +14,7 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shelf/shelf_layout_manager.h"
 #include "ash/shell.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/cleanup_animation_observer.h"
 #include "ash/wm/overview/delayed_animation_observer_impl.h"
@@ -261,7 +261,7 @@
     const bool show_home_launcher =
         hotseat_state == HotseatState::kShownHomeLauncher;
 
-    const bool forest_enabled = features::IsForestFeatureEnabled();
+    const bool forest_enabled = IsForestFeatureEnabled();
 
     // Use the default hotseat size here to avoid the possible re-layout
     // due to the update in HotseatWidget::is_forced_dense_.
diff --git a/ash/wm/overview/scoped_overview_wallpaper_clipper.cc b/ash/wm/overview/scoped_overview_wallpaper_clipper.cc
index 77e5c63a..135821e 100644
--- a/ash/wm/overview/scoped_overview_wallpaper_clipper.cc
+++ b/ash/wm/overview/scoped_overview_wallpaper_clipper.cc
@@ -39,6 +39,9 @@
           ->wallpaper_widget_controller();
   auto* wallpaper_underlay_layer =
       wallpaper_widget_controller->wallpaper_underlay_layer();
+  // TODO(http://b/333952534): Remove this check once `wallpaper_underlay_layer`
+  // is always created.
+  CHECK(wallpaper_underlay_layer);
   wallpaper_underlay_layer->SetVisible(true);
 
   auto* wallpaper_view_layer =
diff --git a/ash/wm/snap_group/snap_group_unittest.cc b/ash/wm/snap_group/snap_group_unittest.cc
index 291abce..7358ffc 100644
--- a/ash/wm/snap_group/snap_group_unittest.cc
+++ b/ash/wm/snap_group/snap_group_unittest.cc
@@ -2310,35 +2310,6 @@
       SnapGroupController::Get()->AreWindowsInSnapGroup(w1.get(), w3.get()));
 }
 
-// TODO(b/326481241): Currently it's not possible to swap windows since
-// `SplitViewController` still manages the windows and updates the bounds in a
-// `SnapGroup`. This will just check that double tap still works after
-// conversion.
-TEST_F(SnapGroupTest, DoubleTapDivider) {
-  std::unique_ptr<aura::Window> w1(CreateAppWindow());
-  std::unique_ptr<aura::Window> w2(CreateAppWindow());
-  SnapTwoTestWindows(w1.get(), w2.get());
-  auto* snap_group = SnapGroupController::Get()->GetTopmostSnapGroup();
-  EXPECT_TRUE(snap_group);
-  auto* new_primary_window = snap_group->window1();
-  auto* new_secondary_window = snap_group->window2();
-
-  // Switch to tablet mode. Test that double tap on the divider swaps the
-  // windows.
-  SwitchToTabletMode();
-  EXPECT_EQ(new_primary_window, split_view_controller()->primary_window());
-  EXPECT_EQ(new_secondary_window, split_view_controller()->secondary_window());
-  EXPECT_TRUE(split_view_divider()->divider_widget());
-  const gfx::Point divider_center =
-      split_view_divider()
-          ->GetDividerBoundsInScreen(/*is_dragging=*/false)
-          .CenterPoint();
-  GetEventGenerator()->GestureTapAt(divider_center);
-  GetEventGenerator()->GestureTapAt(divider_center);
-  EXPECT_EQ(new_secondary_window, split_view_controller()->primary_window());
-  EXPECT_EQ(new_primary_window, split_view_controller()->secondary_window());
-}
-
 TEST_F(SnapGroupTest, DontAutoSnapNewWindowOutsideSplitViewOverview) {
   std::unique_ptr<aura::Window> w1(CreateAppWindow());
   std::unique_ptr<aura::Window> w2(CreateAppWindow());
@@ -2480,6 +2451,44 @@
 
 using SnapGroupDividerTest = SnapGroupTest;
 
+// Tests that the divider starts with a thin default width
+// (`kSplitviewDividerShortSideLength`) in landscape mode, expands to
+// `kSplitviewDividerEnlargedShortSideLength` on mouse hover or drag, and
+// returns to its default thin width on mouse exit.
+TEST_F(SnapGroupDividerTest, HoverToEnlargeDivider) {
+  std::unique_ptr<aura::Window> w1(CreateAppWindow());
+  std::unique_ptr<aura::Window> w2(CreateAppWindow());
+  SnapTwoTestWindows(w1.get(), w2.get());
+
+  SplitViewDivider* divider = snap_group_divider();
+  auto* divider_widget = divider->divider_widget();
+  ASSERT_TRUE(divider_widget);
+
+  const auto divider_bounds_before_hover =
+      divider_widget->GetWindowBoundsInScreen();
+  EXPECT_EQ(kSplitviewDividerShortSideLength,
+            divider_bounds_before_hover.width());
+
+  auto* event_generator = GetEventGenerator();
+
+  // Shift the hover point so that it is not right on the divider handler view
+  // to trigger hover to enlarge.
+  const auto delta_vector = gfx::Vector2d(0, -10);
+  const gfx::Point hover_point =
+      divider_bounds_before_hover.CenterPoint() + delta_vector;
+  event_generator->MoveMouseTo(hover_point);
+  EXPECT_EQ(kSplitviewDividerEnlargedShortSideLength,
+            divider_widget->GetWindowBoundsInScreen().width());
+
+  event_generator->MoveMouseBy(10, 0);
+  EXPECT_EQ(kSplitviewDividerEnlargedShortSideLength,
+            divider_widget->GetWindowBoundsInScreen().width());
+
+  event_generator->MoveMouseTo(gfx::Point(0, 0));
+  EXPECT_EQ(kSplitviewDividerShortSideLength,
+            divider_widget->GetWindowBoundsInScreen().width());
+}
+
 // Tests that the split view divider will be stacked on top of both windows in
 // the snap group and that on a third window activated the split view divider
 // will be stacked below the newly activated window.
@@ -2491,6 +2500,7 @@
 
   SplitViewDivider* divider = snap_group_divider();
   auto* divider_widget = divider->divider_widget();
+  ASSERT_TRUE(divider_widget);
   aura::Window* divider_window = divider_widget->GetNativeWindow();
   EXPECT_TRUE(window_util::IsStackedBelow(w2.get(), w1.get()));
   EXPECT_TRUE(window_util::IsStackedBelow(w1.get(), divider_window));
@@ -2829,6 +2839,35 @@
               (cached_divider_center_point + move_vector).x(), /*abs_error=*/1);
 }
 
+// TODO(b/326481241): Currently it's not possible to swap windows since
+// `SplitViewController` still manages the windows and updates the bounds in a
+// `SnapGroup`. This will just check that double tap still works after
+// conversion.
+TEST_F(SnapGroupTest, DoubleTapDividerInTablet) {
+  std::unique_ptr<aura::Window> w1(CreateAppWindow());
+  std::unique_ptr<aura::Window> w2(CreateAppWindow());
+  SnapTwoTestWindows(w1.get(), w2.get());
+  auto* snap_group = SnapGroupController::Get()->GetTopmostSnapGroup();
+  EXPECT_TRUE(snap_group);
+  auto* new_primary_window = snap_group->window1();
+  auto* new_secondary_window = snap_group->window2();
+
+  // Switch to tablet mode. Test that double tap on the divider swaps the
+  // windows.
+  SwitchToTabletMode();
+  EXPECT_EQ(new_primary_window, split_view_controller()->primary_window());
+  EXPECT_EQ(new_secondary_window, split_view_controller()->secondary_window());
+  EXPECT_TRUE(split_view_divider()->divider_widget());
+  const gfx::Point divider_center =
+      split_view_divider()
+          ->GetDividerBoundsInScreen(/*is_dragging=*/false)
+          .CenterPoint();
+  GetEventGenerator()->GestureTapAt(divider_center);
+  GetEventGenerator()->GestureTapAt(divider_center);
+  EXPECT_EQ(new_secondary_window, split_view_controller()->primary_window());
+  EXPECT_EQ(new_primary_window, split_view_controller()->secondary_window());
+}
+
 // Tests to verify that when a window is dragged out of a snap group and onto
 // another display, it snaps correctly with accurate bounds on the destination
 // display. See regression at http://b/331663949.
diff --git a/ash/wm/splitview/split_view_constants.h b/ash/wm/splitview/split_view_constants.h
index d778e97..c0b4b02 100644
--- a/ash/wm/splitview/split_view_constants.h
+++ b/ash/wm/splitview/split_view_constants.h
@@ -42,42 +42,12 @@
 // The time duration for the window transformation animations.
 constexpr auto kSplitviewWindowTransformDuration = base::Milliseconds(250);
 
-// The time duration for the `split_view_divider_` animations when dragging
-// starts and ends.
-constexpr auto kSplitviewDividerSelectionStatusChangeDuration =
-    base::Milliseconds(250);
-
-// The time duration for the `split_view_divider_` spawning animation.
-constexpr auto kSplitviewDividerSpawnDuration = base::Milliseconds(100);
-
-// The delay before the `split_view_divider_` spawning animation.
-constexpr auto kSplitviewDividerSpawnDelay = base::Milliseconds(183);
-
 // The one-way bouncing animation duration for the `split_view_divider_` when
 // the to-be-snapped window can't fit in the work area. The actual duration when
 // used should be doubled to include the "bouncing out and bounding back in"
 // process.
 constexpr auto kBouncingAnimationOneWayDuration = base::Milliseconds(250);
 
-// The thickness of the `split_view_divider_`'s handler.
-constexpr int kDividerHandlerShortSideLength = 2;
-
-// The length of the `split_view_divider_`'s handler.
-constexpr int kDividerHandlerLongSideLength = 16;
-
-// The corner radius of the `split_view_divider_`'s handler.
-constexpr int kDividerHandlerCornerRadius = 1;
-
-// The radius of the circular handler when the `split_view_divider_` is being
-// dragged.
-constexpr int kDividerHandlerRadius = 2;
-
-// The length of the `split_view_divider_`'s handler when it spawns.
-constexpr int kDividerHandlerSpawnLongSideLength = 2;
-
-// The distance from the `split_view_divider_` to where its handler spawns.
-constexpr int kDividerHandlerSpawnUnsignedOffset = 2;
-
 // The opacity of the highlight area.
 constexpr float kHighlightOpacity = 0.25f;
 
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index e835041..8045a76 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -1201,29 +1201,9 @@
   aura::Window* window = window_state->window();
 
   if (window_state->IsSnapped()) {
-    bool do_divider_spawn_animation = false;
-    // Only need to do divider spawn animation if split view is to be active,
-    // window is not minimized and has an non-identify transform in tablet mode.
-    // If window|is currently minimized then it will undergo the unminimizing
-    // animation instead, therefore skip the divider spawn animation if
-    // the window is minimized.
-    if (state_ == State::kNoSnap && InTabletMode() &&
-        old_type != WindowStateType::kMinimized &&
-        !window->transform().IsIdentity()) {
-      // For the divider spawn animation, at the end of the delay, the divider
-      // shall be visually aligned with an edge of |window|. This effect will
-      // be more easily achieved after |window| has been snapped and the
-      // corresponding transform animation has begun. So for now, just set a
-      // flag to indicate that the divider spawn animation should be done.
-      do_divider_spawn_animation = true;
-    }
-
     OnWindowSnapped(window, old_type,
                     window_state->snap_action_source().value_or(
                         WindowSnapActionSource::kNotSpecified));
-    if (do_divider_spawn_animation) {
-      DoSplitDividerSpawnAnimation(window);
-    }
   } else if (window_state->IsNormalStateType() || window_state->IsMaximized() ||
              window_state->IsFullscreen() || window_state->IsFloated()) {
     // End split view, and also overview if overview is active, in these cases:
@@ -2655,30 +2635,6 @@
   }
 }
 
-void SplitViewController::DoSplitDividerSpawnAnimation(aura::Window* window) {
-  DCHECK(window->layer()->GetAnimator()->GetTargetTransform().IsIdentity());
-  SnapPosition snap_position = GetPositionOfSnappedWindow(window);
-  const gfx::Rect bounds = GetSnappedWindowBoundsInScreen(
-      snap_position, window, window_util::GetSnapRatioForWindow(window),
-      ShouldConsiderDivider());
-  // Get one of the two corners of |window| that meet the divider.
-  gfx::Point p = IsPhysicalLeftOrTop(snap_position, window)
-                     ? bounds.bottom_right()
-                     : bounds.origin();
-  // Apply the transform that |window| will undergo when the divider spawns.
-  static const double value = gfx::Tween::CalculateValue(
-      gfx::Tween::FAST_OUT_SLOW_IN,
-      kSplitviewDividerSpawnDelay / kSplitviewWindowTransformDuration);
-  p = gfx::TransformAboutPivot(
-          gfx::PointF(bounds.origin()),
-          gfx::Tween::TransformValueBetween(value, window->transform(),
-                                            gfx::Transform()))
-          .MapPoint(p);
-  // Use a coordinate of the transformed |window| corner for spawn_position.
-  split_view_divider_.DoSpawningAnimation(IsLayoutHorizontal(window) ? p.x()
-                                                                     : p.y());
-}
-
 void SplitViewController::SwapWindowsAndUpdateBounds() {
   gfx::Rect primary_window_bounds =
       primary_window_ ? primary_window_->GetBoundsInScreen() : gfx::Rect();
diff --git a/ash/wm/splitview/split_view_controller.h b/ash/wm/splitview/split_view_controller.h
index e09307b..3267021 100644
--- a/ash/wm/splitview/split_view_controller.h
+++ b/ash/wm/splitview/split_view_controller.h
@@ -550,10 +550,6 @@
                          const gfx::Point& last_location_in_screen,
                          WindowSnapActionSource snap_action_source);
 
-  // Do the split divider spawn animation. It will add a finishing touch to the
-  // |window| animation that generally accommodates snapping by dragging.
-  void DoSplitDividerSpawnAnimation(aura::Window* window);
-
   // Called by `SwapWindows()` to swap the window(s) if exist that occupy the
   // `SnapPosition::kPrimary` and `SnapPosition::kSecondary`. The bounds of the
   // window(s) will also be updated.
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index 86ed4d6e..5b2f01f 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -4152,8 +4152,11 @@
   std::unique_ptr<aura::Window> bottom_window(CreateWindow(bounds));
   auto bottom_client =
       std::make_unique<TestTextInputClient>(bottom_window.get());
-  split_view_controller()->SnapWindow(bottom_window.get(),
-                                      SnapPosition::kSecondary);
+
+  SplitViewController* split_view_controller =
+      SplitViewController::Get(Shell::GetPrimaryRootWindow());
+  split_view_controller->SnapWindow(bottom_window.get(),
+                                    SnapPosition::kSecondary);
 
   test_api.SetDisplayRotation(display::Display::ROTATE_270,
                               display::Display::RotationSource::ACTIVE);
@@ -4171,18 +4174,23 @@
   const int screen_height = screen_bounds.height();
   const int limit_y = screen_height * kMinDividerPositionRatio;
 
+  gfx::Rect ori_divider_bounds = split_view_controller->split_view_divider()
+                                     ->divider_widget()
+                                     ->GetWindowBoundsInScreen();
+
   // Resize divider to a position that when the bottom window is pushed up, its
   // position will exceeds `1-kMinDividerPositionRatio` of screen height.
-  split_view_divider()->StartResizeWithDivider(divider_bounds.CenterPoint());
+  const auto drag_start_point = divider_bounds.CenterPoint();
+  split_view_divider()->StartResizeWithDivider(drag_start_point);
+  const int drag_end_point_y = screen_height * 0.15f;
   split_view_divider()->ResizeWithDivider(gfx::Point(0, screen_height * 0.15f));
 
-  const gfx::Rect orig_bottom_bounds = bottom_window->GetBoundsInScreen();
-  EXPECT_LT(keyboard_bounds.y() - orig_bottom_bounds.height(), limit_y);
+  // Adjust the `ori_divider_bounds` with the dragging distance to be used for
+  // check later.
+  ori_divider_bounds.Offset(0, drag_end_point_y - drag_start_point.y());
 
-  const gfx::Rect orig_divider_bounds = split_view_controller()
-                                            ->split_view_divider()
-                                            ->divider_widget()
-                                            ->GetWindowBoundsInScreen();
+  const gfx::Rect ori_bottom_bounds = bottom_window->GetBoundsInScreen();
+  EXPECT_LT(keyboard_bounds.y() - ori_bottom_bounds.height(), limit_y);
 
   // Set the caret position in bottom window below the upper bounds of the
   // virtual keyboard. When the virtual keyboard is enabled, the bottom window
@@ -4192,32 +4200,31 @@
                                       keyboard_bounds.y() - limit_y);
   const gfx::Rect shift_divider_bounds(
       shift_bottom_bounds.origin() +
-          gfx::Vector2d(0, -orig_divider_bounds.height()),
-      orig_divider_bounds.size());
+          gfx::Vector2d(0, -ori_divider_bounds.height()),
+      ori_divider_bounds.size());
 
   bottom_client->set_caret_bounds(gfx::Rect(keyboard_bounds.top_center(),
                                             gfx::Size(0, kCaretHeightForTest)));
   bottom_client->Focus();
   EXPECT_TRUE(keyboard_controller()->IsKeyboardVisible());
   EXPECT_EQ(shift_bottom_bounds, bottom_window->GetBoundsInScreen());
+
   // The split view divider will also be shifted and become unadjustable.
-  EXPECT_EQ(shift_divider_bounds, split_view_controller()
-                                      ->split_view_divider()
+  EXPECT_EQ(shift_divider_bounds, split_view_controller->split_view_divider()
                                       ->divider_widget()
                                       ->GetWindowBoundsInScreen());
-  EXPECT_FALSE(split_view_controller()->split_view_divider()->IsAdjustable());
+  EXPECT_FALSE(split_view_controller->split_view_divider()->IsAdjustable());
 
   // Disable the keyboard. The bottom window will restore to original bounds.
   // The split view divider will also be adjustable and restore to original
   // bounds.
   bottom_client->UnFocus();
   EXPECT_FALSE(keyboard_controller()->IsKeyboardVisible());
-  EXPECT_EQ(orig_bottom_bounds, bottom_window->GetBoundsInScreen());
-  EXPECT_EQ(orig_divider_bounds, split_view_controller()
-                                     ->split_view_divider()
-                                     ->divider_widget()
-                                     ->GetWindowBoundsInScreen());
-  EXPECT_TRUE(split_view_controller()->split_view_divider()->IsAdjustable());
+  EXPECT_EQ(ori_bottom_bounds, bottom_window->GetBoundsInScreen());
+  EXPECT_EQ(ori_divider_bounds, split_view_controller->split_view_divider()
+                                    ->divider_widget()
+                                    ->GetWindowBoundsInScreen());
+  EXPECT_TRUE(split_view_controller->split_view_divider()->IsAdjustable());
 }
 
 // Tests that when the bottom window is pushed up due to the virtual keyboard
diff --git a/ash/wm/splitview/split_view_divider.cc b/ash/wm/splitview/split_view_divider.cc
index db99b742..13353f55 100644
--- a/ash/wm/splitview/split_view_divider.cc
+++ b/ash/wm/splitview/split_view_divider.cc
@@ -11,7 +11,6 @@
 #include "ash/public/cpp/window_properties.h"
 #include "ash/screen_util.h"
 #include "ash/wm/desks/desks_util.h"
-#include "ash/wm/snap_group/snap_group_controller.h"
 #include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/splitview/split_view_divider_view.h"
@@ -86,19 +85,19 @@
 }
 
 // Returns the widget init params needed to create the widget.
-views::Widget::InitParams CreateWidgetInitParams(
-    aura::Window* parent_window,
-    const std::string& widget_name) {
+views::Widget::InitParams CreateWidgetInitParams(aura::Window* parent_window,
+                                                 const gfx::Rect& bounds) {
   views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
   params.opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
   params.activatable = views::Widget::InitParams::Activatable::kNo;
   params.parent = parent_window;
+  params.bounds = bounds;
   params.init_properties_container.SetProperty(kHideInDeskMiniViewKey, true);
   // Exclude the divider from getting transformed with its transient parent
   // window when we are resizing. The divider will set its own transforms.
   params.init_properties_container.SetProperty(
       kExcludeFromTransientTreeTransformKey, true);
-  params.name = widget_name;
+  params.name = "SplitViewDividerWidget";
   return params;
 }
 
@@ -115,31 +114,25 @@
     bool landscape,
     int divider_position,
     bool is_dragging) {
-  const int dragging_diff = (kSplitviewDividerEnlargedShortSideLength -
-                             kSplitviewDividerShortSideLength) /
-                            2;
+  const int dragging_diff = is_dragging
+                                ? (kSplitviewDividerEnlargedShortSideLength -
+                                   kSplitviewDividerShortSideLength) /
+                                      2
+                                : 0;
   if (landscape) {
-    return is_dragging
-               ? gfx::Rect(work_area_bounds_in_screen.x() + divider_position -
-                               dragging_diff,
-                           work_area_bounds_in_screen.y(),
-                           kSplitviewDividerEnlargedShortSideLength,
-                           work_area_bounds_in_screen.height())
-               : gfx::Rect(work_area_bounds_in_screen.x() + divider_position,
-                           work_area_bounds_in_screen.y(),
-                           kSplitviewDividerShortSideLength,
-                           work_area_bounds_in_screen.height());
+    return gfx::Rect(
+        work_area_bounds_in_screen.x() + divider_position - dragging_diff,
+        work_area_bounds_in_screen.y(),
+        is_dragging ? kSplitviewDividerEnlargedShortSideLength
+                    : kSplitviewDividerShortSideLength,
+        work_area_bounds_in_screen.height());
   } else {
-    return is_dragging
-               ? gfx::Rect(work_area_bounds_in_screen.x(),
-                           work_area_bounds_in_screen.y() + divider_position -
-                               dragging_diff,
-                           work_area_bounds_in_screen.width(),
-                           kSplitviewDividerEnlargedShortSideLength)
-               : gfx::Rect(work_area_bounds_in_screen.x(),
-                           work_area_bounds_in_screen.y() + divider_position,
-                           work_area_bounds_in_screen.width(),
-                           kSplitviewDividerShortSideLength);
+    return gfx::Rect(
+        work_area_bounds_in_screen.x(),
+        work_area_bounds_in_screen.y() + divider_position - dragging_diff,
+        work_area_bounds_in_screen.width(),
+        is_dragging ? kSplitviewDividerEnlargedShortSideLength
+                    : kSplitviewDividerShortSideLength);
   }
 }
 
@@ -216,7 +209,7 @@
   }
 
   is_resizing_with_divider_ = true;
-  UpdateDividerBounds();
+  EnlargeOrShrinkDivider(/*should_enlarge=*/true);
   previous_event_location_ = location_in_screen;
 
   controller_->StartResizeWithDivider(location_in_screen);
@@ -253,6 +246,7 @@
   // `LayoutDividerController` will update the window and divider bounds in
   // `UpdateResizeWithDivider()`.
   UpdateDividerPosition(modified_location_in_screen);
+  EnlargeOrShrinkDivider(/*should_enlarge=*/true);
   controller_->UpdateResizeWithDivider(modified_location_in_screen);
 
   previous_event_location_ = modified_location_in_screen;
@@ -267,13 +261,16 @@
   is_resizing_with_divider_ = false;
 
   gfx::Point modified_location_in_screen = GetBoundedPosition(
-      location_in_screen,
-      GetWorkAreaBoundsInScreen(divider_widget_->GetNativeWindow()));
+      location_in_screen, GetWorkAreaBoundsInScreen(GetRootWindow()));
 
   // Order here matters: we first update `divider_position_`, then the
   // `LayoutDividerController` will transform and update the windows bounds in
   // `EndResizeWithDivider()`.
   UpdateDividerPosition(modified_location_in_screen);
+  const gfx::Point cursor_point =
+      display::Screen::GetScreen()->GetCursorScreenPoint();
+  EnlargeOrShrinkDivider(
+      GetDividerBoundsInScreen(/*is_dragging=*/true).Contains(cursor_point));
 
   // If the delegate is done with resizing, finish resizing and clean up.
   // Otherwise it will be called later, in
@@ -292,10 +289,6 @@
   controller_->OnResizeEnded();
 }
 
-void SplitViewDivider::DoSpawningAnimation(int spawning_position) {
-  divider_view_->DoSpawningAnimation(spawning_position);
-}
-
 void SplitViewDivider::UpdateDividerBounds() {
   if (divider_widget_) {
     divider_widget_->SetBounds(GetDividerBoundsInScreen(/*is_dragging=*/false));
@@ -311,6 +304,15 @@
                                   divider_position_, is_dragging);
 }
 
+void SplitViewDivider::EnlargeOrShrinkDivider(bool should_enlarge) {
+  if (!divider_widget_ || !divider_widget_->GetNativeWindow()->IsVisible()) {
+    return;
+  }
+
+  divider_widget_->SetBounds(GetDividerBoundsInScreen(should_enlarge));
+  divider_view_->RefreshDividerHandler(should_enlarge);
+}
+
 void SplitViewDivider::SetAdjustable(bool adjustable) {
   if (adjustable == IsAdjustable()) {
     return;
@@ -319,7 +321,7 @@
   divider_widget_->GetNativeWindow()->SetEventTargetingPolicy(
       adjustable ? aura::EventTargetingPolicy::kTargetAndDescendants
                  : aura::EventTargetingPolicy::kNone);
-  divider_view_->SetDividerBarVisible(adjustable);
+  divider_view_->SetHandlerBarVisible(adjustable);
 }
 
 bool SplitViewDivider::IsAdjustable() const {
@@ -543,8 +545,13 @@
   CHECK(top_window);
   parent_container = top_window->parent();
   CHECK(parent_container);
+
+  const gfx::Rect initial_divider_bounds = GetDividerBoundsInScreen(
+      GetWorkAreaBoundsInScreen(observed_windows_[0].get()),
+      IsLayoutHorizontal(observed_windows_[0].get()), divider_position,
+      /*is_dragging=*/false);
   divider_widget_->Init(
-      CreateWidgetInitParams(parent_container, "SplitViewDivider"));
+      CreateWidgetInitParams(parent_container, initial_divider_bounds));
   divider_widget_->SetVisibilityAnimationTransition(
       views::Widget::ANIMATE_NONE);
   divider_view_ = divider_widget_->SetContentsView(
diff --git a/ash/wm/splitview/split_view_divider.h b/ash/wm/splitview/split_view_divider.h
index 5dd43d19..49fbc933 100644
--- a/ash/wm/splitview/split_view_divider.h
+++ b/ash/wm/splitview/split_view_divider.h
@@ -49,7 +49,7 @@
   SplitViewDivider& operator=(const SplitViewDivider&) = delete;
   ~SplitViewDivider() override;
 
-  // static version of GetDividerBoundsInScreen(bool is_dragging) function.
+  // static
   static gfx::Rect GetDividerBoundsInScreen(
       const gfx::Rect& work_area_bounds_in_screen,
       bool landscape,
@@ -61,6 +61,7 @@
   int divider_position() const { return divider_position_; }
 
   bool is_resizing_with_divider() const { return is_resizing_with_divider_; }
+
   // Does not consider any order of `observed_windows_`. Clients of the divider
   // are responsible for maintaining the order themselves.
   const aura::Window::Windows& observed_windows() const {
@@ -97,10 +98,6 @@
   // external events (split view ending, tablet mode ending, etc.).
   void CleanUpWindowResizing();
 
-  // Do the divider spawning animation that adds a finishing touch to the
-  // snapping animation of a window.
-  void DoSpawningAnimation(int spawn_position);
-
   // Updates `divider_widget_`'s bounds.
   void UpdateDividerBounds();
 
@@ -108,6 +105,10 @@
   // position.
   gfx::Rect GetDividerBoundsInScreen(bool is_dragging);
 
+  // Provides visual feedback by adjusting `divider_widget_` bounds in response
+  // to user hover or drag interactions (enlarged on interaction, thin default).
+  void EnlargeOrShrinkDivider(bool should_enlarge);
+
   // Sets the adjustability of the divider bar. Unadjustable divider does not
   // receive event and the divider bar view is not visible. When the divider is
   // moved for the virtual keyboard, the divider will be set unadjustable.
@@ -191,7 +192,7 @@
   //     |<---     divider_position_    --->|
   //     ---------------------------------------------------------------
   //     |                                  | |                        |
-  //     |        primary_window_           | |   secondary_window_    |
+  //     |        primary window            | |   secondary window     |
   //     |                                  | |                        |
   //     ---------------------------------------------------------------
   // Initialized as -1 before `divider_widget_` is created and shown.
diff --git a/ash/wm/splitview/split_view_divider_handler_view.cc b/ash/wm/splitview/split_view_divider_handler_view.cc
deleted file mode 100644
index 6e09e56..0000000
--- a/ash/wm/splitview/split_view_divider_handler_view.cc
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/wm/splitview/split_view_divider_handler_view.h"
-
-#include "ash/display/screen_orientation_controller.h"
-#include "ash/wm/splitview/split_view_constants.h"
-#include "ash/wm/splitview/split_view_divider.h"
-#include "ash/wm/splitview/split_view_utils.h"
-#include "base/memory/raw_ptr.h"
-#include "base/timer/timer.h"
-#include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
-#include "ui/compositor/layer.h"
-#include "ui/gfx/animation/animation_delegate.h"
-#include "ui/gfx/animation/slide_animation.h"
-#include "ui/views/background.h"
-
-namespace ash {
-
-class SplitViewDividerHandlerView::SelectionAnimation
-    : public gfx::SlideAnimation,
-      public gfx::AnimationDelegate {
- public:
-  explicit SelectionAnimation(SplitViewDividerHandlerView* white_handler_view)
-      : gfx::SlideAnimation(this), white_handler_view_(white_handler_view) {
-    SetSlideDuration(kSplitviewDividerSelectionStatusChangeDuration);
-    SetTweenType(gfx::Tween::EASE_IN);
-  }
-
-  SelectionAnimation(const SelectionAnimation&) = delete;
-  SelectionAnimation& operator=(const SelectionAnimation&) = delete;
-
-  ~SelectionAnimation() override = default;
-
-  void UpdateWhiteHandlerBounds() {
-    white_handler_view_->SetBounds(
-        CurrentValueBetween(kDividerHandlerShortSideLength,
-                            kDividerHandlerRadius * 2),
-        CurrentValueBetween(kDividerHandlerLongSideLength,
-                            kDividerHandlerRadius * 2),
-        /*signed_offset=*/0);
-  }
-
- private:
-  // gfx::AnimationDelegate:
-  void AnimationProgressed(const gfx::Animation* animation) override {
-    UpdateWhiteHandlerBounds();
-    white_handler_view_->UpdateCornerRadius(CurrentValueBetween(
-        kDividerHandlerCornerRadius, kDividerHandlerRadius));
-  }
-
-  raw_ptr<SplitViewDividerHandlerView> white_handler_view_;
-};
-
-class SplitViewDividerHandlerView::SpawningAnimation
-    : public gfx::SlideAnimation,
-      public gfx::AnimationDelegate {
- public:
-  SpawningAnimation(SplitViewDividerHandlerView* white_handler_view,
-                    int divider_signed_offset)
-      : gfx::SlideAnimation(this),
-        white_handler_view_(white_handler_view),
-        spawn_signed_offset_(divider_signed_offset +
-                             (divider_signed_offset > 0
-                                  ? kDividerHandlerSpawnUnsignedOffset
-                                  : -kDividerHandlerSpawnUnsignedOffset)) {
-    SetSlideDuration(kSplitviewDividerSpawnDuration);
-    SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
-  }
-
-  SpawningAnimation(const SpawningAnimation&) = delete;
-  SpawningAnimation& operator=(const SpawningAnimation&) = delete;
-
-  ~SpawningAnimation() override = default;
-
-  void Activate() {
-    white_handler_view_->SetVisible(false);
-    delay_timer_.Start(FROM_HERE, kSplitviewDividerSpawnDelay, this,
-                       &SpawningAnimation::StartAnimation);
-  }
-
-  bool IsActive() const { return delay_timer_.IsRunning() || is_animating(); }
-
-  void UpdateWhiteHandlerBounds() {
-    DCHECK(IsActive());
-    white_handler_view_->SetBounds(
-        kDividerHandlerShortSideLength,
-        CurrentValueBetween(kDividerHandlerSpawnLongSideLength,
-                            kDividerHandlerLongSideLength),
-        CurrentValueBetween(spawn_signed_offset_, 0));
-  }
-
- private:
-  void StartAnimation() {
-    DCHECK(!white_handler_view_->GetVisible());
-    white_handler_view_->SetVisible(true);
-    DCHECK(!is_animating());
-    Show();
-    DCHECK_EQ(0.0, GetCurrentValue());
-    UpdateWhiteHandlerBounds();
-  }
-
-  // gfx::AnimationDelegate:
-  void AnimationProgressed(const gfx::Animation* animation) override {
-    UpdateWhiteHandlerBounds();
-  }
-
-  raw_ptr<SplitViewDividerHandlerView> white_handler_view_;
-  int spawn_signed_offset_;
-  base::OneShotTimer delay_timer_;
-};
-
-SplitViewDividerHandlerView::SplitViewDividerHandlerView()
-    : selection_animation_(std::make_unique<SelectionAnimation>(this)) {
-  SetPaintToLayer();
-  SetBackground(views::CreateThemedRoundedRectBackground(
-      cros_tokens::kCrosSysOnSecondary, kDividerHandlerCornerRadius));
-}
-
-SplitViewDividerHandlerView::~SplitViewDividerHandlerView() = default;
-
-void SplitViewDividerHandlerView::DoSpawningAnimation(
-    int divider_signed_offset) {
-  spawning_animation_ =
-      std::make_unique<SpawningAnimation>(this, divider_signed_offset);
-  spawning_animation_->Activate();
-}
-
-void SplitViewDividerHandlerView::Refresh(bool is_resizing) {
-  spawning_animation_.reset();
-  SetVisible(true);
-  selection_animation_->UpdateWhiteHandlerBounds();
-  if (is_resizing)
-    selection_animation_->Show();
-  else
-    selection_animation_->Hide();
-}
-
-void SplitViewDividerHandlerView::UpdateCornerRadius(float radius) {
-  layer()->SetRoundedCornerRadius(gfx::RoundedCornersF{radius});
-}
-
-void SplitViewDividerHandlerView::SetBounds(int short_length,
-                                            int long_length,
-                                            int signed_offset) {
-  const bool landscape = parent()->width() == kSplitviewDividerShortSideLength;
-  gfx::Rect bounds = landscape ? gfx::Rect(short_length, long_length)
-                               : gfx::Rect(long_length, short_length);
-  bounds.Offset(parent()->GetLocalBounds().CenterPoint() -
-                bounds.CenterPoint() +
-                (landscape ? gfx::Vector2d(signed_offset, 0)
-                           : gfx::Vector2d(0, signed_offset)));
-  SetBoundsRect(bounds);
-}
-
-void SplitViewDividerHandlerView::OnPaint(gfx::Canvas* canvas) {
-  views::View::OnPaint(canvas);
-  // It's needed to avoid artifacts when tapping on the divider quickly.
-  canvas->DrawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc);
-  views::View::OnPaint(canvas);
-}
-
-BEGIN_METADATA(SplitViewDividerHandlerView)
-END_METADATA
-
-}  // namespace ash
diff --git a/ash/wm/splitview/split_view_divider_handler_view.h b/ash/wm/splitview/split_view_divider_handler_view.h
deleted file mode 100644
index 953245c3..0000000
--- a/ash/wm/splitview/split_view_divider_handler_view.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_HANDLER_VIEW_H_
-#define ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_HANDLER_VIEW_H_
-
-#include <memory>
-
-#include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/gfx/canvas.h"
-#include "ui/views/view.h"
-
-namespace ash {
-
-// The white handler bar in the middle of the divider.
-class SplitViewDividerHandlerView : public views::View {
-  METADATA_HEADER(SplitViewDividerHandlerView, views::View)
-
- public:
-  SplitViewDividerHandlerView();
-
-  SplitViewDividerHandlerView(const SplitViewDividerHandlerView&) = delete;
-  SplitViewDividerHandlerView& operator=(const SplitViewDividerHandlerView&) =
-      delete;
-
-  ~SplitViewDividerHandlerView() override;
-
-  // Play the white handler's part in the divider spawn animation.
-  // |divider_signed_offset| represents the motion of the center of the divider
-  // during the spawning animation. This parameter is used to make the white
-  // handler move with the center of the divider, as the two views are siblings
-  // because if the white handler view were a child of the divider view, then
-  // the transform that enlarges the divider during dragging would distort the
-  // white handler.
-  void DoSpawningAnimation(int divider_signed_offset);
-
-  // If the spawning animation is running, stop it and show the white handler.
-  // Update bounds. Do the enlarge/shrink animation when starting/ending
-  // dragging.
-  void Refresh(bool is_resizing);
-
-  // Updates the corner radius of the handler bar to |radius|. Happens during
-  // the animation of starting and ending dragging.
-  void UpdateCornerRadius(float radius);
-
- private:
-  class SelectionAnimation;
-  class SpawningAnimation;
-
-  void SetBounds(int short_length, int long_length, int signed_offset);
-
-  // views::View:
-  void OnPaint(gfx::Canvas* canvas) override;
-
-  // Handles the animations for starting and ending dragging.
-  std::unique_ptr<SelectionAnimation> selection_animation_;
-
-  // Handles the spawning animation.
-  std::unique_ptr<SpawningAnimation> spawning_animation_;
-};
-
-}  // namespace ash
-
-#endif  // ASH_WM_SPLITVIEW_SPLIT_VIEW_DIVIDER_HANDLER_VIEW_H_
diff --git a/ash/wm/splitview/split_view_divider_view.cc b/ash/wm/splitview/split_view_divider_view.cc
index 3e73a89..b6ed747 100644
--- a/ash/wm/splitview/split_view_divider_view.cc
+++ b/ash/wm/splitview/split_view_divider_view.cc
@@ -4,17 +4,14 @@
 
 #include "ash/wm/splitview/split_view_divider_view.h"
 
-#include "ash/display/screen_orientation_controller.h"
 #include "ash/public/cpp/window_properties.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/shell_delegate.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/icon_button.h"
-#include "ash/wm/snap_group/snap_group.h"
 #include "ash/wm/splitview/split_view_constants.h"
 #include "ash/wm/splitview/split_view_divider.h"
-#include "ash/wm/splitview/split_view_divider_handler_view.h"
 #include "ash/wm/splitview/split_view_utils.h"
 #include "base/functional/callback_helpers.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
@@ -25,7 +22,6 @@
 #include "ui/events/types/event_type.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/views/background.h"
-#include "ui/views/highlight_border.h"
 #include "ui/views/view.h"
 #include "ui/wm/core/coordinate_conversion.h"
 
@@ -33,6 +29,18 @@
 
 namespace {
 
+// The divider handler's default / enlarged short side length.
+constexpr int kDividerHandlerShortSideLength = 2;
+constexpr int kDividerHandlerEnlargedShortSideLength = 4;
+
+// The divider handler's default / enlarged long side length.
+constexpr int kDividerHandlerLongSideLength = 16;
+constexpr int kDividerHandlerEnlargedLongSideLength = 48;
+
+// The divider handler's default / enlarged corner radius.
+constexpr int kDividerHandlerCornerRadius = 1;
+constexpr int kDividerHandlerEnlargedCornerRadius = 2;
+
 // Distance between the bottom / right edge of the feedback button and the
 // bottom / right of the work area.
 constexpr int kFeedbackButtonDistanceFromEdge = 58;
@@ -46,9 +54,8 @@
 }  // namespace
 
 SplitViewDividerView::SplitViewDividerView(SplitViewDivider* divider)
-    : divider_handler_view_(
-          AddChildView(std::make_unique<SplitViewDividerHandlerView>())),
-      divider_(divider) {
+    : divider_(divider),
+      handler_view_(AddChildView(std::make_unique<views::View>())) {
   SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
 
   SetPaintToLayer(ui::LAYER_TEXTURED);
@@ -56,7 +63,7 @@
 
   SetBackground(
       views::CreateThemedSolidBackground(cros_tokens::kCrosSysSecondary));
-  RefreshFeedbackButton(false);
+  RefreshFeedbackButton(/*visible=*/false);
 }
 
 SplitViewDividerView::~SplitViewDividerView() = default;
@@ -65,37 +72,8 @@
   divider_ = nullptr;
 }
 
-void SplitViewDividerView::DoSpawningAnimation(int spawn_position) {
-  const gfx::Rect bounds = GetBoundsInScreen();
-  int divider_signed_offset;
-
-  // To animate the divider scaling up from nothing, animate its bounds rather
-  // than its transform, mostly because a transform that scales by zero would
-  // be singular. For that bounds animation, express `spawn_position` in local
-  // coordinates by subtracting a coordinate of the origin. Compute
-  // `divider_signed_offset` as described in the comment for
-  // `SplitViewDividerHandlerView::DoSpawningAnimation`.
-  if (IsCurrentScreenOrientationLandscape()) {
-    SetBounds(spawn_position - bounds.x(), 0, 0, bounds.height());
-    divider_signed_offset = spawn_position - bounds.CenterPoint().x();
-  } else {
-    SetBounds(0, spawn_position - bounds.y(), bounds.width(), 0);
-    divider_signed_offset = spawn_position - bounds.CenterPoint().y();
-  }
-
-  ui::LayerAnimator* divider_animator = layer()->GetAnimator();
-  ui::ScopedLayerAnimationSettings settings(divider_animator);
-  settings.SetTransitionDuration(kSplitviewDividerSpawnDuration);
-  settings.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
-  settings.SetPreemptionStrategy(ui::LayerAnimator::ENQUEUE_NEW_ANIMATION);
-  divider_animator->SchedulePauseForProperties(
-      kSplitviewDividerSpawnDelay, ui::LayerAnimationElement::BOUNDS);
-  SetBounds(0, 0, bounds.width(), bounds.height());
-  divider_handler_view_->DoSpawningAnimation(divider_signed_offset);
-}
-
-void SplitViewDividerView::SetDividerBarVisible(bool visible) {
-  divider_handler_view_->SetVisible(visible);
+void SplitViewDividerView::SetHandlerBarVisible(bool visible) {
+  handler_view_->SetVisible(visible);
 }
 
 void SplitViewDividerView::Layout(PassKey) {
@@ -109,17 +87,19 @@
   }
 
   SetBoundsRect(GetLocalBounds());
+  RefreshDividerHandler(/*hover=*/false);
   RefreshFeedbackButtonBounds();
-  divider_handler_view_->Refresh(divider_->is_resizing_with_divider());
 }
 
 void SplitViewDividerView::OnMouseEntered(const ui::MouseEvent& event) {
+  divider_->EnlargeOrShrinkDivider(/*should_enlarge=*/true);
+
   gfx::Point screen_location = event.location();
   ConvertPointToScreen(this, &screen_location);
 
+  // Show `feedback_button_` on mouse entered.
   if (!feedback_button_ ||
       !feedback_button_->GetBoundsInScreen().Contains(screen_location)) {
-    // Show `feedback_button_` on mouse entered.
     RefreshFeedbackButton(/*visible=*/true);
   }
 }
@@ -127,6 +107,14 @@
 void SplitViewDividerView::OnMouseExited(const ui::MouseEvent& event) {
   gfx::Point screen_location = event.location();
   ConvertPointToScreen(this, &screen_location);
+
+  if (handler_view_ &&
+      !handler_view_->GetBoundsInScreen().Contains(screen_location) &&
+      (!feedback_button_ ||
+       !feedback_button_->GetBoundsInScreen().Contains(screen_location))) {
+    divider_->EnlargeOrShrinkDivider(/*should_enlarge=*/false);
+  }
+
   // Hide `feedback_button_` on mouse exited.
   if (feedback_button_ &&
       !feedback_button_->GetBoundsInScreen().Contains(screen_location)) {
@@ -143,7 +131,9 @@
 
 bool SplitViewDividerView::OnMouseDragged(const ui::MouseEvent& event) {
   CHECK(divider_);
+  divider_->EnlargeOrShrinkDivider(/*should_enlarge=*/true);
   RefreshFeedbackButton(/*visible=*/false);
+
   if (!mouse_move_started_) {
     // If this is the first mouse drag event, start the resize and reset
     // `mouse_move_started_`.
@@ -178,6 +168,7 @@
     // synthetic touch event.
     return;
   }
+
   gfx::Point location(event->location());
   views::View::ConvertPointToScreen(this, &location);
   switch (event->type()) {
@@ -187,15 +178,29 @@
       }
       break;
     case ui::ET_GESTURE_TAP_DOWN:
+      divider_->EnlargeOrShrinkDivider(/*should_enlarge=*/true);
+      RefreshFeedbackButton(/*visible=*/true);
+      break;
+    case ui::ET_GESTURE_TAP_CANCEL:
+      divider_->EnlargeOrShrinkDivider(/*should_enlarge=*/false);
+      RefreshFeedbackButton(/*visible=*/false);
       break;
     case ui::ET_GESTURE_SCROLL_BEGIN:
       StartResizing(location);
+      RefreshFeedbackButton(/*visible=*/true);
       break;
     case ui::ET_GESTURE_SCROLL_UPDATE:
       divider_->ResizeWithDivider(location);
+      RefreshFeedbackButton(/*visible=*/true);
+      break;
+    case ui::ET_GESTURE_SCROLL_END:
+      divider_->EnlargeOrShrinkDivider(/*should_enlarge=*/false);
+      RefreshFeedbackButton(/*visible=*/false);
       break;
     case ui::ET_GESTURE_END:
       EndResizing(location, /*swap_windows=*/false);
+      divider_->EnlargeOrShrinkDivider(/*should_enlarge=*/false);
+      RefreshFeedbackButton(/*visible=*/true);
       break;
 
     default:
@@ -221,49 +226,36 @@
   divider_->SwapWindows();
 }
 
-void SplitViewDividerView::OnResizeStatusChanged() {
-  // If split view has ended, the divider widget will be closing. In this case
-  // no need to update the divider layout and do the animation.
-  if (!divider_ || !divider_->divider_widget()) {
-    return;
-  }
-
-  // If `divider_view_`'s bounds are animating, it is for the divider spawning
-  // animation. Stop that before animating `divider_view_`'s transform.
-  ui::LayerAnimator* divider_animator = layer()->GetAnimator();
-  divider_animator->StopAnimatingProperty(ui::LayerAnimationElement::BOUNDS);
-
-  // Do the divider enlarge/shrink animation when starting/ending dragging.
-  const bool is_resizing = divider_->is_resizing_with_divider();
-  SetBoundsRect(GetLocalBounds());
-  const gfx::Rect old_bounds =
-      divider_->GetDividerBoundsInScreen(/*is_dragging=*/false);
-  const gfx::Rect new_bounds = divider_->GetDividerBoundsInScreen(is_resizing);
-  gfx::Transform transform;
-  transform.Translate(new_bounds.x() - old_bounds.x(),
-                      new_bounds.y() - old_bounds.y());
-  transform.Scale(
-      static_cast<float>(new_bounds.width()) / old_bounds.width(),
-      static_cast<float>(new_bounds.height()) / old_bounds.height());
-  ui::ScopedLayerAnimationSettings settings(divider_animator);
-  settings.SetTransitionDuration(
-      kSplitviewDividerSelectionStatusChangeDuration);
-  settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
-  settings.SetPreemptionStrategy(
-      ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
-  SetTransform(transform);
-
-  divider_handler_view_->Refresh(is_resizing);
-}
-
 void SplitViewDividerView::StartResizing(gfx::Point location) {
   CHECK(divider_);
-  // `StartResizeWithDivider()` may cause this view to be destroyed.
-  auto weak_ptr = weak_ptr_factory_.GetWeakPtr();
   divider_->StartResizeWithDivider(location);
-  if (weak_ptr) {
-    OnResizeStatusChanged();
+}
+
+void SplitViewDividerView::RefreshDividerHandler(bool should_enlarge) {
+  CHECK(divider_);
+
+  const gfx::Point divider_center = bounds().CenterPoint();
+  const int handler_short_side = should_enlarge
+                                     ? kDividerHandlerEnlargedShortSideLength
+                                     : kDividerHandlerShortSideLength;
+  const int handler_long_side = should_enlarge
+                                    ? kDividerHandlerEnlargedLongSideLength
+                                    : kDividerHandlerLongSideLength;
+  if (IsLayoutHorizontal(divider_->GetRootWindow())) {
+    handler_view_->SetBounds(divider_center.x() - handler_short_side / 2,
+                             divider_center.y() - handler_long_side / 2,
+                             handler_short_side, handler_long_side);
+  } else {
+    handler_view_->SetBounds(divider_center.x() - handler_long_side / 2,
+                             divider_center.y() - handler_short_side / 2,
+                             handler_long_side, handler_short_side);
   }
+
+  handler_view_->SetBackground(views::CreateThemedRoundedRectBackground(
+      cros_tokens::kCrosSysOnSecondary,
+      should_enlarge ? kDividerHandlerEnlargedCornerRadius
+                     : kDividerHandlerCornerRadius));
+  handler_view_->SetVisible(true);
 }
 
 void SplitViewDividerView::RefreshFeedbackButton(bool visible) {
@@ -322,7 +314,7 @@
   if (!weak_ptr) {
     return;
   }
-  OnResizeStatusChanged();
+
   if (swap_windows) {
     SwapWindows();
   }
diff --git a/ash/wm/splitview/split_view_divider_view.h b/ash/wm/splitview/split_view_divider_view.h
index 58a7f45..ae50beb 100644
--- a/ash/wm/splitview/split_view_divider_view.h
+++ b/ash/wm/splitview/split_view_divider_view.h
@@ -13,9 +13,20 @@
 
 class IconButton;
 class SplitViewDivider;
-class SplitViewDividerHandlerView;
 
-// A view that acts as the contents view of the split view divider widget.
+// A view that acts as the content view within a split view divider widget.
+// It hosts two child views: a handler view and a feedback button. Its
+// responsibility is to update the bounds and visibility of its child views in
+// response to located events.
+//          | |
+//          | |
+//          |||<-----handler_view_
+//          |||
+//          | |
+//         +---+
+//         |   |<----feedback_button_
+//         +---+
+//          | |
 class SplitViewDividerView : public views::View,
                              public views::ViewTargeterDelegate {
   METADATA_HEADER(SplitViewDividerView, views::View)
@@ -26,8 +37,7 @@
   SplitViewDividerView& operator=(const SplitViewDividerView&) = delete;
   ~SplitViewDividerView() override;
 
-  void DoSpawningAnimation(int spawn_position);
-  void SetDividerBarVisible(bool visible);
+  void SetHandlerBarVisible(bool visible);
 
   // Called explicitly by SplitViewDivider when the divider widget is closing.
   void OnDividerClosing();
@@ -49,9 +59,9 @@
   IconButton* feedback_button_for_testing() const { return feedback_button_; }
 
  private:
-  void SwapWindows();
+  friend class SplitViewDivider;
 
-  void OnResizeStatusChanged();
+  void SwapWindows();
 
   void StartResizing(gfx::Point location);
 
@@ -59,6 +69,10 @@
   // `swap_windows` is true, swaps the windows after resizing.
   void EndResizing(gfx::Point location, bool swap_windows);
 
+  // Refreshes the divider handler's bounds and rounded corners  in response to
+  // changes in the divider's dimensions or display properties.
+  void RefreshDividerHandler(bool should_enlarge);
+
   // Initializes, refreshes bounds, or updates visibility for the
   // `feedback_button_` on the divider.
   void RefreshFeedbackButton(bool visible);
@@ -76,9 +90,9 @@
   // a resize.
   bool mouse_move_started_ = false;
 
-  raw_ptr<SplitViewDividerHandlerView> divider_handler_view_ = nullptr;
   raw_ptr<SplitViewDivider> divider_;
 
+  raw_ptr<views::View> handler_view_ = nullptr;
   raw_ptr<IconButton> feedback_button_ = nullptr;
 
   base::WeakPtrFactory<SplitViewDividerView> weak_ptr_factory_{this};
diff --git a/ash/wm/window_restore/pine_controller.cc b/ash/wm/window_restore/pine_controller.cc
index 5b22e54..838ccc6 100644
--- a/ash/wm/window_restore/pine_controller.cc
+++ b/ash/wm/window_restore/pine_controller.cc
@@ -22,6 +22,7 @@
 #include "ash/style/dark_light_mode_controller_impl.h"
 #include "ash/style/system_dialog_delegate_view.h"
 #include "ash/system/toast/toast_manager_impl.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
@@ -213,7 +214,7 @@
 
 void PineController::MaybeStartPineOverviewSession(
     std::unique_ptr<PineContentsData> pine_contents_data) {
-  CHECK(features::IsForestFeatureEnabled());
+  CHECK(IsForestFeatureEnabled());
 
   if (OverviewController::Get()->InOverviewSession()) {
     return;
@@ -267,12 +268,19 @@
 void PineController::OnOverviewModeEndingAnimationComplete(bool canceled) {
   // If `canceled` is true, overview was reentered before the exit animations
   // were finished. `in_pine_` will be reset the next time overview ends.
-  if (canceled || !in_pine_ || !features::IsForestFeatureEnabled()) {
+  if (canceled || !in_pine_) {
     return;
   }
 
   in_pine_ = false;
 
+  // In multi-user scenario, forest may have been available for the user that
+  // started overview, but not for the current user. (Switching users ends
+  // overview.)
+  if (!IsForestFeatureEnabled()) {
+    return;
+  }
+
   PrefService* prefs = GetActivePrefService();
   if (!prefs) {
     return;
diff --git a/base/containers/checked_iterators.h b/base/containers/checked_iterators.h
index 6f62aa81..59654830 100644
--- a/base/containers/checked_iterators.h
+++ b/base/containers/checked_iterators.h
@@ -194,7 +194,7 @@
     CHECK_EQ(end_, other.end_);
   }
 
-  // RAW_PTR_EXCLUSION: T can be a STACK_ALLOCATED class.
+  // RAW_PTR_EXCLUSION: The embedding class is stack-scoped.
   RAW_PTR_EXCLUSION const T* start_ = nullptr;
   RAW_PTR_EXCLUSION T* current_ = nullptr;
   RAW_PTR_EXCLUSION const T* end_ = nullptr;
diff --git a/base/files/file_util.h b/base/files/file_util.h
index 73f39bd..3c19ee8 100644
--- a/base/files/file_util.h
+++ b/base/files/file_util.h
@@ -525,6 +525,15 @@
 #endif
 
 // This function will return if the given file is a symlink or not.
+//
+// IMPORTANT NOTE: This method is subject to race conditions, meaning its
+// results might not always accurately reflect the current state of the file
+// system by the time they are used. Specifically, the link target could change
+// between the time of this check and subsequent operations, leading to
+// potential inconsistencies. Therefore, this method should only be used by
+// callers that need to know nothing more than whether or not a given directory
+// entry is a symlink. When the path to the target is required, callers should
+// instead use `base::ReadSymbolicLink()` or `base::ReadSymbolicLinkAbsolute()`.
 BASE_EXPORT bool IsLink(const FilePath& file_path);
 
 // Returns information about the given file path. Also see |File::GetInfo|.
diff --git a/base/files/file_util_unittest.cc b/base/files/file_util_unittest.cc
index 1421794a..6a92587 100644
--- a/base/files/file_util_unittest.cc
+++ b/base/files/file_util_unittest.cc
@@ -99,6 +99,18 @@
 
 const size_t kLargeFileSize = (1 << 16) + 3;
 
+enum class CreateSymbolicLinkResult {
+  // The symbolic link creation failed because the platform does not support it.
+  // On Windows, that may be due to the lack of the required privilege.
+  kUnsupported = -1,
+
+  // The symbolic link creation failed.
+  kFailed,
+
+  // The symbolic link was created successfully.
+  kSucceeded,
+};
+
 #if BUILDFLAG(IS_WIN)
 // Method that wraps the win32 GetShortPathName API. Returns an empty path on
 // error.
@@ -116,8 +128,51 @@
 
   return FilePath(path_short_str);
 }
+
+CreateSymbolicLinkResult CreateWinSymbolicLink(const FilePath& target,
+                                               const FilePath& symlink) {
+  // Determine whether the target is a directory. This is necessary because
+  // creating a symbolic link requires different flags depending on the type
+  // of the target (file vs. directory).
+  DWORD attrs = GetFileAttributes(target.value().c_str());
+  if (attrs == INVALID_FILE_ATTRIBUTES) {
+    // Unable to retrieve attributes for the target. It might not exist, or
+    // there may be a permissions issue. Either way, we cannot proceed.
+    return CreateSymbolicLinkResult::kFailed;
+  }
+
+  DWORD flags =
+      attrs & FILE_ATTRIBUTE_DIRECTORY ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
+
+  if (!::CreateSymbolicLink(
+          symlink.value().c_str(), target.value().c_str(),
+          flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)) {
+    if (::GetLastError() == ERROR_PRIVILEGE_NOT_HELD) {
+      return CreateSymbolicLinkResult::kUnsupported;
+    }
+    return CreateSymbolicLinkResult::kFailed;
+  }
+
+  return CreateSymbolicLinkResult::kSucceeded;
+}
 #endif  // BUILDFLAG(IS_WIN)
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
+
+CreateSymbolicLinkResult CreateSymbolicLinkForTesting(const FilePath& target,
+                                                      const FilePath& symlink) {
+#if BUILDFLAG(IS_WIN)
+  return CreateWinSymbolicLink(target, symlink);
+#else
+  if (!CreateSymbolicLink(target, symlink)) {
+    return CreateSymbolicLinkResult::kFailed;
+  }
+  return CreateSymbolicLinkResult::kSucceeded;
+#endif  // BUILDFLAG(IS_WIN)
+}
+
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
+
 #if BUILDFLAG(IS_MAC)
 // Provide a simple way to change the permissions bits on |path| in tests.
 // ASSERT failures will return, but not stop the test.  Caller should wrap
@@ -391,6 +446,112 @@
       .IsParent(normalized_file_b_path.DirName()));
 }
 
+#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
+
+TEST_F(FileUtilTest, IsLinkCreateSymbolicLinkOnFile) {
+  FilePath target_file_path;
+  ASSERT_TRUE(CreateTemporaryFileInDir(temp_dir_.GetPath(), &target_file_path));
+  EXPECT_FALSE(IsLink(target_file_path));
+
+  base::FilePath symlink_path =
+      temp_dir_.GetPath().Append(FPL("symlink_to_target"));
+
+  CreateSymbolicLinkResult result =
+      CreateSymbolicLinkForTesting(target_file_path, symlink_path);
+  if (result == CreateSymbolicLinkResult::kUnsupported) {
+    GTEST_SKIP();
+  }
+  ASSERT_EQ(result, CreateSymbolicLinkResult::kSucceeded);
+
+  EXPECT_TRUE(IsLink(symlink_path));
+}
+
+TEST_F(FileUtilTest, IsLinkCreateSymbolicLinkOnDirectory) {
+  FilePath target_path = temp_dir_.GetPath().Append(FPL("target"));
+  ASSERT_TRUE(CreateDirectory(target_path));
+  EXPECT_FALSE(IsLink(target_path));
+
+  base::FilePath symlink_path =
+      temp_dir_.GetPath().Append(FPL("symlink_to_target"));
+
+  CreateSymbolicLinkResult result =
+      CreateSymbolicLinkForTesting(target_path, symlink_path);
+  if (result == CreateSymbolicLinkResult::kUnsupported) {
+    GTEST_SKIP();
+  }
+  ASSERT_EQ(result, CreateSymbolicLinkResult::kSucceeded);
+
+  EXPECT_TRUE(IsLink(symlink_path));
+}
+
+TEST_F(FileUtilTest, IsLinkMissingFile) {
+  EXPECT_FALSE(IsLink(FilePath()));
+}
+
+TEST_F(FileUtilTest, IsLinkWithDeletedTargetFile) {
+  // Set up a symlink pointing to a temporary file.
+  FilePath target_file_path;
+  ASSERT_TRUE(CreateTemporaryFileInDir(temp_dir_.GetPath(), &target_file_path));
+  FilePath symlink_path =
+      temp_dir_.GetPath().Append(FPL("symlink_to_missing_target"));
+
+  CreateSymbolicLinkResult result =
+      CreateSymbolicLinkForTesting(target_file_path, symlink_path);
+  if (result == CreateSymbolicLinkResult::kUnsupported) {
+    GTEST_SKIP();
+  }
+  ASSERT_EQ(result, CreateSymbolicLinkResult::kSucceeded);
+
+  // Verify that the symlink is recognized correctly.
+  EXPECT_TRUE(IsLink(symlink_path));
+
+  // Delete the target file.
+  ASSERT_TRUE(DeleteFile(target_file_path));
+
+  // Verify that IsLink still returns true for the symlink, even though its
+  // target is missing.
+  EXPECT_TRUE(IsLink(symlink_path));
+}
+
+TEST_F(FileUtilTest, IsLinkWithDeletedTargetDirectory) {
+  // Set up a symlink pointing to a temporary file.
+  FilePath target_path = temp_dir_.GetPath().Append(FPL("target"));
+  ASSERT_TRUE(CreateDirectory(target_path));
+  FilePath symlink_path =
+      temp_dir_.GetPath().Append(FPL("symlink_to_missing_target"));
+
+  CreateSymbolicLinkResult result =
+      CreateSymbolicLinkForTesting(target_path, symlink_path);
+  if (result == CreateSymbolicLinkResult::kUnsupported) {
+    GTEST_SKIP();
+  }
+  ASSERT_EQ(result, CreateSymbolicLinkResult::kSucceeded);
+
+  // Verify that the symlink is recognized correctly.
+  EXPECT_TRUE(IsLink(symlink_path));
+
+  // Delete the target file.
+  ASSERT_TRUE(DeleteFile(target_path));
+
+  // Verify that IsLink still returns true for the symlink, even though its
+  // target is missing.
+  EXPECT_TRUE(IsLink(symlink_path));
+}
+
+TEST_F(FileUtilTest, IsLinkWihtoutReparsePointAttributeOnDirectory) {
+  FilePath target_path = temp_dir_.GetPath().Append(FPL("target"));
+  ASSERT_TRUE(CreateDirectory(target_path));
+  EXPECT_FALSE(IsLink(target_path));
+}
+
+TEST_F(FileUtilTest, IsLinkWihtoutReparsePointAttributeOnFile) {
+  FilePath target_file_path;
+  ASSERT_TRUE(CreateTemporaryFileInDir(temp_dir_.GetPath(), &target_file_path));
+  EXPECT_FALSE(IsLink(target_file_path));
+}
+
+#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_POSIX)
+
 #if BUILDFLAG(IS_WIN)
 
 TEST_F(FileUtilTest, NormalizeFileEmptyFile) {
diff --git a/base/files/file_util_win.cc b/base/files/file_util_win.cc
index 60df40b..8780301b 100644
--- a/base/files/file_util_win.cc
+++ b/base/files/file_util_win.cc
@@ -903,10 +903,37 @@
                           nullptr);
 }
 
-// TODO(rkc): Work out if we want to handle NTFS junctions here or not, handle
-// them if we do decide to.
 bool IsLink(const FilePath& file_path) {
-  return false;
+  ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK);
+
+  // Opens the file or directory specified by file_path for querying attributes.
+  // No access rights are requested (FILE_READ_ATTRIBUTES), as we're only
+  // interested in the attributes. The file share mode allows other processes to
+  // read, write, and delete the file while we have it open. The flags
+  // FILE_FLAG_BACKUP_SEMANTICS and FILE_FLAG_OPEN_REPARSE_POINT are used to
+  // ensure we can open directories and work with reparse points, respectively.
+  //
+  // NOTE: In future, we can consider using GetFileInformationByName(...)
+  // instead.
+  win::ScopedHandle file(
+      ::CreateFile(file_path.value().c_str(), FILE_READ_ATTRIBUTES,
+                   FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+                   /*lpSecurityAttributes=*/nullptr, OPEN_EXISTING,
+                   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+                   /*hTemplateFile=*/nullptr));
+
+  if (!file.is_valid()) {
+    return false;
+  }
+
+  FILE_ATTRIBUTE_TAG_INFO attr_taginfo;
+  if (!::GetFileInformationByHandleEx(file.get(), FileAttributeTagInfo,
+                                      &attr_taginfo, sizeof(attr_taginfo))) {
+    return false;
+  }
+
+  return (attr_taginfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
+         (attr_taginfo.ReparseTag == IO_REPARSE_TAG_SYMLINK);
 }
 
 bool GetFileInfo(const FilePath& file_path, File::Info* results) {
diff --git a/build/config/sanitizers/sanitizers.gni b/build/config/sanitizers/sanitizers.gni
index 425e11f..792548d2 100644
--- a/build/config/sanitizers/sanitizers.gni
+++ b/build/config/sanitizers/sanitizers.gni
@@ -116,6 +116,14 @@
 
   # When true, sanitizer warnings will cause test case failures.
   fail_on_san_warnings = false
+
+  # When true, only builds fuzzer targets that require high end machines to run.
+  # Otherwise, builds all the targets.
+  # TODO(paulsemel): once we have everything implemented on the recipe side, we
+  # can change the behaviour for the false case, and only build the non high-end
+  # jobs, so that they do not appear in the zip. As for now, this behaviour
+  # ensures nothing breaks.
+  high_end_fuzzer_targets = false
 }
 
 declare_args() {
diff --git a/build/config/siso/main.star b/build/config/siso/main.star
index e30ba95..7e07066 100644
--- a/build/config/siso/main.star
+++ b/build/config/siso/main.star
@@ -53,16 +53,13 @@
         "./obj/chrome/browser/browser/chrome_content_browser_client.o",
         "./obj/chrome/browser/browser/render_view_context_menu.o",
         "./obj/chrome/browser/ui/ash/holding_space/browser_tests/holding_space_ui_browsertest.o",
-        "./obj/chrome/test/browser_tests/app_list_client_impl_browsertest.o",
         "./obj/chrome/test/browser_tests/browser_non_client_frame_view_chromeos_browsertest.o",
         "./obj/chrome/test/browser_tests/chrome_shelf_controller_browsertest.o",
         "./obj/chrome/test/browser_tests/device_local_account_browsertest.o",
         "./obj/chrome/test/browser_tests/file_manager_browsertest_base.o",
         "./obj/chrome/test/browser_tests/full_restore_app_launch_handler_browsertest.o",
-        "./obj/chrome/test/browser_tests/remote_apps_manager_browsertest.o",
         "./obj/chrome/test/browser_tests/safe_browsing_blocking_page_test.o",
         "./obj/chrome/test/browser_tests/scalable_iph_browsertest.o",
-        "./obj/chrome/test/browser_tests/spoken_feedback_browsertest.o",
         "./obj/chrome/test/interactive_ui_tests/local_card_migration_uitest.o",
         "./obj/chrome/test/interactive_ui_tests/system_web_app_interactive_uitest.o",
         "./obj/chrome/test/interactive_ui_tests/tab_drag_controller_interactive_uitest.o",
@@ -103,6 +100,7 @@
         # target_os = "android"
         # use_remoteexec = true
         "./android_clang_arm/obj/content/browser/browser/browser_interface_binders.o",
+        "./obj/content/test/content_unittests__library/auction_runner_unittest.o",
 
         # Fallback happens with follwoing args.gn (try/fuchsia-x64-cast-receiver-rel).
         # Fallback may happen in other build config too.
diff --git a/chrome/MAJOR_BRANCH_DATE b/chrome/MAJOR_BRANCH_DATE
index b4fe844..a5f277cf 100644
--- a/chrome/MAJOR_BRANCH_DATE
+++ b/chrome/MAJOR_BRANCH_DATE
@@ -1 +1 @@
-MAJOR_BRANCH_DATE=2024-03-19
+MAJOR_BRANCH_DATE=2024-04-16
diff --git a/chrome/VERSION b/chrome/VERSION
index 05a34bd9..0c87028f2 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
-MAJOR=125
+MAJOR=126
 MINOR=0
-BUILD=6422
+BUILD=6423
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index a539a43..fe9f698 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2857,6 +2857,7 @@
       "//chrome/browser/android/intents:unit_device_javatests",
       "//chrome/browser/back_press/android:unit_device_javatests",
       "//chrome/browser/download/internal/android:unit_device_javatests",
+      "//chrome/browser/history:unit_device_javatests",
       "//chrome/browser/hub:unit_device_javatests",
       "//chrome/browser/hub/internal:unit_device_javatests",
       "//chrome/browser/image_descriptions:unit_device_javatests",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index cf84c67..18b91de5 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -4157,6 +4157,10 @@
      flag_descriptions::kAudioSuppressSetRTCAudioActiveName,
      flag_descriptions::kAudioSuppressSetRTCAudioActiveDescription, kOsCrOS,
      PLATFORM_FEATURE_NAME_TYPE("CrOSLateBootAudioSuppressSetRTCAudioActive")},
+    {"cras-processor-dedicated-thread",
+     flag_descriptions::kCrasProcessorDedicatedThreadName,
+     flag_descriptions::kCrasProcessorDedicatedThreadDescription, kOsCrOS,
+     PLATFORM_FEATURE_NAME_TYPE("CrOSLateBootCrasProcessorDedicatedThread")},
     {"cras-split-alsa-usb-internal",
      flag_descriptions::kCrasSplitAlsaUsbInternalName,
      flag_descriptions::kCrasSplitAlsaUsbInternalDescription, kOsCrOS,
diff --git a/chrome/browser/apps/app_preload_service/proto/app_preload.proto b/chrome/browser/apps/app_preload_service/proto/app_preload.proto
index 4967ef5..f040482 100644
--- a/chrome/browser/apps/app_preload_service/proto/app_preload.proto
+++ b/chrome/browser/apps/app_preload_service/proto/app_preload.proto
@@ -26,16 +26,26 @@
   // A list of zero or more apps for APS to install.
   repeated App apps_to_install = 1;
 
+  // The Launcher Configuration.
+  repeated LauncherConfig launcher_config = 2;
+
+  // The Shelf Configuration.
+  repeated ShelfConfig shelf_config = 3;
+
   enum InstallReason {
     // Default for deserialization when an unexpected value is encountered.
-    // Indicates to the client that the server has a new reason and needs
-    // the proto file updated.
+    // Usually indicates to the client that the server has a new reason and
+    // needs the proto file updated.
     INSTALL_REASON_UNKNOWN = 0;
 
-    // A Default App.
+    // An app other than an OEM app. This should be pinned in the Launcher at
+    // the position that matches the PackageId or, if not specified, into the
+    // end of the "OTHER" position, as defined in the launcher config.
     INSTALL_REASON_DEFAULT = 1;
 
-    // An app installed for an OEM.
+    // An app installed for an OEM. This should be placed into the "OEM" folder
+    // defined in the launcher config.
+    // (note: position in the OEM folder itself does not matter).
     INSTALL_REASON_OEM = 2;
 
     // An app which is being returned by the server for testing purposes.
@@ -44,26 +54,12 @@
     INSTALL_REASON_TEST = 3;
   }
 
-  message Icon {
-    // Url to query to get the icon. This will always be from the host
-    // meltingpot.googleusercontent.com.
-    optional string url = 1;
-
-    // Width of the icon in pixels. While App icons are typically square
-    // note there is no guarantee the image provided will be.
-    optional uint32 width_in_pixels = 2;
-
-    // Mime type of the icon.
-    optional string mime_type = 3;
-
-    // Whether or not we have permission from the platform to mask the icon.
-    optional bool is_masking_allowed = 4;
-  }
-
   // For Android-only metadata.
+  // Note: Once Unified App Install has been hooked up, this will be deprecated.
   message AndroidExtras {}
 
   // For Web-only metadata.
+  // Note: Once Unified App Install has been hooked up, this will be deprecated.
   message WebExtras {
     // A URL to the web app's manifest in json format. This will always be from
     // the host meltingpot.googleusercontent.com.
@@ -88,17 +84,103 @@
     // The App's UTF-8 encoded name in the requested language (or next best).
     optional string name = 3;
 
-    // One or more Icons for this App for the requested language (or next best).
-    repeated Icon icons = 4;
+    // Icons (which are not currently used).
+    reserved 4;
 
     // The reason why this app is in the list.
     optional InstallReason install_reason = 5;
 
+    // An optional campaign code for this preload.
+    optional string campaign_code = 8;
+
+    // The localised folder name that this preload should be placed in.
+    // Note: this is not populated and should be ignored for OEM preloads.
+    optional string folder_name = 9;
+
     // Every platform has its own [Platform]Extras message to store platform
     // specific metadata.
+    // Note: Once Unified App Install has been hooked up, this will be
+    // deprecated.
     oneof extras {
       AndroidExtras android_extras = 6;
       WebExtras web_extras = 7;
     }
   }
+
+  // Configuration defining the order of apps pinned in the Shelf.
+  message ShelfConfig {
+    // The order of the entries in this config. Sort by this value and then
+    // process in ascending order.
+    optional uint32 order = 1;
+
+    // An optional feature flag. If specified, evaluate the flag and ignore this
+    // entry if the feature is not enabled.
+    optional string feature_flag = 2;
+
+    // The identifier for the app for this slot (usually one). If more than one
+    // is specified, evaluate the entries in order and place the first app
+    // detected on the device in this slot. Ignore the remaining entries.
+    repeated string package_id = 3;
+  }
+
+  // Indicates the type of Launcher entry.
+  enum LauncherType {
+    // Default for deserialization when an unexpected value is encountered.
+    // Usually Indicates to the client that the server has a new reason and
+    // needs the proto file updated.
+    LAUNCHER_TYPE_UNKNOWN = 0;
+
+    // Indicates where the Chrome Browser (Ash or Lacross) should be placed.
+    // Note: this may be in a folder (see below).
+    LAUNCHER_TYPE_CHROME = 1;
+
+    // An App with the `package_id` should be placed in this position.
+    // Note: this may be in a folder (see below).
+    LAUNCHER_TYPE_APP = 2;
+
+    // All other apps not explicitly listed should be installed in this
+    // position in the order they are installed (ie. doesn't matter).
+    // Note: this may be in a folder (see below).
+    LAUNCHER_TYPE_OTHER = 3;
+
+    // Indicates the position of the OEM folder which should be named with the
+    // localised string in `folder_name`. Preloads marked "INSTALL_REASON_OEM"
+    // should be placed in this folder in the order they install (ie. doesn't
+    // matter).
+    LAUNCHER_TYPE_FOLDER_OEM = 4;
+
+    // Indicates an arbitrary folder should be created in this position named
+    // with the string in `folder_name`. This will have a child configuration
+    // which can only contain Apps (ie. no nested folders).
+    LAUNCHER_TYPE_FOLDER = 5;
+  }
+
+  // Configuration defining the order of items pinned in the Launcher.
+  message LauncherConfig {
+    // Indicates what type of entry this is in the config (see above).
+    optional LauncherType type = 1;
+
+    // The order of the entries in this config. Sort by this value and then
+    // process in ascending order.
+    optional uint32 order = 2;
+
+    // An optional feature flag. If specified, evaluate the flag and ignore
+    // this entry if the feature is not enabled.
+    optional string feature_flag = 3;
+
+    // The identifier for the app for this slot (usually one). If more than one
+    // is specified, evaluate the entries in order and place the first app
+    // detected on the device in this slot. Ignore the remaining entries.
+    repeated string package_id = 4;
+
+    // For LAUNCHER_TYPE_FOLDER_OEM and LAUNCHER_TYPE_FOLDER, the localised name
+    // to use for the folder.
+    optional string folder_name = 5;
+
+    // For LAUNCHER_TYPE_FOLDER the nested configuration controlling the
+    // placement of apps within the folder.
+    // Note: this is not recursive (ie. only valid for 1 level down).
+    // Install the child apps in this configuration in the order specified.
+    repeated LauncherConfig child_config = 6;
+  }
 }
diff --git a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
index fec65ad..fd223e7 100644
--- a/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
+++ b/chrome/browser/ash/accessibility/spoken_feedback_browsertest.cc
@@ -51,7 +51,8 @@
 #include "chrome/browser/ash/login/test/device_state_mixin.h"
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/test/oobe_base_test.h"
-#include "chrome/browser/ash/login/wizard_controller.h"
+#include "chrome/browser/ash/login/ui/login_display_host.h"
+#include "chrome/browser/ash/login/wizard_context.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/ash/shelf/app_shortcut_shelf_item_controller.h"
 #include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
diff --git a/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc b/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
index 4ba5617..421c76c 100644
--- a/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
+++ b/chrome/browser/ash/app_list/app_list_client_impl_browsertest.cc
@@ -56,7 +56,6 @@
 #include "chrome/browser/ash/login/test/login_manager_mixin.h"
 #include "chrome/browser/ash/login/ui/user_adding_screen.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
-#include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
diff --git a/chrome/browser/ash/app_restore/full_restore_prefs.cc b/chrome/browser/ash/app_restore/full_restore_prefs.cc
index 1024add2..2725f52 100644
--- a/chrome/browser/ash/app_restore/full_restore_prefs.cc
+++ b/chrome/browser/ash/app_restore/full_restore_prefs.cc
@@ -4,8 +4,8 @@
 
 #include "chrome/browser/ash/app_restore/full_restore_prefs.h"
 
-#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
+#include "ash/utility/forest_util.h"
 #include "ash/wm/window_restore/window_restore_util.h"
 #include "chrome/browser/prefs/session_startup_pref.h"
 #include "chrome/common/pref_names.h"
@@ -24,7 +24,7 @@
       static_cast<int>(RestoreOption::kAskEveryTime),
       user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
 
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureFlagEnabled()) {
     registry->RegisterBooleanPref(prefs::kShouldShowPineOnboarding, true);
     registry->RegisterIntegerPref(prefs::kPineNudgeShownCount, 0);
     registry->RegisterTimePref(prefs::kPineNudgeLastShown, base::Time());
diff --git a/chrome/browser/ash/app_restore/full_restore_service.cc b/chrome/browser/ash/app_restore/full_restore_service.cc
index a9f8a8a..8954b13c 100644
--- a/chrome/browser/ash/app_restore/full_restore_service.cc
+++ b/chrome/browser/ash/app_restore/full_restore_service.cc
@@ -11,6 +11,7 @@
 #include "ash/glanceables/post_login_glanceables_metrics_recorder.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/shell.h"
+#include "ash/utility/forest_util.h"
 #include "ash/webui/settings/public/constants/routes.mojom.h"
 #include "ash/webui/settings/public/constants/setting.mojom-shared.h"
 #include "ash/wm/desks/templates/saved_desk_controller.h"
@@ -149,6 +150,7 @@
     // A unit test that does not override this default delegate may not have ash
     // shell.
     if (Shell::HasInstance()) {
+      CHECK(Shell::Get()->pine_controller());
       Shell::Get()->pine_controller()->MaybeStartPineOverviewSession(
           std::move(pine_contents_data));
     }
@@ -158,6 +160,7 @@
     // A unit test that does not override this default delegate may not have ash
     // shell.
     if (Shell::HasInstance()) {
+      CHECK(Shell::Get()->pine_controller());
       Shell::Get()->pine_controller()->MaybeEndPineOverviewSession();
     }
   }
@@ -310,7 +313,7 @@
       MaybeInitiateAdminTemplateAutoLaunch();
       break;
     case RestoreOption::kDoNotRestore:
-      if (features::IsForestFeatureEnabled()) {
+      if (IsForestFeatureEnabled()) {
         MaybeShowPineOnboarding();
       }
       ::full_restore::FullRestoreSaveHandler::GetInstance()->AllowSave();
@@ -503,8 +506,7 @@
   }
 
   // Do not show the notification if we have no restore data.
-  if (!features::IsForestFeatureEnabled() &&
-      !app_launch_handler_->HasRestoreData()) {
+  if (!IsForestFeatureEnabled() && !app_launch_handler_->HasRestoreData()) {
     return;
   }
 
@@ -523,7 +525,7 @@
 
   const bool last_session_crashed = id == kRestoreForCrashNotificationId;
   if (!app_launch_handler_->HasRestoreData()) {
-    CHECK(features::IsForestFeatureEnabled());
+    CHECK(IsForestFeatureEnabled());
     MaybeShowPineOnboarding();
     return;
   }
@@ -543,7 +545,7 @@
         ->RecordPostLoginFullRestoreShown();
   }
 
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     CHECK(delegate_);
 
     if (crosapi::browser_util::IsLacrosEnabled()) {
@@ -828,10 +830,10 @@
 }
 
 void FullRestoreService::MaybeShowPineOnboarding() {
-  CHECK(features::IsForestFeatureEnabled());
   if (Shell::HasInstance()) {
     RestoreOption restore_pref = static_cast<RestoreOption>(
         profile_->GetPrefs()->GetInteger(prefs::kRestoreAppsAndPagesPrefName));
+    CHECK(Shell::Get()->pine_controller());
     Shell::Get()->pine_controller()->MaybeShowPineOnboardingMessage(
         /*restore_on=*/restore_pref == RestoreOption::kAskEveryTime);
   }
diff --git a/chrome/browser/ash/arc/file_system_watcher/OWNERS b/chrome/browser/ash/arc/file_system_watcher/OWNERS
index ede84c12..4228552 100644
--- a/chrome/browser/ash/arc/file_system_watcher/OWNERS
+++ b/chrome/browser/ash/arc/file_system_watcher/OWNERS
@@ -1,3 +1,2 @@
-hashimoto@chromium.org
-niwa@chromium.org
 youkichihosoi@chromium.org
+momohatt@chromium.org
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest.cc b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
index d2bef94..bd64772 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest.cc
@@ -425,7 +425,15 @@
         TestCase("fileDisplayCheckReadOnlyIconOnFakeDirectory"),
         TestCase("fileDisplayCheckNoReadOnlyIconOnDownloads"),
         TestCase("fileDisplayCheckNoReadOnlyIconOnLinuxFiles"),
-        TestCase("fileDisplayCheckNoReadOnlyIconOnGuestOs")));
+        TestCase("fileDisplayCheckNoReadOnlyIconOnGuestOs"),
+        TestCase("fileDisplayLocalFilesDisabledUnmountRemovable")
+            .DontMountVolumes()
+            .NewDirectoryTree()
+            .EnableSkyVault(),
+        TestCase("fileDisplayLocalFilesDisableInMyFiles")
+            .DontMountVolumes()
+            .NewDirectoryTree()
+            .EnableSkyVault()));
 
 WRAPPED_INSTANTIATE_TEST_SUITE_P(
     OpenVideoMediaApp, /* open_video_media_app.js */
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index 436ca99..37a4180 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -98,6 +98,7 @@
 #include "chrome/browser/ash/system/timezone_util.h"
 #include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/browser_process.h"
+#include "chrome/browser/download/download_dir_util.h"
 #include "chrome/browser/download/download_prefs.h"
 #include "chrome/browser/enterprise/connectors/connectors_service.h"
 #include "chrome/browser/extensions/mixin_based_extension_apitest.h"
@@ -2424,6 +2425,12 @@
     disabled_features.push_back(ash::features::kFilesNewDirectoryTree);
   }
 
+  if (options.enable_skyvault) {
+    enabled_features.push_back(features::kSkyVault);
+  } else {
+    disabled_features.push_back(features::kSkyVault);
+  }
+
   // This is destroyed in |TearDown()|. We cannot initialize this in the
   // constructor due to this feature values' above dependence on virtual
   // method calls, but by convention subclasses of this fixture may initialize
@@ -3411,6 +3418,14 @@
     return;
   }
 
+  if (name == "setupSkyVault") {
+    profile()->GetPrefs()->SetString(prefs::kFilesAppDefaultLocation,
+                                     download_dir_util::kLocationGoogleDrive);
+    g_browser_process->local_state()->SetBoolean(prefs::kLocalUserFilesAllowed,
+                                                 false);
+    return;
+  }
+
   if (name == "setTrashEnabled") {
     std::optional<bool> enabled = value.FindBool("enabled");
     ASSERT_TRUE(enabled.has_value());
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.h b/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
index 094f9acb..06b0856 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.h
@@ -207,6 +207,9 @@
     // Whether to enable new directory tree implementation.
     bool enable_new_directory_tree = false;
 
+    // Whether test should enable the SkyVault feature.
+    bool enable_skyvault = false;
+
     // Feature IDs associated for mapping test cases and features.
     std::vector<std::string> feature_ids;
   };
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_utils.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_utils.cc
index b93849f..30526cb0 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_utils.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_utils.cc
@@ -219,6 +219,11 @@
   return *this;
 }
 
+TestCase& TestCase::EnableSkyVault() {
+  options.enable_skyvault = true;
+  return *this;
+}
+
 std::string TestCase::GetFullName() const {
   std::string full_name = name;
 
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_utils.h b/chrome/browser/ash/file_manager/file_manager_browsertest_utils.h
index 388cf84..b8e7cdd5 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_utils.h
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_utils.h
@@ -116,6 +116,8 @@
 
   TestCase& EnableCrosComponents();
 
+  TestCase& EnableSkyVault();
+
   std::string GetFullName() const;
 
   const char* const name;
diff --git a/chrome/browser/ash/file_suggest/file_suggest_keyed_service.cc b/chrome/browser/ash/file_suggest/file_suggest_keyed_service.cc
index 827b8696..f3c801ac 100644
--- a/chrome/browser/ash/file_suggest/file_suggest_keyed_service.cc
+++ b/chrome/browser/ash/file_suggest/file_suggest_keyed_service.cc
@@ -6,6 +6,7 @@
 
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
+#include "ash/utility/forest_util.h"
 #include "base/functional/bind.h"
 #include "chrome/browser/ash/file_manager/fileapi_util.h"
 #include "chrome/browser/ash/file_suggest/drive_file_suggestion_provider.h"
@@ -34,7 +35,7 @@
   proto_.Init();
 
   if (features::IsLauncherContinueSectionWithRecentsEnabled() ||
-      features::IsForestFeatureEnabled()) {
+      IsForestFeatureEnabled()) {
     drive_file_suggestion_provider_ =
         std::make_unique<DriveRecentFileSuggestionProvider>(
             profile, base::BindRepeating(
diff --git a/chrome/browser/ash/input_method/input_method_settings.cc b/chrome/browser/ash/input_method/input_method_settings.cc
index fd49d44..170ae6c 100644
--- a/chrome/browser/ash/input_method/input_method_settings.cc
+++ b/chrome/browser/ash/input_method/input_method_settings.cc
@@ -12,6 +12,7 @@
 #include "base/feature_list.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/no_destructor.h"
+#include "base/strings/strcat.h"
 #include "chrome/browser/ash/input_method/autocorrect_enums.h"
 #include "chrome/browser/ash/input_method/autocorrect_prefs.h"
 #include "chrome/common/pref_names.h"
@@ -346,41 +347,26 @@
       ValueOrEmpty(input_method_specific_pref.FindString("zhuyinPageSize")));
   return settings;
 }
+}  // namespace
 
-const base::Value::Dict& GetPrefsDictionaryForEngineId(
+mojom::InputMethodSettingsPtr CreateSettingsFromPrefs(
     const PrefService& prefs,
-    const std::string& engine_id,
-    const base::Value::Dict& fallback_dictionary) {
+    const std::string& engine_id) {
   // All input method settings are stored in a single pref whose value is a
   // dictionary.
-  const base::Value::Dict& all_input_method_pref =
-      prefs.GetDict(::prefs::kLanguageInputMethodSpecificSettings);
-
   // For each input method, the dictionary contains an entry, with the key being
   // a string that identifies the input method, and the value being a
   // subdictionary with the specific settings for that input method.  The
   // subdictionary structure depends on the type of input method it's for.  The
   // subdictionary may be null if the user hasn't changed any settings for that
   // input method.
-  const base::Value::Dict* input_method_specific_pref_or_null =
-      all_input_method_pref.FindDict(engine_id);
+  const base::Value::Dict* ime_prefs_ptr =
+      prefs.GetDict(::prefs::kLanguageInputMethodSpecificSettings)
+          .FindDict(engine_id);
 
-  // For convenience, pass an empty dictionary if there are no settings for this
-  // input method yet.
-  return input_method_specific_pref_or_null
-             ? *input_method_specific_pref_or_null
-             : fallback_dictionary;
-}
-
-// Port the Prefs sett}  // namespace
-}  // namespace
-
-mojom::InputMethodSettingsPtr CreateSettingsFromPrefs(
-    const PrefService& prefs,
-    const std::string& engine_id) {
-  base::Value::Dict empty_dictionary;
-  const auto& input_method_specific_pref =
-      GetPrefsDictionaryForEngineId(prefs, engine_id, empty_dictionary);
+  base::Value::Dict default_dict;
+  const base::Value::Dict& input_method_specific_pref =
+      ime_prefs_ptr == nullptr ? default_dict : *ime_prefs_ptr;
 
   if (IsFstEngine(engine_id)) {
     return mojom::InputMethodSettings::NewLatinSettings(
@@ -414,17 +400,31 @@
   return nullptr;
 }
 
+const base::Value* GetLanguageInputMethodSpecificSetting(
+    PrefService& prefs,
+    const std::string& engine_id,
+    const std::string& preference_name) {
+  return prefs.GetDict(::prefs::kLanguageInputMethodSpecificSettings)
+      .FindByDottedPath(base::StrCat({engine_id, ".", preference_name}));
+}
+
 void SetLanguageInputMethodSpecificSetting(PrefService& prefs,
                                            const std::string& engine_id,
                                            const base::Value::Dict& values) {
   // This creates a dictionary where any changes to the dictionary will notify
   // the prefs service (and its observers).
-  ScopedDictPrefUpdate update = ScopedDictPrefUpdate(
-      &prefs, ::prefs::kLanguageInputMethodSpecificSettings);
+  ScopedDictPrefUpdate update(&prefs,
+                              ::prefs::kLanguageInputMethodSpecificSettings);
 
-  for (const auto [key, value] : values) {
-    update->FindDict(engine_id)->Set(key, value.Clone());
-  }
+  // The "update" dictionary contains nested dictionaries of engine_id -> Dict.
+  // This partial dictionary contains all the new updated files set up in the
+  // same schema so it can be merged.
+  base::Value::Dict partial_dict;
+  partial_dict.Set(engine_id, values.Clone());
+
+  // Does a nested dictionary merge to the "update" dictionary. This does not
+  // modify any existing values that are not inside the partial_dict.
+  update->Merge(std::move(partial_dict));
 }
 
 bool IsAutocorrectSupported(const std::string& engine_id) {
diff --git a/chrome/browser/ash/input_method/input_method_settings.h b/chrome/browser/ash/input_method/input_method_settings.h
index b9615d4..cb3dcee8 100644
--- a/chrome/browser/ash/input_method/input_method_settings.h
+++ b/chrome/browser/ash/input_method/input_method_settings.h
@@ -21,6 +21,14 @@
                                            const std::string& engine_id,
                                            const base::Value::Dict& values);
 
+// Gets a specific settings value that is held under a key for an engine id if
+// it exists.
+// Will return nullptr if it does not exist.
+const base::Value* GetLanguageInputMethodSpecificSetting(
+    PrefService& prefs,
+    const std::string& engine_id,
+    const std::string& preference_name);
+
 // Returns true if Autocorrect is supported for a given engine id.
 bool IsAutocorrectSupported(const std::string& engine_id);
 
diff --git a/chrome/browser/ash/input_method/input_method_settings_unittest.cc b/chrome/browser/ash/input_method/input_method_settings_unittest.cc
index a080679..e8a2191 100644
--- a/chrome/browser/ash/input_method/input_method_settings_unittest.cc
+++ b/chrome/browser/ash/input_method/input_method_settings_unittest.cc
@@ -291,7 +291,29 @@
   ASSERT_FALSE(IsAutocorrectSupported("zh-t-i0-pinyin"));
 }
 
-TEST(InputMethodSettingsTest, SetLanguageSpecificInputMethodSettings) {
+TEST(InputMethodSettingsTest, GetLanguageSpecificInputMethodSettings) {
+  base::Value::Dict dict;
+  dict.SetByDottedPath(base::StrCat({kZhuyinEngineId, ".field1"}), "DEFAULT1");
+  dict.SetByDottedPath(base::StrCat({kZhuyinEngineId, ".field2"}), "DEFAULT2");
+  dict.SetByDottedPath(base::StrCat({kZhuyinEngineId, ".field3"}), "DEFAULT3");
+  TestingPrefServiceSimple prefs;
+  RegisterTestingPrefs(prefs, dict);
+
+  base::Value::Dict new_prefs;
+  new_prefs.Set("field2", "CHANGED");
+  EXPECT_EQ(
+      *GetLanguageInputMethodSpecificSetting(prefs, kZhuyinEngineId, "field1"),
+      "DEFAULT1");
+  EXPECT_EQ(
+      *GetLanguageInputMethodSpecificSetting(prefs, kZhuyinEngineId, "field2"),
+      "DEFAULT2");
+  EXPECT_EQ(
+      *GetLanguageInputMethodSpecificSetting(prefs, kZhuyinEngineId, "field3"),
+      "DEFAULT3");
+}
+
+TEST(InputMethodSettingsTest,
+     SetLanguageInputMethodSpecificSettingExistingEngine) {
   base::Value::Dict dict;
   dict.SetByDottedPath(base::StrCat({kZhuyinEngineId, ".field1"}), "DEFAULT");
   dict.SetByDottedPath(base::StrCat({kZhuyinEngineId, ".field2"}), "DEFAULT");
@@ -317,6 +339,26 @@
   EXPECT_EQ(*prefs_val->GetIfDict(), expected);
 }
 
+TEST(InputMethodSettingsTest, SetLanguageInputMethodSpecificSettingNewEngine) {
+  base::Value::Dict dict;
+  dict.SetByDottedPath("existing-engine.field1", "DEFAULT");
+  TestingPrefServiceSimple prefs;
+  RegisterTestingPrefs(prefs, dict);
+
+  base::Value::Dict new_prefs;
+  new_prefs.Set("field1", "NEW");
+  SetLanguageInputMethodSpecificSetting(prefs, "brand-new-engine", new_prefs);
+
+  const base::Value* prefs_val =
+      prefs.GetUserPref(::prefs::kLanguageInputMethodSpecificSettings);
+
+  base::Value::Dict expected;
+  expected.SetByDottedPath("existing-engine.field1", "DEFAULT");
+  expected.SetByDottedPath("brand-new-engine.field1", "NEW");
+
+  EXPECT_EQ(*prefs_val->GetIfDict(), expected);
+}
+
 }  // namespace
 }  // namespace input_method
 }  // namespace ash
diff --git a/chrome/browser/ash/login/session/user_session_initializer.cc b/chrome/browser/ash/login/session/user_session_initializer.cc
index e884373f..76abf61 100644
--- a/chrome/browser/ash/login/session/user_session_initializer.cc
+++ b/chrome/browser/ash/login/session/user_session_initializer.cc
@@ -268,10 +268,6 @@
   // Ensure that the `HoldingSpaceKeyedService` for `profile` is created.
   HoldingSpaceKeyedServiceFactory::GetInstance()->GetService(profile);
 
-  // Ensure that the `BirchKeyedService` for `profile` is created. It is created
-  // one per user in a multiprofile session.
-  BirchKeyedServiceFactory::GetInstance()->GetService(profile);
-
   // Ensure that the `CalendarKeyedService` for `profile` is created. It is
   // created one per user in a multiprofile session.
   CalendarKeyedServiceFactory::GetInstance()->GetService(profile);
@@ -286,6 +282,10 @@
   if (is_primary_user) {
     DCHECK_EQ(primary_profile_, profile);
 
+    // Ensure that the `BirchKeyedService` for `profile` is created. It is
+    // created one per user in a multiprofile session.
+    BirchKeyedServiceFactory::GetInstance()->GetService(profile);
+
     // Ensure that PhoneHubManager and EcheAppManager are created for the
     // primary profile.
     phonehub::PhoneHubManagerFactory::GetForProfile(profile);
diff --git a/chrome/browser/ash/policy/core/device_local_account_external_cache.cc b/chrome/browser/ash/policy/core/device_local_account_external_cache.cc
index 7e32f01..418991b 100644
--- a/chrome/browser/ash/policy/core/device_local_account_external_cache.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_external_cache.cc
@@ -5,20 +5,19 @@
 #include "chrome/browser/ash/policy/core/device_local_account_external_cache.h"
 
 #include <memory>
+#include <string>
+#include <utility>
 
-#include "base/check_is_test.h"
+#include "base/check.h"
 #include "base/files/file_path.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
-#include "chrome/browser/ash/crosapi/crosapi_ash.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
-#include "chrome/browser/ash/crosapi/device_local_account_extension_service_ash.h"
 #include "chrome/browser/ash/extensions/external_cache_impl.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
-#include "chrome/browser/extensions/external_loader.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -26,11 +25,13 @@
 namespace chromeos {
 
 DeviceLocalAccountExternalCache::DeviceLocalAccountExternalCache(
+    ExtensionListCallback ash_loader,
+    ExtensionListCallback lacros_loader,
     const std::string& user_id,
     const base::FilePath& cache_dir)
-    : user_id_(user_id), cache_dir_(cache_dir) {
-  loader_ = base::MakeRefCounted<DeviceLocalAccountExternalPolicyLoader>();
-}
+    : cache_dir_(cache_dir),
+      ash_loader_(ash_loader),
+      lacros_loader_(lacros_loader) {}
 
 DeviceLocalAccountExternalCache::~DeviceLocalAccountExternalCache() = default;
 
@@ -63,7 +64,8 @@
   }
 
   base::Value::Dict empty_prefs;
-  loader_->OnExtensionListsUpdated(empty_prefs);
+  ash_loader_.Run(user_id_, empty_prefs.Clone());
+  lacros_loader_.Run(user_id_, empty_prefs.Clone());
 }
 
 bool DeviceLocalAccountExternalCache::IsCacheRunning() const {
@@ -72,15 +74,8 @@
 
 void DeviceLocalAccountExternalCache::OnExtensionListsUpdated(
     const base::Value::Dict& prefs) {
-  if (crosapi::CrosapiManager::IsInitialized()) {
-    crosapi::CrosapiManager::Get()
-        ->crosapi_ash()
-        ->device_local_account_extension_service()
-        ->SetForceInstallExtensionsFromCache(user_id_, prefs.Clone());
-  } else {
-    CHECK_IS_TEST();
-  }
-  loader_->OnExtensionListsUpdated(prefs);
+  lacros_loader_.Run(user_id_, prefs.Clone());
+  ash_loader_.Run(user_id_, prefs.Clone());
 }
 
 bool DeviceLocalAccountExternalCache::IsRollbackAllowed() const {
@@ -96,11 +91,6 @@
   return true;
 }
 
-scoped_refptr<extensions::ExternalLoader>
-DeviceLocalAccountExternalCache::GetExtensionLoader() {
-  return loader_;
-}
-
 base::Value::Dict DeviceLocalAccountExternalCache::GetCachedExtensions() const {
   return external_cache_->GetCachedExtensions().Clone();
 }
diff --git a/chrome/browser/ash/policy/core/device_local_account_external_cache.h b/chrome/browser/ash/policy/core/device_local_account_external_cache.h
index b07656e6..0265d68 100644
--- a/chrome/browser/ash/policy/core/device_local_account_external_cache.h
+++ b/chrome/browser/ash/policy/core/device_local_account_external_cache.h
@@ -5,13 +5,15 @@
 #ifndef CHROME_BROWSER_ASH_POLICY_CORE_DEVICE_LOCAL_ACCOUNT_EXTERNAL_CACHE_H_
 #define CHROME_BROWSER_ASH_POLICY_CORE_DEVICE_LOCAL_ACCOUNT_EXTERNAL_CACHE_H_
 
+#include <memory>
+#include <string>
+
 #include "base/files/file_path.h"
-#include "base/functional/callback_forward.h"
+#include "base/functional/callback.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
 #include "chrome/browser/ash/extensions/external_cache_delegate.h"
-#include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
 #include "chrome/browser/extensions/external_loader.h"
 
 namespace chromeos {
@@ -24,7 +26,14 @@
  */
 class DeviceLocalAccountExternalCache : public ExternalCacheDelegate {
  public:
-  DeviceLocalAccountExternalCache(const std::string& user_id,
+  // Callback invoked when the list of cached extensions is updated.
+  using ExtensionListCallback =
+      base::RepeatingCallback<void(const std::string& user_id,
+                                   base::Value::Dict cached_extensions)>;
+
+  DeviceLocalAccountExternalCache(ExtensionListCallback ash_loader,
+                                  ExtensionListCallback lacros_loader,
+                                  const std::string& user_id,
                                   const base::FilePath& cache_dir);
   ~DeviceLocalAccountExternalCache() override;
 
@@ -41,20 +50,26 @@
   // Send the new extension dictionary down to the ExternalCache.
   void UpdateExtensionsList(base::Value::Dict dict);
 
-  // ExternalCacheDelegate:
-  void OnExtensionListsUpdated(const base::Value::Dict& prefs) override;
-  bool IsRollbackAllowed() const override;
-  bool CanRollbackNow() const override;
-
   scoped_refptr<extensions::ExternalLoader> GetExtensionLoader();
 
   base::Value::Dict GetCachedExtensions() const;
 
  private:
+  // `ExternalCacheDelegate`:
+  void OnExtensionListsUpdated(const base::Value::Dict& prefs) override;
+  bool IsRollbackAllowed() const override;
+  bool CanRollbackNow() const override;
+
   const std::string user_id_;
   const base::FilePath cache_dir_;
   std::unique_ptr<ExternalCache> external_cache_;
-  scoped_refptr<DeviceLocalAccountExternalPolicyLoader> loader_;
+
+  // Callback invoked when the list of cached extensions that must be installed
+  // in Ash is updated.
+  ExtensionListCallback ash_loader_;
+  // Callback invoked when the list of cached extensions that must be installed
+  // in the Lacros browser is updated.
+  ExtensionListCallback lacros_loader_;
 };
 
 }  // namespace chromeos
diff --git a/chrome/browser/ash/policy/core/device_local_account_external_cache_unittest.cc b/chrome/browser/ash/policy/core/device_local_account_external_cache_unittest.cc
index 4904394..d52dc4e 100644
--- a/chrome/browser/ash/policy/core/device_local_account_external_cache_unittest.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_external_cache_unittest.cc
@@ -5,31 +5,34 @@
 #include "chrome/browser/ash/policy/core/device_local_account_external_cache.h"
 
 #include <memory>
+#include <set>
 #include <string>
 #include <utility>
+#include <vector>
 
+#include "base/check_op.h"
 #include "base/files/file_util.h"
 #include "base/files/scoped_temp_dir.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback.h"
+#include "base/functional/callback_helpers.h"
+#include "base/location.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "base/sequence_checker.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/current_thread.h"
 #include "base/task/single_thread_task_runner.h"
+#include "base/time/time.h"
 #include "base/values.h"
-#include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
-#include "chrome/browser/ash/crosapi/crosapi_manager.h"
-#include "chrome/browser/ash/crosapi/idle_service_ash.h"
-#include "chrome/browser/ash/crosapi/test_crosapi_dependency_registry.h"
 #include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
+#include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
 #include "chrome/browser/extensions/external_provider_impl.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chrome/test/base/testing_profile_manager.h"
-#include "chromeos/ash/components/login/login_state/login_state.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/external_install_info.h"
@@ -38,6 +41,7 @@
 #include "extensions/browser/updater/extension_update_found_test_observer.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_urls.h"
+#include "extensions/common/mojom/manifest.mojom-shared.h"
 #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/test/test_url_loader_factory.h"
@@ -181,8 +185,8 @@
           base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
               &test_url_loader_factory_)};
   base::FilePath test_dir_;
-  std::unique_ptr<crosapi::CrosapiManager> crosapi_manager_;
 
+  scoped_refptr<DeviceLocalAccountExternalPolicyLoader> extension_loader_;
   std::unique_ptr<DeviceLocalAccountExternalCache> external_cache_;
   MockExternalPolicyProviderVisitor visitor_;
   std::unique_ptr<extensions::ExternalProviderImpl> provider_;
@@ -203,16 +207,24 @@
   ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir_));
 
   ASSERT_TRUE(testing_profile_manager_.SetUp());
-  ash::LoginState::Initialize();
-  crosapi::IdleServiceAsh::DisableForTesting();
   Profile* profile = testing_profile_manager_.CreateTestingProfile("Default");
-  crosapi_manager_ = crosapi::CreateCrosapiManagerWithTestRegistry();
 
-  external_cache_ =
-      std::make_unique<DeviceLocalAccountExternalCache>(kAccountId, cache_dir_);
+  extension_loader_ =
+      base::MakeRefCounted<chromeos::DeviceLocalAccountExternalPolicyLoader>();
+
+  external_cache_ = std::make_unique<DeviceLocalAccountExternalCache>(
+      /*ash_loader=*/
+      base::BindRepeating(
+          [](scoped_refptr<chromeos::DeviceLocalAccountExternalPolicyLoader>
+                 loader,
+             const std::string&, base::Value::Dict cached_extensions) {
+            loader->OnExtensionListsUpdated(cached_extensions);
+          },
+          extension_loader_),
+      /*lacros_loader=*/
+      base::DoNothing(), kAccountId, cache_dir_);
   provider_ = std::make_unique<extensions::ExternalProviderImpl>(
-      &visitor_, external_cache_->GetExtensionLoader(), profile,
-      ManifestLocation::kExternalPolicy,
+      &visitor_, extension_loader_, profile, ManifestLocation::kExternalPolicy,
       ManifestLocation::kExternalPolicyDownload,
       extensions::Extension::NO_FLAGS);
 
@@ -220,9 +232,7 @@
 }
 
 void DeviceLocalAccountExternalCacheTest::TearDown() {
-  crosapi_manager_.reset();
   testing_profile_manager_.DeleteAllTestingProfiles();
-  ash::LoginState::Shutdown();
   TestingBrowserProcess::GetGlobal()->SetSharedURLLoaderFactory(nullptr);
 }
 
@@ -269,7 +279,7 @@
 // is manually requested.
 TEST_F(DeviceLocalAccountExternalCacheTest, CacheNotStarted) {
   // Manually request a load.
-  external_cache_->GetExtensionLoader()->StartLoading();
+  extension_loader_->StartLoading();
 
   EXPECT_FALSE(external_cache_->IsCacheRunning());
 }
diff --git a/chrome/browser/ash/policy/core/device_local_account_policy_broker.cc b/chrome/browser/ash/policy/core/device_local_account_policy_broker.cc
index c7c337f..86d09ec 100644
--- a/chrome/browser/ash/policy/core/device_local_account_policy_broker.cc
+++ b/chrome/browser/ash/policy/core/device_local_account_policy_broker.cc
@@ -3,26 +3,49 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ash/policy/core/device_local_account_policy_broker.h"
+
 #include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
 
 #include "ash/constants/ash_paths.h"
+#include "base/check_is_test.h"
+#include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/path_service.h"
-#include "base/strings/string_number_conversions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
+#include "chrome/browser/ash/crosapi/crosapi_ash.h"
+#include "chrome/browser/ash/crosapi/crosapi_manager.h"
+#include "chrome/browser/ash/crosapi/device_local_account_extension_service_ash.h"
 #include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/ash/policy/core/device_local_account_external_cache.h"
 #include "chrome/browser/ash/policy/core/file_util.h"
+#include "chrome/browser/ash/policy/external_data/device_local_account_external_data_manager.h"
+#include "chrome/browser/ash/policy/invalidation/affiliated_cloud_policy_invalidator.h"
+#include "chrome/browser/ash/policy/invalidation/affiliated_invalidation_service_provider.h"
+#include "chrome/browser/ash/settings/device_settings_service.h"
+#include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h"
+#include "chrome/browser/extensions/external_loader.h"
 #include "chrome/browser/extensions/policy_handlers.h"
 #include "components/policy/core/common/chrome_schema.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
+#include "components/policy/core/common/cloud/cloud_policy_store.h"
+#include "components/policy/core/common/cloud/component_cloud_policy_service.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
+#include "components/policy/core/common/cloud/policy_invalidation_scope.h"
 #include "components/policy/core/common/cloud/resource_cache.h"
+#include "components/policy/core/common/policy_namespace.h"
 #include "components/policy/policy_constants.h"
-#include "components/prefs/pref_value_map.h"
+#include "components/policy/proto/device_management_backend.pb.h"
 #include "content/public/browser/network_service_instance.h"
-#include "extensions/browser/pref_names.h"
+#include "device_local_account_extension_tracker.h"
+#include "device_local_account_policy_store.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace policy {
@@ -68,6 +91,26 @@
   return policy_handler.GetPolicyDict(policy_map);
 }
 
+void SendExtensionsToAsh(
+    scoped_refptr<chromeos::DeviceLocalAccountExternalPolicyLoader> loader,
+    const std::string& user_id,
+    base::Value::Dict cached_extensions) {
+  loader->OnExtensionListsUpdated(cached_extensions);
+}
+
+void SendExtensionsToLacros(const std::string& user_id,
+                            base::Value::Dict cached_extensions) {
+  if (crosapi::CrosapiManager::IsInitialized()) {
+    crosapi::CrosapiManager::Get()
+        ->crosapi_ash()
+        ->device_local_account_extension_service()
+        ->SetForceInstallExtensionsFromCache(user_id,
+                                             std::move(cached_extensions));
+  } else {
+    CHECK_IS_TEST();
+  }
+}
+
 }  // namespace
 
 DeviceLocalAccountPolicyBroker::DeviceLocalAccountPolicyBroker(
@@ -85,6 +128,8 @@
       component_policy_cache_path_(component_policy_cache_path),
       store_(std::move(store)),
       external_data_manager_(external_data_manager),
+      extension_loader_(base::MakeRefCounted<
+                        chromeos::DeviceLocalAccountExternalPolicyLoader>()),
       core_(dm_protocol::kChromePublicAccountPolicyType,
             store_->account_id(),
             store_.get(),
@@ -98,7 +143,9 @@
         account, store_.get(), &schema_registry_);
   }
   external_cache_ = std::make_unique<chromeos::DeviceLocalAccountExternalCache>(
-      user_id_,
+      /*ash_loader=*/base::BindRepeating(SendExtensionsToAsh,
+                                         extension_loader_),
+      /*lacros_loader=*/base::BindRepeating(SendExtensionsToLacros), user_id_,
       base::PathService::CheckedGet(ash::DIR_DEVICE_LOCAL_ACCOUNT_EXTENSIONS)
           .Append(GetUniqueSubDirectoryForAccountID(account.account_id)));
   store_->AddObserver(this);
@@ -124,6 +171,11 @@
   store_->LoadImmediately();
 }
 
+scoped_refptr<extensions::ExternalLoader>
+DeviceLocalAccountPolicyBroker::extension_loader() const {
+  return extension_loader_;
+}
+
 bool DeviceLocalAccountPolicyBroker::HasInvalidatorForTest() const {
   return invalidator_ != nullptr;
 }
diff --git a/chrome/browser/ash/policy/core/device_local_account_policy_broker.h b/chrome/browser/ash/policy/core/device_local_account_policy_broker.h
index 1079b58..b0336bb 100644
--- a/chrome/browser/ash/policy/core/device_local_account_policy_broker.h
+++ b/chrome/browser/ash/policy/core/device_local_account_policy_broker.h
@@ -5,11 +5,16 @@
 #ifndef CHROME_BROWSER_ASH_POLICY_CORE_DEVICE_LOCAL_ACCOUNT_POLICY_BROKER_H_
 #define CHROME_BROWSER_ASH_POLICY_CORE_DEVICE_LOCAL_ACCOUNT_POLICY_BROKER_H_
 
+#include <memory>
 #include <string>
 
+#include "base/files/file_path.h"
 #include "base/functional/callback_forward.h"
+#include "base/memory/raw_ptr.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "base/values.h"
+#include "build/buildflag.h"
 #include "chrome/browser/ash/policy/core/device_local_account.h"
 #include "chrome/browser/ash/policy/core/device_local_account_extension_tracker.h"
 #include "chrome/browser/ash/policy/core/device_local_account_external_cache.h"
@@ -17,12 +22,20 @@
 #include "chrome/browser/ash/policy/external_data/device_local_account_external_data_manager.h"
 #include "chrome/browser/ash/policy/invalidation/affiliated_cloud_policy_invalidator.h"
 #include "chrome/browser/ash/policy/invalidation/affiliated_invalidation_service_provider.h"
+#include "chrome/browser/ash/settings/device_settings_service.h"
 #include "chrome/browser/extensions/external_loader.h"
+#include "components/policy/core/common/cloud/cloud_external_data_manager.h"
+#include "components/policy/core/common/cloud/cloud_policy_client.h"
 #include "components/policy/core/common/cloud/cloud_policy_store.h"
 #include "components/policy/core/common/cloud/component_cloud_policy_service.h"
+#include "components/policy/core/common/cloud/device_management_service.h"
 
 static_assert(BUILDFLAG(IS_CHROMEOS_ASH), "For ChromeOS ash-chrome only");
 
+namespace chromeos {
+class DeviceLocalAccountExternalPolicyLoader;
+}  // namespace chromeos
+
 namespace policy {
 
 // The main switching central that downloads, caches, refreshes, etc. policy for
@@ -67,9 +80,7 @@
   const std::string& account_id() const { return account_id_; }
   const std::string& user_id() const { return user_id_; }
 
-  scoped_refptr<extensions::ExternalLoader> extension_loader() const {
-    return external_cache_->GetExtensionLoader();
-  }
+  scoped_refptr<extensions::ExternalLoader> extension_loader() const;
 
   CloudPolicyCore* core() { return &core_; }
   const CloudPolicyCore* core() const { return &core_; }
@@ -132,6 +143,8 @@
   const std::unique_ptr<DeviceLocalAccountPolicyStore> store_;
   std::unique_ptr<DeviceLocalAccountExtensionTracker> extension_tracker_;
   scoped_refptr<DeviceLocalAccountExternalDataManager> external_data_manager_;
+  scoped_refptr<chromeos::DeviceLocalAccountExternalPolicyLoader>
+      extension_loader_;
   std::unique_ptr<chromeos::DeviceLocalAccountExternalCache> external_cache_;
   CloudPolicyCore core_;
   std::unique_ptr<ComponentCloudPolicyService> component_policy_service_;
diff --git a/chrome/browser/ash/release_notes/release_notes_notification.cc b/chrome/browser/ash/release_notes/release_notes_notification.cc
index 95ceff4..33f2d105 100644
--- a/chrome/browser/ash/release_notes/release_notes_notification.cc
+++ b/chrome/browser/ash/release_notes/release_notes_notification.cc
@@ -6,10 +6,10 @@
 
 #include <string>
 
-#include "ash/constants/ash_features.h"
 #include "ash/constants/notifier_catalogs.h"
 #include "ash/public/cpp/notification_utils.h"
 #include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/utility/forest_util.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
 #include "base/strings/string_util.h"
@@ -39,8 +39,7 @@
 
 void ReleaseNotesNotification::MaybeShowReleaseNotes() {
   release_notes_storage_ = std::make_unique<ReleaseNotesStorage>(profile_);
-  if (!release_notes_storage_->ShouldNotify() ||
-      features::IsForestFeatureEnabled()) {
+  if (!release_notes_storage_->ShouldNotify() || IsForestFeatureEnabled()) {
     return;
   }
   ShowReleaseNotesNotification();
diff --git a/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc b/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
index a1f5c3e..b368302 100644
--- a/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
+++ b/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
@@ -38,7 +38,6 @@
 #include "chrome/browser/ash/app_list/app_list_client_impl.h"
 #include "chrome/browser/ash/app_list/app_list_syncable_service_factory.h"
 #include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
-#include "chrome/browser/ash/login/wizard_controller.h"
 #include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
 #include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
diff --git a/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.cc b/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.cc
index e7ecab7..2023ace 100644
--- a/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.cc
+++ b/chrome/browser/ash/system_web_apps/apps/camera_app/chrome_camera_app_ui_delegate.cc
@@ -327,6 +327,8 @@
                                     ash::features::kCameraAppAutoQRDetection));
   source->AddBoolean("digital_zoom", base::FeatureList::IsEnabled(
                                          ash::features::kCameraAppDigitalZoom));
+  source->AddBoolean("super_res", base::FeatureList::IsEnabled(
+                                      ash::features::kCameraSuperResSupported));
 
   const PrefService* prefs = Profile::FromWebUI(web_ui_)->GetPrefs();
   source->AddBoolean("video_capture_disallowed",
diff --git a/chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller.cc b/chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller.cc
index 47aa5f3..b365da9 100644
--- a/chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller.cc
+++ b/chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ash/system_web_apps/apps/help_app/help_app_notification_controller.h"
 
 #include "ash/constants/ash_features.h"
+#include "ash/utility/forest_util.h"
 #include "base/logging.h"
 #include "base/version.h"
 #include "chrome/browser/ash/release_notes/release_notes_notification.h"
@@ -66,7 +67,7 @@
           features::kReleaseNotesNotificationAlwaysEligible)) {
     return;
   }
-  if (features::IsForestFeatureEnabled()) {
+  if (IsForestFeatureEnabled()) {
     return;
   }
   ReleaseNotesStorage release_notes_storage(profile_);
diff --git a/chrome/browser/autofill/mock_autofill_popup_controller.h b/chrome/browser/autofill/mock_autofill_popup_controller.h
index e2d0947..4483ebf 100644
--- a/chrome/browser/autofill/mock_autofill_popup_controller.h
+++ b/chrome/browser/autofill/mock_autofill_popup_controller.h
@@ -114,6 +114,10 @@
   MOCK_METHOD(void, DisableThresholdForTesting, (bool), (override));
   MOCK_METHOD(void, KeepPopupOpenForTesting, (), (override));
   MOCK_METHOD(void,
+              SetViewForTesting,
+              (base::WeakPtr<AutofillPopupView>),
+              (override));
+  MOCK_METHOD(void,
               UpdateDataListValues,
               (base::span<const SelectOption>),
               (override));
diff --git a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_converters.cc b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_converters.cc
index 8f40536e..d77b23e 100644
--- a/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_converters.cc
+++ b/chrome/browser/chromeos/extensions/telemetry/api/diagnostics/diagnostics_api_converters.cc
@@ -413,9 +413,9 @@
   if (result) {
     return result;
   }
-  // When extension is newer than the brwowser, extension might pass in a
-  // routine argument that cannot be recognized by the browser. For better
-  // developer experience, don't treat it as an invalid union.
+  // When extension is newer than the browser, extension might pass in a routine
+  // argument that cannot be recognized by the browser. For better developer
+  // experience, don't treat it as an invalid union.
   return crosapi::TelemetryDiagnosticRoutineArgument::NewUnrecognizedArgument(
       false);
 }
@@ -436,9 +436,9 @@
   if (result) {
     return result;
   }
-  // When extension is newer than the brwowser, extension might pass in a
-  // reply that cannot be recognized by the browser. For better developer
-  // experience, don't treat it as an invalid union.
+  // When extension is newer than the browser, extension might pass in a reply
+  // that cannot be recognized by the browser. For better developer experience,
+  // don't treat it as an invalid union.
   return crosapi::TelemetryDiagnosticRoutineInquiryReply::NewUnrecognizedReply(
       false);
 }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 6b16529..dffb13a8 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -1574,6 +1574,11 @@
     "expiry_milestone": 130
   },
   {
+    "name": "cras-processor-dedicated-thread",
+    "owners": ["aaronyu@google.com", "chromeos-audio@google.com" ],
+    "expiry_milestone": 130
+  },
+  {
     "name": "cras-split-alsa-usb-internal",
     "owners": [ "whalechang@google.com", "chromeos-audio-sw@google.com" ],
     "expiry_milestone": 125
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 2893c4d98..616c1bf 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2001,6 +2001,11 @@
     "When enabled, the audio indicators in the tab strip double as tab audio "
     "mute controls.";
 
+const char kCrasProcessorDedicatedThreadName[] =
+    "Run CrasProcessor in a dedicated thread";
+const char kCrasProcessorDedicatedThreadDescription[] =
+    "Run CrasProcessor in a separate thread out of the audio thread";
+
 const char kCrasSplitAlsaUsbInternalName[] =
     "CRAS Split USB/Internal refactor control";
 const char kCrasSplitAlsaUsbInternalDescription[] =
@@ -8015,9 +8020,7 @@
 const char kEnableBoundSessionCredentialsName[] =
     "Device Bound Session Credentials";
 const char kEnableBoundSessionCredentialsDescription[] =
-    "Enables Google session credentials binding to cryptographic keys that are "
-    "practically impossible to extract from the user device. This will mostly "
-    "prevent the usage of bound credentials outside of the user device.";
+    "Enables Google session credentials binding to cryptographic keys.";
 
 const char kEnableBoundSessionCredentialsSoftwareKeysForManualTestingName[] =
     "Device Bound Session Credentials with software keys";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 7970f47..629f52c5 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1139,6 +1139,9 @@
 extern const char kTabAudioMutingName[];
 extern const char kTabAudioMutingDescription[];
 
+extern const char kCrasProcessorDedicatedThreadName[];
+extern const char kCrasProcessorDedicatedThreadDescription[];
+
 extern const char kCrasSplitAlsaUsbInternalName[];
 extern const char kCrasSplitAlsaUsbInternalDescription[];
 
diff --git a/chrome/browser/history/BUILD.gn b/chrome/browser/history/BUILD.gn
index 4041fd029..feffc5c8 100644
--- a/chrome/browser/history/BUILD.gn
+++ b/chrome/browser/history/BUILD.gn
@@ -9,19 +9,69 @@
 
 android_library("java") {
   srcjar_deps = [ ":jni_headers" ]
-  sources =
-      [ "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java" ]
+  sources = [
+    "java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java",
+    "java/src/org/chromium/chrome/browser/history/AppFilterMediator.java",
+    "java/src/org/chromium/chrome/browser/history/AppFilterProperties.java",
+    "java/src/org/chromium/chrome/browser/history/AppFilterSheetContent.java",
+    "java/src/org/chromium/chrome/browser/history/AppFilterViewBinder.java",
+    "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java",
+  ]
 
   deps = [
+    ":java_resources",
     "//base:base_java",
     "//chrome/browser/tab:java",
+    "//components/browser_ui/bottomsheet/android:java",
     "//content/public/android:content_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_annotation_annotation_java",
+    "//third_party/androidx:androidx_recyclerview_recyclerview_java",
     "//third_party/jni_zero:jni_zero_java",
+    "//ui/android:ui_full_java",
   ]
+  resources_package = "org.chromium.chrome.browser.history"
 }
 
 generate_jni("jni_headers") {
   sources =
       [ "java/src/org/chromium/chrome/browser/history/HistoryTabHelper.java" ]
 }
+
+android_resources("java_resources") {
+  sources = [
+    "java/res/layout/appfilter_content.xml",
+    "java/res/layout/appfilter_header.xml",
+  ]
+  deps = [
+    "//chrome/browser/ui/android/strings:ui_strings_grd",
+    "//components/browser_ui/widget/android:java_resources",
+  ]
+}
+
+android_library("unit_device_javatests") {
+  testonly = true
+  resources_package = "org.chromium.chrome.browser.history"
+
+  sources = [ "java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java" ]
+
+  deps = [
+    ":java",
+    ":java_resources",
+    "//base:base_java",
+    "//base:base_java_test_support",
+    "//chrome/browser/flags:java",
+    "//chrome/test/android:chrome_java_unit_test_support",
+    "//components/browser_ui/bottomsheet/android:factory_java",
+    "//components/browser_ui/bottomsheet/android:java",
+    "//components/browser_ui/bottomsheet/android:manager_java",
+    "//components/browser_ui/widget/android:java",
+    "//content/public/test/android:content_java_test_support",
+    "//third_party/androidx:androidx_test_monitor_java",
+    "//third_party/androidx:androidx_test_rules_java",
+    "//third_party/androidx:androidx_test_runner_java",
+    "//third_party/junit",
+    "//ui/android:ui_java",
+    "//ui/android:ui_java_test_support",
+  ]
+}
diff --git a/chrome/browser/history/java/DEPS b/chrome/browser/history/java/DEPS
new file mode 100644
index 0000000..fe3f4c93
--- /dev/null
+++ b/chrome/browser/history/java/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+components/browser_ui/bottomsheet/android",
+  "+ui/android",
+]
diff --git a/chrome/browser/history/java/OWNERS b/chrome/browser/history/java/OWNERS
new file mode 100644
index 0000000..0edc952
--- /dev/null
+++ b/chrome/browser/history/java/OWNERS
@@ -0,0 +1,2 @@
+jinsukkim@chromium.org
+katzz@google.com
diff --git a/chrome/browser/history/java/res/layout/appfilter_content.xml b/chrome/browser/history/java/res/layout/appfilter_content.xml
new file mode 100644
index 0000000..41aeb57e
--- /dev/null
+++ b/chrome/browser/history/java/res/layout/appfilter_content.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2024 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/list_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="@dimen/min_touch_target_size"
+    android:orientation="vertical">
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/appfilter_item_list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:scrollbars="vertical"
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
+    <View
+        android:layout_marginEnd="@dimen/list_item_default_margin"
+        android:layout_marginStart="@dimen/list_item_default_margin"
+        android:importantForAccessibility="no"
+        style="@style/HorizontalDivider" />
+    <TextView
+        android:id="@+id/close_button"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/min_touch_target_size"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:text="@string/close"
+        style="@style/TextButton" />
+</LinearLayout>
diff --git a/chrome/browser/history/java/res/layout/appfilter_header.xml b/chrome/browser/history/java/res/layout/appfilter_header.xml
new file mode 100644
index 0000000..6ccd1f4
--- /dev/null
+++ b/chrome/browser/history/java/res/layout/appfilter_header.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2024 The Chromium Authors
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/min_touch_target_size"
+    android:orientation="vertical"
+    tools:ignore="UseCompoundDrawables">
+    <ImageView
+        android:id="@+id/drag_handlebar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_horizontal"
+        android:layout_marginTop="8dp"
+        android:importantForAccessibility="no"
+        android:src="@drawable/drag_handlebar" />
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="24dp"
+        android:layout_gravity="bottom"
+        android:textDirection="locale"
+        android:text="@string/history_app_filter_sheet_header"
+        style="@style/TextAppearance.TextAccentMediumThick.Primary" />
+</LinearLayout>
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java
new file mode 100644
index 0000000..db6ba57
--- /dev/null
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinator.java
@@ -0,0 +1,192 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.history;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
+import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
+
+import java.util.List;
+
+/** Coordinator class of the app filter bottom sheet UI for history page. */
+class AppFilterCoordinator implements View.OnLayoutChangeListener {
+    // Maximum number of app filter items shown on the sheet at once if screen dimension allows.
+    static final int MAX_VISIBLE_ITEM_COUNT = 5;
+
+    // Maximum ratio of the sheet height against the base view height.
+    static final float MAX_SHEET_HEIGHT_RATIO = 0.7f;
+
+    private final Context mContext;
+    private final BottomSheetController mBottomSheetController;
+    private final AppFilterMediator mMediator;
+    private final RecyclerView mItemListView;
+    private final BottomSheetContent mSheetContent;
+    private final PropertyModel mCloseButtonModel;
+
+    private final View mContentView;
+    private final View mBaseView;
+
+    private final CloseCallback mCloseCallback;
+    private final int mAppCount;
+
+    private int mBaseViewHeight;
+
+    /** Data class for individual app item in the filter list. */
+    public static class AppInfo {
+        public final String id;
+        public final Drawable icon;
+        public final CharSequence label;
+
+        public AppInfo(String id, Drawable icon, CharSequence label) {
+            this.id = id;
+            this.icon = icon;
+            this.label = label;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) return true;
+            return (o instanceof AppInfo appInfo) ? TextUtils.equals(id, appInfo.id) : false;
+        }
+    }
+
+    /** Callback to be invoked when the sheet gets closed with updated app info. */
+    public interface CloseCallback {
+        /**
+         * @param appInfo {@link AppInfo} containing the app information. May be {@code null} if no
+         *     app is selected.
+         */
+        void onAppUpdated(AppInfo appInfo);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param context {@link Context} for resources, views.
+     * @param baseView Base view on which the sheet is opened.
+     * @param bottomSheetController {@link BottomSheetController} to open/close the sheet.
+     * @param closeCallback Callback invoked when the sheet is closed
+     * @param appInfoList List of the apps to display in the sheet.
+     */
+    AppFilterCoordinator(
+            Context context,
+            View baseView,
+            BottomSheetController bottomSheetController,
+            CloseCallback closeCallback,
+            List<AppInfo> appInfoList) {
+        mContext = context;
+        mBaseView = baseView;
+        mBaseViewHeight = mBaseView.getHeight();
+        mBottomSheetController = bottomSheetController;
+        mCloseCallback = closeCallback;
+        var layoutInflater = LayoutInflater.from(context);
+        mContentView = layoutInflater.inflate(R.layout.appfilter_content, null);
+        mItemListView = (RecyclerView) mContentView.findViewById(R.id.appfilter_item_list);
+        mSheetContent =
+                new AppFilterSheetContent(context, mContentView, mItemListView, this::destroy);
+
+        ModelList listItems = new ModelList();
+        var adapter = new SimpleRecyclerViewAdapter(listItems);
+        adapter.registerType(
+                0,
+                (parent) -> layoutInflater.inflate(R.layout.modern_list_item_view, parent, false),
+                AppFilterViewBinder::bind);
+        mItemListView.setAdapter(adapter);
+
+        // Close button at the bottom.
+        View closeButton = mContentView.findViewById(R.id.close_button);
+        mCloseButtonModel =
+                new PropertyModel.Builder(AppFilterProperties.CLOSE_BUTTON_KEY)
+                        .with(
+                                AppFilterProperties.CLOSE_BUTTON_CALLBACK,
+                                v -> mBottomSheetController.hideContent(mSheetContent, true))
+                        .build();
+        PropertyModelChangeProcessor.create(
+                mCloseButtonModel, closeButton, AppFilterViewBinder::bind);
+
+        mMediator = new AppFilterMediator(context, listItems, appInfoList, this::closeSheet);
+        mAppCount = listItems.size();
+    }
+
+    @Override
+    public void onLayoutChange(
+            View view,
+            int left,
+            int top,
+            int right,
+            int bottom,
+            int oldLeft,
+            int oldTop,
+            int oldRight,
+            int oldBottom) {
+        if (!mBottomSheetController.isSheetOpen()) return;
+        if (mBaseViewHeight != mBaseView.getHeight()) {
+            mBaseViewHeight = mBaseView.getHeight();
+            updateSheetHeight();
+        }
+    }
+
+    /** Open app filter bottom sheet. */
+    public void openSheet() {
+        updateSheetHeight();
+        mBottomSheetController.requestShowContent(mSheetContent, true);
+    }
+
+    /**
+     * Update the sheet height. Called before opening it for the first time, or while it is open in
+     * order to adjust the height if the base view layout change occurs.
+     */
+    private void updateSheetHeight() {
+        ViewGroup.LayoutParams layoutParams = mItemListView.getLayoutParams();
+        if (layoutParams == null) {
+            layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
+        }
+
+        int rowHeight = mContext.getResources().getDimensionPixelSize(R.dimen.list_item_min_height);
+        layoutParams.height = calculateSheetHeight(rowHeight, mBaseView.getHeight(), mAppCount);
+        mItemListView.setLayoutParams(layoutParams);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static int calculateSheetHeight(int rowHeight, int baseViewHeight, int rowCount) {
+        int maxHeight = (int) (baseViewHeight * MAX_SHEET_HEIGHT_RATIO);
+        int visibleRowCount = Math.min(rowCount, MAX_VISIBLE_ITEM_COUNT);
+        return Math.min(visibleRowCount * rowHeight, maxHeight);
+    }
+
+    private void closeSheet(AppInfo appInfo) {
+        mBottomSheetController.hideContent(mSheetContent, true);
+        mCloseCallback.onAppUpdated(appInfo);
+    }
+
+    private void destroy() {
+        mBaseView.removeOnLayoutChangeListener(this);
+    }
+
+    void clickItemForTesting(String appId) {
+        mMediator.clickItemForTesting(appId); // IN-TEST
+    }
+
+    void clickCloseButtonForTesting() {
+        mCloseButtonModel.get(AppFilterProperties.CLOSE_BUTTON_CALLBACK).onClick(null); // IN-TEST
+    }
+
+    void setCurrentAppForTesting(String appId) {
+        mMediator.setCurrentAppForTesting(appId); // IN-TEST
+    }
+}
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java
new file mode 100644
index 0000000..3344341
--- /dev/null
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterCoordinatorTest.java
@@ -0,0 +1,237 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.history;
+
+import static org.junit.Assert.assertEquals;
+
+import static org.chromium.chrome.browser.history.AppFilterCoordinator.MAX_SHEET_HEIGHT_RATIO;
+import static org.chromium.chrome.browser.history.AppFilterCoordinator.MAX_VISIBLE_ITEM_COUNT;
+import static org.chromium.content_public.browser.test.util.TestThreadUtils.runOnUiThreadBlocking;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.view.ViewGroup;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseActivityTestRule;
+import org.chromium.base.test.util.ApplicationTestUtils;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.history.AppFilterCoordinator.AppInfo;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetControllerFactory;
+import org.chromium.components.browser_ui.widget.scrim.ScrimCoordinator;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+import org.chromium.ui.KeyboardVisibilityDelegate;
+import org.chromium.ui.test.util.BlankUiTestActivity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Integration tests for the app filter sheet for history page. */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@Batch(Batch.PER_CLASS)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+public class AppFilterCoordinatorTest {
+    /** {@link AppInfo} indicating that no app is selected i.e. full history. */
+    private static final AppInfo APP_NOTSELECTED = new AppInfo(null, null, null);
+
+    private static final String APPID_YOUTUBE = "com.google.android.youtube";
+    private static final String APPID_CHROME = "com.android.chrome";
+    private static final String APPID_CALENDAR = "com.google.android.calendar";
+    private static final String APPID_MESSAGE = "com.google.android.apps.messaging";
+
+    private static final CharSequence APPLABEL_YOUTUBE = "YouTube";
+    private static final CharSequence APPLABEL_CHROME = "Chrome";
+    private static final CharSequence APPLABEL_CALENDAR = "Calendar";
+    private static final CharSequence APPLABEL_MESSAGE = "Message";
+
+    @Rule
+    public final BaseActivityTestRule<BlankUiTestActivity> mActivityRule =
+            new BaseActivityTestRule<>(BlankUiTestActivity.class);
+
+    private BottomSheetController mBottomSheetController;
+    private AppFilterCoordinator mAppFilterSheet;
+    private Drawable mIcon;
+    private String mAppId;
+    private CharSequence mAppLabel;
+
+    @Before
+    public void setUp() throws InterruptedException {
+        mActivityRule.launchActivity(null);
+        Activity activity = getActivity();
+        ApplicationTestUtils.waitForActivityState(activity, Stage.RESUMED);
+        runOnUiThreadBlocking(
+                () -> {
+                    mBottomSheetController = createBottomSheetController();
+
+                    mIcon = activity.getResources().getDrawable(R.drawable.ic_devices_16dp);
+                    List<AppInfo> apps = new ArrayList<>();
+                    apps.add(new AppInfo(APPID_YOUTUBE, mIcon, APPLABEL_YOUTUBE));
+                    apps.add(new AppInfo(APPID_CHROME, mIcon, APPLABEL_CHROME));
+                    apps.add(new AppInfo(APPID_CALENDAR, mIcon, APPLABEL_CALENDAR));
+                    apps.add(new AppInfo(APPID_MESSAGE, mIcon, APPLABEL_MESSAGE));
+                    mAppFilterSheet =
+                            new AppFilterCoordinator(
+                                    activity,
+                                    activity.getWindow().getDecorView(),
+                                    mBottomSheetController,
+                                    this::onAppUpdated,
+                                    apps);
+                });
+    }
+
+    private BlankUiTestActivity getActivity() {
+        return mActivityRule.getActivity();
+    }
+
+    private BottomSheetController createBottomSheetController() {
+        ViewGroup activityContentView = getActivity().findViewById(android.R.id.content);
+        ScrimCoordinator scrimCoordinator =
+                new ScrimCoordinator(
+                        getActivity(),
+                        new ScrimCoordinator.SystemUiScrimDelegate() {
+                            @Override
+                            public void setStatusBarScrimFraction(float scrimFraction) {}
+
+                            @Override
+                            public void setNavigationBarScrimFraction(float scrimFraction) {}
+                        },
+                        activityContentView,
+                        Color.WHITE);
+        return BottomSheetControllerFactory.createBottomSheetController(
+                () -> scrimCoordinator,
+                (unused) -> {},
+                getActivity().getWindow(),
+                KeyboardVisibilityDelegate.getInstance(),
+                () -> activityContentView,
+                () -> 0);
+    }
+
+    private void onAppUpdated(AppInfo appInfo) {
+        mAppId = appInfo != null ? appInfo.id : null;
+        mAppLabel = appInfo != null ? appInfo.label : null;
+    }
+
+    private void setCurrentAppInfo(String appId, CharSequence appLabel) {
+        mAppId = appId;
+        mAppLabel = appLabel;
+        mAppFilterSheet.setCurrentAppForTesting(appId);
+    }
+
+    private int calcSheetHeight(int rowHeight, int baseViewHeight, int rowCount) {
+        return AppFilterCoordinator.calculateSheetHeight(rowHeight, baseViewHeight, rowCount);
+    }
+
+    @Test
+    @SmallTest
+    public void testSheetSize() {
+        final int rowHeight = 64;
+        final int baseHeight = 1200;
+        final int defaultMaxHeight = rowHeight * MAX_VISIBLE_ITEM_COUNT;
+
+        int rowCount = MAX_VISIBLE_ITEM_COUNT - 1;
+        assertEquals(rowHeight * rowCount, calcSheetHeight(rowHeight, baseHeight, rowCount));
+
+        rowCount = MAX_VISIBLE_ITEM_COUNT;
+        assertEquals(rowHeight * rowCount, calcSheetHeight(rowHeight, baseHeight, rowCount));
+
+        rowCount = MAX_VISIBLE_ITEM_COUNT + 1;
+        assertEquals(defaultMaxHeight, calcSheetHeight(rowHeight, baseHeight, rowCount));
+
+        rowCount = MAX_VISIBLE_ITEM_COUNT * 2;
+        assertEquals(defaultMaxHeight, calcSheetHeight(rowHeight, baseHeight, rowCount));
+
+        final int smallBase = 300;
+        final int maxHeight = (int) (smallBase * MAX_SHEET_HEIGHT_RATIO);
+
+        rowCount = 2;
+        assertEquals(rowHeight * rowCount, calcSheetHeight(rowHeight, smallBase, rowCount));
+
+        rowCount = MAX_VISIBLE_ITEM_COUNT;
+        assertEquals(maxHeight, calcSheetHeight(rowHeight, smallBase, rowCount));
+
+        rowCount = 100;
+        assertEquals(maxHeight, calcSheetHeight(rowHeight, smallBase, rowCount));
+    }
+
+    @Test
+    @MediumTest
+    public void testFullHistoryToApp() {
+        assertEquals("Selected app is not correct.", null, mAppId);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mAppFilterSheet.openSheet();
+                    mAppFilterSheet.clickItemForTesting(APPID_MESSAGE);
+                });
+
+        // Tapping an app selects it.
+        assertEquals("Chosen app is not correct.", APPID_MESSAGE, mAppId);
+        assertEquals("Chosen app is not correct.", APPLABEL_MESSAGE, mAppLabel);
+    }
+
+    @Test
+    @MediumTest
+    public void testSelectNewApp() {
+        setCurrentAppInfo(APPID_CALENDAR, APPLABEL_CALENDAR);
+        assertEquals("Selected app is not correct.", APPID_CALENDAR, mAppId);
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mAppFilterSheet.openSheet();
+                    mAppFilterSheet.clickItemForTesting(APPID_CHROME);
+                });
+
+        // Tapping an app makes it a newly selected one.
+        assertEquals("Chosen app is not correct.", APPID_CHROME, mAppId);
+        assertEquals("Chosen app is not correct.", APPLABEL_CHROME, mAppLabel);
+    }
+
+    @Test
+    @MediumTest
+    public void testUnselectApp() {
+        setCurrentAppInfo(APPID_CALENDAR, APPLABEL_CALENDAR);
+        assertEquals("Selected app is not correct.", APPID_CALENDAR, mAppId);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mAppFilterSheet.openSheet();
+                    mAppFilterSheet.clickItemForTesting(APPID_CALENDAR);
+                });
+
+        // Tapping the already selected app unselects it.
+        assertEquals("Chosen app is not correct.", APP_NOTSELECTED.id, mAppId);
+        assertEquals("Chosen app is not correct.", APP_NOTSELECTED.label, mAppLabel);
+    }
+
+    @Test
+    @MediumTest
+    public void testCloseSheetWithoutSelection() {
+        setCurrentAppInfo(APPID_CALENDAR, APPLABEL_CALENDAR);
+        assertEquals("Selected app is not correct.", APPID_CALENDAR, mAppId);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mAppFilterSheet.openSheet();
+                    mAppFilterSheet.clickCloseButtonForTesting();
+                });
+
+        // Closing the sheet preserves the previously selected app.
+        assertEquals("Chosen app is not correct.", APPID_CALENDAR, mAppId);
+        assertEquals("Chosen app is not correct.", APPLABEL_CALENDAR, mAppLabel);
+    }
+}
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterMediator.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterMediator.java
new file mode 100644
index 0000000..107eba3
--- /dev/null
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterMediator.java
@@ -0,0 +1,86 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.history;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import org.chromium.chrome.browser.history.AppFilterCoordinator.AppInfo;
+import org.chromium.chrome.browser.history.AppFilterCoordinator.CloseCallback;
+import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
+
+import java.util.List;
+
+/** Mediator class for history app filter sheet. */
+class AppFilterMediator {
+    private final ModelList mModelList;
+    private final CloseCallback mCloseCallback;
+
+    private @Nullable PropertyModel mSelectedModel;
+
+    AppFilterMediator(
+            Context context,
+            ModelList modelList,
+            List<AppInfo> appInfoList,
+            CloseCallback closeCallback) {
+        mModelList = modelList;
+        mCloseCallback = closeCallback;
+        for (AppInfo info : appInfoList) {
+            PropertyModel item = generateListItem(info);
+            mModelList.add(new SimpleRecyclerViewAdapter.ListItem(0, item));
+        }
+    }
+
+    private @Nullable PropertyModel generateListItem(AppInfo info) {
+        PropertyModel model =
+                new PropertyModel.Builder(AppFilterProperties.LIST_ITEM_KEYS)
+                        .with(AppFilterProperties.ID, info.id)
+                        .with(AppFilterProperties.ICON, info.icon)
+                        .with(AppFilterProperties.LABEL, info.label)
+                        .with(AppFilterProperties.SELECTED, false)
+                        .build();
+        model.set(AppFilterProperties.CLICK_LISTENER, v -> handleClick(model));
+        return model;
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    void handleClick(PropertyModel model) {
+        PropertyModel prevModel = mSelectedModel;
+
+        String appId = model.get(AppFilterProperties.ID);
+        boolean toFullHistory = prevModel != null && prevModel == model;
+
+        if (prevModel != null) prevModel.set(AppFilterProperties.SELECTED, false);
+        mSelectedModel = model;
+        if (toFullHistory) {
+            mCloseCallback.onAppUpdated(null);
+        } else {
+            mSelectedModel.set(AppFilterProperties.SELECTED, true);
+            AppInfo appInfo = new AppInfo(appId, null, model.get(AppFilterProperties.LABEL));
+            mCloseCallback.onAppUpdated(appInfo);
+        }
+    }
+
+    private PropertyModel getModelForAppIdForTesting(String appId) {
+        for (SimpleRecyclerViewAdapter.ListItem item : mModelList) {
+            if (appId.equals(item.model.get(AppFilterProperties.ID))) {
+                return item.model;
+            }
+        }
+        return null;
+    }
+
+    void clickItemForTesting(String appId) {
+        handleClick(getModelForAppIdForTesting(appId));
+    }
+
+    void setCurrentAppForTesting(String appId) {
+        mSelectedModel = getModelForAppIdForTesting(appId);
+    }
+}
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterProperties.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterProperties.java
new file mode 100644
index 0000000..01d949f
--- /dev/null
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterProperties.java
@@ -0,0 +1,32 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.history;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.ReadableObjectPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
+import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
+
+/** Property model properties for app filter sheet UI. */
+class AppFilterProperties {
+    public static final ReadableObjectPropertyKey<String> ID = new ReadableObjectPropertyKey();
+    public static final ReadableObjectPropertyKey<Drawable> ICON = new ReadableObjectPropertyKey();
+    public static final ReadableObjectPropertyKey<CharSequence> LABEL =
+            new ReadableObjectPropertyKey();
+    public static final WritableObjectPropertyKey<View.OnClickListener> CLICK_LISTENER =
+            new WritableObjectPropertyKey();
+    public static final WritableBooleanPropertyKey SELECTED = new WritableBooleanPropertyKey();
+    public static final ReadableObjectPropertyKey<View.OnClickListener> CLOSE_BUTTON_CALLBACK =
+            new ReadableObjectPropertyKey();
+
+    /** Property keys for a list item. */
+    public static final PropertyKey[] LIST_ITEM_KEYS = {ID, ICON, LABEL, CLICK_LISTENER, SELECTED};
+
+    /** Property keys for the close button. */
+    public static final PropertyKey[] CLOSE_BUTTON_KEY = {CLOSE_BUTTON_CALLBACK};
+}
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterSheetContent.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterSheetContent.java
new file mode 100644
index 0000000..ce5f4cf
--- /dev/null
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterSheetContent.java
@@ -0,0 +1,96 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.history;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetContent;
+
+/** BottomSheetContent implementation for app filter bottom sheet. */
+class AppFilterSheetContent implements BottomSheetContent {
+    private final View mContentView;
+    private final View mToolbarView;
+    private final RecyclerView mListView;
+    private final Runnable mCloseRunnable;
+
+    /** Construct a new AppFilterSheet. */
+    AppFilterSheetContent(
+            Context context, View contentView, RecyclerView listView, Runnable closeRunnable) {
+        var layoutInflater = LayoutInflater.from(context);
+        mToolbarView = layoutInflater.inflate(R.layout.appfilter_header, null);
+        mContentView = contentView;
+        mListView = listView;
+        mCloseRunnable = closeRunnable;
+    }
+
+    @Override
+    public View getContentView() {
+        return mContentView;
+    }
+
+    @Override
+    public View getToolbarView() {
+        return mToolbarView;
+    }
+
+    @Override
+    public int getVerticalScrollOffset() {
+        return mListView.computeVerticalScrollOffset();
+    }
+
+    @Override
+    public void destroy() {
+        mCloseRunnable.run();
+    }
+
+    @Override
+    public @ContentPriority int getPriority() {
+        return ContentPriority.HIGH;
+    }
+
+    @Override
+    public boolean swipeToDismissEnabled() {
+        return true;
+    }
+
+    @Override
+    public int getPeekHeight() {
+        return BottomSheetContent.HeightMode.DISABLED;
+    }
+
+    @Override
+    public float getHalfHeightRatio() {
+        return BottomSheetContent.HeightMode.DISABLED;
+    }
+
+    @Override
+    public float getFullHeightRatio() {
+        return BottomSheetContent.HeightMode.WRAP_CONTENT;
+    }
+
+    @Override
+    public int getSheetContentDescriptionStringId() {
+        return R.string.history_app_filter_sheet_description;
+    }
+
+    @Override
+    public int getSheetHalfHeightAccessibilityStringId() {
+        return 0; // disabled
+    }
+
+    @Override
+    public int getSheetFullHeightAccessibilityStringId() {
+        return R.string.history_app_filter_sheet_opened;
+    }
+
+    @Override
+    public int getSheetClosedAccessibilityStringId() {
+        return R.string.history_app_filter_sheet_closed;
+    }
+}
diff --git a/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterViewBinder.java b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterViewBinder.java
new file mode 100644
index 0000000..95390eb
--- /dev/null
+++ b/chrome/browser/history/java/src/org/chromium/chrome/browser/history/AppFilterViewBinder.java
@@ -0,0 +1,36 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.history;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.chromium.ui.modelutil.PropertyKey;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** Binder object for the appfilter sheet content model and the view. */
+class AppFilterViewBinder {
+    static void bind(PropertyModel model, View view, PropertyKey key) {
+        if (AppFilterProperties.ICON == key) {
+            var icon = (ImageView) view.findViewById(R.id.start_icon);
+            icon.setImageDrawable(model.get(AppFilterProperties.ICON));
+            icon.setScaleType(ImageView.ScaleType.FIT_CENTER);
+        } else if (AppFilterProperties.LABEL == key) {
+            ((TextView) view.findViewById(R.id.title))
+                    .setText(model.get(AppFilterProperties.LABEL));
+            view.findViewById(R.id.description).setVisibility(View.GONE);
+        } else if (AppFilterProperties.SELECTED == key) {
+            var checkMark = (ImageView) view.findViewById(R.id.end_button);
+            checkMark.setImageResource(R.drawable.ic_check_googblue_24dp);
+            boolean selected = model.get(AppFilterProperties.SELECTED);
+            checkMark.setVisibility(selected ? View.VISIBLE : View.INVISIBLE);
+        } else if (AppFilterProperties.CLICK_LISTENER == key) {
+            view.setOnClickListener(model.get(AppFilterProperties.CLICK_LISTENER));
+        } else if (AppFilterProperties.CLOSE_BUTTON_CALLBACK == key) {
+            view.setOnClickListener(model.get(AppFilterProperties.CLOSE_BUTTON_CALLBACK));
+        }
+    }
+}
diff --git a/chrome/browser/model_execution/model_manager_impl.cc b/chrome/browser/model_execution/model_manager_impl.cc
index e3012692..f9bcc1e 100644
--- a/chrome/browser/model_execution/model_manager_impl.cc
+++ b/chrome/browser/model_execution/model_manager_impl.cc
@@ -4,16 +4,23 @@
 
 #include "chrome/browser/model_execution/model_manager_impl.h"
 
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/functional/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/task_traits.h"
+#include "base/task/thread_pool.h"
 #include "chrome/browser/model_execution/model_execution_session.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/optimization_guide/core/model_execution/feature_keys.h"
+#include "components/optimization_guide/core/model_util.h"
 #include "components/optimization_guide/core/optimization_guide_features.h"
-#include "content/public/browser/content_browser_client.h"
+#include "components/optimization_guide/core/optimization_guide_switches.h"
 #include "content/public/browser/render_frame_host.h"
-#include "content/public/common/content_client.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "third_party/blink/public/mojom/devtools/console_message.mojom-shared.h"
 #include "third_party/blink/public/mojom/model_execution/model_manager.mojom.h"
 
 DOCUMENT_USER_DATA_KEY_IMPL(ModelManagerImpl);
@@ -34,15 +41,63 @@
   model_manager->receiver_.Bind(std::move(receiver));
 }
 
+bool ModelManagerImpl::IsModelPathValid(const std::string& model_path_str) {
+  std::optional<base::FilePath> model_path =
+      optimization_guide::StringToFilePath(model_path_str);
+  if (!model_path) {
+    return false;
+  }
+  return base::PathExists(*model_path);
+}
+
 void ModelManagerImpl::CanCreateGenericSession(
     CanCreateGenericSessionCallback callback) {
-  // TODO(leimy): add the checks after optimization guide component provide more
-  // method to determine if a session could be started.
+  // If the `OptimizationGuideKeyedService` cannot be retrieved, return false.
+  // TODO(https://crbug.com/330819915): add the checks after optimization guide
+  // component provide more method to determine if a session could be started.
   content::BrowserContext* browser_context = browser_context_.get();
-  std::move(callback).Run(
-      /*can_create=*/browser_context &&
-      !!OptimizationGuideKeyedServiceFactory::GetForProfile(
-          Profile::FromBrowserContext(browser_context)));
+  CHECK(browser_context);
+  if (!OptimizationGuideKeyedServiceFactory::GetForProfile(
+          Profile::FromBrowserContext(browser_context))) {
+    render_frame_host().AddMessageToConsole(
+        blink::mojom::ConsoleMessageLevel::kWarning,
+        "Unable to create generic session because the service is not running.");
+    std::move(callback).Run(/*can_create=*/false);
+    return;
+  }
+
+  // If the model path is empty or invalid, return false.
+  auto model_path =
+      optimization_guide::switches::GetOnDeviceModelExecutionOverride();
+  if (!model_path) {
+    render_frame_host().AddMessageToConsole(
+        blink::mojom::ConsoleMessageLevel::kWarning,
+        "Unable to create generic session because the model path is not "
+        "provided.");
+    std::move(callback).Run(/*can_create=*/false);
+    return;
+  }
+
+  // This needs to be done in a task runner with `MayBlock` trait.
+  base::ThreadPool::PostTaskAndReplyWithResult(
+      FROM_HERE, {base::MayBlock()},
+      base::BindOnce(&ModelManagerImpl::IsModelPathValid,
+                     base::Unretained(this), model_path.value()),
+      base::BindOnce(
+          [](CanCreateGenericSessionCallback callback,
+             content::RenderFrameHost& rfh, const std::string& model_path,
+             bool is_valid_path) {
+            if (!is_valid_path) {
+              rfh.AddMessageToConsole(
+                  blink::mojom::ConsoleMessageLevel::kWarning,
+                  base::StringPrintf("Unable to create generic session because "
+                                     "the model path ('%s') is invalid.",
+                                     model_path.c_str()));
+            }
+            std::move(callback).Run(is_valid_path);
+          },
+          std::move(callback), std::ref(render_frame_host()),
+          model_path.value()));
 }
 
 void ModelManagerImpl::CreateGenericSession(
diff --git a/chrome/browser/model_execution/model_manager_impl.h b/chrome/browser/model_execution/model_manager_impl.h
index 69fccbe..c7967d4c 100644
--- a/chrome/browser/model_execution/model_manager_impl.h
+++ b/chrome/browser/model_execution/model_manager_impl.h
@@ -34,6 +34,9 @@
 
   explicit ModelManagerImpl(content::RenderFrameHost* rfh);
 
+  // Checks if the model path configured via command line is valid.
+  bool IsModelPathValid(const std::string& model_path);
+
   // `blink::mojom::ModelManager` implementation.
   void CanCreateGenericSession(
       CanCreateGenericSessionCallback callback) override;
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/LocalTabGroupMutationHelper.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/LocalTabGroupMutationHelper.java
index 9d1975e..f9a0d3b 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/LocalTabGroupMutationHelper.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/LocalTabGroupMutationHelper.java
@@ -74,7 +74,7 @@
                 tabs, tabs.get(0), /* isSameGroup= */ true, /* notify= */ false);
 
         // Notify sync backend about IDs of the newly created group and tabs.
-        mTabGroupSyncService.updateLocalTabGroupId(tabGroup.syncId, groupId);
+        mTabGroupSyncService.updateLocalTabGroupMapping(tabGroup.syncId, groupId);
         for (String syncTabId : tabIdMappings.keySet()) {
             mTabGroupSyncService.updateLocalTabId(groupId, syncTabId, tabIdMappings.get(syncTabId));
         }
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/StartupHelper.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/StartupHelper.java
index 3220508..e7a5581 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/StartupHelper.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/StartupHelper.java
@@ -98,7 +98,7 @@
             Integer localGroupId = idMappingsFromPref.get(syncGroupId);
             if (localGroupId == null) continue;
 
-            mTabGroupSyncService.updateLocalTabGroupId(syncGroupId, localGroupId);
+            mTabGroupSyncService.updateLocalTabGroupMapping(syncGroupId, localGroupId);
         }
     }
 
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/StartupHelperUnitTest.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/StartupHelperUnitTest.java
index dac3ae5..75ff63c 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/StartupHelperUnitTest.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/StartupHelperUnitTest.java
@@ -78,7 +78,7 @@
         when(mTabGroupModelFilter.getTabGroupSyncId(1)).thenReturn(SYNC_ID_1);
         when(mTabGroupSyncService.getAllGroupIds()).thenReturn(new String[] {SYNC_ID_1});
         mStartupHelper.initializeTabGroupSync();
-        verify(mTabGroupSyncService).updateLocalTabGroupId(eq(SYNC_ID_1), eq(1));
+        verify(mTabGroupSyncService).updateLocalTabGroupMapping(eq(SYNC_ID_1), eq(1));
     }
 
     @Test
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncRemoteObserverUnitTest.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncRemoteObserverUnitTest.java
index e767b3f..0379eb1 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncRemoteObserverUnitTest.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TabGroupSyncRemoteObserverUnitTest.java
@@ -88,7 +88,7 @@
                 .mergeListOfTabsToGroup(anyList(), any(), anyBoolean(), anyBoolean());
         verify(mTabGroupModelFilter).setTabGroupColor(anyInt(), anyInt());
         verify(mTabGroupModelFilter).setTabGroupTitle(anyInt(), any());
-        verify(mTabGroupSyncService).updateLocalTabGroupId(any(), anyInt());
+        verify(mTabGroupSyncService).updateLocalTabGroupMapping(any(), anyInt());
         verify(mTabGroupSyncService, times(2)).updateLocalTabId(anyInt(), any(), anyInt());
     }
 
diff --git a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TestTabGroupSyncService.java b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TestTabGroupSyncService.java
index 9c70d8e..d155ee0 100644
--- a/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TestTabGroupSyncService.java
+++ b/chrome/browser/tab_group_sync/android/java/src/org/chromium/chrome/browser/tab_group_sync/TestTabGroupSyncService.java
@@ -65,7 +65,10 @@
     }
 
     @Override
-    public void updateLocalTabGroupId(String syncId, int localId) {}
+    public void updateLocalTabGroupMapping(String syncId, int localId) {}
+
+    @Override
+    public void removeLocalTabGroupMapping(int localId) {}
 
     @Override
     public void updateLocalTabId(int localGroupId, String syncTabId, int localTabId) {}
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index ca54ecd..6b31fe6 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -64,6 +64,8 @@
     "autofill/autofill_popup_hide_helper.h",
     "autofill/autofill_popup_view.h",
     "autofill/autofill_popup_view_delegate.h",
+    "autofill/autofill_suggestion_controller_utils.cc",
+    "autofill/autofill_suggestion_controller_utils.h",
     "autofill/chrome_autofill_client.cc",
     "autofill/chrome_autofill_client.h",
     "autofill/next_idle_time_ticks.cc",
@@ -953,6 +955,9 @@
       "autofill/autofill_bubble_base.h",
       "autofill/autofill_bubble_controller_base.cc",
       "autofill/autofill_bubble_controller_base.h",
+      "autofill/autofill_keyboard_accessory_controller.h",
+      "autofill/autofill_keyboard_accessory_controller_impl.cc",
+      "autofill/autofill_keyboard_accessory_controller_impl.h",
       "autofill/payments/autofill_snackbar_controller.h",
       "autofill/payments/autofill_snackbar_controller_impl.cc",
       "autofill/payments/autofill_snackbar_controller_impl.h",
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 07b70ad..57538fee 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -1720,6 +1720,20 @@
         Learn more about <ph name="BEGIN_LINK">&lt;link&gt;</ph>how Chrome keeps your data private<ph name="END_LINK">&lt;/link&gt;</ph>
       </message>
 
+      <!-- History App filter -->
+      <message name="IDS_HISTORY_APP_FILTER_SHEET_HEADER" desc="Title in the header of history app filter sheet.">
+        Filter by app
+      </message>
+      <message name="IDS_HISTORY_APP_FILTER_SHEET_DESCRIPTION" desc="The content description of history app filter where an app id to filter the history with can be chosen.">
+        App filter sheet
+      </message>
+      <message name="IDS_HISTORY_APP_FILTER_SHEET_OPENED" desc="The accessibility string read when the app filter sheet is fully opened.">
+        App filter sheet is opened.
+      </message>
+      <message name="IDS_HISTORY_APP_FILTER_SHEET_CLOSED" desc="The accessibility string read when the app filter sheet is closed.">
+        App filter sheet is closed.
+      </message>
+
       <!-- Safe Browsing standard protection preferences -->
       <message name="IDS_SAFE_BROWSING_STANDARD_PROTECTION_SUBTITLE" desc="Subtitle for Safe Browsing standard protection mode.">
         Standard protection:
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_CLOSED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_CLOSED.png.sha1
new file mode 100644
index 0000000..7342f3d
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_CLOSED.png.sha1
@@ -0,0 +1 @@
+e74641ec28634952df75cb08cf8e423ddd5319a6
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_DESCRIPTION.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..7342f3d
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+e74641ec28634952df75cb08cf8e423ddd5319a6
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_HEADER.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_HEADER.png.sha1
new file mode 100644
index 0000000..7342f3d
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_HEADER.png.sha1
@@ -0,0 +1 @@
+e74641ec28634952df75cb08cf8e423ddd5319a6
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_OPENED.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_OPENED.png.sha1
new file mode 100644
index 0000000..7342f3d
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_HISTORY_APP_FILTER_SHEET_OPENED.png.sha1
@@ -0,0 +1 @@
+e74641ec28634952df75cb08cf8e423ddd5319a6
\ No newline at end of file
diff --git a/chrome/browser/ui/ash/birch/birch_keyed_service_factory.cc b/chrome/browser/ui/ash/birch/birch_keyed_service_factory.cc
index 22f23ab4..4d9a10a5 100644
--- a/chrome/browser/ui/ash/birch/birch_keyed_service_factory.cc
+++ b/chrome/browser/ui/ash/birch/birch_keyed_service_factory.cc
@@ -6,7 +6,7 @@
 
 #include <memory>
 
-#include "ash/constants/ash_features.h"
+#include "ash/utility/forest_util.h"
 #include "base/no_destructor.h"
 #include "chrome/browser/ash/file_suggest/file_suggest_keyed_service_factory.h"
 #include "chrome/browser/profiles/profile.h"
@@ -39,7 +39,7 @@
     content::BrowserContext* context) {
   return static_cast<BirchKeyedService*>(
       GetInstance()->GetServiceForBrowserContext(
-          context, /*create=*/features::IsForestFeatureEnabled()));
+          context, /*create=*/IsForestFeatureEnabled()));
 }
 
 std::unique_ptr<KeyedService>
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
index 0f5ea7f..9ca791d6 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.cc
@@ -835,14 +835,7 @@
   if (target_index < 0 || model_->IsAppPinned(app_id))
     return;
 
-  ash::ShelfItem item;
-  item.type = ash::TYPE_PINNED_APP;
-  item.id = ash::ShelfID(app_id);
-
-  model_->AddAt(target_index, item,
-                std::make_unique<AppShortcutShelfItemController>(item.id));
-
-  ReportUpdateShelfIconList(model_);
+  EnsureAppPinnedInModelAtIndex(app_id, /*current_index=*/-1, target_index);
 }
 
 int ChromeShelfController::PinnedItemIndexByAppID(const std::string& app_id) {
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.h b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.h
index 3d4395f..7f639e7 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller.h
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller.h
@@ -250,11 +250,7 @@
   void ReplacePinnedItem(const std::string& old_app_id,
                          const std::string& new_app_id);
 
-  // This method is only used by ApkWebAppService and tests. This method
-  // relies on implicit assumptions and is likely unsuitable for other use
-  // cases.
-  //
-  // Pins app with |app_id| at |target_index|.
+  // Pins app with |app_id| at |target_index| if it is not already pinned.
   void PinAppAtIndex(const std::string& app_id, int target_index);
 
   // Converts |app_id| to shelf_id and calls ShelfModel function ItemIndexbyID
diff --git a/chrome/browser/ui/autofill/autofill_keyboard_accessory_adapter.cc b/chrome/browser/ui/autofill/autofill_keyboard_accessory_adapter.cc
index a9022f6d..e6f389c 100644
--- a/chrome/browser/ui/autofill/autofill_keyboard_accessory_adapter.cc
+++ b/chrome/browser/ui/autofill/autofill_keyboard_accessory_adapter.cc
@@ -251,6 +251,11 @@
   NOTREACHED();
 }
 
+void AutofillKeyboardAccessoryAdapter::SetViewForTesting(
+    base::WeakPtr<AutofillPopupView> view) {
+  NOTREACHED();
+}
+
 void AutofillKeyboardAccessoryAdapter::UpdateDataListValues(
     base::span<const SelectOption> options) {
   NOTREACHED();
diff --git a/chrome/browser/ui/autofill/autofill_keyboard_accessory_adapter.h b/chrome/browser/ui/autofill/autofill_keyboard_accessory_adapter.h
index 880aef09..5a0a9d1 100644
--- a/chrome/browser/ui/autofill/autofill_keyboard_accessory_adapter.h
+++ b/chrome/browser/ui/autofill/autofill_keyboard_accessory_adapter.h
@@ -132,6 +132,7 @@
             AutoselectFirstSuggestion autoselect_first_suggestion) override;
   void DisableThresholdForTesting(bool disable_threshold) override;
   void KeepPopupOpenForTesting() override;
+  void SetViewForTesting(base::WeakPtr<AutofillPopupView> view) override;
   void UpdateDataListValues(base::span<const SelectOption> options) override;
   void PinView() override;
 
diff --git a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller.h b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller.h
new file mode 100644
index 0000000..6768ce5
--- /dev/null
+++ b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller.h
@@ -0,0 +1,18 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_CONTROLLER_H_
+#define CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_CONTROLLER_H_
+
+#include "chrome/browser/ui/autofill/autofill_popup_controller.h"
+
+namespace autofill {
+
+class AutofillKeyboardAccessoryController : public AutofillPopupController {
+ public:
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_CONTROLLER_H_
diff --git a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc
new file mode 100644
index 0000000..e22b1f3
--- /dev/null
+++ b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc
@@ -0,0 +1,578 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/check_op.h"
+#include "base/memory/weak_ptr.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/notreached.h"
+#include "base/time/time.h"
+#include "chrome/browser/autofill/personal_data_manager_factory.h"
+#include "chrome/browser/keyboard_accessory/android/manual_filling_controller.h"
+#include "chrome/browser/password_manager/android/local_passwords_migration_warning_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/autofill/autofill_popup_view.h"
+#include "chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h"
+#include "components/autofill/core/browser/personal_data_manager.h"
+#include "components/autofill/core/browser/ui/autofill_popup_delegate.h"
+#include "components/autofill/core/browser/ui/popup_hiding_reasons.h"
+#include "components/autofill/core/common/autofill_features.h"
+#include "components/password_manager/core/browser/password_manager_metrics_util.h"
+#include "components/password_manager/core/common/password_manager_features.h"
+#include "components/strings/grit/components_strings.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace autofill {
+
+using FillingSource = ManualFillingController::FillingSource;
+
+// static
+base::WeakPtr<AutofillPopupController> AutofillPopupController::GetOrCreate(
+    base::WeakPtr<AutofillPopupController> previous,
+    base::WeakPtr<AutofillPopupDelegate> delegate,
+    content::WebContents* web_contents,
+    PopupControllerCommon controller_common,
+    int32_t form_control_ax_id) {
+  // All controllers on Android derive from
+  // `AutofillKeyboardAccessoryControllerImpl`.
+  if (AutofillKeyboardAccessoryControllerImpl* previous_impl =
+          static_cast<AutofillKeyboardAccessoryControllerImpl*>(previous.get());
+      previous_impl && previous_impl->delegate_.get() == delegate.get() &&
+      previous_impl->container_view() == controller_common.container_view) {
+    if (previous_impl->self_deletion_weak_ptr_factory_.HasWeakPtrs()) {
+      previous_impl->self_deletion_weak_ptr_factory_.InvalidateWeakPtrs();
+    }
+    previous_impl->controller_common_ = std::move(controller_common);
+    previous_impl->suggestions_.clear();
+    return previous_impl->GetWeakPtr();
+  }
+
+  if (previous) {
+    previous->Hide(PopupHidingReason::kViewDestroyed);
+  }
+  auto* controller = new AutofillKeyboardAccessoryControllerImpl(
+      delegate, web_contents, std::move(controller_common),
+      base::BindRepeating(&local_password_migration::ShowWarning));
+  return controller->GetWeakPtr();
+}
+
+AutofillKeyboardAccessoryControllerImpl::
+    AutofillKeyboardAccessoryControllerImpl(
+        base::WeakPtr<AutofillPopupDelegate> delegate,
+        content::WebContents* web_contents,
+        PopupControllerCommon controller_common,
+        ShowPasswordMigrationWarningCallback
+            show_pwd_migration_warning_callback)
+    : delegate_(delegate),
+      web_contents_(web_contents->GetWeakPtr()),
+      controller_common_(std::move(controller_common)),
+      show_pwd_migration_warning_callback_(
+          std::move(show_pwd_migration_warning_callback)) {}
+
+AutofillKeyboardAccessoryControllerImpl::
+    ~AutofillKeyboardAccessoryControllerImpl() = default;
+
+void AutofillKeyboardAccessoryControllerImpl::Hide(PopupHidingReason reason) {
+  // If the reason for hiding is only stale data or a user interacting with
+  // native Chrome UI (kFocusChanged/kEndEditing), the popup might be kept open.
+  if (is_view_pinned_ && (reason == PopupHidingReason::kStaleData ||
+                          reason == PopupHidingReason::kFocusChanged ||
+                          reason == PopupHidingReason::kEndEditing)) {
+    return;  // Don't close the popup while waiting for an update.
+  }
+  // For tests, keep open when hiding is due to external stimuli.
+  if (keep_popup_open_for_testing_ &&
+      (reason == PopupHidingReason::kWidgetChanged ||
+       reason == PopupHidingReason::kEndEditing)) {
+    // Don't close the popup because the browser window is resized or because
+    // too many fields get focus one after each other (this can happen on
+    // Desktop, if multiple password forms are present, and they are all
+    // autofilled by default).
+    return;
+  }
+
+  if (delegate_) {
+    delegate_->ClearPreviewedForm();
+    delegate_->OnPopupHidden();
+  }
+  popup_hide_helper_.reset();
+  AutofillMetrics::LogAutofillPopupHidingReason(reason);
+  HideViewAndDie();
+}
+
+void AutofillKeyboardAccessoryControllerImpl::HideViewAndDie() {
+  // Invalidates in particular ChromeAutofillClient's WeakPtr to `this`, which
+  // prevents recursive calls triggered by `view_->Hide()`
+  // (crbug.com/1267047).
+  weak_ptr_factory_.InvalidateWeakPtrs();
+
+  // Mark the popup-like filling sources as unavailable.
+  // Note: We don't invoke ManualFillingController::Hide() here, as we might
+  // switch between text input fields.
+  if (web_contents_) {
+    if (base::WeakPtr<ManualFillingController> manual_filling_controller =
+            ManualFillingController::GetOrCreate(web_contents_.get())) {
+      manual_filling_controller->UpdateSourceAvailability(
+          FillingSource::AUTOFILL,
+          /*has_suggestions=*/false);
+    }
+  }
+
+  // TODO(crbug.com/1341374, crbug.com/1277218): Move this into the asynchronous
+  // call?
+  if (view_) {
+    view_->Hide();
+    view_ = nullptr;
+  }
+
+  if (self_deletion_weak_ptr_factory_.HasWeakPtrs()) {
+    return;
+  }
+
+  // TODO: Examine whether this is really enough or revert to the one from
+  // the popup controller.
+  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          [](base::WeakPtr<AutofillKeyboardAccessoryControllerImpl> weak_this) {
+            delete weak_this.get();
+          },
+          self_deletion_weak_ptr_factory_.GetWeakPtr()));
+}
+
+void AutofillKeyboardAccessoryControllerImpl::ViewDestroyed() {
+  Hide(PopupHidingReason::kViewDestroyed);
+}
+
+gfx::NativeView AutofillKeyboardAccessoryControllerImpl::container_view()
+    const {
+  return controller_common_.container_view;
+}
+
+content::WebContents* AutofillKeyboardAccessoryControllerImpl::GetWebContents()
+    const {
+  return web_contents_.get();
+}
+
+const gfx::RectF& AutofillKeyboardAccessoryControllerImpl::element_bounds()
+    const {
+  return controller_common_.element_bounds;
+}
+
+base::i18n::TextDirection
+AutofillKeyboardAccessoryControllerImpl::GetElementTextDirection() const {
+  return controller_common_.text_direction;
+}
+
+void AutofillKeyboardAccessoryControllerImpl::OnSuggestionsChanged() {
+  // Assume that suggestions are (still) available. If this is wrong, the method
+  // `HideViewAndDie` will be called soon after and will hide all suggestions.
+  if (base::WeakPtr<ManualFillingController> manual_filling_controller =
+          ManualFillingController::GetOrCreate(web_contents_.get())) {
+    manual_filling_controller->UpdateSourceAvailability(
+        FillingSource::AUTOFILL,
+        /*has_suggestions=*/true);
+  }
+  if (view_) {
+    view_->OnSuggestionsChanged();
+  }
+}
+
+void AutofillKeyboardAccessoryControllerImpl::SelectSuggestion(int index) {
+  CHECK_GE(index, 0);
+  CHECK_LT(index, static_cast<int>(suggestions_.size()));
+
+  if (!IsAcceptablePopupItemId(GetSuggestionAt(index).popup_item_id)) {
+    UnselectSuggestion();
+    return;
+  }
+
+  delegate_->DidSelectSuggestion(GetSuggestionAt(index));
+}
+
+void AutofillKeyboardAccessoryControllerImpl::UnselectSuggestion() {
+  delegate_->ClearPreviewedForm();
+}
+
+void AutofillKeyboardAccessoryControllerImpl::AcceptSuggestion(int index) {
+  // Ignore clicks immediately after the popup was shown. This is to prevent
+  // users accidentally accepting suggestions (crbug.com/1279268).
+  if (time_view_shown_.value().is_null() && !disable_threshold_for_testing_) {
+    return;
+  }
+  const base::TimeDelta time_elapsed =
+      base::TimeTicks::Now() - time_view_shown_.value();
+  // If `kAutofillPopupImprovedTimingChecksV2` is enabled, then
+  // `time_view_shown_` will remain null for at least
+  // `kIgnoreEarlyClicksOnPopupDuration`. Therefore we do not have to check any
+  // times here.
+  // TODO(crbug.com/1475902): Once `kAutofillPopupImprovedTimingChecksV2` is
+  // launched, clean up most of the timing checks. That is:
+  // - Remove paint checks inside views.
+  // - Remove `event_time` parameters.
+  // - Rename `NextIdleTimeTicks` to `IdleDelayBarrier` or something similar
+  //   that indicates that just contains a boolean signaling whether a certain
+  //   delay has (safely) passed.
+  if (time_elapsed < kIgnoreEarlyClicksOnPopupDuration &&
+      !disable_threshold_for_testing_ &&
+      !base::FeatureList::IsEnabled(
+          features::kAutofillPopupImprovedTimingChecksV2)) {
+    base::UmaHistogramCustomTimes(
+        "Autofill.Popup.AcceptanceDelayThresholdNotMet", time_elapsed,
+        base::Milliseconds(0), kIgnoreEarlyClicksOnPopupDuration,
+        /*buckets=*/50);
+    return;
+  }
+
+  if (static_cast<size_t>(index) >= suggestions_.size()) {
+    return;
+  }
+
+  if (IsPointerLocked(web_contents_.get())) {
+    Hide(PopupHidingReason::kMouseLocked);
+    return;
+  }
+
+  // Use a copy instead of a reference here. Under certain circumstances,
+  // `DidAcceptSuggestion()` invalidate the reference.
+  Suggestion suggestion = suggestions_[index];
+  if (base::WeakPtr<ManualFillingController> manual_filling_controller =
+          ManualFillingController::GetOrCreate(web_contents_.get())) {
+    // Accepting a suggestion should hide all suggestions. To prevent them from
+    // coming up in Multi-Window mode, mark the source as unavailable.
+    manual_filling_controller->UpdateSourceAvailability(
+        FillingSource::AUTOFILL,
+        /*has_suggestions=*/false);
+    manual_filling_controller->Hide();
+  }
+
+  NotifyIphAboutAcceptedSuggestion(web_contents_->GetBrowserContext(),
+                                   suggestion);
+  if (suggestion.acceptance_a11y_announcement && view_) {
+    view_->AxAnnounce(*suggestion.acceptance_a11y_announcement);
+  }
+
+  delegate_->DidAcceptSuggestion(
+      suggestion, AutofillPopupDelegate::SuggestionPosition{.row = index});
+
+  if (suggestion.popup_item_id == PopupItemId::kPasswordEntry &&
+      base::FeatureList::IsEnabled(
+          password_manager::features::
+              kUnifiedPasswordManagerLocalPasswordsMigrationWarning)) {
+    show_pwd_migration_warning_callback_.Run(
+        web_contents_->GetTopLevelNativeWindow(),
+        Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
+        password_manager::metrics_util::PasswordMigrationWarningTriggers::
+            kKeyboardAcessoryBar);
+  }
+}
+
+void AutofillKeyboardAccessoryControllerImpl::PerformButtonActionForSuggestion(
+    int index) {
+  NOTREACHED() << "Button actions do not exist for the keyboard accessory.";
+}
+
+bool AutofillKeyboardAccessoryControllerImpl::RemoveSuggestion(
+    int index,
+    AutofillMetrics::SingleEntryRemovalMethod removal_method) {
+  CHECK_EQ(removal_method,
+           AutofillMetrics::SingleEntryRemovalMethod::kKeyboardAccessory);
+
+  // This function might be called in a callback, so ensure the list index is
+  // still in bounds. If not, terminate the removing and consider it failed.
+  // TODO(crbug.com/1209792): Replace these checks with a stronger identifier.
+  if (index < 0 || static_cast<size_t>(index) >= suggestions_.size()) {
+    return false;
+  }
+
+  if (!delegate_->RemoveSuggestion(suggestions_[index])) {
+    return false;
+  }
+  PopupItemId suggestion_type = suggestions_[index].popup_item_id;
+  switch (GetFillingProductFromPopupItemId(suggestion_type)) {
+    case FillingProduct::kAddress:
+      AutofillMetrics::LogDeleteAddressProfileFromKeyboardAccessory();
+      break;
+    case FillingProduct::kAutocomplete:
+      AutofillMetrics::OnAutocompleteSuggestionDeleted(removal_method);
+      if (view_) {
+        view_->AxAnnounce(l10n_util::GetStringFUTF16(
+            IDS_AUTOFILL_AUTOCOMPLETE_ENTRY_DELETED_A11Y_HINT,
+            suggestions_[index].main_text.value));
+      }
+      break;
+    case FillingProduct::kCreditCard:
+      // TODO(1509457): Add metrics for credit cards.
+      break;
+    case FillingProduct::kNone:
+    case FillingProduct::kMerchantPromoCode:
+    case FillingProduct::kIban:
+    case FillingProduct::kPassword:
+    case FillingProduct::kCompose:
+    case FillingProduct::kPlusAddresses:
+      break;
+  }
+
+  // Remove the deleted element.
+  suggestions_.erase(suggestions_.begin() + index);
+
+  if (HasSuggestions()) {
+    delegate_->ClearPreviewedForm();
+    OnSuggestionsChanged();
+  } else {
+    Hide(PopupHidingReason::kNoSuggestions);
+  }
+
+  return true;
+}
+
+int AutofillKeyboardAccessoryControllerImpl::GetLineCount() const {
+  return suggestions_.size();
+}
+
+std::vector<Suggestion>
+AutofillKeyboardAccessoryControllerImpl::GetSuggestions() const {
+  return suggestions_;
+}
+
+const Suggestion& AutofillKeyboardAccessoryControllerImpl::GetSuggestionAt(
+    int row) const {
+  return suggestions_[row];
+}
+
+std::u16string AutofillKeyboardAccessoryControllerImpl::GetSuggestionMainTextAt(
+    int row) const {
+  return suggestions_[row].main_text.value;
+}
+
+std::u16string
+AutofillKeyboardAccessoryControllerImpl::GetSuggestionMinorTextAt(
+    int row) const {
+  return suggestions_[row].minor_text.value;
+}
+
+std::vector<std::vector<Suggestion::Text>>
+AutofillKeyboardAccessoryControllerImpl::GetSuggestionLabelsAt(int row) const {
+  return suggestions_[row].labels;
+}
+
+bool AutofillKeyboardAccessoryControllerImpl::GetRemovalConfirmationText(
+    int index,
+    std::u16string* title,
+    std::u16string* body) {
+  const std::u16string& value = suggestions_[index].main_text.value;
+  const PopupItemId popup_item_id = suggestions_[index].popup_item_id;
+  const Suggestion::BackendId backend_id =
+      suggestions_[index].GetPayload<Suggestion::BackendId>();
+
+  if (popup_item_id == PopupItemId::kAutocompleteEntry) {
+    if (title) {
+      title->assign(value);
+    }
+    if (body) {
+      body->assign(l10n_util::GetStringUTF16(
+          IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_CONFIRMATION_BODY));
+    }
+    return true;
+  }
+
+  if (popup_item_id != PopupItemId::kAddressEntry &&
+      popup_item_id != PopupItemId::kCreditCardEntry) {
+    return false;
+  }
+  PersonalDataManager* pdm = PersonalDataManagerFactory::GetForBrowserContext(
+      web_contents_->GetBrowserContext());
+
+  if (const CreditCard* credit_card = pdm->GetCreditCardByGUID(
+          absl::get<Suggestion::Guid>(backend_id).value())) {
+    if (!CreditCard::IsLocalCard(credit_card)) {
+      return false;
+    }
+    if (title) {
+      title->assign(credit_card->CardNameAndLastFourDigits());
+    }
+    if (body) {
+      body->assign(l10n_util::GetStringUTF16(
+          IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY));
+    }
+    return true;
+  }
+
+  if (const AutofillProfile* profile = pdm->GetProfileByGUID(
+          absl::get<Suggestion::Guid>(backend_id).value())) {
+    if (title) {
+      std::u16string street_address = profile->GetRawInfo(ADDRESS_HOME_CITY);
+      if (!street_address.empty()) {
+        title->swap(street_address);
+      } else {
+        title->assign(value);
+      }
+    }
+    if (body) {
+      body->assign(l10n_util::GetStringUTF16(
+          IDS_AUTOFILL_DELETE_PROFILE_SUGGESTION_CONFIRMATION_BODY));
+    }
+
+    return true;
+  }
+
+  return false;  // The ID was valid. The entry may have been deleted in a race.
+}
+
+FillingProduct AutofillKeyboardAccessoryControllerImpl::GetMainFillingProduct()
+    const {
+  return delegate_->GetMainFillingProduct();
+}
+
+bool AutofillKeyboardAccessoryControllerImpl::
+    ShouldIgnoreMouseObservedOutsideItemBoundsCheck() const {
+  return false;
+}
+
+base::WeakPtr<AutofillPopupController>
+AutofillKeyboardAccessoryControllerImpl::OpenSubPopup(
+    const gfx::RectF& anchor_bounds,
+    std::vector<Suggestion> suggestions,
+    AutoselectFirstSuggestion autoselect_first_suggestion) {
+  NOTREACHED_NORETURN() << "Sub-popups do not exist in the keyboard accessory.";
+}
+
+void AutofillKeyboardAccessoryControllerImpl::HideSubPopup() {
+  NOTREACHED() << "Sub-popups do not exist in the keyboard accessory.";
+}
+
+std::optional<AutofillClient::PopupScreenLocation>
+AutofillKeyboardAccessoryControllerImpl::GetPopupScreenLocation() const {
+  return std::nullopt;
+}
+
+void AutofillKeyboardAccessoryControllerImpl::Show(
+    std::vector<Suggestion> suggestions,
+    AutofillSuggestionTriggerSource trigger_source,
+    AutoselectFirstSuggestion autoselect_first_suggestion) {
+  if (auto* rwhv = web_contents_->GetRenderWidgetHostView();
+      !rwhv || !rwhv->HasFocus()) {
+    return;
+  }
+
+  // The focused frame may be a different frame than the one the delegate is
+  // associated with. This happens in two scenarios:
+  // - With frame-transcending forms: the focused frame is subframe, whose
+  //   form has been flattened into an ancestor form.
+  // - With race conditions: while Autofill parsed the form, the focused may
+  //   have moved to another frame.
+  // We support the case where the focused frame is a descendant of the
+  // `delegate_`'s frame. We observe the focused frame's RenderFrameDeleted()
+  // event.
+  content::RenderFrameHost* rfh = web_contents_->GetFocusedFrame();
+  if (!rfh || !delegate_ ||
+      !IsAncestorOf(GetRenderFrameHost(*delegate_), rfh)) {
+    Hide(PopupHidingReason::kNoFrameHasFocus);
+    return;
+  }
+
+  if (IsPointerLocked(web_contents_.get())) {
+    Hide(PopupHidingReason::kMouseLocked);
+    return;
+  }
+
+  AutofillPopupHideHelper::HidingParams hiding_params = {
+      // Currently, this is only relevant for Compose on Desktop.
+      .hide_on_text_field_change = false,
+      .hide_on_web_contents_lost_focus = true};
+  AutofillPopupHideHelper::HidingCallback hiding_callback = base::BindRepeating(
+      &AutofillKeyboardAccessoryControllerImpl::Hide, base::Unretained(this));
+  AutofillPopupHideHelper::PictureInPictureDetectionCallback
+      pip_detection_callback = base::BindRepeating(
+          [](base::WeakPtr<AutofillKeyboardAccessoryControllerImpl>
+                 controller) {
+            return controller && controller->view_ &&
+                   controller->view_->OverlapsWithPictureInPictureWindow();
+          },
+          GetWeakPtr());
+  popup_hide_helper_.emplace(
+      web_contents_.get(), rfh->GetGlobalId(), std::move(hiding_params),
+      std::move(hiding_callback), std::move(pip_detection_callback));
+
+  suggestions_ = std::move(suggestions);
+  trigger_source_ = trigger_source;
+
+  if (view_) {
+    OnSuggestionsChanged();
+  } else {
+    view_ = AutofillPopupView::Create(GetWeakPtr());
+    // It is possible to fail to create the popup.
+    if (!view_) {
+      Hide(PopupHidingReason::kViewDestroyed);
+      return;
+    }
+
+    if (base::WeakPtr<ManualFillingController> manual_filling_controller =
+            ManualFillingController::GetOrCreate(web_contents_.get())) {
+      manual_filling_controller->UpdateSourceAvailability(
+          FillingSource::AUTOFILL, !suggestions_.empty());
+    }
+    if (!view_ || !view_->Show(autoselect_first_suggestion)) {
+      return;
+    }
+  }
+
+  time_view_shown_ = base::FeatureList::IsEnabled(
+                         features::kAutofillPopupImprovedTimingChecksV2)
+                         ? NextIdleTimeTicks::CaptureNextIdleTimeTicksWithDelay(
+                               kIgnoreEarlyClicksOnPopupDuration)
+                         : NextIdleTimeTicks::CaptureNextIdleTimeTicks();
+}
+
+void AutofillKeyboardAccessoryControllerImpl::DisableThresholdForTesting(
+    bool disable_threshold) {
+  disable_threshold_for_testing_ = disable_threshold;
+}
+
+void AutofillKeyboardAccessoryControllerImpl::KeepPopupOpenForTesting() {
+  keep_popup_open_for_testing_ = true;
+}
+
+void AutofillKeyboardAccessoryControllerImpl::SetViewForTesting(
+    base::WeakPtr<AutofillPopupView> view) {
+  view_ = std::move(view);
+  time_view_shown_ = NextIdleTimeTicks::CaptureNextIdleTimeTicks();
+}
+
+void AutofillKeyboardAccessoryControllerImpl::UpdateDataListValues(
+    base::span<const SelectOption> options) {
+  UpdateSuggestionsFromDataList(options, suggestions_);
+  if (HasSuggestions()) {
+    OnSuggestionsChanged();
+  } else {
+    Hide(PopupHidingReason::kNoSuggestions);
+  }
+}
+
+void AutofillKeyboardAccessoryControllerImpl::PinView() {
+  is_view_pinned_ = true;
+}
+
+bool AutofillKeyboardAccessoryControllerImpl::HasSuggestions() const {
+  if (suggestions_.empty()) {
+    return false;
+  }
+  PopupItemId popup_item_id = suggestions_[0].popup_item_id;
+  return base::Contains(kItemsTriggeringFieldFilling, popup_item_id) ||
+         popup_item_id == PopupItemId::kScanCreditCard;
+}
+
+base::WeakPtr<AutofillKeyboardAccessoryControllerImpl>
+AutofillKeyboardAccessoryControllerImpl::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h
new file mode 100644
index 0000000..b638aab
--- /dev/null
+++ b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h
@@ -0,0 +1,158 @@
+// 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_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_CONTROLLER_IMPL_H_
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/ui/autofill/autofill_keyboard_accessory_controller.h"
+#include "chrome/browser/ui/autofill/autofill_popup_hide_helper.h"
+#include "chrome/browser/ui/autofill/next_idle_time_ticks.h"
+#include "chrome/browser/ui/autofill/popup_controller_common.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "components/autofill/core/browser/metrics/autofill_metrics.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
+#include "components/password_manager/core/browser/password_manager_metrics_util.h"
+
+namespace content {
+class WebContents;
+}  // namespace content
+
+class Profile;
+
+namespace autofill {
+
+class AutofillPopupDelegate;
+class AutofillPopupView;
+struct Suggestion;
+
+class AutofillKeyboardAccessoryControllerImpl
+    : public AutofillKeyboardAccessoryController {
+ public:
+  using ShowPasswordMigrationWarningCallback = base::RepeatingCallback<void(
+      gfx::NativeWindow,
+      Profile*,
+      password_manager::metrics_util::PasswordMigrationWarningTriggers)>;
+
+  AutofillKeyboardAccessoryControllerImpl(
+      base::WeakPtr<AutofillPopupDelegate> delegate,
+      content::WebContents* web_contents,
+      PopupControllerCommon controller_common,
+      ShowPasswordMigrationWarningCallback show_pwd_migration_warning_callback);
+
+  AutofillKeyboardAccessoryControllerImpl(
+      const AutofillKeyboardAccessoryControllerImpl&) = delete;
+  AutofillKeyboardAccessoryControllerImpl& operator=(
+      const AutofillKeyboardAccessoryControllerImpl&) = delete;
+
+  ~AutofillKeyboardAccessoryControllerImpl() override;
+
+  // AutofillPopupViewDelegate:
+  void Hide(PopupHidingReason reason) override;
+  void ViewDestroyed() override;
+  gfx::NativeView container_view() const override;
+  content::WebContents* GetWebContents() const override;
+  const gfx::RectF& element_bounds() const override;
+  base::i18n::TextDirection GetElementTextDirection() const override;
+
+  // AutofillPopupController:
+  void OnSuggestionsChanged() override;
+  void SelectSuggestion(int index) override;
+  void UnselectSuggestion() override;
+  void AcceptSuggestion(int index) override;
+  void PerformButtonActionForSuggestion(int index) override;
+  bool RemoveSuggestion(
+      int index,
+      AutofillMetrics::SingleEntryRemovalMethod removal_method) override;
+  int GetLineCount() const override;
+  std::vector<Suggestion> GetSuggestions() const override;
+  const Suggestion& GetSuggestionAt(int row) const override;
+  std::u16string GetSuggestionMainTextAt(int row) const override;
+  std::u16string GetSuggestionMinorTextAt(int row) const override;
+  std::vector<std::vector<Suggestion::Text>> GetSuggestionLabelsAt(
+      int row) const override;
+  bool GetRemovalConfirmationText(int index,
+                                  std::u16string* title,
+                                  std::u16string* body) override;
+  FillingProduct GetMainFillingProduct() const override;
+  bool ShouldIgnoreMouseObservedOutsideItemBoundsCheck() const override;
+  base::WeakPtr<AutofillPopupController> OpenSubPopup(
+      const gfx::RectF& anchor_bounds,
+      std::vector<Suggestion> suggestions,
+      AutoselectFirstSuggestion autoselect_first_suggestion) override;
+  void HideSubPopup() override;
+  std::optional<AutofillClient::PopupScreenLocation> GetPopupScreenLocation()
+      const override;
+  void Show(std::vector<Suggestion> suggestions,
+            AutofillSuggestionTriggerSource trigger_source,
+            AutoselectFirstSuggestion autoselect_first_suggestion) override;
+  void DisableThresholdForTesting(bool disable_threshold) override;
+  void KeepPopupOpenForTesting() override;
+  void SetViewForTesting(base::WeakPtr<AutofillPopupView> view) override;
+  void UpdateDataListValues(base::span<const SelectOption> options) override;
+  void PinView() override;
+
+  base::WeakPtr<AutofillKeyboardAccessoryControllerImpl> GetWeakPtr();
+
+ private:
+  friend class AutofillPopupController;
+
+  // Returns true if the popup has entries that are not "Manage ...".
+  bool HasSuggestions() const;
+
+  // Hides the view and asynchronously deletes itself.
+  void HideViewAndDie();
+
+  base::WeakPtr<AutofillPopupDelegate> delegate_;
+  base::WeakPtr<content::WebContents> web_contents_;
+  PopupControllerCommon controller_common_;
+  base::WeakPtr<AutofillPopupView> view_;
+
+  // A helper that detects events that should hide the popup.
+  std::optional<AutofillPopupHideHelper> popup_hide_helper_;
+
+  // The suggestions to be shown in the Keyboard Accessory.
+  std::vector<Suggestion> suggestions_;
+
+  // The trigger source of the `suggestions_`.
+  AutofillSuggestionTriggerSource trigger_source_ =
+      AutofillSuggestionTriggerSource::kUnspecified;
+
+  // The time the view was shown the last time. It is used to safeguard against
+  // accepting suggestions too quickly after a the popup view was shown (see the
+  // `show_threshold` parameter of `AcceptSuggestion`).
+  NextIdleTimeTicks time_view_shown_;
+
+  // An override to suppress minimum show thresholds. It should only be set
+  // during tests that cannot mock time (e.g. the autofill interactive
+  // browsertests).
+  bool disable_threshold_for_testing_ = false;
+
+  // If set to true, the popup will never be hidden because of stale data or if
+  // the user interacts with native UI.
+  bool is_view_pinned_ = false;
+
+  // If set to true, the popup will stay open regardless of external changes on
+  // the machine that would normally cause the popup to be hidden.
+  bool keep_popup_open_for_testing_ = false;
+
+  // Callback invoked to try to show the password migration warning on Android.
+  // Used to facilitate testing.
+  // TODO(crbug.com/1454469): Remove when the warning isn't needed anymore.
+  ShowPasswordMigrationWarningCallback show_pwd_migration_warning_callback_;
+
+  base::WeakPtrFactory<AutofillKeyboardAccessoryControllerImpl>
+      self_deletion_weak_ptr_factory_{this};
+
+  base::WeakPtrFactory<AutofillKeyboardAccessoryControllerImpl>
+      weak_ptr_factory_{this};
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_KEYBOARD_ACCESSORY_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller.h b/chrome/browser/ui/autofill/autofill_popup_controller.h
index ab0f4ec..27b0f3f 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller.h
+++ b/chrome/browser/ui/autofill/autofill_popup_controller.h
@@ -24,6 +24,7 @@
 
 namespace autofill {
 
+class AutofillPopupView;
 struct PopupControllerCommon;
 
 // This interface provides data to an AutofillPopupView.
@@ -137,8 +138,11 @@
   // set during tests that cannot mock time (e.g. the autofill interactive
   // browsertests).
   virtual void DisableThresholdForTesting(bool disable_threshold) = 0;
+
   virtual void KeepPopupOpenForTesting() = 0;
 
+  virtual void SetViewForTesting(base::WeakPtr<AutofillPopupView> view) = 0;
+
   // Updates the data list values currently shown with the popup.
   virtual void UpdateDataListValues(base::span<const SelectOption> options) = 0;
 
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
index 13acd71..a5f5e52 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
@@ -13,7 +13,6 @@
 #include "base/check_op.h"
 #include "base/command_line.h"
 #include "base/functional/bind.h"
-#include "base/functional/overloaded.h"
 #include "base/i18n/rtl.h"
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
@@ -23,11 +22,10 @@
 #include "build/build_config.h"
 #include "chrome/browser/accessibility/accessibility_state_utils.h"
 #include "chrome/browser/autofill/personal_data_manager_factory.h"
-#include "chrome/browser/feature_engagement/tracker_factory.h"
 #include "chrome/browser/ui/autofill/autofill_popup_view.h"
+#include "chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h"
 #include "chrome/browser/ui/autofill/next_idle_time_ticks.h"
 #include "chrome/browser/ui/autofill/popup_controller_common.h"
-#include "components/autofill/content/browser/content_autofill_driver.h"
 #include "components/autofill/core/browser/autofill_manager.h"
 #include "components/autofill/core/browser/filling_product.h"
 #include "components/autofill/core/browser/metrics/autofill_metrics.h"
@@ -38,9 +36,6 @@
 #include "components/autofill/core/browser/ui/suggestion.h"
 #include "components/autofill/core/common/autofill_features.h"
 #include "components/compose/core/browser/config.h"
-#include "components/feature_engagement/public/feature_constants.h"
-#include "components/feature_engagement/public/tracker.h"
-#include "components/password_manager/content/browser/content_password_manager_driver.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_process_host.h"
@@ -68,54 +63,17 @@
 #include "components/compose/core/browser/compose_features.h"
 #endif
 
-using base::WeakPtr;
-
 namespace autofill {
 
-namespace {
-
-// Returns true if the given id refers to an element that can be accepted.
-bool CanAccept(PopupItemId id) {
-  return id != PopupItemId::kSeparator &&
-         id != PopupItemId::kInsecureContextPaymentDisabledMessage &&
-         id != PopupItemId::kMixedFormMessage && id != PopupItemId::kTitle;
-}
-
-content::RenderFrameHost* GetRenderFrameHost(AutofillPopupDelegate& delegate) {
-  return absl::visit(
-      base::Overloaded{
-          [](AutofillDriver* driver) {
-            return static_cast<ContentAutofillDriver*>(driver)
-                ->render_frame_host();
-          },
-          [](password_manager::PasswordManagerDriver* driver) {
-            return static_cast<password_manager::ContentPasswordManagerDriver*>(
-                       driver)
-                ->render_frame_host();
-          }},
-      delegate.GetDriver());
-}
-
-bool IsAncestorOf(content::RenderFrameHost* ancestor,
-                  content::RenderFrameHost* descendant) {
-  for (auto* rfh = descendant; rfh; rfh = rfh->GetParent()) {
-    if (rfh == ancestor) {
-      return true;
-    }
-  }
-  return false;
-}
-
-}  // namespace
-
-#if !BUILDFLAG(IS_MAC)
+#if !BUILDFLAG(IS_MAC) && !BUILDFLAG(IS_ANDROID)
 // static
-WeakPtr<AutofillPopupController> AutofillPopupController::GetOrCreate(
-    WeakPtr<AutofillPopupController> previous,
-    WeakPtr<AutofillPopupDelegate> delegate,
+base::WeakPtr<AutofillPopupController> AutofillPopupController::GetOrCreate(
+    base::WeakPtr<AutofillPopupController> previous,
+    base::WeakPtr<AutofillPopupDelegate> delegate,
     content::WebContents* web_contents,
     PopupControllerCommon controller_common,
     int32_t form_control_ax_id) {
+  // All controllers on Desktop derive from `AutofillPopupControllerImpl`.
   if (AutofillPopupControllerImpl* previous_impl =
           static_cast<AutofillPopupControllerImpl*>(previous.get());
       previous_impl && previous_impl->delegate_.get() == delegate.get() &&
@@ -132,16 +90,9 @@
   if (previous) {
     previous->Hide(PopupHidingReason::kViewDestroyed);
   }
-#if BUILDFLAG(IS_ANDROID)
-  auto* controller = new AutofillPopupControllerImpl(
-      delegate, web_contents, std::move(controller_common), form_control_ax_id,
-      base::BindRepeating(&local_password_migration::ShowWarning),
-      /*parent=*/std::nullopt);
-#else
   auto* controller = new AutofillPopupControllerImpl(
       delegate, web_contents, std::move(controller_common), form_control_ax_id,
       base::DoNothing(), /*parent=*/std::nullopt);
-#endif
   return controller->GetWeakPtr();
 }
 #endif
@@ -195,7 +146,7 @@
     return;
   }
 
-  if (IsPointerLocked()) {
+  if (IsPointerLocked(web_contents_.get())) {
     Hide(PopupHidingReason::kMouseLocked);
     return;
   }
@@ -307,47 +258,12 @@
 
 void AutofillPopupControllerImpl::UpdateDataListValues(
     base::span<const SelectOption> options) {
-  // Remove all the old data list values, which should always be at the top of
-  // the list if they are present.
-  while (!suggestions_.empty() &&
-         suggestions_[0].popup_item_id == PopupItemId::kDatalistEntry) {
-    suggestions_.erase(suggestions_.begin());
+  UpdateSuggestionsFromDataList(options, suggestions_);
+  if (HasSuggestions()) {
+    OnSuggestionsChanged();
+  } else {
+    Hide(PopupHidingReason::kNoSuggestions);
   }
-
-  // If there are no new data list values, exit (clearing the separator if there
-  // is one).
-  if (options.empty()) {
-    if (!suggestions_.empty() &&
-        suggestions_[0].popup_item_id == PopupItemId::kSeparator) {
-      suggestions_.erase(suggestions_.begin());
-    }
-
-    // The popup contents have changed, so either update the bounds or hide it.
-    if (HasSuggestions())
-      OnSuggestionsChanged();
-    else
-      Hide(PopupHidingReason::kNoSuggestions);
-
-    return;
-  }
-
-  // Add a separator if there are any other values.
-  if (!suggestions_.empty() &&
-      suggestions_[0].popup_item_id != PopupItemId::kSeparator) {
-    suggestions_.insert(suggestions_.begin(),
-                        Suggestion(PopupItemId::kSeparator));
-  }
-
-  // Prepend the parameters to the suggestions we already have.
-  suggestions_.insert(suggestions_.begin(), options.size(), Suggestion());
-  for (size_t i = 0; i < options.size(); i++) {
-    suggestions_[i].main_text =
-        Suggestion::Text(options[i].value, Suggestion::Text::IsPrimary(true));
-    suggestions_[i].labels = {{Suggestion::Text(options[i].content)}};
-    suggestions_[i].popup_item_id = PopupItemId::kDatalistEntry;
-  }
-
-  OnSuggestionsChanged();
 }
 
 void AutofillPopupControllerImpl::PinView() {
@@ -453,7 +369,7 @@
     return;
   }
 
-  if (IsPointerLocked()) {
+  if (IsPointerLocked(web_contents_.get())) {
     Hide(PopupHidingReason::kMouseLocked);
     return;
   }
@@ -480,29 +396,10 @@
   }
 #endif
 
-  if (suggestion.popup_item_id == PopupItemId::kVirtualCreditCardEntry) {
-    std::string event_name =
-        suggestion.feature_for_iph ==
-                &feature_engagement::kIPHAutofillVirtualCardCVCSuggestionFeature
-            ? "autofill_virtual_card_cvc_suggestion_accepted"
-            : "autofill_virtual_card_suggestion_accepted";
-    feature_engagement::TrackerFactory::GetForBrowserContext(
-        web_contents_->GetBrowserContext())
-        ->NotifyEvent(event_name);
-  }
-
-  if (suggestion.feature_for_iph ==
-      &feature_engagement::
-          kIPHAutofillExternalAccountProfileSuggestionFeature) {
-    feature_engagement::TrackerFactory::GetForBrowserContext(
-        web_contents_->GetBrowserContext())
-        ->NotifyEvent("autofill_external_account_profile_suggestion_accepted");
-  }
-
-  std::optional<std::u16string> announcement =
-      suggestion.acceptance_a11y_announcement;
-  if (announcement && view_) {
-    view_->AxAnnounce(*announcement);
+  NotifyIphAboutAcceptedSuggestion(web_contents_->GetBrowserContext(),
+                                   suggestion);
+  if (suggestion.acceptance_a11y_announcement && view_) {
+    view_->AxAnnounce(*suggestion.acceptance_a11y_announcement);
   }
 
   delegate_->DidAcceptSuggestion(
@@ -607,6 +504,7 @@
     int list_index,
     std::u16string* title,
     std::u16string* body) {
+  // TODO(crbug.com/333316034): Remove - this is not relevant on Desktop.
   const std::u16string& value = suggestions_[list_index].main_text.value;
   const PopupItemId popup_item_id = suggestions_[list_index].popup_item_id;
   const Suggestion::BackendId backend_id =
@@ -669,7 +567,7 @@
 bool AutofillPopupControllerImpl::RemoveSuggestion(
     int list_index,
     AutofillMetrics::SingleEntryRemovalMethod removal_method) {
-  if (IsPointerLocked()) {
+  if (IsPointerLocked(web_contents_.get())) {
     Hide(PopupHidingReason::kMouseLocked);
     return false;
   }
@@ -739,14 +637,15 @@
 }
 
 void AutofillPopupControllerImpl::SelectSuggestion(int index) {
+  CHECK_GE(index, 0);
   CHECK_LT(index, static_cast<int>(suggestions_.size()));
 
-  if (IsPointerLocked()) {
+  if (IsPointerLocked(web_contents_.get())) {
     Hide(PopupHidingReason::kMouseLocked);
     return;
   }
 
-  if (!CanAccept(GetSuggestionAt(index).popup_item_id)) {
+  if (!IsAcceptablePopupItemId(GetSuggestionAt(index).popup_item_id)) {
     UnselectSuggestion();
     return;
   }
@@ -782,7 +681,8 @@
   suggestions_ = std::move(suggestions);
 }
 
-WeakPtr<AutofillPopupControllerImpl> AutofillPopupControllerImpl::GetWeakPtr() {
+base::WeakPtr<AutofillPopupControllerImpl>
+AutofillPopupControllerImpl::GetWeakPtr() {
   return weak_ptr_factory_.GetWeakPtr();
 }
 
@@ -828,20 +728,13 @@
 
   base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
       FROM_HERE, base::BindOnce(
-                     [](WeakPtr<AutofillPopupControllerImpl> weak_this) {
+                     [](base::WeakPtr<AutofillPopupControllerImpl> weak_this) {
                        if (weak_this)
                          delete weak_this.get();
                      },
                      self_deletion_weak_ptr_factory_.GetWeakPtr()));
 }
 
-bool AutofillPopupControllerImpl::IsPointerLocked() const {
-  content::RenderFrameHost* rfh;
-  content::RenderWidgetHostView* rwhv;
-  return web_contents_ && (rfh = web_contents_->GetFocusedFrame()) &&
-         (rwhv = rfh->GetView()) && rwhv->IsPointerLocked();
-}
-
 base::WeakPtr<AutofillPopupView>
 AutofillPopupControllerImpl::CreateSubPopupView(
     base::WeakPtr<AutofillPopupController> controller) {
@@ -948,4 +841,10 @@
   handler_ = content::RenderWidgetHost::KeyPressEventCallback();
 }
 
+void AutofillPopupControllerImpl::SetViewForTesting(
+    base::WeakPtr<AutofillPopupView> view) {
+  view_ = std::move(view);
+  time_view_shown_ = NextIdleTimeTicks::CaptureNextIdleTimeTicks();
+}
+
 }  // namespace autofill
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.h b/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
index eb840af..551fb3c 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
@@ -117,10 +117,9 @@
   void UpdateDataListValues(base::span<const SelectOption> options) override;
   void PinView() override;
 
-  void SetViewForTesting(base::WeakPtr<AutofillPopupView> view) {
-    view_ = std::move(view);
-    time_view_shown_ = NextIdleTimeTicks::CaptureNextIdleTimeTicks();
-  }
+  base::WeakPtr<AutofillPopupControllerImpl> GetWeakPtr();
+
+  void SetViewForTesting(base::WeakPtr<AutofillPopupView> view) override;
 
  protected:
   AutofillPopupControllerImpl(
@@ -133,7 +132,8 @@
           Profile*,
           password_manager::metrics_util::PasswordMigrationWarningTriggers)>
           show_pwd_migration_warning_callback,
-      std::optional<base::WeakPtr<ExpandablePopupParentControllerImpl>> parent);
+      std::optional<base::WeakPtr<ExpandablePopupParentControllerImpl>> parent =
+          std::nullopt);
   ~AutofillPopupControllerImpl() override;
 
   gfx::NativeView container_view() const override;
@@ -148,8 +148,6 @@
   // without showing the popup.
   void SetSuggestions(std::vector<Suggestion> suggestions);
 
-  base::WeakPtr<AutofillPopupControllerImpl> GetWeakPtr();
-
   // Raise an accessibility event to indicate the controls relation of the
   // form control of the popup and popup itself has changed based on the popup's
   // show or hide action.
@@ -169,11 +167,6 @@
   // when the popup is reused it doesn't leak values between uses.
   void ClearState();
 
-  // Returns true iff the focused frame has a pointer lock, which may be used to
-  // trick the user into accepting some suggestion (crbug.com/1239496). In such
-  // a case, we should hide the popup.
-  bool IsPointerLocked() const;
-
   // ExpandablePopupParentControllerImpl:
   base::WeakPtr<AutofillPopupView> CreateSubPopupView(
       base::WeakPtr<AutofillPopupController> controller) override;
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl_unittest.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl_unittest.cc
index 44899f04..a5c427e 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl_unittest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl_unittest.cc
@@ -31,10 +31,10 @@
 using ::testing::AllOf;
 using ::testing::Field;
 using ::testing::Matcher;
+using ::testing::Mock;
 using ::testing::NiceMock;
 using ::testing::Return;
 
-#if !BUILDFLAG(IS_ANDROID)
 Matcher<const AutofillPopupDelegate::SuggestionPosition&>
 EqualsSuggestionPosition(AutofillPopupDelegate::SuggestionPosition position) {
   return AllOf(
@@ -42,13 +42,11 @@
       Field(&AutofillPopupDelegate::SuggestionPosition::sub_popup_level,
             position.sub_popup_level));
 }
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace
 
 using AutofillPopupControllerImplTest = AutofillPopupControllerTestBase<>;
 
-#if !BUILDFLAG(IS_ANDROID)
 TEST_F(AutofillPopupControllerImplTest, SubPopupIsCreatedWithViewFromParent) {
   base::WeakPtr<AutofillPopupController> sub_controller =
       client().popup_controller(manager()).OpenSubPopup(
@@ -115,7 +113,6 @@
   task_environment()->FastForwardBy(base::Milliseconds(1000));
   sub_controller->AcceptSuggestion(/*index=*/0);
 }
-#endif  // !BUILDFLAG(IS_ANDROID)
 
 TEST_F(AutofillPopupControllerImplTest,
        ManualFallBackTriggerSource_IgnoresClickOutsideCheck) {
@@ -132,6 +129,74 @@
                   .ShouldIgnoreMouseObservedOutsideItemBoundsCheck());
 }
 
+// Tests that the popup controller queries the view for its screen location.
+TEST_F(AutofillPopupControllerImplTest, GetPopupScreenLocationCallsView) {
+  ShowSuggestions(manager(), {PopupItemId::kCompose});
+
+  using PopupScreenLocation = AutofillClient::PopupScreenLocation;
+  constexpr gfx::Rect kSampleRect = gfx::Rect(123, 234);
+  EXPECT_CALL(client().popup_view(), GetPopupScreenLocation)
+      .WillOnce(Return(PopupScreenLocation{.bounds = kSampleRect}));
+  EXPECT_THAT(client().popup_controller(manager()).GetPopupScreenLocation(),
+              Optional(Field(&PopupScreenLocation::bounds, kSampleRect)));
+}
+
+// Tests that a change to a text field hides a popup with a Compose suggestion.
+TEST_F(AutofillPopupControllerImplTest, HidesOnFieldChangeForComposeEntries) {
+  ShowSuggestions(manager(), {PopupItemId::kCompose});
+  EXPECT_CALL(client().popup_controller(manager()),
+              Hide(PopupHidingReason::kFieldValueChanged));
+  manager().NotifyObservers(
+      &AutofillManager::Observer::OnBeforeTextFieldDidChange, FormGlobalId(),
+      FieldGlobalId());
+}
+
+// Tests that Compose saved state notification popup gets hidden after 2
+// seconds, but not after 1 second.
+TEST_F(AutofillPopupControllerImplTest,
+       TimedHideComposeSavedStateNotification) {
+  ShowSuggestions(manager(), {PopupItemId::kComposeSavedStateNotification});
+  test::GenerateTestAutofillPopup(&manager().external_delegate());
+  ::testing::MockFunction<void()> check;
+  {
+    ::testing::InSequence s;
+    EXPECT_CALL(check, Call);
+    EXPECT_CALL(client().popup_controller(manager()),
+                Hide(PopupHidingReason::kFadeTimerExpired));
+  }
+  task_environment()->FastForwardBy(base::Seconds(1));
+  check.Call();
+  task_environment()->FastForwardBy(base::Seconds(1));
+  Mock::VerifyAndClearExpectations(&client().popup_controller(manager()));
+}
+
+TEST_F(AutofillPopupControllerImplTest,
+       RemoveAutocompleteSuggestion_IgnoresClickOutsideCheck) {
+  ShowSuggestions(manager(), {PopupItemId::kAutocompleteEntry,
+                              PopupItemId::kAutocompleteEntry});
+
+  // Generate a popup, so it can be hidden later. It doesn't matter what the
+  // external_delegate thinks is being shown in the process, since we are just
+  // testing the popup here.
+  test::GenerateTestAutofillPopup(&manager().external_delegate());
+
+  EXPECT_CALL(manager().external_delegate(),
+              RemoveSuggestion(Field(&Suggestion::popup_item_id,
+                                     PopupItemId::kAutocompleteEntry)))
+      .WillOnce(Return(true));
+  // Remove the first entry. The popup should be redrawn since its size has
+  // changed.
+  EXPECT_CALL(client().popup_view(), OnSuggestionsChanged());
+  EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
+      0,
+      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
+  Mock::VerifyAndClearExpectations(&client().popup_view());
+
+  EXPECT_TRUE(client()
+                  .popup_controller(manager())
+                  .ShouldIgnoreMouseObservedOutsideItemBoundsCheck());
+}
+
 #if !BUILDFLAG(IS_CHROMEOS_ASH)
 namespace {
 
@@ -152,6 +217,7 @@
   using AutofillPopupControllerForPopupTest::
       AutofillPopupControllerForPopupTest;
 
+  using AutofillPopupControllerForPopupTest::FireControlsChangedEvent;
   MOCK_METHOD(ui::AXPlatformNode*,
               GetRootAXPlatformNodeForWebContents,
               (),
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_test_base.cc b/chrome/browser/ui/autofill/autofill_popup_controller_test_base.cc
index 8ce9d3b..4a04c3a4 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_test_base.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_test_base.cc
@@ -25,17 +25,18 @@
         void(gfx::NativeWindow,
              Profile*,
              password_manager::metrics_util::PasswordMigrationWarningTriggers)>
-        show_pwd_migration_warning_callback,
-    std::optional<base::WeakPtr<ExpandablePopupParentControllerImpl>> parent)
-    : AutofillPopupControllerImpl(
+        show_pwd_migration_warning_callback)
+    : AutofillPopupControllerForPopupTestBase(
           external_delegate,
           web_contents,
           PopupControllerCommon(element_bounds,
                                 base::i18n::UNKNOWN_DIRECTION,
                                 nullptr),
+#if !BUILDFLAG(IS_ANDROID)
           /*form_control_ax_id=*/0,
-          std::move(show_pwd_migration_warning_callback),
-          parent) {}
+#endif
+          std::move(show_pwd_migration_warning_callback)) {
+}
 
 AutofillPopupControllerForPopupTest::~AutofillPopupControllerForPopupTest() =
     default;
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_test_base.h b/chrome/browser/ui/autofill/autofill_popup_controller_test_base.h
index 90969935..2f6e2a7 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_test_base.h
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_test_base.h
@@ -38,6 +38,7 @@
 #include "chrome/browser/keyboard_accessory/test_utils/android/mock_address_accessory_controller.h"
 #include "chrome/browser/keyboard_accessory/test_utils/android/mock_credit_card_accessory_controller.h"
 #include "chrome/browser/keyboard_accessory/test_utils/android/mock_password_accessory_controller.h"
+#include "chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h"
 #endif  // BUILDFLAG(IS_ANDROID)
 
 namespace autofill {
@@ -332,7 +333,15 @@
       show_pwd_migration_warning_callback_;
 };
 
-class AutofillPopupControllerForPopupTest : public AutofillPopupControllerImpl {
+using AutofillPopupControllerForPopupTestBase =
+#if BUILDFLAG(IS_ANDROID)
+    AutofillKeyboardAccessoryControllerImpl;
+#else
+    AutofillPopupControllerImpl;
+#endif
+
+class AutofillPopupControllerForPopupTest
+    : public AutofillPopupControllerForPopupTestBase {
  public:
   AutofillPopupControllerForPopupTest(
       base::WeakPtr<AutofillExternalDelegate> external_delegate,
@@ -342,27 +351,24 @@
           gfx::NativeWindow,
           Profile*,
           password_manager::metrics_util::PasswordMigrationWarningTriggers)>
-          show_pwd_migration_warning_callback,
-      std::optional<base::WeakPtr<ExpandablePopupParentControllerImpl>> parent =
-          std::nullopt);
+          show_pwd_migration_warning_callback);
   ~AutofillPopupControllerForPopupTest() override;
 
   // Making protected functions public for testing
-  using AutofillPopupControllerImpl::AcceptSuggestion;
-  using AutofillPopupControllerImpl::element_bounds;
-  using AutofillPopupControllerImpl::FireControlsChangedEvent;
-  using AutofillPopupControllerImpl::GetLineCount;
-  using AutofillPopupControllerImpl::GetSuggestionAt;
-  using AutofillPopupControllerImpl::GetSuggestionLabelsAt;
-  using AutofillPopupControllerImpl::GetSuggestionMainTextAt;
-  using AutofillPopupControllerImpl::GetWeakPtr;
-  using AutofillPopupControllerImpl::PerformButtonActionForSuggestion;
-  using AutofillPopupControllerImpl::RemoveSuggestion;
-  using AutofillPopupControllerImpl::SelectSuggestion;
+  using AutofillPopupControllerForPopupTestBase::AcceptSuggestion;
+  using AutofillPopupControllerForPopupTestBase::element_bounds;
+  using AutofillPopupControllerForPopupTestBase::GetLineCount;
+  using AutofillPopupControllerForPopupTestBase::GetSuggestionAt;
+  using AutofillPopupControllerForPopupTestBase::GetSuggestionLabelsAt;
+  using AutofillPopupControllerForPopupTestBase::GetSuggestionMainTextAt;
+  using AutofillPopupControllerForPopupTestBase::
+      PerformButtonActionForSuggestion;
+  using AutofillPopupControllerForPopupTestBase::RemoveSuggestion;
+  using AutofillPopupControllerForPopupTestBase::SelectSuggestion;
   MOCK_METHOD(void, Hide, (PopupHidingReason reason), (override));
 
   void DoHide(PopupHidingReason reason = PopupHidingReason::kTabGone) {
-    AutofillPopupControllerImpl::Hide(reason);
+    AutofillPopupControllerForPopupTestBase::Hide(reason);
   }
 };
 
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
index 050b3599..59e6caf 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_unittest.cc
@@ -108,6 +108,15 @@
   return simulator->GetFinalRenderFrameHost();
 }
 
+// Returns an `AutofillMetrics::SingleEntryRemovalMethod` that is appropriate
+// for the OS this test is run on.
+AutofillMetrics::SingleEntryRemovalMethod GetDefaultRemovalMethod() {
+  if constexpr (BUILDFLAG(IS_ANDROID)) {
+    return AutofillMetrics::SingleEntryRemovalMethod::kKeyboardAccessory;
+  }
+  return AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed;
+}
+
 content::RenderFrameHost* NavigateAndCommitFrame(content::RenderFrameHost* rfh,
                                                  const GURL& url) {
   std::unique_ptr<content::NavigationSimulator> simulator =
@@ -138,8 +147,7 @@
   // changed.
   EXPECT_CALL(client().popup_view(), OnSuggestionsChanged());
   EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
+      0, GetDefaultRemovalMethod()));
   Mock::VerifyAndClearExpectations(&client().popup_view());
 
   // Remove the next entry. The popup should then be hidden since there are
@@ -147,8 +155,7 @@
   EXPECT_CALL(client().popup_controller(manager()),
               Hide(PopupHidingReason::kNoSuggestions));
   EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
+      0, GetDefaultRemovalMethod()));
 }
 
 // Regression test for (crbug.com/1513574): Showing an Autofill Compose
@@ -172,35 +179,7 @@
   EXPECT_CALL(client().popup_view(),
               AxAnnounce(Eq(u"Entry main text has been deleted")));
   EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
-}
-
-TEST_F(AutofillPopupControllerTest,
-       RemoveAutocompleteSuggestion_IgnoresClickOutsideCheck) {
-  ShowSuggestions(manager(), {PopupItemId::kAutocompleteEntry,
-                              PopupItemId::kAutocompleteEntry});
-
-  // Generate a popup, so it can be hidden later. It doesn't matter what the
-  // external_delegate thinks is being shown in the process, since we are just
-  // testing the popup here.
-  test::GenerateTestAutofillPopup(&manager().external_delegate());
-
-  EXPECT_CALL(manager().external_delegate(),
-              RemoveSuggestion(Field(&Suggestion::popup_item_id,
-                                     PopupItemId::kAutocompleteEntry)))
-      .WillOnce(Return(true));
-  // Remove the first entry. The popup should be redrawn since its size has
-  // changed.
-  EXPECT_CALL(client().popup_view(), OnSuggestionsChanged());
-  EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
-  Mock::VerifyAndClearExpectations(&client().popup_view());
-
-  EXPECT_TRUE(client()
-                  .popup_controller(manager())
-                  .ShouldIgnoreMouseObservedOutsideItemBoundsCheck());
+      0, GetDefaultRemovalMethod()));
 }
 
 TEST_F(AutofillPopupControllerTest,
@@ -214,12 +193,10 @@
       .WillOnce(Return(false));
 
   EXPECT_FALSE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
+      0, GetDefaultRemovalMethod()));
   histogram_tester.ExpectUniqueSample(
       "Autofill.Autocomplete.SingleEntryRemovalMethod",
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
-      0);
+      GetDefaultRemovalMethod(), 0);
   histogram_tester.ExpectUniqueSample(
       "Autocomplete.Events2",
       AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 0);
@@ -236,12 +213,10 @@
       .WillOnce(Return(true));
 
   EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
+      0, GetDefaultRemovalMethod()));
   histogram_tester.ExpectUniqueSample(
       "Autofill.Autocomplete.SingleEntryRemovalMethod",
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
-      1);
+      GetDefaultRemovalMethod(), 1);
   histogram_tester.ExpectUniqueSample(
       "Autocomplete.Events2",
       AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 1);
@@ -253,7 +228,7 @@
 }
 
 TEST_F(AutofillPopupControllerTest,
-       RemoveAddressSuggestion_ShiftDelete_NoMetricsEmittedOnFail) {
+       RemoveAddressSuggestion_NoMetricsEmittedOnFail) {
   base::HistogramTester histogram_tester;
   ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
   test::GenerateTestAutofillPopup(&manager().external_delegate());
@@ -263,8 +238,7 @@
       .WillOnce(Return(false));
 
   EXPECT_FALSE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
+      0, GetDefaultRemovalMethod()));
   histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
   histogram_tester.ExpectUniqueSample(
       "Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
@@ -272,7 +246,7 @@
 }
 
 TEST_F(AutofillPopupControllerTest,
-       RemoveAddressSuggestion_ShiftDelete_MetricsEmittedOnSuccess) {
+       RemoveAddressSuggestion_MetricsEmittedOnSuccess) {
   base::HistogramTester histogram_tester;
   ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
   test::GenerateTestAutofillPopup(&manager().external_delegate());
@@ -282,61 +256,21 @@
       .WillOnce(Return(true));
 
   EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
-  histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 1);
+      0, GetDefaultRemovalMethod()));
   histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 1);
-  // Also no autocomplete or keyboard accessory metrics are emitted.
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
+  if constexpr (BUILDFLAG(IS_ANDROID)) {
+    histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.ProfileDeleted.KeyboardAccessory", 1, 1);
+  } else {
+    histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 1);
+    histogram_tester.ExpectUniqueSample(
+        "Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
+  }
+  // No autocomplete deletion metrics are emitted.
   histogram_tester.ExpectUniqueSample(
       "Autofill.Autocomplete.SingleEntryRemovalMethod",
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
-      0);
-  histogram_tester.ExpectUniqueSample(
-      "Autocomplete.Events2",
-      AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 0);
-}
-
-TEST_F(AutofillPopupControllerTest,
-       RemoveAddressSuggestion_KeyboardAccessory_NoMetricsEmittedOnFail) {
-  base::HistogramTester histogram_tester;
-  ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
-  test::GenerateTestAutofillPopup(&manager().external_delegate());
-  EXPECT_CALL(manager().external_delegate(),
-              RemoveSuggestion(Field(&Suggestion::popup_item_id,
-                                     PopupItemId::kAddressEntry)))
-      .WillOnce(Return(false));
-
-  EXPECT_FALSE(client().popup_controller(manager()).RemoveSuggestion(
-      0, AutofillMetrics::SingleEntryRemovalMethod::kKeyboardAccessory));
-  histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.ProfileDeleted.KeyboardAccessory", 1, 0);
-  histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 0);
-}
-
-TEST_F(AutofillPopupControllerTest,
-       RemoveAddressSuggestion_KeyboardAccessory_MetricsEmittedOnSuccess) {
-  base::HistogramTester histogram_tester;
-  ShowSuggestions(manager(), {PopupItemId::kAddressEntry});
-  test::GenerateTestAutofillPopup(&manager().external_delegate());
-  EXPECT_CALL(manager().external_delegate(),
-              RemoveSuggestion(Field(&Suggestion::popup_item_id,
-                                     PopupItemId::kAddressEntry)))
-      .WillOnce(Return(true));
-
-  EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
-      0, AutofillMetrics::SingleEntryRemovalMethod::kKeyboardAccessory));
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.ProfileDeleted.KeyboardAccessory", 1, 1);
-  histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Any", 1, 1);
-  // Also no autocomplete or shift+delete metrics are emitted.
-  histogram_tester.ExpectUniqueSample("Autofill.ProfileDeleted.Popup", 1, 0);
-  histogram_tester.ExpectUniqueSample(
-      "Autofill.Autocomplete.SingleEntryRemovalMethod",
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
-      0);
+      GetDefaultRemovalMethod(), 0);
   histogram_tester.ExpectUniqueSample(
       "Autocomplete.Events2",
       AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 0);
@@ -353,12 +287,10 @@
       .WillOnce(Return(true));
 
   EXPECT_TRUE(client().popup_controller(manager()).RemoveSuggestion(
-      0,
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed));
+      0, GetDefaultRemovalMethod()));
   histogram_tester.ExpectUniqueSample(
       "Autofill.Autocomplete.SingleEntryRemovalMethod",
-      AutofillMetrics::SingleEntryRemovalMethod::kKeyboardShiftDeletePressed,
-      0);
+      GetDefaultRemovalMethod(), 0);
   histogram_tester.ExpectUniqueSample(
       "Autocomplete.Events2",
       AutofillMetrics::AutocompleteEvent::AUTOCOMPLETE_SUGGESTION_DELETED, 0);
@@ -552,7 +484,7 @@
                               PopupItemId::kAutocompleteEntry});
 
   // Now show a new popup with the same controller, but with fewer items.
-  WeakPtr<AutofillPopupController> controller =
+  base::WeakPtr<AutofillPopupController> controller =
       AutofillPopupController::GetOrCreate(
           client().popup_controller(manager()).GetWeakPtr(),
           manager().external_delegate().GetWeakPtrForTest(), nullptr,
@@ -858,28 +790,6 @@
 
 #endif  // BUILDFLAG(IS_ANDROID)
 
-// Tests that the popup controller queries the view for its screen location.
-TEST_F(AutofillPopupControllerTest, GetPopupScreenLocationCallsView) {
-  ShowSuggestions(manager(), {PopupItemId::kCompose});
-
-  using PopupScreenLocation = AutofillClient::PopupScreenLocation;
-  constexpr gfx::Rect kSampleRect = gfx::Rect(123, 234);
-  EXPECT_CALL(client().popup_view(), GetPopupScreenLocation)
-      .WillOnce(Return(PopupScreenLocation{.bounds = kSampleRect}));
-  EXPECT_THAT(client().popup_controller(manager()).GetPopupScreenLocation(),
-              Optional(Field(&PopupScreenLocation::bounds, kSampleRect)));
-}
-
-// Tests that a change to a text field hides a popup with a Compose suggestion.
-TEST_F(AutofillPopupControllerTest, HidesOnFieldChangeForComposeEntries) {
-  ShowSuggestions(manager(), {PopupItemId::kCompose});
-  EXPECT_CALL(client().popup_controller(manager()),
-              Hide(PopupHidingReason::kFieldValueChanged));
-  manager().NotifyObservers(
-      &AutofillManager::Observer::OnBeforeTextFieldDidChange, FormGlobalId(),
-      FieldGlobalId());
-}
-
 // Tests that a change to a text field does not hide a popup with an
 // Autocomplete suggestion.
 TEST_F(AutofillPopupControllerTest,
@@ -1015,25 +925,6 @@
   NavigateAndCommitFrame(main_frame(), GURL("https://bar.com/"));
 }
 
-// Tests that Compose saved state notification popup gets hidden after 2
-// seconds, but not after 1 second.
-TEST_F(AutofillPopupControllerTestHidingLogic,
-       TimedHideComposeSavedStateNotification) {
-  ShowSuggestions(manager(), {PopupItemId::kComposeSavedStateNotification});
-  test::GenerateTestAutofillPopup(&manager().external_delegate());
-  ::testing::MockFunction<void()> check;
-  {
-    ::testing::InSequence s;
-    EXPECT_CALL(check, Call);
-    EXPECT_CALL(client().popup_controller(manager()),
-                Hide(PopupHidingReason::kFadeTimerExpired));
-  }
-  task_environment()->FastForwardBy(base::Seconds(1));
-  check.Call();
-  task_environment()->FastForwardBy(base::Seconds(1));
-  Mock::VerifyAndClearExpectations(&client().popup_controller(manager()));
-}
-
 #if !BUILDFLAG(IS_ANDROID)
 // Tests that if the popup is shown in the *main frame*, changing the zoom hides
 // the popup.
diff --git a/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.cc b/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.cc
new file mode 100644
index 0000000..f0fb5e5
--- /dev/null
+++ b/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.cc
@@ -0,0 +1,121 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h"
+
+#include <string>
+#include <vector>
+
+#include "base/functional/overloaded.h"
+#include "chrome/browser/feature_engagement/tracker_factory.h"
+#include "components/autofill/content/browser/content_autofill_driver.h"
+#include "components/autofill/core/browser/ui/popup_item_ids.h"
+#include "components/autofill/core/browser/ui/suggestion.h"
+#include "components/autofill/core/common/dense_set.h"
+#include "components/autofill/core/common/form_field_data.h"
+#include "components/feature_engagement/public/feature_constants.h"
+#include "components/feature_engagement/public/tracker.h"
+#include "components/password_manager/content/browser/content_password_manager_driver.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_widget_host_view.h"
+#include "content/public/browser/web_contents.h"
+
+namespace autofill {
+
+bool IsAcceptablePopupItemId(PopupItemId id) {
+  using enum PopupItemId;
+  static constexpr auto kUnacceptableItemIds =
+      DenseSet({kSeparator, kInsecureContextPaymentDisabledMessage,
+                kMixedFormMessage, kTitle});
+  return !kUnacceptableItemIds.contains(id);
+}
+
+content::RenderFrameHost* GetRenderFrameHost(AutofillPopupDelegate& delegate) {
+  return absl::visit(
+      base::Overloaded{
+          [](AutofillDriver* driver) {
+            return static_cast<ContentAutofillDriver*>(driver)
+                ->render_frame_host();
+          },
+          [](password_manager::PasswordManagerDriver* driver) {
+            return static_cast<password_manager::ContentPasswordManagerDriver*>(
+                       driver)
+                ->render_frame_host();
+          }},
+      delegate.GetDriver());
+}
+
+bool IsAncestorOf(content::RenderFrameHost* ancestor,
+                  content::RenderFrameHost* descendant) {
+  for (auto* rfh = descendant; rfh; rfh = rfh->GetParent()) {
+    if (rfh == ancestor) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool IsPointerLocked(content::WebContents* web_contents) {
+  content::RenderFrameHost* rfh;
+  content::RenderWidgetHostView* rwhv;
+  return web_contents && (rfh = web_contents->GetFocusedFrame()) &&
+         (rwhv = rfh->GetView()) && rwhv->IsPointerLocked();
+}
+
+void NotifyIphAboutAcceptedSuggestion(content::BrowserContext* browser_context,
+                                      const Suggestion& suggestion) {
+  if (suggestion.popup_item_id == PopupItemId::kVirtualCreditCardEntry) {
+    feature_engagement::TrackerFactory::GetForBrowserContext(browser_context)
+        ->NotifyEvent(suggestion.feature_for_iph ==
+                              &feature_engagement::
+                                  kIPHAutofillVirtualCardCVCSuggestionFeature
+                          ? "autofill_virtual_card_cvc_suggestion_accepted"
+                          : "autofill_virtual_card_suggestion_accepted");
+  }
+
+  if (suggestion.feature_for_iph ==
+      &feature_engagement::
+          kIPHAutofillExternalAccountProfileSuggestionFeature) {
+    feature_engagement::TrackerFactory::GetForBrowserContext(browser_context)
+        ->NotifyEvent("autofill_external_account_profile_suggestion_accepted");
+  }
+}
+
+void UpdateSuggestionsFromDataList(base::span<const SelectOption> options,
+                                   std::vector<Suggestion>& suggestions) {
+  // Remove all the old data list values, which should always be at the top of
+  // the list if they are present.
+  std::erase_if(suggestions, [](const Suggestion& suggestion) {
+    return suggestion.popup_item_id == PopupItemId::kDatalistEntry;
+  });
+
+  // If there are no new data list values, exit (clearing the separator if there
+  // is one).
+  if (options.empty()) {
+    if (!suggestions.empty() &&
+        suggestions[0].popup_item_id == PopupItemId::kSeparator) {
+      suggestions.erase(suggestions.begin());
+    }
+
+    return;
+  }
+
+  // Add a separator if there are any other values.
+  if (!suggestions.empty() &&
+      suggestions[0].popup_item_id != PopupItemId::kSeparator) {
+    suggestions.insert(suggestions.begin(),
+                       Suggestion(PopupItemId::kSeparator));
+  }
+
+  // Prepend the parameters to the suggestions we already have.
+  suggestions.insert(suggestions.begin(), options.size(), Suggestion());
+  for (size_t i = 0; i < options.size(); i++) {
+    suggestions[i].main_text =
+        Suggestion::Text(options[i].value, Suggestion::Text::IsPrimary(true));
+    suggestions[i].labels = {{Suggestion::Text(options[i].content)}};
+    suggestions[i].popup_item_id = PopupItemId::kDatalistEntry;
+  }
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h b/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h
new file mode 100644
index 0000000..5d50b03
--- /dev/null
+++ b/chrome/browser/ui/autofill/autofill_suggestion_controller_utils.h
@@ -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.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_SUGGESTION_CONTROLLER_UTILS_H_
+#define CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_SUGGESTION_CONTROLLER_UTILS_H_
+
+#include <vector>
+
+#include "base/containers/span.h"
+#include "components/autofill/core/browser/ui/popup_item_ids.h"
+
+namespace content {
+class BrowserContext;
+class RenderFrameHost;
+class WebContents;
+}  // namespace content
+
+namespace autofill {
+
+class AutofillPopupDelegate;
+struct SelectOption;
+struct Suggestion;
+
+// Returns whether this `PopupItemId` can, in principle, be accepted. Note that
+// even if this is true, the suggestion itself may still not be acceptable.
+bool IsAcceptablePopupItemId(PopupItemId id);
+
+// Returns the RenderFrameHost` corresponding to an `AutofillPopupDelegate`.
+content::RenderFrameHost* GetRenderFrameHost(AutofillPopupDelegate& delegate);
+
+// Returns whether `descendendant` is a `descendant` of `ancestor`.
+bool IsAncestorOf(content::RenderFrameHost* ancestor,
+                  content::RenderFrameHost* descendant);
+
+// Returns whether the pointer is locked in `web_contents`.
+bool IsPointerLocked(content::WebContents* web_contents);
+
+// Informs the IPH trackers about an accepted suggestion if the suggestion had
+// relevance for IPH.
+void NotifyIphAboutAcceptedSuggestion(content::BrowserContext* browser_context,
+                                      const Suggestion& suggestion);
+
+void UpdateSuggestionsFromDataList(base::span<const SelectOption> options,
+                                   std::vector<Suggestion>& suggestions);
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_AUTOFILL_SUGGESTION_CONTROLLER_UTILS_H_
diff --git a/chrome/browser/ui/browser_tab_strip_tracker.h b/chrome/browser/ui/browser_tab_strip_tracker.h
index 8669d461..badfcf3 100644
--- a/chrome/browser/ui/browser_tab_strip_tracker.h
+++ b/chrome/browser/ui/browser_tab_strip_tracker.h
@@ -14,6 +14,15 @@
 // BrowserTabStripTracker attaches a TabStripModelObserver to a subset of
 // pre-existing and future Browsers. The subset of Browsers that are tracked is
 // determined by an optional BrowserTabStripTrackerDelegate.
+//
+// This class is typically not the right helper to use. Its primary purpose is
+// to hook up a TabStripModelObserver across multiple TabStripModels. As per the
+// documentation for TabStripModelObserver, only features that need to interact
+// with the tab strip like tab groups and tab search should use
+// TabStripModelObserver. Other features should use TabInterface and
+// TabFeatures. Furthermore, this class mixes state across multiple
+// TabStripModels. This is typically not desirable and instead features should
+// hold state on a per-browser-window basis, using BrowserWindowFeatures.
 class BrowserTabStripTracker : public BrowserListObserver {
  public:
   // |tab_strip_model_observer| is a non-nullptr TabStripModelObserver
diff --git a/chrome/browser/ui/tabs/tab_strip_model_observer.h b/chrome/browser/ui/tabs/tab_strip_model_observer.h
index d19aae1..07caa90 100644
--- a/chrome/browser/ui/tabs/tab_strip_model_observer.h
+++ b/chrome/browser/ui/tabs/tab_strip_model_observer.h
@@ -29,6 +29,13 @@
 //
 // TabStripModelChange / TabStripSelectionChange
 //
+// This observer is not appropriate for most use cases. It's primarily used for
+// features that must directly interface with the tab strip, for example: tab
+// groups, tab search, etc.
+// Most features in Chrome need to hold state on a per-tab basis. In that case,
+// add a controller to TabFeatures and use TabInterface to observe for the tab
+// events.
+//
 // The following class and structures are used to inform TabStripModelObservers
 // of changes to:
 // 1) selection model
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
index 3494adc..8aec76a 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
@@ -57,6 +57,14 @@
   return std::nullopt;
 }
 
+bool IsShowingFrame(bool use_custom_frame,
+                    ui::PlatformWindowState window_state) {
+  return use_custom_frame &&
+         window_state != ui::PlatformWindowState::kMaximized &&
+         window_state != ui::PlatformWindowState::kMinimized &&
+         !ui::IsPlatformWindowStateFullscreen(window_state);
+}
+
 }  // namespace
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -188,34 +196,31 @@
 
 void BrowserDesktopWindowTreeHostLinux::UpdateFrameHints() {
   auto* window = platform_window();
+  auto window_state = window->GetPlatformWindowState();
   float scale = device_scale_factor();
   auto* view =
       static_cast<BrowserNonClientFrameView*>(browser_frame_->GetFrameView());
-  bool showing_frame =
-      browser_frame_->native_browser_frame()->UseCustomFrame() &&
-      !view->IsFrameCondensed() && !view->frame()->IsMinimized();
   const gfx::Size widget_size =
       view->GetWidget()->GetWindowBoundsInScreen().size();
 
   if (SupportsClientFrameShadow()) {
-    // Set the frame decoration insets.
-    const gfx::Insets insets_dip = view->MirroredFrameBorderInsets();
-    const gfx::Insets insets_px = gfx::ScaleToCeiledInsets(insets_dip, scale);
-    window->SetDecorationInsets(showing_frame ? &insets_px : nullptr);
-
-    // Set the input region.
-    gfx::Rect input_bounds(widget_size);
-    input_bounds.Inset(insets_dip - view->GetInputInsets());
-    input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale);
-    window->SetInputRegion(
-        showing_frame ? std::optional<std::vector<gfx::Rect>>({input_bounds})
-                      : std::nullopt);
+    auto insets = CalculateInsetsInDIP(window_state);
+    if (insets.IsEmpty()) {
+      window->SetInputRegion(std::nullopt);
+    } else {
+      gfx::Rect input_bounds(widget_size);
+      input_bounds.Inset(insets - view->GetInputInsets());
+      input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale);
+      window->SetInputRegion(
+          std::optional<std::vector<gfx::Rect>>({input_bounds}));
+    }
   }
 
   if (ui::OzonePlatform::GetInstance()->IsWindowCompositingSupported()) {
     // Set the opaque region.
     std::vector<gfx::Rect> opaque_region;
-    if (showing_frame) {
+    if (IsShowingFrame(browser_frame_->native_browser_frame()->UseCustomFrame(),
+                       window_state)) {
       // The opaque region is a list of rectangles that contain only fully
       // opaque pixels of the window.  We need to convert the clipping
       // rounded-rect into this format.
@@ -326,6 +331,18 @@
          x11_extension->CanResetOverrideRedirect();
 }
 
+gfx::Insets BrowserDesktopWindowTreeHostLinux::CalculateInsetsInDIP(
+    ui::PlatformWindowState window_state) const {
+  // If we are not showing frame, the insets should be zero.
+  if (!IsShowingFrame(browser_frame_->native_browser_frame()->UseCustomFrame(),
+                      window_state)) {
+    return gfx::Insets();
+  }
+
+  return static_cast<BrowserNonClientFrameView*>(browser_frame_->GetFrameView())
+      ->MirroredFrameBorderInsets();
+}
+
 void BrowserDesktopWindowTreeHostLinux::OnBoundsChanged(
     const BoundsChange& change) {
   DesktopWindowTreeHostLinux::OnBoundsChanged(change);
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h
index b52acac..213ff5bd 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h
@@ -88,6 +88,8 @@
   bool IsOverrideRedirect() const override;
 
   // ui::PlatformWindowDelegate
+  gfx::Insets CalculateInsetsInDIP(
+      ui::PlatformWindowState window_state) const override;
   void OnBoundsChanged(const BoundsChange& change) override;
   void OnWindowStateChanged(ui::PlatformWindowState old_state,
                             ui::PlatformWindowState new_state) override;
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 9b4b1af..37c8554 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1713225556-0014a6d0c9887bed01444e78dfb49cc516874d85-a6f44326964cd416e2c0899245ba5db3de538fb6.profdata
+chrome-android32-main-1713247156-e21c1c6a1aad09116cec86d7ad696d195b1f1fb9-050b5f225e98d132a97d05816bf94319f003398d.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index a559d63..fda17d4 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1713203948-2f75700a84a0d0b4b2cc6e4b1ba0ca06e9826d5b-6634c2892d4249eaa2432bedc1ea607e23df50c1.profdata
+chrome-linux-main-1713247156-94d76bd2db3dbb00e47e33f8bed1e818a56115ed-050b5f225e98d132a97d05816bf94319f003398d.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index 365c2ee..238c643e 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1713225556-148617ef6de999b69bce717b2ada3384aaea0a60-a6f44326964cd416e2c0899245ba5db3de538fb6.profdata
+chrome-win-arm64-main-1713247156-74fa95ddd60c5b1b34c0b8f1d3d985c18b2fe1ad-050b5f225e98d132a97d05816bf94319f003398d.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 119b6aa..ec65dc3 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1713214724-f52b1d687c54d7fea32d4239bd9f6ff7e1c25ea2-dc3fec1593e34a66d148c068a5b78f5e421c98e9.profdata
+chrome-win32-main-1713236188-2d0952c6240f2a255998613bfc59c3da8fdab717-224f55c0f0a227b0da6c08269564115e48562fe8.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 46d7797..3b1e15e 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1713214724-7e0f7844b86444d8bbcf8c70b81af7354523d6c0-dc3fec1593e34a66d148c068a5b78f5e421c98e9.profdata
+chrome-win64-main-1713236188-bd3cb4bc0986851fb18963d696a924b288e7b189-224f55c0f0a227b0da6c08269564115e48562fe8.profdata
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 2af71eb..86d7a71 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -6494,7 +6494,6 @@
     "../browser/translate/translate_service_unittest.cc",
     "../browser/trusted_vault/trusted_vault_encryption_keys_tab_helper_unittest.cc",
     "../browser/ui/autofill/autofill_client_provider_unittest.cc",
-    "../browser/ui/autofill/autofill_popup_controller_impl_unittest.cc",
     "../browser/ui/autofill/autofill_popup_controller_unittest.cc",
     "../browser/ui/autofill/chrome_autofill_client_unittest.cc",
     "../browser/ui/autofill/payments/card_unmask_authentication_selection_dialog_controller_impl_unittest.cc",
@@ -6567,6 +6566,7 @@
       "../browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc",
       "../browser/segmentation_platform/client_util/local_tab_handler_unittest.cc",
       "../browser/ssl/generated_https_first_mode_pref_unittest.cc",
+      "../browser/ui/autofill/autofill_popup_controller_impl_unittest.cc",
       "../browser/ui/download/download_bubble_row_list_view_info_unittest.cc",
       "../browser/ui/download/download_bubble_row_view_info_unittest.cc",
       "../browser/ui/download/download_bubble_security_view_info_unittest.cc",
diff --git a/chrome/test/data/webui/test_util.ts b/chrome/test/data/webui/test_util.ts
index 04d9ece..f629f18c4 100644
--- a/chrome/test/data/webui/test_util.ts
+++ b/chrome/test/data/webui/test_util.ts
@@ -139,6 +139,7 @@
   await new Promise<void>(res => {
     window.setTimeout(() => res(), timeout);
   });
+   /* eslint-disable-next-line no-debugger */
   debugger;
 }
 
diff --git a/chrome/test/enterprise/e2e/.vpython3 b/chrome/test/enterprise/e2e/.vpython3
index d3e1ce8..ff9fcf9 100644
--- a/chrome/test/enterprise/e2e/.vpython3
+++ b/chrome/test/enterprise/e2e/.vpython3
@@ -7,43 +7,47 @@
 #
 # More information:
 # https://chromium.googlesource.com/infra/infra/+/main/doc/users/vpython.md
-python_version: "3.8"
+python_version: "3.11"
 
+# The default set of platforms vpython checks for does not yet include mac-arm64.
+# Setting `verify_pep425_tag` to the list of platforms we explicitly must support
+# allows us to ensure that vpython specs stay mac-arm64-friendly
 verify_pep425_tag: [
-    {python: "cp38", abi: "cp38", platform: "manylinux1_x86_64"},
-    {python: "cp38", abi: "cp38", platform: "linux_arm64"},
+    {python: "cp311", abi: "cp311", platform: "manylinux1_x86_64"},
+    {python: "cp311", abi: "cp311", platform: "linux_arm64"},
 
-    {python: "cp38", abi: "cp38", platform: "macosx_10_10_intel"},
-    {python: "cp38", abi: "cp38", platform: "macosx_11_0_arm64"},
+    {python: "cp311", abi: "cp311", platform: "macosx_10_10_intel"},
+    {python: "cp311", abi: "cp311", platform: "macosx_11_0_arm64"},
 
-    {python: "cp38", abi: "cp38", platform: "win32"},
-    {python: "cp38", abi: "cp38", platform: "win_amd64"}
+    {python: "cp311", abi: "cp311", platform: "win32"},
+    {python: "cp311", abi: "cp311", platform: "win_amd64"}
 ]
 
 wheel: <
   name: "infra/celab/celab/windows-amd64"
-  # Source: https://ci.chromium.org/ui/p/celab/builders/ci/Windows/b8750922101336036641
-  version: "EOOjjcFkcpSYVHrlcfgxHwBEhIAqwWKNgwX20BvUB8YC"
+  # Source: https://ci.chromium.org/ui/p/celab/builders/ci/Windows/b8750569502072854417
+  version: "j16S6jSsbiySxDfowruIiMXN-3WZ8GB80b8uEys-KpwC"
 >
 
 # googleapiclient
 wheel: <
-  name: "infra/python/wheels/google-api-python-client-py2_py3"
-  version: "version:1.12.8"
+  name: "infra/python/wheels/google-api-python-client-py3"
+  version: "version:2.111.0"
 >
 
 # googleapiclient's dependencies
 wheel: <
-  name: "infra/python/wheels/cachetools-py2_py3"
-  version: "version:2.0.1"
+  name: "infra/python/wheels/cachetools-py3"
+  version: "version:5.3.2"
 >
+
 wheel: <
   name: "infra/python/wheels/google-api-core-py3"
-  version: "version:1.31.5"
+  version: "version:2.14.0"
 >
 wheel: <
   name: "infra/python/wheels/google-auth-py2_py3"
-  version: "version:1.35.0"
+  version: "version:2.23.4"
 >
 wheel: <
   name: "infra/python/wheels/google-auth-httplib2-py2_py3"
@@ -51,7 +55,7 @@
 >
 wheel: <
   name: "infra/python/wheels/httplib2-py3"
-  version: "version:0.19.1"
+  version: "version:0.22.0"
 >
 wheel: <
   name: "infra/python/wheels/oauth2client-py2_py3"
@@ -106,8 +110,13 @@
   version: "version:1.16.0"
 >
 wheel: <
-  name: "infra/python/wheels/uritemplate-py2_py3"
-  version: "version:3.0.0"
+  name: "infra/python/wheels/uritemplate-py3"
+  version: "version:4.1.1"
+>
+
+wheel: <
+  name: "infra/python/wheels/grpcio-status-py3"
+  version: "version:1.57.0"
 >
 
 # google.cloud pubsub
@@ -138,8 +147,8 @@
 
 # google.protobuf
 wheel: <
-  name: "infra/python/wheels/protobuf-py2_py3"
-  version: "version:3.18.1"
+  name: "infra/python/wheels/protobuf-py3"
+  version: "version:4.25.1"
 >
 
 # iam.admin.v1
@@ -156,12 +165,12 @@
 
 wheel: <
   name: "infra/python/wheels/googleapis-common-protos-py2_py3"
-  version: "version:1.52.0"
+  version: "version:1.61.0"
 >
 
 wheel: <
   name: "infra/python/wheels/grpcio/${vpython_platform}"
-  version: "version:1.44.0"
+  version: "version:1.57.0"
 >
 
 wheel: <
@@ -175,8 +184,8 @@
 >
 
 wheel: <
-  name: "infra/python/wheels/absl-py-py2_py3"
-  version: "version:0.7.1"
+  name: "infra/python/wheels/absl-py-py3"
+  version: "version:0.11.0"
 >
 
 wheel: <
@@ -191,6 +200,6 @@
 
 # attrs for simple dataclass
 wheel: <
-  name: "infra/python/wheels/attrs-py2_py3"
-  version: "version:21.4.0"
+  name: "infra/python/wheels/attrs-py3"
+  version: "version:23.1.0"
 >
diff --git a/chrome/test/fuzzing/in_process_fuzzer.gni b/chrome/test/fuzzing/in_process_fuzzer.gni
index 49b9038..1dc9f89 100644
--- a/chrome/test/fuzzing/in_process_fuzzer.gni
+++ b/chrome/test/fuzzing/in_process_fuzzer.gni
@@ -23,6 +23,8 @@
       target_type = "fuzzer_test"
     }
     target(target_type, target_name) {
+      # Those tests are big, they need a lot of resources.
+      high_end_job_required = true
       deps = [ "//chrome/test/fuzzing:in_process_fuzzer_runner" ]
       if (defined(invoker.deps)) {
         deps += invoker.deps
diff --git a/clank b/clank
index 7fd047c4..8b6ec11 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 7fd047c41ca4c77672258cc2644b116844100171
+Subproject commit 8b6ec119f001eb5ad89d77c153fa132bd65801c0
diff --git a/components/browser_ui/styles/android/java/res/values/styles.xml b/components/browser_ui/styles/android/java/res/values/styles.xml
index e0861e7..42a8323 100644
--- a/components/browser_ui/styles/android/java/res/values/styles.xml
+++ b/components/browser_ui/styles/android/java/res/values/styles.xml
@@ -117,6 +117,10 @@
         <item name="android:textColor">@color/default_text_color_list</item>
     </style>
 
+    <style name="TextAppearance.TextAccentMediumThick.Primary">
+        <item name="android:textColor">@color/default_text_color_list</item>
+    </style>
+
     <style name="TextAppearance.TextLarge.Primary">
         <item name="android:textColor">@color/default_text_color_list</item>
     </style>
diff --git a/components/prefs/android/pref_service_android.cc b/components/prefs/android/pref_service_android.cc
index 79d7dd4..2c2b248 100644
--- a/components/prefs/android/pref_service_android.cc
+++ b/components/prefs/android/pref_service_android.cc
@@ -8,11 +8,24 @@
 
 #include "components/prefs/android/jni_headers/PrefService_jni.h"
 #include "components/prefs/pref_service.h"
+#include "components/prefs/prefs_export.h"
 
 using base::android::JavaParamRef;
+using base::android::JavaRef;
 using base::android::ScopedJavaLocalRef;
 using jni_zero::AttachCurrentThread;
 
+namespace jni_zero {
+
+template <>
+COMPONENTS_PREFS_EXPORT PrefService* FromJniType<PrefService*, jobject>(
+    JNIEnv* env,
+    const JavaRef<jobject>& obj) {
+  return PrefServiceAndroid::FromPrefServiceAndroid(obj);
+}
+
+}  // namespace jni_zero
+
 PrefServiceAndroid::PrefServiceAndroid(PrefService* pref_service)
     : pref_service_(pref_service) {}
 
@@ -25,7 +38,7 @@
 
 // static
 PrefService* PrefServiceAndroid::FromPrefServiceAndroid(
-    const JavaParamRef<jobject>& obj) {
+    const JavaRef<jobject>& obj) {
   if (obj.is_null()) {
     return nullptr;
   }
diff --git a/components/prefs/android/pref_service_android.h b/components/prefs/android/pref_service_android.h
index 73cf688..f40f2105 100644
--- a/components/prefs/android/pref_service_android.h
+++ b/components/prefs/android/pref_service_android.h
@@ -25,7 +25,7 @@
 
   // Returns the native counterpart of a Java `PrefService`.
   static PrefService* FromPrefServiceAndroid(
-      const base::android::JavaParamRef<jobject>& obj);
+      const base::android::JavaRef<jobject>& obj);
 
   base::android::ScopedJavaLocalRef<jobject> GetJavaObject();
 
diff --git a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java
index b581ec22..cbc1a08 100644
--- a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java
+++ b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncService.java
@@ -159,7 +159,14 @@
      * @param syncId The remote tab group ID.
      * @param localId The local tab group ID.
      */
-    void updateLocalTabGroupId(String syncId, int localId);
+    void updateLocalTabGroupMapping(String syncId, int localId);
+
+    /**
+     * Removes the in-memory mapping between sync and local tab group IDs.
+     *
+     * @param localId The local tab group ID whose mapping is to be forgotten.
+     */
+    void removeLocalTabGroupMapping(int localId);
 
     /**
      * Updates the in-memory mapping between sync and local IDs for a given tab.
diff --git a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceAndroidUnitTest.java b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceAndroidUnitTest.java
index 62e105c..a2b07be 100644
--- a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceAndroidUnitTest.java
+++ b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceAndroidUnitTest.java
@@ -132,8 +132,13 @@
     }
 
     @CalledByNative
-    public void testUpdateLocalTabGroupId(String syncId, int localId) {
-        mService.updateLocalTabGroupId(syncId, localId);
+    public void testUpdateLocalTabGroupMapping(String syncId, int localId) {
+        mService.updateLocalTabGroupMapping(syncId, localId);
+    }
+
+    @CalledByNative
+    public void testRemoveLocalTabGroupMapping(int localId) {
+        mService.removeLocalTabGroupMapping(localId);
     }
 
     @CalledByNative
diff --git a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java
index 83150dc..05fa0cf 100644
--- a/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java
+++ b/components/saved_tab_groups/android/java/src/org/chromium/components/tab_group_sync/TabGroupSyncServiceImpl.java
@@ -107,9 +107,16 @@
     }
 
     @Override
-    public void updateLocalTabGroupId(String syncId, int localId) {
+    public void updateLocalTabGroupMapping(String syncId, int localId) {
         if (mNativePtr == 0) return;
-        TabGroupSyncServiceImplJni.get().updateLocalTabGroupId(mNativePtr, this, syncId, localId);
+        TabGroupSyncServiceImplJni.get()
+                .updateLocalTabGroupMapping(mNativePtr, this, syncId, localId);
+    }
+
+    @Override
+    public void removeLocalTabGroupMapping(int localId) {
+        if (mNativePtr == 0) return;
+        TabGroupSyncServiceImplJni.get().removeLocalTabGroupMapping(mNativePtr, this, localId);
     }
 
     @Override
@@ -212,12 +219,15 @@
                 TabGroupSyncServiceImpl caller,
                 int localGroupId);
 
-        void updateLocalTabGroupId(
+        void updateLocalTabGroupMapping(
                 long nativeTabGroupSyncServiceAndroid,
                 TabGroupSyncServiceImpl caller,
                 String syncId,
                 int localId);
 
+        void removeLocalTabGroupMapping(
+                long nativeTabGroupSyncServiceAndroid, TabGroupSyncServiceImpl caller, int localId);
+
         void updateLocalTabId(
                 long nativeTabGroupSyncServiceAndroid,
                 TabGroupSyncServiceImpl caller,
diff --git a/components/saved_tab_groups/android/tab_group_sync_service_android.cc b/components/saved_tab_groups/android/tab_group_sync_service_android.cc
index 497be6b0..f64ef93 100644
--- a/components/saved_tab_groups/android/tab_group_sync_service_android.cc
+++ b/components/saved_tab_groups/android/tab_group_sync_service_android.cc
@@ -214,14 +214,22 @@
   return TabGroupSyncConversionsBridge::CreateGroup(env, group.value());
 }
 
-void TabGroupSyncServiceAndroid::UpdateLocalTabGroupId(
+void TabGroupSyncServiceAndroid::UpdateLocalTabGroupMapping(
     JNIEnv* env,
     const JavaParamRef<jobject>& j_caller,
     const JavaParamRef<jstring>& j_sync_id,
     jint j_local_id) {
   auto sync_id = JavaStringToUuid(env, j_sync_id);
   auto local_id = FromJavaTabGroupId(j_local_id);
-  tab_group_sync_service_->UpdateLocalTabGroupId(sync_id, local_id);
+  tab_group_sync_service_->UpdateLocalTabGroupMapping(sync_id, local_id);
+}
+
+void TabGroupSyncServiceAndroid::RemoveLocalTabGroupMapping(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& j_caller,
+    jint j_local_id) {
+  auto local_id = FromJavaTabGroupId(j_local_id);
+  tab_group_sync_service_->RemoveLocalTabGroupMapping(local_id);
 }
 
 void TabGroupSyncServiceAndroid::UpdateLocalTabId(
diff --git a/components/saved_tab_groups/android/tab_group_sync_service_android.h b/components/saved_tab_groups/android/tab_group_sync_service_android.h
index c75ee020..33b5df6d 100644
--- a/components/saved_tab_groups/android/tab_group_sync_service_android.h
+++ b/components/saved_tab_groups/android/tab_group_sync_service_android.h
@@ -90,10 +90,13 @@
       jint j_group_id);
 
   // Book-keeping methods to maintain in-memory mapping of sync and local IDs.
-  void UpdateLocalTabGroupId(JNIEnv* env,
-                             const JavaParamRef<jobject>& j_caller,
-                             const JavaParamRef<jstring>& j_sync_id,
-                             jint j_local_id);
+  void UpdateLocalTabGroupMapping(JNIEnv* env,
+                                  const JavaParamRef<jobject>& j_caller,
+                                  const JavaParamRef<jstring>& j_sync_id,
+                                  jint j_local_id);
+  void RemoveLocalTabGroupMapping(JNIEnv* env,
+                                  const JavaParamRef<jobject>& j_caller,
+                                  jint j_local_id);
   void UpdateLocalTabId(JNIEnv* env,
                         const JavaParamRef<jobject>& j_caller,
                         jint j_group_id,
diff --git a/components/saved_tab_groups/android/tab_group_sync_service_android_unittest.cc b/components/saved_tab_groups/android/tab_group_sync_service_android_unittest.cc
index 9b30562..3324772 100644
--- a/components/saved_tab_groups/android/tab_group_sync_service_android_unittest.cc
+++ b/components/saved_tab_groups/android/tab_group_sync_service_android_unittest.cc
@@ -58,8 +58,9 @@
   MOCK_METHOD(std::optional<SavedTabGroup>, GetGroup, (LocalTabGroupID&));
 
   MOCK_METHOD(void,
-              UpdateLocalTabGroupId,
+              UpdateLocalTabGroupMapping,
               (const base::Uuid&, const LocalTabGroupID&));
+  MOCK_METHOD(void, RemoveLocalTabGroupMapping, (const LocalTabGroupID&));
   MOCK_METHOD(void,
               UpdateLocalTabId,
               (const LocalTabGroupID&, const base::Uuid&, const LocalTabID&));
@@ -226,15 +227,21 @@
       env, j_test_, j_uuid1, j_uuid2);
 }
 
-TEST_F(TabGroupSyncServiceAndroidTest, UpdateLocalTabGroupId) {
+TEST_F(TabGroupSyncServiceAndroidTest, UpdateLocalTabGroupMapping) {
   auto* env = AttachCurrentThread();
   base::Uuid group_id = base::Uuid::GenerateRandomV4();
   auto j_group_id = UuidToJavaString(env, group_id);
 
+  // Update the mapping.
   EXPECT_CALL(tab_group_sync_service_,
-              UpdateLocalTabGroupId(Eq(group_id), Eq(4)));
-  Java_TabGroupSyncServiceAndroidUnitTest_testUpdateLocalTabGroupId(
+              UpdateLocalTabGroupMapping(Eq(group_id), Eq(4)));
+  Java_TabGroupSyncServiceAndroidUnitTest_testUpdateLocalTabGroupMapping(
       AttachCurrentThread(), j_test_, j_group_id, 4);
+
+  // Remove the mapping.
+  EXPECT_CALL(tab_group_sync_service_, RemoveLocalTabGroupMapping(Eq(4)));
+  Java_TabGroupSyncServiceAndroidUnitTest_testRemoveLocalTabGroupMapping(
+      AttachCurrentThread(), j_test_, 4);
 }
 
 TEST_F(TabGroupSyncServiceAndroidTest, UpdateLocalTabId) {
diff --git a/components/saved_tab_groups/tab_group_sync_service.h b/components/saved_tab_groups/tab_group_sync_service.h
index 6e9382b5..18c26f4b 100644
--- a/components/saved_tab_groups/tab_group_sync_service.h
+++ b/components/saved_tab_groups/tab_group_sync_service.h
@@ -114,8 +114,9 @@
   virtual std::optional<SavedTabGroup> GetGroup(LocalTabGroupID& local_id) = 0;
 
   // Book-keeping methods to maintain in-memory mapping of sync and local IDs.
-  virtual void UpdateLocalTabGroupId(const base::Uuid& sync_id,
-                                     const LocalTabGroupID& local_id) = 0;
+  virtual void UpdateLocalTabGroupMapping(const base::Uuid& sync_id,
+                                          const LocalTabGroupID& local_id) = 0;
+  virtual void RemoveLocalTabGroupMapping(const LocalTabGroupID& local_id) = 0;
   virtual void UpdateLocalTabId(const LocalTabGroupID& local_group_id,
                                 const base::Uuid& sync_tab_id,
                                 const LocalTabID& local_tab_id) = 0;
diff --git a/components/saved_tab_groups/tab_group_sync_service_impl.cc b/components/saved_tab_groups/tab_group_sync_service_impl.cc
index d28d418..e3191788 100644
--- a/components/saved_tab_groups/tab_group_sync_service_impl.cc
+++ b/components/saved_tab_groups/tab_group_sync_service_impl.cc
@@ -142,12 +142,17 @@
                    : std::nullopt;
 }
 
-void TabGroupSyncServiceImpl::UpdateLocalTabGroupId(
+void TabGroupSyncServiceImpl::UpdateLocalTabGroupMapping(
     const base::Uuid& sync_id,
     const LocalTabGroupID& local_id) {
   model_->OnGroupOpenedInTabStrip(sync_id, local_id);
 }
 
+void TabGroupSyncServiceImpl::RemoveLocalTabGroupMapping(
+    const LocalTabGroupID& local_id) {
+  model_->OnGroupClosedInTabStrip(local_id);
+}
+
 void TabGroupSyncServiceImpl::UpdateLocalTabId(
     const LocalTabGroupID& local_group_id,
     const base::Uuid& sync_tab_id,
diff --git a/components/saved_tab_groups/tab_group_sync_service_impl.h b/components/saved_tab_groups/tab_group_sync_service_impl.h
index 21059cc7..0f806ff 100644
--- a/components/saved_tab_groups/tab_group_sync_service_impl.h
+++ b/components/saved_tab_groups/tab_group_sync_service_impl.h
@@ -57,8 +57,9 @@
   std::vector<SavedTabGroup> GetAllGroups() override;
   std::optional<SavedTabGroup> GetGroup(const base::Uuid& guid) override;
   std::optional<SavedTabGroup> GetGroup(LocalTabGroupID& local_id) override;
-  void UpdateLocalTabGroupId(const base::Uuid& sync_id,
-                             const LocalTabGroupID& local_id) override;
+  void UpdateLocalTabGroupMapping(const base::Uuid& sync_id,
+                                  const LocalTabGroupID& local_id) override;
+  void RemoveLocalTabGroupMapping(const LocalTabGroupID& local_id) override;
   void UpdateLocalTabId(const LocalTabGroupID& local_group_id,
                         const base::Uuid& sync_tab_id,
                         const LocalTabID& local_tab_id) override;
diff --git a/components/saved_tab_groups/tab_group_sync_service_unittest.cc b/components/saved_tab_groups/tab_group_sync_service_unittest.cc
index 9ec5718..8750b9c 100644
--- a/components/saved_tab_groups/tab_group_sync_service_unittest.cc
+++ b/components/saved_tab_groups/tab_group_sync_service_unittest.cc
@@ -208,10 +208,10 @@
   EXPECT_EQ(group->color(), visual_data.color());
 }
 
-TEST_F(TabGroupSyncServiceTest, UpdateLocalTabGroupId) {
+TEST_F(TabGroupSyncServiceTest, UpdateLocalTabGroupMapping) {
   LocalTabGroupID local_id_2 = test::GenerateRandomTabGroupID();
-  tab_group_sync_service_->UpdateLocalTabGroupId(group_1_.saved_guid(),
-                                                 local_id_2);
+  tab_group_sync_service_->UpdateLocalTabGroupMapping(group_1_.saved_guid(),
+                                                      local_id_2);
 
   auto retrieved_group = tab_group_sync_service_->GetGroup(local_id_2);
   EXPECT_TRUE(retrieved_group.has_value());
diff --git a/components/supervised_user/android/java/src/org/chromium/components/supervised_user/SupervisedUserPreferences.java b/components/supervised_user/android/java/src/org/chromium/components/supervised_user/SupervisedUserPreferences.java
index b2cedf4f..002e724 100644
--- a/components/supervised_user/android/java/src/org/chromium/components/supervised_user/SupervisedUserPreferences.java
+++ b/components/supervised_user/android/java/src/org/chromium/components/supervised_user/SupervisedUserPreferences.java
@@ -4,6 +4,7 @@
 
 package org.chromium.components.supervised_user;
 
+import org.jni_zero.JniType;
 import org.jni_zero.NativeMethods;
 
 import org.chromium.components.prefs.PrefService;
@@ -17,6 +18,6 @@
 
     @NativeMethods
     public interface Natives {
-        boolean isSubjectToParentalControls(PrefService prefService);
+        boolean isSubjectToParentalControls(@JniType("PrefService*") PrefService prefService);
     }
 }
diff --git a/components/supervised_user/core/browser/supervised_user_preferences.cc b/components/supervised_user/core/browser/supervised_user_preferences.cc
index 302df37..c7127a8 100644
--- a/components/supervised_user/core/browser/supervised_user_preferences.cc
+++ b/components/supervised_user/core/browser/supervised_user_preferences.cc
@@ -17,7 +17,9 @@
 #include "components/supervised_user/core/common/supervised_user_constants.h"
 
 #if BUILDFLAG(IS_ANDROID)
-#include "components/prefs/android/pref_service_android.h"
+#include "components/prefs/pref_service.h"
+
+// Must come after other includes, because FromJniType() uses PrefService.
 #include "components/supervised_user/android/supervised_user_preferences_jni_headers/SupervisedUserPreferences_jni.h"
 #endif
 
@@ -216,8 +218,7 @@
 #if BUILDFLAG(IS_ANDROID)
 static jboolean JNI_SupervisedUserPreferences_IsSubjectToParentalControls(
     JNIEnv* env,
-    const base::android::JavaParamRef<jobject>& jprefs) {
-  PrefService* prefs = PrefServiceAndroid::FromPrefServiceAndroid(jprefs);
+    PrefService* prefs) {
   return prefs && supervised_user::IsSubjectToParentalControls(*prefs);
 }
 #endif
diff --git a/content/browser/renderer_host/input/composited_scrolling_browsertest.cc b/content/browser/renderer_host/input/composited_scrolling_browsertest.cc
index 68fd5a7..bd99796a 100644
--- a/content/browser/renderer_host/input/composited_scrolling_browsertest.cc
+++ b/content/browser/renderer_host/input/composited_scrolling_browsertest.cc
@@ -307,7 +307,15 @@
 // Tests the composited vs main thread scrolling histogram in the presence of
 // passive event handlers. These should behave the same as the case without any
 // event handlers at all.
-IN_PROC_BROWSER_TEST_P(CompositedScrollingMetricTest, PassiveEventHandlers) {
+
+// TODO(crbug.com/335028963): Re-enable this test
+#if BUILDFLAG(IS_MAC)
+#define MAYBE_PassiveEventHandlers DISABLED_PassiveEventHandlers
+#else
+#define MAYBE_PassiveEventHandlers PassiveEventHandlers
+#endif
+IN_PROC_BROWSER_TEST_P(CompositedScrollingMetricTest,
+                       MAYBE_PassiveEventHandlers) {
   LoadURL(R"HTML(
     data:text/html;charset=utf-8,
     <!DOCTYPE html>
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 5b2a0a5..aadf013 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -5713,12 +5713,6 @@
   common_params_->should_replace_current_entry =
       ShouldReplaceCurrentEntryForFailedNavigation();
 
-  // Set |origin_to_commit| for the renderer; error pages should always commit
-  // in an opaque origin (with the precursor reflecting the destination URL).
-  DCHECK(!commit_params_->origin_to_commit);
-  commit_params_->origin_to_commit = GetOriginToCommit();
-  DCHECK(commit_params_->origin_to_commit->opaque());
-
   // Don't pass the base url in a failed navigation.
   common_params_->initiator_base_url = std::nullopt;
 
@@ -7801,8 +7795,15 @@
         did_receive_early_hints_before_cross_origin_redirect_);
   }
 
-  // TODO(https://crbug.com/888079) Take sandbox into account.
-  std::optional<url::Origin> origin_to_commit = GetOriginToCommit();
+  // Calculate origin in which this navigation would commit so it can be
+  // compared with what is generated by the renderer process and the browser
+  // process at commit time.
+  // TODO(https://crbug.com/888079): Consider using this cached value everywhere
+  // we currently call `GetOriginToCommit()`, to prevent nonce mismatches.
+  browser_side_origin_to_commit_with_debug_info_ =
+      GetOriginToCommitWithDebugInfo();
+  std::optional<url::Origin> origin_to_commit =
+      browser_side_origin_to_commit_with_debug_info_.first;
   same_origin_ = (previous_render_frame_host->GetLastCommittedOrigin() ==
                   origin_to_commit);
 
@@ -7810,26 +7811,24 @@
 
   commit_params_->is_load_data_with_base_url = IsLoadDataWithBaseURL();
 
-  // Calculate origin in which this navigation would commit so it can be
-  // compared with what is generated by the renderer process and the browser
-  // process at commit time.
-  browser_side_origin_to_commit_with_debug_info_ =
-      GetOriginToCommitWithDebugInfo();
-
-  // Set origin_to_commit for data: URLs. Set it here because otherwise the
-  // origin (and hence the nonce) is separately calculated on the renderer side
-  // and sent with DidCommit. At that point the origin used to create the
-  // SiteInstance will differ from the one committed in the renderer. A
-  // consistent nonce across the browser and renderer can be used to determine
-  // which data: URL SiteInstance should be used in
-  // RenderFrameProxyHost::OpenURL.
+  // Set origin_to_commit for:
+  // 1) Error pages, which should always commit in an opaque origin (with the
+  // precursor reflecting the destination URL).
+  // 2) data: URLs, which should also be opaque.
+  // Set the origin here because otherwise the origin (and hence the nonce) is
+  // separately calculated on the renderer side and sent with DidCommit. At that
+  // point the origin used to create the SiteInstance will differ from the one
+  // committed in the renderer. For data: URLs, a consistent nonce across the
+  // browser and renderer can be used to determine which data: URL SiteInstance
+  // should be used in RenderFrameProxyHost::OpenURL.
   // We do not need to set it for LoadDataWithBaseURL cases, because it will not
   // lead to ambiguous cases where multiple data: SiteInstances will be in the
   // same group. However, when the base URL is empty, LoadDataWithBaseURL is
   // treated like a regular data: URL.
-  if (common_params_->url.SchemeIs(url::kDataScheme) &&
-      !IsLoadDataWithBaseURL()) {
+  if (is_error || (common_params_->url.SchemeIs(url::kDataScheme) &&
+                   !IsLoadDataWithBaseURL())) {
     commit_params_->origin_to_commit = origin_to_commit;
+    CHECK(!is_error || commit_params_->origin_to_commit->opaque());
   }
 
   if (!IsSameDocument()) {
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index 983778c..b16c0c8 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -756,7 +756,7 @@
   const url::Origin& renderer_side_origin = params.origin;
   std::pair<std::optional<url::Origin>, std::string>
       browser_side_origin_and_debug_info =
-          navigation_request->GetOriginToCommitWithDebugInfo();
+          navigation_request->browser_side_origin_to_commit_with_debug_info();
   if (renderer_side_origin.scheme() == url::kContentScheme &&
       browser_side_origin_and_debug_info.first->opaque()) {
     return;
@@ -767,10 +767,21 @@
   bool origins_match = (browser_side_origin_and_debug_info.first.value() ==
                         renderer_side_origin);
 
-  // For opaque origins, we say the browser and renderer calculated origins
-  // match if their precursor origins match (their nonces might not match).
+  // For opaque origins, we can check for equality if the opaque origin is not
+  // newly created in the renderer. If the opaque origin can be known by the
+  // browser,  e.g. if the opaque origin is inherited/copied from another
+  // document, or is from the browser-sent `origin_to_commit`, then the browser
+  // calculated origin must match the one used by the renderer in the end. On
+  // the other hand, if the opaque origin is newly created, e.g. a new sandboxed
+  // opaque origin, we can only match the precursor origin. The renderer will
+  // tell us if the origin is newly created in the renderer or not through
+  // appending "is_newly_created" in the end of `origin_calculation_debug_info`.
+  // See also `DocumentLoader::CalculateOrigin()`.
+  // TODO(https://crbug.com/888079): Consider adding a separate boolean that
+  // tracks this instead of piggybacking `origin_calculation_debug_info`.
   if (renderer_side_origin.opaque() &&
-      browser_side_origin_and_debug_info.first->opaque()) {
+      browser_side_origin_and_debug_info.first->opaque() &&
+      params.origin_calculation_debug_info.ends_with("is_newly_created")) {
     origins_match = (renderer_side_origin.GetTupleOrPrecursorTupleIfOpaque() ==
                      browser_side_origin_and_debug_info.first
                          ->GetTupleOrPrecursorTupleIfOpaque());
@@ -14108,8 +14119,12 @@
   DCHECK_EQ(net::OK, navigation_request->GetNetErrorCode());
   // `origin_to_commit` is currently only set only on failed navigations or
   // data: URL navigations.
-  DCHECK(!commit_params->origin_to_commit ||
-         common_params->url.SchemeIs(url::kDataScheme));
+  if (commit_params->origin_to_commit) {
+    DCHECK(common_params->url.SchemeIs(url::kDataScheme));
+    CHECK_EQ(commit_params->origin_to_commit.value(),
+             navigation_request->browser_side_origin_to_commit_with_debug_info()
+                 .first.value());
+  }
   IncreaseCommitNavigationCounter();
   mojo::PendingRemote<blink::mojom::CodeCacheHost> code_cache_host;
   mojo::PendingRemote<blink::mojom::CodeCacheHost>
@@ -14274,6 +14289,9 @@
     blink::mojom::PolicyContainerPtr policy_container) {
   // `origin_to_commit` must be set on failed navigations.
   DCHECK(commit_params->origin_to_commit);
+  CHECK_EQ(commit_params->origin_to_commit.value(),
+           navigation_request->browser_side_origin_to_commit_with_debug_info()
+               .first.value());
   DCHECK(navigation_client && navigation_request);
   DCHECK_NE(GURL(), common_params->url);
   DCHECK_NE(net::OK, error_code);
diff --git a/content/public/browser/on_device_model_service_instance.cc b/content/public/browser/on_device_model_service_instance.cc
index 95227a3..ee42377da 100644
--- a/content/public/browser/on_device_model_service_instance.cc
+++ b/content/public/browser/on_device_model_service_instance.cc
@@ -10,23 +10,29 @@
 
 namespace content {
 
+namespace {
+void InitOnDeviceModelService(
+    mojo::Remote<on_device_model::mojom::OnDeviceModelService>&
+        service_remote) {
+  auto receiver = service_remote.BindNewPipeAndPassReceiver();
+  service_remote.reset_on_disconnect();
+  ServiceProcessHost::Launch<on_device_model::mojom::OnDeviceModelService>(
+      std::move(receiver), ServiceProcessHost::Options()
+                               .WithDisplayName("On-Device Model Service")
+                               .Pass());
+}
+}  // namespace
+
 // static
 const mojo::Remote<on_device_model::mojom::OnDeviceModelService>&
 GetRemoteOnDeviceModelService() {
   static base::NoDestructor<
       mojo::Remote<on_device_model::mojom::OnDeviceModelService>>
-      service_remote([]() {
-        mojo::Remote<on_device_model::mojom::OnDeviceModelService>
-            service_remote;
-        auto receiver = service_remote.BindNewPipeAndPassReceiver();
-        service_remote.reset_on_disconnect();
-        ServiceProcessHost::Launch<
-            on_device_model::mojom::OnDeviceModelService>(
-            std::move(receiver), ServiceProcessHost::Options()
-                                     .WithDisplayName("On-Device Model Service")
-                                     .Pass());
-        return service_remote;
-      }());
+      service_remote;
+
+  if (!*service_remote) {
+    InitOnDeviceModelService(*service_remote);
+  }
 
   return *service_remote;
 }
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 1c618b7..f92f89a 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -1231,11 +1231,11 @@
              "MacAllowBackgroundingRenderProcesses",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Enables a fix for a macOS IME Live Conversion issue. crbug.com/1328530 and
-// crbug.com/1342551
+// Enables a fix for a macOS IME Live Conversion issue. crbug.com/40226470 and
+// crbug.com/40060200
 BASE_FEATURE(kMacImeLiveConversionFix,
              "MacImeLiveConversionFix",
-             base::FEATURE_DISABLED_BY_DEFAULT);
+             base::FEATURE_ENABLED_BY_DEFAULT);
 
 BASE_FEATURE(kMacSyscallSandbox,
              "MacSyscallSandbox",
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 34da5b74..045a2a27 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -818,7 +818,6 @@
 crbug.com/angleproject/3684 [ android angle-opengles ] conformance2/renderbuffers/multisample-with-full-sample-counts.html [ Failure ]
 
 crbug.com/1521117 [ android android-pixel-4 angle-opengles passthrough ] deqp/functional/gles3/multisample/fbo_4_samples.html [ RetryOnFailure ]
-crbug.com/1521117 [ android android-pixel-4 angle-opengles passthrough ] deqp/functional/gles3/multisample/fbo_max_samples.html [ RetryOnFailure ]
 
 
 #####################
diff --git a/content/test/navigation_simulator_impl.cc b/content/test/navigation_simulator_impl.cc
index 0e87c46..e0be162 100644
--- a/content/test/navigation_simulator_impl.cc
+++ b/content/test/navigation_simulator_impl.cc
@@ -1588,7 +1588,9 @@
   if (same_document) {
     params->origin = current_rfh->GetLastCommittedOrigin();
   } else {
-    params->origin = origin_.value_or(request_->GetOriginToCommit().value());
+    params->origin = origin_.value_or(
+        request_->browser_side_origin_to_commit_with_debug_info()
+            .first.value());
   }
 
   if (same_document) {
diff --git a/docs/testing/chromeos_integration/crosier_metadata.md b/docs/testing/chromeos_integration/crosier_metadata.md
new file mode 100644
index 0000000..15ee6f3
--- /dev/null
+++ b/docs/testing/chromeos_integration/crosier_metadata.md
@@ -0,0 +1,58 @@
+# Metadata for Crosier tests
+
+## Create a yaml file with test case details
+
+Create a yaml file in the same location of your test source file. Example of
+such yaml file:
+
+```yaml
+---
+name: "LockScreen"
+harness: "crosier"
+category: "integration"
+owners:
+  - email: "test-owner-email@google.com"
+  - email: "chromeos-sw-engprod@google.com"
+hw_agnostic: False
+criteria: |
+  Tests that dbus messages for lid close trigger screen lock. This only tests
+  the "lock on lid close" pref state.
+cases:
+  - id: "CloseLidDbusIntegration"
+    tags: ["crosier:crosierdemosuite", "crosier:cq"]
+  - id: "CloseLidPref"
+    tags: ["crosier:crosierdemosuite", "crosier:cq", "crosier:informational"]
+...
+```
+
+Pay specifal attention to the list of `tags` for each test case. Using these
+tags, tests are being allocated to ChromeOS CI/CQ scheduling suites.
+
+Following tags are recognized and supported:
+
+* `crosier:crosierdemosuite` - test case will run daily in a dedicated,
+non-critical test suite for stability and regression monitoring.
+* `crosier:cq` - test case will run in global CQ and post-submit snapshot
+builds.
+* `crosier:informational` - test case will be considered as non-critical, i.e.
+not included in the critical CQ test suite.
+
+## Include new yaml file in Crosier binary build
+
+The link to all yaml files should be added to the `crosier_metadata` rules that
+is included by the Crosier `chromeos_integration_tests` rule of the
+[BUILD.gn](https://chromium.googlesource.com/chromium/src/+/main/chrome/test/BUILD.gn)
+file:
+
+```
+copy("crosier_metadata") {
+    sources = [
+        ...
+        "../browser/ash/login/lock/lock_screen_integration_test.yaml",
+        ...
+        ...
+    ]
+    outputs = [ "$root_out_dir/crosier_metadata/{{source_file_part}}" ]
+}
+
+```
diff --git a/docs/testing/chromeos_integration/development_guide.md b/docs/testing/chromeos_integration/development_guide.md
index e08dbde..9b8e4fd4 100644
--- a/docs/testing/chromeos_integration/development_guide.md
+++ b/docs/testing/chromeos_integration/development_guide.md
@@ -1,4 +1,4 @@
-# Development Guide
+# Crosier Development Guide
 
 This doc assumes you're already familiar with ChromeOS on-device development.
 If not, please follow
@@ -21,6 +21,13 @@
 See the [demo test](https://source.chromium.org/chromium/chromium/src/+/main:chrome/test/base/chromeos/crosier/demo_integration_test.cc;l=19)
 for instructions
 
+## Test metadata
+
+Each Crosier test being added should include test metadata in `yaml` format.
+
+See [Crosier metadata guide](https://source.chromium.org/chromium/chromium/src/+/main:docs/testing/chromeos_integration/crosier_metadata.md)
+for more information on how to add it.
+
 ## Continuous builders
 Currently the test binary runs against both Ash and Lacros on CI only.
 
diff --git a/infra/config/generated/builders/build/linux-chromeos-build-perf-siso/properties.json b/infra/config/generated/builders/build/linux-chromeos-build-perf-siso/properties.json
index 71c43f9..7294a8b 100644
--- a/infra/config/generated/builders/build/linux-chromeos-build-perf-siso/properties.json
+++ b/infra/config/generated/builders/build/linux-chromeos-build-perf-siso/properties.json
@@ -54,7 +54,8 @@
   "$build/siso": {
     "configs": [
       "builder",
-      "remote-library-link"
+      "remote-library-link",
+      "remote-exec-link"
     ],
     "enable_cloud_profiler": true,
     "enable_cloud_trace": true,
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index c299769..ef3f1d5 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -6440,7 +6440,7 @@
       name: "Comparison ios (reclient)"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
-      dimensions: "cpu:x86-64"
+      dimensions: "cpu:arm64"
       dimensions: "free_space:standard"
       dimensions: "os:Mac-13|Mac-14"
       dimensions: "pool:luci.chromium.ci"
diff --git a/infra/config/subprojects/build/build.star b/infra/config/subprojects/build/build.star
index 840c40f..e4d7dc7c 100644
--- a/infra/config/subprojects/build/build.star
+++ b/infra/config/subprojects/build/build.star
@@ -352,8 +352,6 @@
         category = "cros",
         short_name = "siso",
     ),
-    # TODO: b/329399631#comment39 - Enable remote-exec-link after resolving the Segmentation fault issue.
-    siso_configs = ["builder", "remote-library-link"],
 )
 
 cq_build_perf_builder(
diff --git a/infra/config/subprojects/chromium/ci/chromium.fyi.star b/infra/config/subprojects/chromium/ci/chromium.fyi.star
index 699cbe0..5362982 100644
--- a/infra/config/subprojects/chromium/ci/chromium.fyi.star
+++ b/infra/config/subprojects/chromium/ci/chromium.fyi.star
@@ -1412,6 +1412,7 @@
     },
     builderless = True,
     cores = None,
+    cpu = cpu.ARM64,
     console_view_entry = consoles.console_view_entry(
         category = "ios",
         short_name = "cmp",
diff --git a/internal b/internal
index 6365d27..5fb766e 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 6365d276cb6ee8b2d6992bcc8699f3e1eff7fe3b
+Subproject commit 5fb766e4a38b8e77fc63d347b829a1903c23a919
diff --git a/ios/chrome/app/main_controller.mm b/ios/chrome/app/main_controller.mm
index c12a6d6..81c096b 100644
--- a/ios/chrome/app/main_controller.mm
+++ b/ios/chrome/app/main_controller.mm
@@ -906,47 +906,57 @@
   // _chromeMain.reset() is a blocking call that regularly causes
   // applicationWillTerminate to fail after a 5s delay. Experiment with skipping
   // this shutdown call. See: crbug.com/1328891
-  // TODO(crbug.com/325597817): Update this logic for multiple browser states.
   if (base::FeatureList::IsEnabled(kFastApplicationWillTerminate)) {
-    // Expected number of time the `completionBlock` defined below needs to
+    // Expected number of time the `closure` defined below needs to
     // be called before it signal the semaphore. This corresponds to the
     // number of services that needs to be waited for.
-    uint32_t expected_count = 1;
+    uint32_t expectedCount = 0;
 
-    ChromeBrowserState* browserState = self.appState.mainBrowserState;
-    if (browserState->HasOffTheRecordChromeBrowserState()) {
-      expected_count += 1;
-    }
-
+    // MetricsService doesn't depend on a browser state.
     metrics::MetricsService* metrics =
         GetApplicationContext()->GetMetricsService();
     if (metrics) {
-      expected_count += 1;
+      expectedCount += 1;
     }
 
+    std::vector<ChromeBrowserState*> loadedBrowserStates =
+        GetApplicationContext()
+            ->GetChromeBrowserStateManager()
+            ->GetLoadedBrowserStates();
+    for (ChromeBrowserState* browserState : loadedBrowserStates) {
+      expectedCount += 1;
+      if (browserState->HasOffTheRecordChromeBrowserState()) {
+        expectedCount += 1;
+      }
+    }
+
+    // `dispatch_semaphore_signal` is called only once when `closure` is called
+    // `expectedCount` times.
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
     base::RepeatingClosure closure =
-        ExpectNCall(expected_count, base::BindRepeating(^{
+        ExpectNCall(expectedCount, base::BindRepeating(^{
                       dispatch_semaphore_signal(semaphore);
                     }));
 
-    SessionRestorationServiceFactory::GetForBrowserState(browserState)
-        ->InvokeClosureWhenBackgroundProcessingDone(closure);
-
-    if (browserState->HasOffTheRecordChromeBrowserState()) {
-      ChromeBrowserState* otrBrowserState =
-          browserState->GetOffTheRecordChromeBrowserState();
-      SessionRestorationServiceFactory::GetForBrowserState(otrBrowserState)
+    for (ChromeBrowserState* browserState : loadedBrowserStates) {
+      SessionRestorationServiceFactory::GetForBrowserState(browserState)
           ->InvokeClosureWhenBackgroundProcessingDone(closure);
+
+      if (browserState->HasOffTheRecordChromeBrowserState()) {
+        ChromeBrowserState* otrBrowserState =
+            browserState->GetOffTheRecordChromeBrowserState();
+        SessionRestorationServiceFactory::GetForBrowserState(otrBrowserState)
+            ->InvokeClosureWhenBackgroundProcessingDone(closure);
+      }
     }
 
     if (metrics) {
       metrics->Stop();
-      // MetricsService::Stop() depends on a committed local state, and does so
-      // asynchronously. To avoid losing metrics, this minimum wait is required.
-      // This will introduce a wait that will likely be the source of a number
-      // of watchdog kills, but it should still be fewer than the number of
-      // kills `_chromeMain.reset()` is responsible for.
+      // MetricsService::Stop() depends on a committed local state, and does
+      // so asynchronously. To avoid losing metrics, this minimum wait is
+      // required. This will introduce a wait that will likely be the source
+      // of a number of watchdog kills, but it should still be fewer than the
+      // number of kills `_chromeMain.reset()` is responsible for.
       GetApplicationContext()->GetLocalState()->CommitPendingWrite({}, closure);
     }
 
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.h b/ios/chrome/browser/ui/settings/settings_table_view_controller.h
index 27daa3b..77c3ce1 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.h
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.h
@@ -5,15 +5,10 @@
 #ifndef IOS_CHROME_BROWSER_UI_SETTINGS_SETTINGS_TABLE_VIEW_CONTROLLER_H_
 #define IOS_CHROME_BROWSER_UI_SETTINGS_SETTINGS_TABLE_VIEW_CONTROLLER_H_
 
-#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
+#import "ios/chrome/browser/ui/settings/settings_controller_protocol.h"
 #import "ios/chrome/browser/ui/settings/settings_root_table_view_controller.h"
 
-@protocol ApplicationCommands;
 class Browser;
-@protocol BrowserCommands;
-@protocol BrowsingDataCommands;
-@class SigninInteractionController;
-@protocol SnackbarCommands;
 
 // This class is the TableView for the application settings.
 @interface SettingsTableViewController
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
index 632bbf2..66143b9d 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller.mm
@@ -4,7 +4,6 @@
 
 #import "ios/chrome/browser/ui/settings/settings_table_view_controller.h"
 
-#import <MaterialComponents/MaterialSnackbar.h>
 #import <memory>
 
 #import "base/apple/foundation_util.h"
@@ -65,11 +64,9 @@
 #import "ios/chrome/browser/shared/model/prefs/pref_names.h"
 #import "ios/chrome/browser/shared/model/utils/first_run_util.h"
 #import "ios/chrome/browser/shared/public/commands/application_commands.h"
-#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
 #import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
 #import "ios/chrome/browser/shared/public/commands/show_signin_command.h"
-#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/shared/public/features/system_flags.h"
 #import "ios/chrome/browser/shared/ui/symbols/buildflags.h"
@@ -190,7 +187,6 @@
     NotificationsCoordinatorDelegate,
     PrivacyCoordinatorDelegate,
     SafetyCheckCoordinatorDelegate,
-    SettingsControllerProtocol,
     SearchEngineObserving,
     SyncObserverModelBridge> {
   // The browser where the settings are being displayed.
@@ -295,10 +291,6 @@
 @property(nonatomic, strong, readonly)
     TableViewInfoButtonItem* managedFeedSettingsItem;
 
-@property(nonatomic, readonly, weak)
-    id<ApplicationCommands, BrowserCommands, BrowsingDataCommands>
-        dispatcher;
-
 // YES if the default browser settings row is currently showing the notification
 // dot.
 @property(nonatomic, assign) BOOL showingDefaultBrowserNotificationDot;
diff --git a/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
index 4d66faf..f24c6eb9 100644
--- a/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/settings_table_view_controller_unittest.mm
@@ -29,6 +29,7 @@
 #import "ios/chrome/browser/shared/public/commands/application_commands.h"
 #import "ios/chrome/browser/shared/public/commands/browsing_data_commands.h"
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
+#import "ios/chrome/browser/shared/public/commands/settings_commands.h"
 #import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/shared/ui/table_view/cells/table_view_detail_icon_item.h"
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator.mm
index 3a51f7b..c6955f45 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/base_grid_mediator.mm
@@ -419,9 +419,7 @@
     return;
   }
 
-  if (detachChange.group()) {
-    [self updateCellGroup:detachChange.group()];
-  } else {
+  if (!detachChange.group()) {
     // Get the identifier to remove.
     web::WebState* detachedWebState = detachChange.detached_web_state();
     GridItemIdentifier* identifierToRemove =
@@ -510,10 +508,16 @@
       // The activation is handled after this switch statement.
       break;
     }
-    case WebStateListChange::Type::kDetach:
-      // Do nothing when a WebState is detached, as this is already handled in
+    case WebStateListChange::Type::kDetach: {
+      const WebStateListChangeDetach& detachChange =
+          change.As<WebStateListChangeDetach>();
+      if (detachChange.group()) {
+        [self updateCellGroup:detachChange.group()];
+      }
+      // Do not manage other case scenarios as this is already handled in
       // `-willChangeWebStateList:change:status:` function.
       break;
+    }
     case WebStateListChange::Type::kMove: {
       const WebStateListChangeMove& moveChange =
           change.As<WebStateListChangeMove>();
diff --git a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/tab_groups/tab_groups_egtest.mm b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/tab_groups/tab_groups_egtest.mm
index 64d5857..8301dec 100644
--- a/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/tab_groups/tab_groups_egtest.mm
+++ b/ios/chrome/browser/ui/tab_switcher/tab_grid/grid/tab_groups/tab_groups_egtest.mm
@@ -110,8 +110,7 @@
 }
 
 // Tests that creates a tab group and opens the grouped tab.
-// TODO(crbug.com/333892967): The test fails on bots.
-- (void)DISABLED_testCompleteTabGroupCreation {
+- (void)testCompleteTabGroupCreation {
   [ChromeEarlGreyUI openTabGrid];
 
   // Open the creation view.
diff --git a/ios_internal b/ios_internal
index f758ded..ce28903 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit f758ded9acac8b0e8eb6f1638b8fb945c15da69d
+Subproject commit ce28903ff84c8484e00400f18816c7dac205a29b
diff --git a/media/gpu/test/video_player/decoder_wrapper.cc b/media/gpu/test/video_player/decoder_wrapper.cc
index cbd9f0ae..d4ec9bb36 100644
--- a/media/gpu/test/video_player/decoder_wrapper.cc
+++ b/media/gpu/test/video_player/decoder_wrapper.cc
@@ -369,9 +369,7 @@
   // should (naively moving this task there doesn't work because it prevents the
   // V4L2VideoDecoder backend from polling the device driver).
 #if BUILDFLAG(USE_V4L2_CODEC)
-  delay = base::FeatureList::IsEnabled(kV4L2FlatStatefulVideoDecoder)
-              ? base::Milliseconds(5)
-              : base::Milliseconds(1);
+  delay = base::Milliseconds(1);
   static bool log_delay_message = true;
   if (log_delay_message) {
     LOG(INFO) << "Using a delay of " << delay
diff --git a/services/webnn/tflite/graph_builder.cc b/services/webnn/tflite/graph_builder.cc
index dccaee07..569d05d 100644
--- a/services/webnn/tflite/graph_builder.cc
+++ b/services/webnn/tflite/graph_builder.cc
@@ -386,6 +386,9 @@
     case mojom::Operation::Tag::kTranspose:
       operator_offset = SerializeTranspose(*op.get_transpose());
       break;
+    case mojom::Operation::Tag::kWhere:
+      operator_offset = SerializeWhere(*op.get_where());
+      break;
     case mojom::Operation::Tag::kBatchNormalization:
       return base::unexpected("batchNormalization is not implemented");
     case mojom::Operation::Tag::kExpand:
@@ -416,8 +419,6 @@
       return base::unexpected("softsign is not implemented");
     case mojom::Operation::Tag::kTriangular:
       return base::unexpected("triangular is not implemented");
-    case mojom::Operation::Tag::kWhere:
-      return base::unexpected("where is not implemented");
   }
   operators_.emplace_back(operator_offset);
 
@@ -1594,4 +1595,44 @@
       transpose.permutation);
 }
 
+auto GraphBuilder::SerializeWhere(const mojom::Where& where) -> OperatorOffset {
+  // The data type of WebNN condition operand is uint8, but TFLite requires the
+  // condition operand to be of type bool, so a cast operation need to be
+  // inserted before the operation to convert uint8 to bool for the condition
+  // operand.
+  const mojom::Operand& condition_operand =
+      GetOperand(where.condition_operand_id);
+  // The shape of condition operand has been validated to not overflow before
+  // creating tensor.
+  const auto signed_condition_dimensions =
+      ToSignedDimensions(condition_operand.dimensions);
+  CHECK(signed_condition_dimensions.has_value());
+  const int32_t condition_bool_tensor_index =
+      base::checked_cast<int32_t>(tensors_.size());
+  tensors_.emplace_back(::tflite::CreateTensor(
+      builder_, builder_.CreateVector<int32_t>(*signed_condition_dimensions),
+      ::tflite::TensorType_BOOL));
+
+  CHECK_EQ(condition_operand.data_type, mojom::Operand::DataType::kUint8);
+  operators_.emplace_back(SerializeCastOperation(
+      operand_to_index_map_.at(where.condition_operand_id),
+      /*input_tensor_type=*/::tflite::TensorType_UINT8,
+      condition_bool_tensor_index,
+      /*output_tensor_type=*/::tflite::TensorType_BOOL));
+
+  // TFLite SELECT_V2 builtin operator supports broadcastable shapes between
+  // `condition`, `true` and `false` operand.
+  const uint32_t operator_code_index =
+      GetOperatorCodeIndex(::tflite::BuiltinOperator_SELECT_V2);
+  const std::array<int32_t, 3> op_inputs = {
+      condition_bool_tensor_index,
+      operand_to_index_map_.at(where.true_value_operand_id),
+      operand_to_index_map_.at(where.false_value_operand_id)};
+  const std::array<int32_t, 1> op_outputs = {
+      operand_to_index_map_.at(where.output_operand_id)};
+  return ::tflite::CreateOperator(builder_, operator_code_index,
+                                  builder_.CreateVector<int32_t>(op_inputs),
+                                  builder_.CreateVector<int32_t>(op_outputs));
+}
+
 }  // namespace webnn::tflite
diff --git a/services/webnn/tflite/graph_builder.h b/services/webnn/tflite/graph_builder.h
index 786dc00..b68ae4a2 100644
--- a/services/webnn/tflite/graph_builder.h
+++ b/services/webnn/tflite/graph_builder.h
@@ -186,6 +186,7 @@
       const mojom::Split& split);
   OperatorOffset SerializeTanh(const mojom::Tanh& tanh);
   OperatorOffset SerializeTranspose(const mojom::Transpose& transpose);
+  OperatorOffset SerializeWhere(const mojom::Where& where);
 
   // No further methods may be called on this class after calling this method
   // because the buffer of `buffer_` is now owned by the detached buffer.
diff --git a/services/webnn/tflite/op_resolver.cc b/services/webnn/tflite/op_resolver.cc
index 3aa8fce..2f093a1 100644
--- a/services/webnn/tflite/op_resolver.cc
+++ b/services/webnn/tflite/op_resolver.cc
@@ -165,6 +165,8 @@
              ::tflite::ops::builtin::Register_RESIZE_NEAREST_NEIGHBOR(),
              /* min_version = */ 1,
              /* max_version = */ 3);
+  AddBuiltin(::tflite::BuiltinOperator_SELECT_V2,
+             ::tflite::ops::builtin::Register_SELECT_V2());
   AddBuiltin(::tflite::BuiltinOperator_SIN,
              ::tflite::ops::builtin::Register_SIN());
   AddBuiltin(::tflite::BuiltinOperator_SLICE,
diff --git a/testing/libfuzzer/fuzzer_test.gni b/testing/libfuzzer/fuzzer_test.gni
index 226db68..097f327 100644
--- a/testing/libfuzzer/fuzzer_test.gni
+++ b/testing/libfuzzer/fuzzer_test.gni
@@ -32,6 +32,8 @@
 # - seed_corpus_deps - dependencies for generating the seed corpus.
 # - grammar_options - defines a grammar used by a grammar based mutator.
 # - exclude_main - if you're going to provide your own 'main' function
+# - high_end_job_required - whether the fuzzer requires bigger machines to run.
+#   Optional argument.
 #
 # If use_libfuzzer gn flag is defined, then proper fuzzer would be build.
 # Without use_libfuzzer or use_afl a unit-test style binary would be built on
@@ -42,7 +44,19 @@
 # config (.options file) file would be generated or modified in root output
 # dir (next to test).
 template("fuzzer_test") {
-  if (!disable_libfuzzer && use_fuzzing_engine) {
+  _high_end_job_required = false
+  if (defined(invoker.high_end_job_required)) {
+    _high_end_job_required = invoker.high_end_job_required
+  }
+
+  # If the job is a high_end job and that we are currently building a high_end
+  # target, we should compile this fuzzer_test in. Otherwise, for now, we just
+  # compile everything in.
+  # TODO(crbug.com/333831251): Once CF supports high end jobs, do not compile
+  # high end targets except for high end jobs.
+  _should_build = (_high_end_job_required || !high_end_fuzzer_targets) &&
+                  !disable_libfuzzer && use_fuzzing_engine
+  if (_should_build) {
     assert(defined(invoker.sources), "Need sources in $target_name.")
 
     test_deps = []
@@ -315,6 +329,12 @@
     # noop on unsupported platforms.
     # mark attributes as used.
     not_needed(invoker, "*")
+    not_needed(invoker,
+               [
+                 "deps",
+                 "seed_corpus",
+                 "sources",
+               ])
 
     group(target_name) {
     }
diff --git a/testing/test.gni b/testing/test.gni
index 9b057e7..5f92dd5c 100644
--- a/testing/test.gni
+++ b/testing/test.gni
@@ -146,34 +146,51 @@
     # don't have issues.
     import("//third_party/jni_zero/jni_zero.gni")
   }
-  target(invoker.target_type, target_name) {
-    forward_variables_from(invoker,
-                           "*",
-                           TESTONLY_AND_VISIBILITY + _rs_vars + [ "fuzztests" ])
-    forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
-    sources = _cc_sources
-    if (!defined(deps)) {
-      deps = []
-    }
-    if (!defined(ldflags)) {
-      ldflags = []
-    }
 
-    if (_rs_sources != []) {
-      deps += [ ":${_rust_target_name}" ]
+  _building_fuzztest_fuzzer =
+      defined(invoker.fuzztests) && use_fuzzing_engine && is_linux
+
+  # Fuzz tests are small fuzzers that do not require particularly-powerful
+  # machines to run, so we do not build them when `high_end_fuzzer_targets`
+  # is true and we are building fuzztests in fuzzing mode.
+  if (_building_fuzztest_fuzzer && high_end_fuzzer_targets) {
+    not_needed(invoker, "*")
+    not_needed("*")
+
+    # We still want a reachable target, so make it a no-op empty group. This
+    # will let the fuzzer builders crawl the build graph and invoke ninja in
+    # the same way regardless of GN args.
+    group(target_name) {
     }
-    if (defined(invoker.fuzztests)) {
-      deps += [ "//third_party/fuzztest" ]
+  } else {
+    target(invoker.target_type, target_name) {
+      forward_variables_from(
+          invoker,
+          "*",
+          TESTONLY_AND_VISIBILITY + _rs_vars + [ "fuzztests" ])
+      forward_variables_from(invoker, TESTONLY_AND_VISIBILITY)
+      sources = _cc_sources
+      if (!defined(deps)) {
+        deps = []
+      }
+      if (!defined(ldflags)) {
+        ldflags = []
+      }
+
+      if (_rs_sources != []) {
+        deps += [ ":${_rust_target_name}" ]
+      }
+      if (defined(invoker.fuzztests)) {
+        deps += [ "//third_party/fuzztest" ]
+      }
     }
   }
 
-  if (defined(invoker.fuzztests) && use_fuzzing_engine && is_linux) {
+  if (_building_fuzztest_fuzzer && !high_end_fuzzer_targets) {
     # This test contains fuzztests. We want to package them up in a way
     # which ClusterFuzz knows how to extract. We need to:
     # 1) make an executable for each individual fuzz test;
-    # 2) name the main executable something specific so that ClusterFuzz
-    #    knows how to extract it
-    # 3) check that the fuzztests variable is correct.
+    # 2) check that the fuzztests variable is correct.
     # At present, all this is likely to work only if invoker.target_type
     # is 'executable', since we generate a wrapper script that assumes so.
     # At the moment, we aim to fuzz these fuzztests only on Linux so that's
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 761f25ae..d7413509 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -11737,12 +11737,13 @@
             ],
             "experiments": [
                 {
-                    "name": "LowDeadline",
+                    "name": "Deadline1500_20240415_M124+",
                     "params": {
-                        "MinorModeRestrictionsFetchDeadlineMs": "1000"
+                        "MinorModeRestrictionsFetchDeadlineMs": "1500"
                     },
                     "enable_features": [
-                        "MinorModeRestrictionsForHistorySyncOptIn"
+                        "MinorModeRestrictionsForHistorySyncOptIn",
+                        "UseSystemCapabilitiesForMinorModeRestrictions"
                     ]
                 }
             ]
@@ -17751,6 +17752,21 @@
             ]
         }
     ],
+    "SearchEnginePromoDialogRewrite": [
+        {
+            "platforms": [
+                "android"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "SearchEnginePromoDialogRewrite"
+                    ]
+                }
+            ]
+        }
+    ],
     "SearchEnginesPromoV3": [
         {
             "platforms": [
@@ -22172,6 +22188,24 @@
             ]
         }
     ],
+    "WebRTC-LibvpxVp9Encoder-SvcFrameDropConfig": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled,layer_drop_mode:1,max_consec_drop:2,_20240416"
+                }
+            ]
+        }
+    ],
     "WebRTC-PaddingMode-RecentLargePacket": [
         {
             "platforms": [
diff --git a/third_party/angle b/third_party/angle
index 49e434db..6557da0 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 49e434dba2f98a749fe36d44137cee2b8f039800
+Subproject commit 6557da03c85eee30448e1fefc2d89bdc348c580d
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 4861831..740bc1c 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -1303,6 +1303,11 @@
 const base::FeatureParam<int> kLCPCriticalPathPredictorMaxElementLocatorLength{
     &kLCPCriticalPathPredictor, "lcpp_max_element_locator_length", 1024};
 
+const base::FeatureParam<bool>
+    kLCPCriticalPathAdjustImageLoadPriorityOverrideFirstNBoost{
+        &kLCPCriticalPathPredictor,
+        "lcpp_adjust_image_load_priority_override_first_n_boost", false};
+
 const base::FeatureParam<LcppRecordedLcpElementTypes>::Option
     lcpp_recorded_element_types[] = {
         {LcppRecordedLcpElementTypes::kAll, "all"},
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h
index 3b4e6d8f..abb9762 100644
--- a/third_party/blink/public/common/features.h
+++ b/third_party/blink/public/common/features.h
@@ -729,6 +729,11 @@
 BLINK_COMMON_EXPORT extern const base::FeatureParam<int>
     kLCPCriticalPathPredictorMaxElementLocatorLength;
 
+// If true, LCP critical path predictor mechanism overrides the first N image
+// prioritization when there is LCP hint.
+BLINK_COMMON_EXPORT extern const base::FeatureParam<bool>
+    kLCPCriticalPathAdjustImageLoadPriorityOverrideFirstNBoost;
+
 // The type of LCP elements recorded by LCPP.
 enum class LcppRecordedLcpElementTypes {
   kAll,
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
index 7eaeedf7..35b9f0a 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.cc
@@ -1738,9 +1738,19 @@
   return false;
 }
 
+namespace {
+
+CSSValue* ConsumeColorInternal(CSSParserTokenRange&,
+                               const CSSParserContext&,
+                               bool accept_quirky_colors,
+                               AllowedColors);
+
+}  // namespace
+
 // https://www.w3.org/TR/css-color-5/#color-mix
 static CSSValue* ConsumeColorMixFunction(CSSParserTokenRange& range,
-                                         const CSSParserContext& context) {
+                                         const CSSParserContext& context,
+                                         AllowedColors allowed_colors) {
   DCHECK(range.Peek().FunctionId() == CSSValueID::kColorMix);
   context.Count(WebFeature::kCSSColorMixFunction);
 
@@ -1759,12 +1769,16 @@
     return nullptr;
   }
 
-  CSSValue* color1 = ConsumeColor(args, context);
+  const bool no_quirky_colors = false;
+
+  CSSValue* color1 =
+      ConsumeColorInternal(args, context, no_quirky_colors, allowed_colors);
   CSSPrimitiveValue* p1 =
       ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
   // Color can come after the percentage
   if (!color1) {
-    color1 = ConsumeColor(args, context);
+    color1 = ConsumeColorInternal(args, context, no_quirky_colors,
+                                  allowed_colors);
     if (!color1) {
       return nullptr;
     }
@@ -1779,12 +1793,14 @@
     return nullptr;
   }
 
-  CSSValue* color2 = ConsumeColor(args, context);
+  CSSValue* color2 =
+      ConsumeColorInternal(args, context, no_quirky_colors, allowed_colors);
   CSSPrimitiveValue* p2 =
       ConsumePercent(args, context, CSSPrimitiveValue::ValueRange::kAll);
   // Color can come after the percentage
   if (!color2) {
-    color2 = ConsumeColor(args, context);
+    color2 = ConsumeColorInternal(args, context, no_quirky_colors,
+                                  allowed_colors);
     if (!color2) {
       return nullptr;
     }
@@ -1878,13 +1894,17 @@
 }  // namespace
 
 CSSValue* ConsumeColorContrast(CSSParserTokenRange& range,
-                               const CSSParserContext& context) {
+                               const CSSParserContext& context,
+                               AllowedColors allowed_colors) {
   DCHECK_EQ(range.Peek().FunctionId(), CSSValueID::kColorContrast);
 
   CSSParserTokenRange range_copy = range;
   CSSParserTokenRange args = ConsumeFunction(range_copy);
 
-  CSSValue* background_color = ConsumeColor(args, context);
+  const bool no_quirky_colors = false;
+
+  CSSValue* background_color =
+      ConsumeColorInternal(args, context, no_quirky_colors, allowed_colors);
   if (!background_color) {
     return nullptr;
   }
@@ -1895,7 +1915,8 @@
 
   VectorOf<CSSValue> colors_to_compare_against;
   do {
-    CSSValue* color = ConsumeColor(args, context);
+    CSSValue* color = ConsumeColorInternal(args, context, no_quirky_colors,
+                                           allowed_colors);
     if (!color) {
       return nullptr;
     }
@@ -1997,14 +2018,14 @@
 CSSValue* ConsumeColorInternal(CSSParserTokenRange& range,
                                const CSSParserContext& context,
                                bool accept_quirky_colors,
-                               AllowedColorKeywords allowed_keywords) {
+                               AllowedColors allowed_colors) {
   if (RuntimeEnabledFeatures::CSSColorContrastEnabled() &&
       range.Peek().FunctionId() == CSSValueID::kColorContrast) {
-    return ConsumeColorContrast(range, context);
+    return ConsumeColorContrast(range, context, allowed_colors);
   }
 
   if (range.Peek().FunctionId() == CSSValueID::kColorMix) {
-    CSSValue* color = ConsumeColorMixFunction(range, context);
+    CSSValue* color = ConsumeColorMixFunction(range, context, allowed_colors);
     return color;
   }
 
@@ -2017,8 +2038,9 @@
     if (!isValueAllowedInMode(id, context.Mode())) {
       return nullptr;
     }
-    if (allowed_keywords != AllowedColorKeywords::kAllowSystemColor &&
-        (StyleColor::IsSystemColorIncludingDeprecated(id) ||
+    if (allowed_colors == AllowedColors::kAbsolute &&
+        (id == CSSValueID::kCurrentcolor ||
+         StyleColor::IsSystemColorIncludingDeprecated(id) ||
          StyleColor::IsSystemColor(id))) {
       return nullptr;
     }
@@ -2046,8 +2068,9 @@
     }
   }
 
-  if (IsUASheetBehavior(context.Mode()) ||
-      RuntimeEnabledFeatures::CSSLightDarkColorsEnabled()) {
+  if ((IsUASheetBehavior(context.Mode()) ||
+       RuntimeEnabledFeatures::CSSLightDarkColorsEnabled()) &&
+      allowed_colors == AllowedColors::kAll) {
     return ConsumeLightDark(ConsumeColor, range, context);
   }
   return nullptr;
@@ -2059,19 +2082,19 @@
                                   const CSSParserContext& context) {
   return ConsumeColorInternal(range, context,
                               IsQuirksModeBehavior(context.Mode()),
-                              AllowedColorKeywords::kAllowSystemColor);
+                              AllowedColors::kAll);
 }
 
 CSSValue* ConsumeColor(CSSParserTokenRange& range,
                        const CSSParserContext& context) {
   return ConsumeColorInternal(range, context, false /* accept_quirky_colors */,
-                              AllowedColorKeywords::kAllowSystemColor);
+                              AllowedColors::kAll);
 }
 
 CSSValue* ConsumeAbsoluteColor(CSSParserTokenRange& range,
                                const CSSParserContext& context) {
   return ConsumeColorInternal(range, context, false /* accept_quirky_colors */,
-                              AllowedColorKeywords::kNoSystemColor);
+                              AllowedColors::kAbsolute);
 }
 
 CSSValue* ConsumeLineWidth(CSSParserTokenRange& range,
@@ -7030,7 +7053,7 @@
     return ConsumeAppearanceAutoBaseSelectColor(range, context);
   }
   return ConsumeColorInternal(range, context, allow_quirky_colors,
-                              AllowedColorKeywords::kAllowSystemColor);
+                              AllowedColors::kAll);
 }
 
 CSSValue* ConsumeBorderWidth(CSSParserTokenRange& range,
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
index 64c88cd..d2717c9 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils.h
@@ -61,7 +61,7 @@
 };
 enum class UnitlessQuirk { kAllow, kForbid };
 enum class AllowCalcSize { kAllowWithAuto, kAllowWithoutAuto, kForbid };
-enum class AllowedColorKeywords { kAllowSystemColor, kNoSystemColor };
+enum class AllowedColors { kAll, kAbsolute };
 enum class EmptyPathStringHandling { kFailure, kTreatAsNone };
 
 using ConsumeAnimationItemValue = CSSValue* (*)(CSSPropertyID,
diff --git a/third_party/blink/renderer/core/css/properties/css_parsing_utils_test.cc b/third_party/blink/renderer/core/css/properties/css_parsing_utils_test.cc
index cd49dee1..e6c1217b 100644
--- a/third_party/blink/renderer/core/css/properties/css_parsing_utils_test.cc
+++ b/third_party/blink/renderer/core/css/properties/css_parsing_utils_test.cc
@@ -228,7 +228,6 @@
     CSSParserContext* context = MakeContext();
     return func(range, *context);
   };
-  using css_parsing_utils::AllowedColorKeywords;
 
   struct {
     STACK_ALLOCATED();
@@ -249,6 +248,8 @@
        nullptr},
       {"WindowText", CSSIdentifierValue::Create(CSSValueID::kWindowtext),
        nullptr},
+      {"currentcolor", CSSIdentifierValue::Create(CSSValueID::kCurrentcolor),
+       nullptr},
   };
   for (auto& expectation : expectations) {
     EXPECT_EQ(ConsumeColorForTest(expectation.css_text,
diff --git a/third_party/blink/renderer/core/dom/layout_tree_builder.h b/third_party/blink/renderer/core/dom/layout_tree_builder.h
index d6993d80..cc76bd4 100644
--- a/third_party/blink/renderer/core/dom/layout_tree_builder.h
+++ b/third_party/blink/renderer/core/dom/layout_tree_builder.h
@@ -87,6 +87,10 @@
     auto* const parent = next->Parent();
     if (!IsAnonymousInline(parent))
       return next;
+    // Should return a normal result for display:ruby though it can be
+    // an anonymous inline.
+    if (UNLIKELY(parent->IsInlineRuby()))
+      return next;
     if (!LIKELY(parent->IsLayoutTextCombine())) {
       return parent;
     }
diff --git a/third_party/blink/renderer/core/html/resources/permission.css b/third_party/blink/renderer/core/html/resources/permission.css
index 75ca5e1f..5847fc9 100644
--- a/third_party/blink/renderer/core/html/resources/permission.css
+++ b/third_party/blink/renderer/core/html/resources/permission.css
@@ -27,20 +27,22 @@
 }
 
 permission::-internal-permission-text {
-  display: inherit;
-  font-family: Arial, Helvetica, sans-serif !important;
+  align-items: center;
+  display: flex;
+  font-family: Arial, Helvetica, sans-serif;
   font-size: inherit;
   font-style: inherit;
   font-weight: inherit;
   height: 100%;
-  hyphenate-character: auto !important;
-  line-height: inherit;
+  hyphenate-character: auto;
+  line-height: normal;
+  margin: auto;
   min-height: inherit;
   outline: none;
-  user-select:none !important;
+  user-select:none;
   vertical-align: inherit;
-  white-space:nowrap !important;
-  width: fit-content !important;
-  word-wrap: normal !important;
+  white-space:nowrap;
+  width: fit-content;
+  word-wrap: normal;
   word-spacing: inherit;
 }
diff --git a/third_party/blink/renderer/core/layout/inline/inline_layout_algorithm.cc b/third_party/blink/renderer/core/layout/inline/inline_layout_algorithm.cc
index 6d77e9bf..f61a35c 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_layout_algorithm.cc
@@ -435,8 +435,10 @@
     container_builder_.SetBfcLineOffset(bfc_line_offset);
   }
 
-  // Force an editable empty line to have metrics, so that is has a height.
-  if (UNLIKELY(line_info->HasLineEvenIfEmpty())) {
+  // Force an editable empty line or a line with ruby annotations to have
+  // metrics, so that is has a height.
+  if (UNLIKELY(line_info->HasLineEvenIfEmpty() ||
+               !box_states_->RubyColumnList().empty())) {
     box_states_->LineBoxState().EnsureTextMetrics(
         line_info->LineStyle(), *box_states_->LineBoxState().font,
         baseline_type_);
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 fd5b961..023ff76 100644
--- a/third_party/blink/renderer/core/layout/inline/inline_node.cc
+++ b/third_party/blink/renderer/core/layout/inline/inline_node.cc
@@ -1994,7 +1994,7 @@
           // fragment (line) that we cannot compute max-content from
           // min-content.
           !line_breaker.HasClonedBoxDecorations() &&
-          !line_breaker.MayHaveRubyOverhang();
+          !line_info.MayHaveRubyOverhang();
       if (can_compute_max_size_from_min_size)
         max_size_from_min_size.ComputeFromMinSize(line_info);
     } else {
diff --git a/third_party/blink/renderer/core/layout/inline/line_breaker.cc b/third_party/blink/renderer/core/layout/inline/line_breaker.cc
index 4e787be..82503161 100644
--- a/third_party/blink/renderer/core/layout/inline/line_breaker.cc
+++ b/third_party/blink/renderer/core/layout/inline/line_breaker.cc
@@ -733,7 +733,6 @@
   hyphen_index_.reset();
   has_any_hyphens_ = false;
   resume_block_in_inline_in_same_flow_ = false;
-  may_have_ruby_overhang_ = false;
 
   // Use 'text-indent' as the initial position. This lets tab positions to align
   // regardless of 'text-indent'.
@@ -3036,6 +3035,9 @@
   data->base_line = base_line_info;
   data->base_line.SetIsRubyBase();
   data->base_line.UpdateTextAlign();
+  if (data->base_line.MayHaveRubyOverhang()) {
+    line_info.SetMayHaveRubyOverhang();
+  }
   line_info.SetHaveTextCombineOrRubyItem();
 
   data->annotation_line_list = annotation_line_list;
@@ -3057,7 +3059,7 @@
   column_result->can_break_after = CanBreakAfterRubyColumn(*column_result);
 
   if (base_line_info.Width() < ruby_size) {
-    may_have_ruby_overhang_ = true;
+    line_info.SetMayHaveRubyOverhang();
 
     AnnotationOverhang overhang = GetOverhang(*column_result);
     if (overhang.end > LayoutUnit()) {
diff --git a/third_party/blink/renderer/core/layout/inline/line_breaker.h b/third_party/blink/renderer/core/layout/inline/line_breaker.h
index aed1a956..3f7ffcd 100644
--- a/third_party/blink/renderer/core/layout/inline/line_breaker.h
+++ b/third_party/blink/renderer/core/layout/inline/line_breaker.h
@@ -61,9 +61,6 @@
   // True if the last line has `box-decoration-break: clone`, which affected the
   // size.
   bool HasClonedBoxDecorations() const { return has_cloned_box_decorations_; }
-  // True if the last processed line might contain ruby overhang. It affects
-  // min-max computation.
-  bool MayHaveRubyOverhang() const { return may_have_ruby_overhang_; }
 
   // Compute the next line break point and produces InlineItemResults for
   // the line.
@@ -363,8 +360,6 @@
 
   // True if the resultant line contains a RubyColumn with inline-end overhang.
   bool maybe_have_end_overhang_ = false;
-  // True if the last processed line might contain ruby overhang.
-  bool may_have_ruby_overhang_ = false;
 
   // True if ShouldCreateNewSvgSegment() should be called.
   bool needs_svg_segmentation_ = false;
diff --git a/third_party/blink/renderer/core/layout/inline/line_info.cc b/third_party/blink/renderer/core/layout/inline/line_info.cc
index 475231d..7ae168dc 100644
--- a/third_party/blink/renderer/core/layout/inline/line_info.cc
+++ b/third_party/blink/renderer/core/layout/inline/line_info.cc
@@ -67,6 +67,7 @@
   is_ruby_base_ = false;
   is_ruby_text_ = false;
   may_have_text_combine_or_ruby_item_ = false;
+  may_have_ruby_overhang_ = false;
   allow_hang_for_alignment_ = false;
 }
 
diff --git a/third_party/blink/renderer/core/layout/inline/line_info.h b/third_party/blink/renderer/core/layout/inline/line_info.h
index b49e239d..eb31f41 100644
--- a/third_party/blink/renderer/core/layout/inline/line_info.h
+++ b/third_party/blink/renderer/core/layout/inline/line_info.h
@@ -229,6 +229,11 @@
     may_have_text_combine_or_ruby_item_ = true;
   }
 
+  // True if the line might contain ruby overhang. It affects min-max
+  // computation.
+  bool MayHaveRubyOverhang() const { return may_have_ruby_overhang_; }
+  void SetMayHaveRubyOverhang() { may_have_ruby_overhang_ = true; }
+
   // Returns annotation block start adjustment base on annotation and initial
   // letter.
   LayoutUnit ComputeAnnotationBlockOffsetAdjustment() const;
@@ -321,6 +326,8 @@
   // Note: To avoid scanning |InlineItemResults|, this variable is true
   // when |InlineItemResult| to |results_|.
   bool may_have_text_combine_or_ruby_item_ = false;
+  // True if the last processed line might contain ruby overhang.
+  bool may_have_ruby_overhang_ = false;
   bool allow_hang_for_alignment_ = false;
 
   // When adding fields, pelase ensure `Reset()` is in sync.
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 7ae08b8..c7fa7a9 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -2168,6 +2168,11 @@
     Document* owner_document) {
   scoped_refptr<SecurityOrigin> origin;
   StringBuilder debug_info_builder;
+  // Whether the origin is newly created within this call, instead of copied
+  // from an existing document's origin or from `origin_to_commit_`. If this is
+  // true, we won't try to compare the nonce of this origin (if it's opaque) to
+  // the browser-calculated origin later on.
+  bool origin_is_newly_created = false;
   if (origin_to_commit_) {
     // Origin to commit is specified by the browser process, it must be taken
     // and used directly. It is currently supplied only for failed navigations
@@ -2244,6 +2249,7 @@
     // initiator origin as the precursor.
     origin = SecurityOrigin::CreateWithReferenceOrigin(url_,
                                                        requestor_origin_.get());
+    origin_is_newly_created = true;
   }
 
   if ((policy_container_->GetPolicies().sandbox_flags &
@@ -2291,6 +2297,7 @@
       }
     }
     origin = sandbox_origin;
+    origin_is_newly_created = true;
   }
 
   if (commit_reason_ == CommitReason::kInitialization &&
@@ -2335,7 +2342,14 @@
       debug_info_builder.Append(", is_potentially_trustworthy");
     }
   }
-
+  if (origin_is_newly_created) {
+    // This information will be used by the browser side to figure out if it can
+    // do browser vs renderer calculated origin equality check. Note that this
+    // information must be the last part of the debug info string.
+    // TODO(https://crbug.com/888079): Consider adding a separate boolean that
+    // tracks this instead of piggybacking `origin_calculation_debug_info_`.
+    debug_info_builder.Append(", is_newly_created");
+  }
   origin_calculation_debug_info_ = debug_info_builder.ToAtomicString();
   return origin;
 }
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
index 6b37b2b..96bfeb8 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -550,6 +550,18 @@
   return lcpp->HasAnyHintData();
 }
 
+bool FrameFetchContext::DoesLCPPHaveLcpElementLocatorHintData() {
+  if (GetResourceFetcherProperties().IsDetached()) {
+    return false;
+  }
+
+  LCPCriticalPathPredictor* lcpp = GetFrame()->GetLCPP();
+  if (!lcpp) {
+    return false;
+  }
+  return !lcpp->lcp_element_locators().empty();
+}
+
 void FrameFetchContext::SetFirstPartyCookie(ResourceRequest& request) {
   // Set the first party for cookies url if it has not been set yet (new
   // requests). This value will be updated during redirects, consistent with
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/third_party/blink/renderer/core/loader/frame_fetch_context.h
index 0c185cd3..82839b6 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.h
@@ -105,6 +105,8 @@
 
   bool DoesLCPPHaveAnyHintData() override;
 
+  bool DoesLCPPHaveLcpElementLocatorHintData() override;
+
   // Exposed for testing.
   void ModifyRequestForCSP(ResourceRequest&);
   void AddClientHintsIfNecessary(const std::optional<float> resource_width,
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
index 35049da..e5f9ae7 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
@@ -962,8 +962,7 @@
 
   auto conv2d_attributes = ConvertToConv2dAttributes(options);
   if (!conv2d_attributes.has_value()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
-                                      conv2d_attributes.error());
+    exception_state.ThrowTypeError(conv2d_attributes.error());
     return nullptr;
   }
 
@@ -971,9 +970,7 @@
       ConvertToComponentOperand(input), ConvertToComponentOperand(filter),
       std::move(conv2d_attributes.value()));
   if (!validated_output.has_value()) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kDataError,
-        WTF::String::FromUTF8(validated_output.error()));
+    exception_state.ThrowTypeError(String::FromUTF8(validated_output.error()));
     return nullptr;
   }
   // Create conv2d operator and its output operand. Connect the conv2d operator
@@ -985,8 +982,7 @@
       this, ComponentOperandTypeToBlink(validated_output.value().data_type),
       Vector<uint32_t>(validated_output.value().dimensions), conv2d);
   if (!output.has_value()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
-                                      output.error());
+    exception_state.ThrowTypeError(output.error());
     return nullptr;
   }
   conv2d->Connect(std::move(inputs), {output.value()});
@@ -1011,8 +1007,7 @@
 
   auto convTranspose2d_attributes = ConvertToConvTranspose2dAttributes(options);
   if (!convTranspose2d_attributes.has_value()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
-                                      convTranspose2d_attributes.error());
+    exception_state.ThrowTypeError(convTranspose2d_attributes.error());
     return nullptr;
   }
 
@@ -1020,9 +1015,7 @@
       ConvertToComponentOperand(input), ConvertToComponentOperand(filter),
       std::move(convTranspose2d_attributes.value()));
   if (!validated_output.has_value()) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kDataError,
-        WTF::String::FromUTF8(validated_output.error()));
+    exception_state.ThrowTypeError(String::FromUTF8(validated_output.error()));
     return nullptr;
   }
   // Create convTranspose2d operator and its output operand. Connect the
@@ -1034,8 +1027,7 @@
       this, ComponentOperandTypeToBlink(validated_output.value().data_type),
       Vector<uint32_t>(validated_output.value().dimensions), convTranspose2d);
   if (!output.has_value()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
-                                      output.error());
+    exception_state.ThrowTypeError(output.error());
     return nullptr;
   }
   convTranspose2d->Connect(std::move(inputs), {output.value()});
@@ -1716,8 +1708,7 @@
   auto validated_output = webnn::ValidateMatmulAndInferOutput(
       ConvertToComponentOperand(a), ConvertToComponentOperand(b));
   if (!validated_output.has_value()) {
-    exception_state.ThrowDOMException(
-        DOMExceptionCode::kDataError,
+    exception_state.ThrowTypeError(
         WTF::String::FromUTF8(validated_output.error()));
     return nullptr;
   }
@@ -1729,8 +1720,7 @@
       this, ComponentOperandTypeToBlink(validated_output.value().data_type),
       Vector<uint32_t>(validated_output.value().dimensions), matmul);
   if (!output.has_value()) {
-    exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
-                                      output.error());
+    exception_state.ThrowTypeError(output.error());
     return nullptr;
   }
   matmul->Connect(std::move(inputs), {output.value()});
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
index c8b55ef0..bbe952c 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.cc
@@ -588,1322 +588,6 @@
   return output;
 }
 
-TEST_F(MLGraphBuilderTest, Conv2dTest) {
-  V8TestingScope scope;
-  auto* builder =
-      CreateMLGraphBuilder(scope.GetExecutionContext(), scope.GetScriptState(),
-                           scope.GetExceptionState());
-  {
-    // Test conv2d with default options.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    EXPECT_FALSE(options->hasBias());
-    EXPECT_FALSE(options->hasDilations());
-    EXPECT_FALSE(options->hasActivation());
-    EXPECT_TRUE(options->hasFilterLayout());
-    EXPECT_EQ(options->filterLayout(),
-              V8MLConv2dFilterOperandLayout::Enum::kOihw);
-    EXPECT_TRUE(options->hasInputLayout());
-    EXPECT_EQ(options->inputLayout(), V8MLInputOperandLayout::Enum::kNchw);
-    EXPECT_TRUE(options->hasGroups());
-    EXPECT_EQ(options->groups(), 1u);
-    EXPECT_FALSE(options->hasPadding());
-    EXPECT_FALSE(options->hasStrides());
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 3, 3}));
-  }
-  {
-    // Test conv2d with padding=1.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setPadding({1, 1, 1, 1});
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 5, 5}));
-  }
-  {
-    // Test conv2d with strides=2 and padding=1.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setPadding({1, 1, 1, 1});
-    options->setStrides({2, 2});
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 3, 3}));
-  }
-  {
-    // Test conv2d with strides=2 and asymmetric padding.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 4, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setPadding({1, 2, 0, 1});
-    options->setStrides({2, 2});
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 3, 3}));
-  }
-  {
-    // Test depthwise conv2d by setting groups to input channels.
-    auto* input = BuildInput(builder, "input", {1, 4, 2, 2},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {4, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setGroups(4);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 4, 1, 1}));
-  }
-  {
-    // Test depthwise conv2d with groups=4, inputLayout="nhwc" and
-    // filterLayout="ihwo".
-    auto* input = BuildInput(builder, "input", {1, 2, 2, 4},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 2, 4},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setGroups(4);
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kIhwo);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 1, 4}));
-  }
-  {
-    // Test conv2d with dilations=4, inputLayout="nhwc" and
-    // filterLayout="ihwo".
-    auto* input = BuildInput(builder, "input", {1, 65, 65, 1},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 3, 3, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kIhwo);
-    options->setDilations({4, 4});
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 57, 57, 1}));
-  }
-  {
-    // Test conv2d with inputLayout="nchw" and filterLayout="oihw".
-    auto* input = BuildInput(builder, "input", {1, 2, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNchw);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kOihw);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 3, 3}));
-  }
-  {
-    // Test conv2d with inputLayout="nchw" and filterLayout="hwio".
-    auto* input = BuildInput(builder, "input", {1, 2, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {3, 3, 2, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNchw);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kHwio);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 3, 3}));
-  }
-  {
-    // Test conv2d with inputLayout="nchw" and filterLayout="ohwi".
-    auto* input = BuildInput(builder, "input", {1, 2, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 3, 3, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNchw);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kOhwi);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 3, 3}));
-  }
-  {
-    // Test conv2d with inputLayout="nchw" and filterLayout="ihwo".
-    auto* input = BuildInput(builder, "input", {1, 2, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {2, 3, 3, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNchw);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kIhwo);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 3, 3}));
-  }
-  {
-    // Test conv2d with inputLayout="nhwc" and filterLayout="oihw".
-    auto* input = BuildInput(builder, "input", {1, 5, 5, 2},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kOihw);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 3, 3, 1}));
-  }
-  {
-    // Test conv2d with inputLayout="nhwc" and filterLayout="hwio".
-    auto* input = BuildInput(builder, "input", {1, 5, 5, 2},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {3, 3, 2, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kHwio);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 3, 3, 1}));
-  }
-  {
-    // Test conv2d with inputLayout="nhwc" and filterLayout="ohwi".
-    auto* input = BuildInput(builder, "input", {1, 5, 5, 2},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 3, 3, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kOhwi);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 3, 3, 1}));
-  }
-  {
-    // Test conv2d with inputLayout="nhwc" and filterLayout="ihwo".
-    auto* input = BuildInput(builder, "input", {1, 5, 5, 2},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {2, 3, 3, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kIhwo);
-    auto* output = BuildConv2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 3, 3, 1}));
-  }
-  {
-    // Test throwing exception if the output operand's number of elements is too
-    // large.
-    // Set the input and filter dimensions that let the output's number of
-    // lements be 2 * SIZE_MAX.
-    auto* input = BuildInput(
-        builder, "input",
-        {1, 1, kSquareRootOfSizeMax / 2, kSquareRootOfSizeMax / 2},
-        V8MLOperandDataType::Enum::kFloat32, scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {8, 1, 1, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* output = builder->conv2d(input, filter, MLConv2dOptions::Create(),
-                                   scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(
-        scope.GetExceptionState().Message(),
-        "Invalid operand descriptor: The number of elements is too large.");
-  }
-  {
-    // Test throwing exception if the output operand's byte length is too large.
-    // Set the dimensions and data type of input and filter that let the
-    // output's byte length be 4 * SIZE_MAX.
-    auto* input = BuildInput(
-        builder, "input",
-        {1, 1, kSquareRootOfSizeMax / 2, kSquareRootOfSizeMax / 2},
-        V8MLOperandDataType::Enum::kFloat32, scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {4, 1, 1, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* output = builder->conv2d(input, filter, MLConv2dOptions::Create(),
-                                   scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "Invalid operand descriptor: The byte length is too large.");
-  }
-  {
-    // Test throwing exception when the input is not a 4-D tensor.
-    auto* input = BuildInput(builder, "input", {1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 2, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The input should be a 4-D tensor.");
-  }
-  {
-    // Test throwing exception when the filter is not a 4-D tensor.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter =
-        BuildConstant(builder, {2, 2}, V8MLOperandDataType::Enum::kFloat32,
-                      scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The filter should be a 4-D tensor.");
-  }
-  {
-    // Test throwing exception when the filter data type doesn't match the input
-    // data type.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter =
-        BuildConstant(builder, {1, 1, 2, 2}, V8MLOperandDataType::Enum::kInt32,
-                      scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The filter data type doesn't match the input data type.");
-  }
-  {
-    // Test throwing exception when the length of padding is not 4.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setPadding({2, 2});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The length of padding should be 4.");
-  }
-  {
-    // Test throwing exception when the length of strides is not 2.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setStrides({2});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The length of strides should be 2.");
-  }
-  {
-    // Test throwing exception when one stride value is smaller than 1.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setStrides({1, 0});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "All strides should be greater than 0.");
-  }
-  {
-    // Test throwing exception when the length of dilations is not 2.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setDilations({1});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The length of dilations should be 2.");
-  }
-  {
-    // Test throwing exception when the one dilation value is smaller than 1.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setDilations({1, 0});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "All dilations should be greater than 0.");
-  }
-  {
-    // Test throwing exception when input_channels % groups() != 0.
-    auto* input = BuildInput(builder, "input", {1, 4, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setGroups(3);
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The groups must evenly divide the input "
-              "channels to filter input channels.");
-  }
-  {
-    // Test throwing exception when filter_input_channels != input_channels /
-    // groups().
-    auto* input = BuildInput(builder, "input", {1, 4, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setGroups(2);
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The groups must evenly divide the input "
-              "channels to filter input channels.");
-  }
-  {
-    // Test throwing exception when the groups is smaller than 1.
-    auto* input = BuildInput(builder, "input", {1, 4, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setGroups(0);
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The groups should be greater than 0.");
-  }
-  {
-    // Test throwing exception due to overflow when calculating the effective
-    // filter height.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 434983, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setDilations({328442, 1});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(
-        scope.GetExceptionState().Message(),
-        "Failed to calculate the output height: The effective filter size is "
-        "too large.");
-  }
-  {
-    // Test throwing exception due to overflow when calculating the effective
-    // filter width.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 234545},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setDilations({2, 843452});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(
-        scope.GetExceptionState().Message(),
-        "Failed to calculate the output width: The effective filter size is "
-        "too large.");
-  }
-  {
-    // Test throwing exception due to underflow when calculating the output
-    // height.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 4, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setDilations({4, 1});
-    options->setPadding({1, 1, 1, 1});
-    options->setStrides({2, 2});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "Failed to calculate the output height: The input size is too "
-              "small to fill the window.");
-  }
-  {
-    // Test throwing exception due to underflow when calculating the output
-    // width.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 8},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    options->setDilations({1, 4});
-    options->setPadding({1, 1, 1, 1});
-    options->setStrides({2, 2});
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "Failed to calculate the output width: The input size is too "
-              "small to fill the window.");
-  }
-  {
-    // Test throwing exception when the bias is not a 1-D tensor.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    auto* bias =
-        BuildConstant(builder, {1, 2}, V8MLOperandDataType::Enum::kFloat32,
-                      scope.GetExceptionState());
-    options->setBias(bias);
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The bias should be a 1-D tensor.");
-  }
-  {
-    // Test throwing exception when the bias shape is not equal to
-    // [output_channels].
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    auto* bias =
-        BuildConstant(builder, {2}, V8MLOperandDataType::Enum::kFloat32,
-                      scope.GetExceptionState());
-    options->setBias(bias);
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The bias shape should be [1].");
-  }
-  {
-    // Test throwing exception when the bias data type doesn't match input data
-    // type.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConv2dOptions::Create();
-    auto* bias = BuildConstant(builder, {1}, V8MLOperandDataType::Enum::kInt32,
-                               scope.GetExceptionState());
-    options->setBias(bias);
-    auto* output =
-        builder->conv2d(input, filter, options, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The bias data type doesn't match input data type.");
-  }
-}
-
-MLOperand* BuildConvTranspose2d(V8TestingScope& scope,
-                                MLGraphBuilder* builder,
-                                const MLOperand* input,
-                                const MLOperand* filter,
-                                const MLConvTranspose2dOptions* options) {
-  auto* output = builder->convTranspose2d(input, filter, options,
-                                          scope.GetExceptionState());
-  EXPECT_THAT(output, testing::NotNull());
-  EXPECT_EQ(output->Kind(), webnn::mojom::blink::Operand::Kind::kOutput);
-  EXPECT_EQ(output->DataType(), input->DataType());
-  auto* convTranspose2d = output->Operator();
-  EXPECT_THAT(convTranspose2d, testing::NotNull());
-  EXPECT_EQ(convTranspose2d->Kind(),
-            webnn::mojom::blink::Operation::Tag::kConv2d);
-  EXPECT_EQ(convTranspose2d->SubKind<webnn::mojom::blink::Conv2d::Kind>(),
-            webnn::mojom::blink::Conv2d::Kind::kTransposed);
-  EXPECT_TRUE(convTranspose2d->IsConnected());
-  EXPECT_THAT(convTranspose2d->Options(), testing::NotNull());
-  return output;
-}
-
-TEST_F(MLGraphBuilderTest, ConvTranspose2dTest) {
-  V8TestingScope scope;
-  auto* builder =
-      CreateMLGraphBuilder(scope.GetExecutionContext(), scope.GetScriptState(),
-                           scope.GetExceptionState());
-  {
-    // Test convTranspose2d with default options.
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    EXPECT_FALSE(options->hasBias());
-    EXPECT_FALSE(options->hasDilations());
-    EXPECT_FALSE(options->hasActivation());
-    EXPECT_TRUE(options->hasFilterLayout());
-    EXPECT_EQ(options->filterLayout(),
-              V8MLConvTranspose2dFilterOperandLayout::Enum::kIohw);
-    EXPECT_TRUE(options->hasInputLayout());
-    EXPECT_EQ(options->inputLayout(), V8MLInputOperandLayout::Enum::kNchw);
-    EXPECT_TRUE(options->hasGroups());
-    EXPECT_EQ(options->groups(), 1u);
-    EXPECT_FALSE(options->hasPadding());
-    EXPECT_FALSE(options->hasStrides());
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 5, 5}));
-  }
-  {
-    // Test convTranspose2d with inputLayout="nchw" and filterLayout="hwoi".
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {3, 3, 2, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNchw);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kHwoi);
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 2, 5, 5}));
-  }
-  {
-    // Test convTranspose2d with inputLayout="nchw" and filterLayout="ohwi".
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {2, 3, 3, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNchw);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kOhwi);
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 2, 5, 5}));
-  }
-  {
-    // Test convTranspose2d with inputLayout="nhwc" and filterLayout="iohw".
-    auto* input = BuildInput(builder, "input", {1, 3, 3, 1},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kIohw);
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 5, 5, 2}));
-  }
-  {
-    // Test convTranspose2d with inputLayout="nhwc" and filterLayout="hwoi".
-    auto* input = BuildInput(builder, "input", {1, 3, 3, 1},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {3, 3, 2, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kHwoi);
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 5, 5, 2}));
-  }
-  {
-    // Test convTranspose2d with inputLayout="nhwc" and filterLayout="ohwi".
-    auto* input = BuildInput(builder, "input", {1, 3, 3, 1},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {2, 3, 3, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kOhwi);
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 5, 5, 2}));
-  }
-  {
-    // Test convTranspose2d with strides=[3, 2], outputSizes=[10, 8].
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setStrides({3, 2});
-    options->setOutputSizes({10, 8});
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 2, 10, 8}));
-  }
-  {
-    // Test convTranspose2d with strides=[3, 2], outputPadding=[1, 1].
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setStrides({3, 2});
-    options->setOutputPadding({1, 1});
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 2, 10, 8}));
-  }
-  {
-    // Test convTranspose2d with padding=1.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setPadding({1, 1, 1, 1});
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 5, 5}));
-  }
-  {
-    // Test convTranspose2d with padding=1, groups=3.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setPadding({1, 1, 1, 1});
-    options->setGroups(3);
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 3, 5, 5}));
-  }
-  {
-    // Test convTranspose2d with strides=2.
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setStrides({2, 2});
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 2, 7, 7}));
-  }
-  {
-    // Test convTranspose2d with strides=2 and padding=1.
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setPadding({1, 1, 1, 1});
-    options->setStrides({2, 2});
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 1, 5, 5}));
-  }
-  {
-    // Test convTranspose2d with outputSizes and outputPadding. When the output
-    // sizes are explicitly specified, the output padding values are ignored.
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setStrides({3, 2});
-    options->setOutputPadding({1, 1});
-    options->setOutputSizes({10, 8});
-    auto* output = BuildConvTranspose2d(scope, builder, input, filter, options);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({1, 2, 10, 8}));
-  }
-  {
-    // Test throwing exception if the output operand's number of elements is too
-    // large.
-    // Set the input and filter dimensions that let the output's number of
-    // lements be 2 * SIZE_MAX.
-    auto* input = BuildInput(
-        builder, "input",
-        {1, 1, kSquareRootOfSizeMax / 2, kSquareRootOfSizeMax / 2},
-        V8MLOperandDataType::Enum::kFloat32, scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 8, 1, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(
-        scope.GetExceptionState().Message(),
-        "Invalid operand descriptor: The number of elements is too large.");
-  }
-  {
-    // Test throwing exception if the output operand's byte length is too large.
-    // Set the dimensions and data type of input and filter that let the
-    // output's byte length be 4 * SIZE_MAX.
-    auto* input = BuildInput(
-        builder, "input",
-        {1, 1, kSquareRootOfSizeMax / 2, kSquareRootOfSizeMax / 2},
-        V8MLOperandDataType::Enum::kFloat32, scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 4, 1, 1},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "Invalid operand descriptor: The byte length is too large.");
-  }
-  {
-    // Test throwing exception when the input is not a 4-D tensor.
-    auto* input = BuildInput(builder, "input", {1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The input should be a 4-D tensor.");
-  }
-  {
-    // Test throwing exception when the filter is not a 4-D tensor.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter =
-        BuildConstant(builder, {2, 2}, V8MLOperandDataType::Enum::kFloat32,
-                      scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The filter should be a 4-D tensor.");
-  }
-  {
-    // Test throwing exception when the filter data type doesn't match the input
-    // data type.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter =
-        BuildConstant(builder, {1, 1, 2, 2}, V8MLOperandDataType::Enum::kInt32,
-                      scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The filter data type doesn't match the input data type.");
-  }
-  {
-    // Test throwing exception when the length of padding is not 4.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setPadding({2, 2});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The length of padding should be 4.");
-  }
-  {
-    // Test throwing exception when the length of strides is not 2.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setStrides({2});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The length of strides should be 2.");
-  }
-  {
-    // Test throwing exception when one stride value is smaller than 1.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setStrides({1, 0});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "All strides should be greater than 0.");
-  }
-  {
-    // Test throwing exception when the length of dilations is not 2.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setDilations({1});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The length of dilations should be 2.");
-  }
-  {
-    // Test throwing exception when the one dilation value is smaller than 1.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setDilations({1, 0});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "All dilations should be greater than 0.");
-  }
-  {
-    // Test throwing exception when the input channels is not equal to the
-    // filter input channels.
-    auto* input = BuildInput(builder, "input", {1, 4, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setGroups(3);
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The input channels should equal to filter input channels.");
-  }
-  {
-    // Test throwing exception when output channels is too large.
-    auto* input = BuildInput(builder, "input", {1, 4, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {4, 2, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setGroups(std::numeric_limits<uint32_t>::max());
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The output channels is too large.");
-  }
-  {
-    // Test throwing exception when the groups is smaller than 1.
-    auto* input = BuildInput(builder, "input", {1, 4, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setGroups(0);
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The groups should be greater than 0.");
-  }
-  {
-    // Test throwing exception due to overflow when calculating the effective
-    // filter height.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 434983, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setDilations({328442, 1});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(
-        scope.GetExceptionState().Message(),
-        "Failed to calculate the output height: The effective filter size is "
-        "too large.");
-  }
-  {
-    // Test throwing exception due to overflow when calculating the effective
-    // filter width.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 234545},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setDilations({2, 843452});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(
-        scope.GetExceptionState().Message(),
-        "Failed to calculate the output width: The effective filter size is "
-        "too large.");
-  }
-  {
-    // Test throwing exception when the bias is not a 1-D tensor.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    auto* bias =
-        BuildConstant(builder, {1, 2}, V8MLOperandDataType::Enum::kFloat32,
-                      scope.GetExceptionState());
-    options->setBias(bias);
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The bias should be a 1-D tensor.");
-  }
-  {
-    // Test throwing exception when the bias shape is not equal to
-    // [output_channels].
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    auto* bias =
-        BuildConstant(builder, {2}, V8MLOperandDataType::Enum::kFloat32,
-                      scope.GetExceptionState());
-    options->setBias(bias);
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The bias shape should be [1].");
-  }
-  {
-    // Test throwing exception when the bias data type doesn't match input data
-    // type.
-    auto* input = BuildInput(builder, "input", {1, 1, 5, 5},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 2, 2},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    auto* bias = BuildConstant(builder, {1}, V8MLOperandDataType::Enum::kInt32,
-                               scope.GetExceptionState());
-    options->setBias(bias);
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The bias data type doesn't match input data type.");
-  }
-  {
-    // Test throwing exception when the outputPadding is not a sequence of
-    // length 2.
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setStrides({3, 2});
-    options->setOutputPadding({1, 1, 1, 1});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The length of output padding should be 2.");
-  }
-  {
-    // Test throwing exception when the outputPadding is greater than stride
-    // along the same dimension.
-    auto* input = BuildInput(builder, "input", {1, 1, 2, 2},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setPadding({0, 0, 3, 3});
-    options->setStrides({2, 2});
-    options->setOutputPadding({0, 2});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The output padding must be smaller than the stride along the "
-              "same dimension.");
-  }
-  {
-    // Test throwing exception when the outputSizes is not a sequence of
-    // length 2.
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 2, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setStrides({3, 2});
-    options->setOutputSizes({1, 2, 10, 8});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The length of output sizes should be 2.");
-  }
-  {
-    // Test throwing exception due to underflow when calculating the output
-    // height.
-    auto* input = BuildInput(builder, "input", {1, 1, 2, 2},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setPadding({4, 4, 0, 0});
-    options->setStrides({2, 2});
-    options->setOutputPadding({1, 0});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "Failed to calculate the output height: The stride is too large "
-              "or the input size is too small for padding.");
-  }
-  {
-    // Test throwing exception due to outputSizes values are smaller than the
-    // output sizes calculated by not using outputPadding.
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setPadding({1, 1, 1, 1});
-    options->setStrides({2, 2});
-    options->setOutputSizes({4, 4});
-    options->setOutputPadding({1, 1});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The height of output sizes is invalid.");
-  }
-  {
-    // Test throwing exception due to outputSizes values are greater than the
-    // output sizes calculated by not using outputPadding.
-    auto* input = BuildInput(builder, "input", {1, 1, 3, 3},
-                             V8MLOperandDataType::Enum::kFloat32,
-                             scope.GetExceptionState());
-    auto* filter = BuildConstant(builder, {1, 1, 3, 3},
-                                 V8MLOperandDataType::Enum::kFloat32,
-                                 scope.GetExceptionState());
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setPadding({1, 1, 1, 1});
-    options->setStrides({2, 2});
-    options->setOutputSizes({6, 8});
-    options->setOutputPadding({1, 1});
-    auto* output = builder->convTranspose2d(input, filter, options,
-                                            scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The width of output sizes is invalid.");
-  }
-}
-
 TEST_F(MLGraphBuilderTest, PReluTest) {
   V8TestingScope scope;
   auto* builder =
@@ -4261,146 +2945,6 @@
   }
 }
 
-MLOperand* BuildMatmul(V8TestingScope& scope,
-                       MLGraphBuilder* builder,
-                       const MLOperand* a,
-                       const MLOperand* b) {
-  auto* output = builder->matmul(a, b, scope.GetExceptionState());
-  EXPECT_THAT(output, testing::NotNull());
-  EXPECT_EQ(output->Kind(), webnn::mojom::blink::Operand::Kind::kOutput);
-  EXPECT_EQ(output->DataType(), a->DataType());
-  auto* matmul = output->Operator();
-  EXPECT_THAT(matmul, testing::NotNull());
-  EXPECT_EQ(matmul->Kind(), webnn::mojom::blink::Operation::Tag::kMatmul);
-  EXPECT_TRUE(matmul->IsConnected());
-  EXPECT_THAT(matmul->Options(), testing::IsNull());
-  return output;
-}
-
-TEST_F(MLGraphBuilderTest, MatmulTest) {
-  V8TestingScope scope;
-  auto* builder =
-      CreateMLGraphBuilder(scope.GetExecutionContext(), scope.GetScriptState(),
-                           scope.GetExceptionState());
-  {
-    // Test throwing exception when the rank of input is smaller than 2.
-    auto* a = BuildInput(builder, "a", {2}, V8MLOperandDataType::Enum::kFloat32,
-                         scope.GetExceptionState());
-    auto* b = BuildInput(builder, "b", {2}, V8MLOperandDataType::Enum::kFloat32,
-                         scope.GetExceptionState());
-    auto* output = builder->matmul(a, b, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The rank of input must be larger than or equal to 2.");
-  }
-  {
-    // Test building matmul with 2-D * 4-D inputs.
-    auto* a =
-        BuildInput(builder, "a", {1, 4}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* b = BuildInput(builder, "b", {2, 2, 4, 2},
-                         V8MLOperandDataType::Enum::kFloat32,
-                         scope.GetExceptionState());
-    auto* output = BuildMatmul(scope, builder, a, b);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({2, 2, 1, 2}));
-  }
-  {
-    // Test building matmul with 2-D * 2-D inputs.
-    auto* a =
-        BuildInput(builder, "a", {4, 2}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* b =
-        BuildInput(builder, "b", {2, 3}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* output = BuildMatmul(scope, builder, a, b);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({4, 3}));
-  }
-  {
-    // Test building matmul with 3-D * 3-D inputs using broadcast.
-    auto* a =
-        BuildInput(builder, "a", {2, 3, 4}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* b =
-        BuildInput(builder, "b", {1, 4, 1}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* output = BuildMatmul(scope, builder, a, b);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({2, 3, 1}));
-  }
-  {
-    // Test building matmul with 4-D * 3-D inputs using broadcast.
-    auto* a = BuildInput(builder, "a", {2, 2, 3, 4},
-                         V8MLOperandDataType::Enum::kFloat32,
-                         scope.GetExceptionState());
-    auto* b =
-        BuildInput(builder, "b", {1, 4, 5}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* output = BuildMatmul(scope, builder, a, b);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({2, 2, 3, 5}));
-  }
-  {
-    // Test building matmul with 3-D * 3-D inputs.
-    auto* a =
-        BuildInput(builder, "a", {2, 3, 4}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* b =
-        BuildInput(builder, "b", {2, 4, 5}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* output = BuildMatmul(scope, builder, a, b);
-    EXPECT_EQ(output->Dimensions(), Vector<uint32_t>({2, 3, 5}));
-  }
-  {
-    // Test throwing exception when the data types of first two inputs don't
-    // match.
-    auto* a =
-        BuildInput(builder, "a", {2, 3}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* b =
-        BuildInput(builder, "b", {3, 4}, V8MLOperandDataType::Enum::kInt32,
-                   scope.GetExceptionState());
-    auto* output = builder->matmul(a, b, scope.GetExceptionState());
-    ;
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The data types of first two inputs don't match.");
-  }
-  {
-    // Test throwing exception when the number of columns in first matrix
-    // mismatches with the number of rows in second matrix.
-    auto* a =
-        BuildInput(builder, "a", {2, 3}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* b =
-        BuildInput(builder, "b", {2, 4}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* output = builder->matmul(a, b, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The number of columns (3) in the first matrix isn't equal to "
-              "the number of rows (2) in the second matrix.");
-  }
-  {
-    // Test throwing exception when the input shapes are not broadcastable.
-    auto* a =
-        BuildInput(builder, "a", {3, 3, 4}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* b =
-        BuildInput(builder, "b", {2, 4, 1}, V8MLOperandDataType::Enum::kFloat32,
-                   scope.GetExceptionState());
-    auto* output = builder->matmul(a, b, scope.GetExceptionState());
-    EXPECT_THAT(output, testing::IsNull());
-    EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
-              DOMExceptionCode::kDataError);
-    EXPECT_EQ(scope.GetExceptionState().Message(),
-              "The matmul input shapes are not broadcastable.");
-  }
-}
-
 class FakeMLGraphBackend final : public MLGraph {
  public:
   // Create and build a FakeMLGraphBackend object. Resolve the promise with
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.h b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.h
index b3f88e3a..0e873d8e 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.h
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_builder_test.h
@@ -11,7 +11,6 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_batch_normalization_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_clamp_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_conv_2d_options.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_ml_conv_transpose_2d_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_elu_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_gemm_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_gru_options.h"
@@ -68,13 +67,6 @@
     const MLOperand* filter,
     const MLConv2dOptions* options = MLConv2dOptions::Create());
 
-MLOperand* BuildConvTranspose2d(V8TestingScope& scope,
-                                MLGraphBuilder* builder,
-                                const MLOperand* input,
-                                const MLOperand* filter,
-                                const MLConvTranspose2dOptions* options =
-                                    MLConvTranspose2dOptions::Create());
-
 MLOperand* BuildLeakyRelu(
     V8TestingScope& scope,
     MLGraphBuilder* builder,
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
index a61d2d3..d3cd4e4 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_mojo_test.cc
@@ -1408,493 +1408,6 @@
   }
 }
 
-struct Conv2dTester {
-  OperandInfoBlink input;
-  OperandInfoBlink filter;
-  struct Conv2dOptions {
-    std::optional<Vector<uint32_t>> padding;
-    std::optional<Vector<uint32_t>> strides;
-    std::optional<Vector<uint32_t>> dilations;
-    std::optional<uint32_t> groups;
-    std::optional<blink::V8MLInputOperandLayout::Enum> input_layout;
-    std::optional<blink::V8MLConv2dFilterOperandLayout::Enum> filter_layout;
-    std::optional<OperandInfoBlink> bias;
-    std::optional<Activation> activation;
-  };
-  struct Conv2dAttributes {
-    Vector<uint32_t> padding = {0, 0, 0, 0};
-    Vector<uint32_t> strides = {1, 1};
-    Vector<uint32_t> dilations = {1, 1};
-    uint32_t groups = 1;
-    blink_mojom::InputOperandLayout input_layout =
-        blink_mojom::InputOperandLayout::kChannelsFirst;
-    std::optional<OperandInfoMojo> bias;
-    std::optional<Activation> activation;
-  };
-  Conv2dOptions options;
-  OperandInfoMojo expected_operand;
-  Conv2dAttributes expected_attributes;
-
-  void Test(MLGraphTestMojo& helper,
-            V8TestingScope& scope,
-            MLGraphBuilder* builder) {
-    // Build the graph.
-    auto* input_operand =
-        BuildInput(builder, "input", input.dimensions, input.data_type,
-                   scope.GetExceptionState());
-    auto* filter_operand =
-        BuildInput(builder, "filter", filter.dimensions, filter.data_type,
-                   scope.GetExceptionState());
-    MLConv2dOptions* ml_conv2d_options = MLConv2dOptions::Create();
-    if (options.padding) {
-      ml_conv2d_options->setPadding(options.padding.value());
-    }
-    if (options.strides) {
-      ml_conv2d_options->setStrides(options.strides.value());
-    }
-    if (options.dilations) {
-      ml_conv2d_options->setDilations(options.dilations.value());
-    }
-    if (options.groups) {
-      ml_conv2d_options->setGroups(options.groups.value());
-    }
-    if (options.input_layout) {
-      ml_conv2d_options->setInputLayout(options.input_layout.value());
-    }
-    if (options.filter_layout) {
-      ml_conv2d_options->setFilterLayout(options.filter_layout.value());
-    }
-    if (options.bias) {
-      ml_conv2d_options->setBias(
-          BuildInput(builder, "bias", options.bias->dimensions,
-                     options.bias->data_type, scope.GetExceptionState()));
-    }
-    if (options.activation) {
-      auto* activation =
-          CreateActivation(scope, builder, options.activation.value());
-      CHECK(activation);
-      ml_conv2d_options->setActivation(activation);
-    }
-    auto* output_operand =
-        builder->conv2d(input_operand, filter_operand, ml_conv2d_options,
-                        scope.GetExceptionState());
-    auto [graph, error_name, error_message] =
-        helper.BuildGraph(scope, builder, {{"output", output_operand}});
-    ASSERT_THAT(graph, testing::NotNull());
-
-    auto graph_info = helper.GetGraphInfo();
-    // Verify the graph information of mojo are as expected.
-    ASSERT_EQ(graph_info->operations.size(), 1u);
-    auto& operation = graph_info->operations[0];
-    EXPECT_TRUE(operation->is_conv2d());
-    auto& conv2d = operation->get_conv2d();
-    // Validate explicit padding.
-    auto& expected_padding = expected_attributes.padding;
-    EXPECT_EQ(conv2d->padding->beginning->height, expected_padding[0]);
-    EXPECT_EQ(conv2d->padding->ending->height, expected_padding[1]);
-    EXPECT_EQ(conv2d->padding->beginning->width, expected_padding[2]);
-    EXPECT_EQ(conv2d->padding->ending->width, expected_padding[3]);
-    // Validate strides
-    EXPECT_EQ(conv2d->strides->height, expected_attributes.strides[0]);
-    EXPECT_EQ(conv2d->strides->width, expected_attributes.strides[1]);
-    // Validate dilations.
-    EXPECT_EQ(conv2d->dilations->height, expected_attributes.dilations[0]);
-    EXPECT_EQ(conv2d->dilations->width, expected_attributes.dilations[1]);
-    EXPECT_EQ(conv2d->groups, expected_attributes.groups);
-    EXPECT_EQ(conv2d->input_layout, expected_attributes.input_layout);
-    if (options.bias) {
-      auto bias_operand_iter =
-          graph_info->id_to_operand_map.find(conv2d->bias_operand_id.value());
-      ASSERT_TRUE(bias_operand_iter != graph_info->id_to_operand_map.end());
-      EXPECT_EQ(bias_operand_iter->value->data_type,
-                expected_attributes.bias->data_type);
-      EXPECT_EQ(bias_operand_iter->value->dimensions,
-                expected_attributes.bias->dimensions);
-    }
-    if (options.activation) {
-      CHECK(expected_attributes.activation);
-      CheckActivation(conv2d->activation,
-                      expected_attributes.activation.value());
-    }
-    EXPECT_EQ(graph_info->output_operands.size(), 1u);
-    auto output_operand_id = graph_info->output_operands[0];
-    auto output_operand_iter =
-        graph_info->id_to_operand_map.find(output_operand_id);
-    ASSERT_TRUE(output_operand_iter != graph_info->id_to_operand_map.end());
-    EXPECT_EQ(output_operand_iter->value->data_type,
-              expected_operand.data_type);
-    EXPECT_EQ(output_operand_iter->value->dimensions,
-              expected_operand.dimensions);
-  }
-};
-
-TEST_P(MLGraphTestMojo, Conv2dTest) {
-  V8TestingScope scope;
-  // Bind fake WebNN Context in the service for testing.
-  ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
-
-  auto* options = MLContextOptions::Create();
-  // Create WebNN Context with GPU device type.
-  options->setDeviceType(V8MLDeviceType::Enum::kGpu);
-  auto* builder = CreateGraphBuilder(scope, options);
-  ASSERT_THAT(builder, testing::NotNull());
-  {
-    // Test conv2d with default options.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes = {.padding = {0, 0, 0, 0},
-                                .strides = {1, 1},
-                                .dilations = {1, 1},
-                                .groups = 1}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with strides=2 and padding=1.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options = {.padding = Vector<uint32_t>({1, 1, 1, 1}),
-                    .strides = Vector<uint32_t>({2, 2})},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes = {.padding = {1, 1, 1, 1},
-                                .strides = {2, 2},
-                                .dilations = {1, 1},
-                                .groups = 1}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test depthwise conv2d by setting groups to input channels.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 4, 2, 2}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {4, 1, 2, 2}},
-        .options = {.groups = 4},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 4, 1, 1}},
-        .expected_attributes = {.padding = {0, 0, 0, 0},
-                                .strides = {1, 1},
-                                .dilations = {1, 1},
-                                .groups = 4}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with clamp activation.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options = {.activation =
-                        Activation{
-                            .kind =
-                                webnn::mojom::blink::Activation::Tag::kClamp,
-                            .clamp_options =
-                                ClampTester::ClampOptions{.min_value = 1.0,
-                                                          .max_value = 6.0}}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kClamp,
-                     .clamp_options =
-                         ClampTester::ClampOptions{.min_value = 1.0,
-                                                   .max_value = 6.0}}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with elu activation with default options.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options = {.activation =
-                        Activation{
-                            .kind =
-                                webnn::mojom::blink::Activation::Tag::kElu}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{.kind = webnn::mojom::blink::Activation::Tag::kElu,
-                            .elu_alpha = 1.0}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with elu activation with given alpha.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options = {.activation =
-                        Activation{
-                            .kind = webnn::mojom::blink::Activation::Tag::kElu,
-                            .elu_alpha = 0.5}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{.kind = webnn::mojom::blink::Activation::Tag::kElu,
-                            .elu_alpha = 0.5}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with hardSigmoid activation with default alpha = 0.1 and beta
-    // = -1.0.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options =
-            {.activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kHardSigmoid,
-                     .hard_sigmoid_alpha = 0.1,
-                     .hard_sigmoid_beta = -1.0}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kHardSigmoid,
-                     .hard_sigmoid_alpha = 0.1,
-                     .hard_sigmoid_beta = -1.0}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with leaky relu activation with default options.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options =
-            {.activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kLeakyRelu}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kLeakyRelu,
-                     .leaky_relu_alpha = 0.01}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with leaky relu activation with given alpha.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options =
-            {.activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kLeakyRelu,
-                     .leaky_relu_alpha = 0.02}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kLeakyRelu,
-                     .leaky_relu_alpha = 0.02}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with relu activation.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options = {.activation =
-                        Activation{
-                            .kind =
-                                webnn::mojom::blink::Activation::Tag::kRelu}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{.kind =
-                                webnn::mojom::blink::Activation::Tag::kRelu}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with sigmoid activation.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options =
-            {.activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kSigmoid}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kSigmoid}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with softmax activation.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat16,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat16,
-                   .dimensions = {1, 1, 3, 3}},
-        .options =
-            {.activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kSoftmax}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat16,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kSoftmax}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with softplus activation with steepness = 2.0.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat16,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat16,
-                   .dimensions = {1, 1, 3, 3}},
-        .options = {.activation =
-                        Activation{
-                            .kind =
-                                webnn::mojom::blink::Activation::Tag::kSoftplus,
-                            .softplus_steepness = 2.0}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat16,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kSoftplus,
-                     .softplus_steepness = 2.0}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with softsign activation.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat16,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat16,
-                   .dimensions = {1, 1, 3, 3}},
-        .options =
-            {.activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kSoftsign}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat16,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{
-                     .kind = webnn::mojom::blink::Activation::Tag::kSoftsign}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test conv2d with tanh activation.
-    Conv2dTester{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 1, 5, 5}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 1, 3, 3}},
-        .options = {.activation =
-                        Activation{
-                            .kind =
-                                webnn::mojom::blink::Activation::Tag::kTanh}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {1, 1, 3, 3}},
-        .expected_attributes =
-            {.padding = Vector<uint32_t>({0, 0, 0, 0}),
-             .strides = Vector<uint32_t>({1, 1}),
-             .dilations = Vector<uint32_t>({1, 1}),
-             .groups = 1,
-             .activation =
-                 Activation{.kind =
-                                webnn::mojom::blink::Activation::Tag::kTanh}}}
-        .Test(*this, scope, builder);
-  }
-}
-
 struct ElementWiseBinaryTester {
   OperandInfoBlink lhs;
   OperandInfoBlink rhs;
@@ -3393,101 +2906,6 @@
   }
 }
 
-struct MatmulTester {
-  OperandInfoBlink a;
-  OperandInfoBlink b;
-  OperandInfoMojo expected_operand;
-
-  void Test(MLGraphTestMojo& helper,
-            V8TestingScope& scope,
-            MLGraphBuilder* builder) {
-    // Build the graph.
-    auto* a_operand = BuildInput(builder, "a", a.dimensions, a.data_type,
-                                 scope.GetExceptionState());
-    auto* b_operand = BuildInput(builder, "b", b.dimensions, b.data_type,
-                                 scope.GetExceptionState());
-    auto* output_operand =
-        builder->matmul(a_operand, b_operand, scope.GetExceptionState());
-    auto [graph, error_name, error_message] =
-        helper.BuildGraph(scope, builder, {{"output", output_operand}});
-    ASSERT_THAT(graph, testing::NotNull());
-
-    auto graph_info = helper.GetGraphInfo();
-    // Verify the graph information of mojo are as expected.
-    EXPECT_EQ(graph_info->id_to_operand_map.size(), 3u);
-    EXPECT_EQ(graph_info->input_operands.size(), 2u);
-    // Verify the a `mojo::Operand`.
-    auto a_operand_id = graph_info->input_operands[0];
-    auto a_operand_iter = graph_info->id_to_operand_map.find(a_operand_id);
-    ASSERT_TRUE(a_operand_iter != graph_info->id_to_operand_map.end());
-    EXPECT_EQ(a_operand_iter->value->kind, blink_mojom::Operand::Kind::kInput);
-    EXPECT_EQ(a_operand_iter->value->data_type, expected_operand.data_type);
-    EXPECT_EQ(a_operand_iter->value->dimensions, a.dimensions);
-    EXPECT_EQ(a_operand_iter->value->name, "a");
-    // Verify the b `mojo::Operand`.
-    auto b_operand_id = graph_info->input_operands[1];
-    auto b_operand_iter = graph_info->id_to_operand_map.find(b_operand_id);
-    ASSERT_TRUE(b_operand_iter != graph_info->id_to_operand_map.end());
-    EXPECT_EQ(b_operand_iter->value->kind, blink_mojom::Operand::Kind::kInput);
-    EXPECT_EQ(b_operand_iter->value->data_type, expected_operand.data_type);
-    EXPECT_EQ(b_operand_iter->value->dimensions, b.dimensions);
-    EXPECT_EQ(b_operand_iter->value->name, "b");
-    // Verify the output `mojo::Operand`.
-    ASSERT_EQ(graph_info->output_operands.size(), 1u);
-    auto output_operand_id = graph_info->output_operands[0];
-    auto output_operand_iter =
-        graph_info->id_to_operand_map.find(output_operand_id);
-    ASSERT_TRUE(output_operand_iter != graph_info->id_to_operand_map.end());
-    EXPECT_EQ(output_operand_iter->value->data_type,
-              expected_operand.data_type);
-    EXPECT_EQ(output_operand_iter->value->dimensions,
-              expected_operand.dimensions);
-    EXPECT_EQ(output_operand_iter->value->data_type,
-              expected_operand.data_type);
-    EXPECT_EQ(output_operand_iter->value->name, "output");
-    // Verify the `mojo::Operator`.
-    ASSERT_EQ(graph_info->operations.size(), 1u);
-    auto& operation = graph_info->operations[0];
-    EXPECT_TRUE(operation->is_matmul());
-  }
-};
-
-TEST_P(MLGraphTestMojo, MatmulTest) {
-  V8TestingScope scope;
-  // Bind fake WebNN Context in the service for testing.
-  ScopedWebNNServiceBinder scoped_setup_binder(*this, scope);
-
-  auto* options = MLContextOptions::Create();
-  // Create WebNN Context with GPU device type.
-  options->setDeviceType(V8MLDeviceType::Enum::kGpu);
-  auto* builder = CreateGraphBuilder(scope, options);
-  ASSERT_THAT(builder, testing::NotNull());
-  {
-    // Test building matmul with 2-D * 2-D.
-    MatmulTester{
-        .a = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-              .dimensions = {2, 3}},
-        .b = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-              .dimensions = {3, 4}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat32,
-                             .dimensions = {2, 4}}}
-        .Test(*this, scope, builder);
-  }
-  {
-    // Test building matmul with 3-D * 4-D using broadcasting.
-    MatmulTester{
-        .a = {.data_type = V8MLOperandDataType::Enum::kFloat16,
-              .dimensions = {2, 2, 3}},
-        .b = {.data_type = V8MLOperandDataType::Enum::kFloat16,
-              .dimensions = {3, 1, 3, 4}},
-        .expected_operand = {.data_type =
-                                 blink_mojom::Operand::DataType::kFloat16,
-                             .dimensions = {3, 2, 2, 4}}}
-        .Test(*this, scope, builder);
-  }
-}
-
 struct PadTester {
   OperandInfoBlink input;
   Vector<uint32_t> beginning_padding;
diff --git a/third_party/blink/renderer/modules/ml/webnn/ml_graph_test.cc b/third_party/blink/renderer/modules/ml/webnn/ml_graph_test.cc
index eced28c..5589ab5 100644
--- a/third_party/blink/renderer/modules/ml/webnn/ml_graph_test.cc
+++ b/third_party/blink/renderer/modules/ml/webnn/ml_graph_test.cc
@@ -6,8 +6,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_clamp_options.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_ml_conv_2d_options.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_ml_conv_transpose_2d_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_leaky_relu_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_pad_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_ml_reduce_options.h"
@@ -944,362 +942,6 @@
 }
 
 template <typename T>
-struct Conv2dTester {
-  OperandInfo<T> input;
-  OperandInfo<T> filter;
-  std::optional<OperandInfo<T>> bias = std::nullopt;
-  Vector<T> expected;
-
-  void Test(MLGraphTest& helper,
-            V8TestingScope& scope,
-            MLGraphBuilder* builder,
-            MLConv2dOptions* options = MLConv2dOptions::Create()) {
-    // Build the graph.
-    auto* input_operand =
-        BuildInput(builder, "input", input.dimensions, input.data_type,
-                   scope.GetExceptionState());
-    auto* filter_operand =
-        BuildConstant(builder, filter.dimensions, filter.data_type,
-                      filter.values, scope.GetExceptionState());
-    if (bias) {
-      options->setBias(BuildConstant(
-          builder, bias.value().dimensions, bias.value().data_type,
-          bias.value().values, scope.GetExceptionState()));
-    }
-    auto* output_operand =
-        BuildConv2d(scope, builder, input_operand, filter_operand, options);
-    auto [graph, error_name, error_message] =
-        helper.BuildGraph(scope, builder, {{"output", output_operand}});
-    ASSERT_THAT(graph, testing::NotNull());
-
-    // Compute the graph.
-    MLNamedArrayBufferViews inputs(
-        {{"input",
-          CreateArrayBufferViewForOperand(input_operand, input.values)}});
-    MLNamedArrayBufferViews outputs(
-        {{"output", CreateArrayBufferViewForOperand(output_operand)}});
-    std::tie(error_name, error_message) =
-        helper.ComputeGraph(scope, graph, inputs, outputs);
-    EXPECT_TRUE(error_name.IsNull());
-    auto results = GetArrayBufferViewValues<T>(outputs[0].second);
-    EXPECT_EQ(results, expected);
-  }
-};
-
-TEST_P(MLGraphTest, Conv2dTest) {
-  V8TestingScope scope;
-  auto* builder =
-      CreateMLGraphBuilder(scope.GetExecutionContext(), scope.GetScriptState(),
-                           scope.GetExceptionState());
-  {
-    // Test conv2d operator for nhwc input layout and ohwi filter layout.
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kOhwi);
-    Conv2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 2, 3, 3},
-                  .values = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
-                             11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {3, 1, 1, 3},
-                   .values = {1.0, 4.0, 7.0, 2.0, 5.0, 8.0, 3.0, 6.0, 9.0}},
-        .expected = {30.0, 36.0, 42.0, 66.0, 81.0, 96.0, 102.0, 126.0, 150.0,
-                     138.0, 171.0, 204.0, 174.0, 216.0, 258.0, 210.0, 261.0,
-                     312.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test conv2d operator for explicit padding are not same as the calculated
-    // padding with kSameUpper, input, filter size, stride and dilation that
-    // are used by CalculateConv2dPadding function.
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kOhwi);
-    // The paddings are {1, 1, 1, 1} with calculating by CalculateConv2dPadding
-    // function.
-    options->setPadding({2, 2, 1, 1});
-    options->setStrides({2, 2});
-    Conv2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 7, 5, 1},
-                  .values = Vector<float>(35, 1.0)},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 3, 3, 1},
-                   .values = Vector<float>(9, 1.0)},
-        .expected = {2.0, 3.0, 2.0, 6.0, 9.0, 6.0, 6.0, 9.0, 6.0, 6.0, 9.0, 6.0,
-                     2.0, 3.0, 2.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test fused conv2d operator for nhwc input layout and ohwi filter
-    // layout, fusing with bias operand and relu activation.
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kOhwi);
-    options->setActivation(builder->relu(scope.GetExceptionState()));
-    Conv2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 2, 3, 3},
-                  .values = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,
-                             11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {3, 1, 1, 3},
-                   .values = {1.0, 4.0, 7.0, 2.0, 5.0, 8.0, 3.0, 6.0, 9.0}},
-        .bias =
-            OperandInfo<float>{.data_type = V8MLOperandDataType::Enum::kFloat32,
-                               .dimensions = {3},
-                               .values = {-6000.0, -7000.0, 8000.0}},
-        .expected = {0.0, 0.0, 8042.0, 0.0, 0.0, 8096.0, 0.0, 0.0, 8150.0, 0.0,
-                     0.0, 8204.0, 0.0, 0.0, 8258.0, 0.0, 0.0, 8312.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test depthwise conv2d operator by setting groups to input channels,
-    // nhwc input layout, ihwo filter layout.
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kIhwo);
-    options->setGroups(4);
-    Conv2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 2, 2, 4},
-                  .values = {10.0, 21.0, 10.0, 0.0, 10.0, 22.0, 20.0, 0.0, 10.0,
-                             23.0, 30.0, 0.0, 10.0, 24.0, 40.0, 0.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 2, 2, 4},
-                   .values = {0.25, 0.0, 10.0, 50.0, 0.25, 1.0, 20.0, 50.0,
-                              0.25, 0.0, 30.0, 50.0, 0.25, 1.0, 40.0, 50.0}},
-        .expected = {10.0, 46.0, 3000.0, 0.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test fused depthwise conv2d operator by setting groups to input
-    // channels, nhwc input layout, ihwo filter layout, fusing with bias
-    // operand and relu activation.
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kIhwo);
-    options->setGroups(4);
-    options->setActivation(builder->relu(scope.GetExceptionState()));
-    Conv2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 2, 2, 4},
-                  .values = {10.0, 21.0, 10.0, 0.0, 10.0, 22.0, 20.0, 0.0, 10.0,
-                             23.0, 30.0, 0.0, 10.0, 24.0, 40.0, 0.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 2, 2, 4},
-                   .values = {0.25, 0.0, 10.0, 50.0, 0.25, 1.0, 20.0, 50.0,
-                              0.25, 0.0, 30.0, 50.0, 0.25, 1.0, 40.0, 50.0}},
-        .bias =
-            OperandInfo<float>{.data_type = V8MLOperandDataType::Enum::kFloat32,
-                               .dimensions = {4},
-                               .values = {-6000.0, -7000.0, 8000.0, 9000.0}},
-        .expected = {0.0, 0.0, 11000.0, 9000.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test fused depthwise conv2d operator by setting groups to input
-    // channels, nhwc input layout, ihwo filter layout, fusing with bias
-    // operand and clamp activation.
-    auto* options = MLConv2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(V8MLConv2dFilterOperandLayout::Enum::kIhwo);
-    options->setGroups(4);
-    auto* clamp_options = MLClampOptions::Create();
-    clamp_options->setMinValue(0.0);
-    clamp_options->setMaxValue(6.0);
-    options->setActivation(
-        builder->clamp(clamp_options, scope.GetExceptionState()));
-    Conv2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 2, 2, 4},
-                  .values = {10.0, 21.0, 10.0, 0.0, 10.0, 22.0, 20.0, 0.0, 10.0,
-                             23.0, 30.0, 0.0, 10.0, 24.0, 40.0, 0.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 2, 2, 4},
-                   .values = {0.25, 0.0, 10.0, 50.0, 0.25, 1.0, 20.0, 50.0,
-                              0.25, 0.0, 30.0, 50.0, 0.25, 1.0, 40.0, 50.0}},
-        .bias =
-            OperandInfo<float>{.data_type = V8MLOperandDataType::Enum::kFloat32,
-                               .dimensions = {4},
-                               .values = {-6000.0, -7000.0, 8000.0, 9000.0}},
-        .expected = {0.0, 0.0, 6.0, 6.0}}
-        .Test(*this, scope, builder, options);
-  }
-}
-
-template <typename T>
-struct ConvTranspose2dTester {
-  OperandInfo<T> input;
-  OperandInfo<T> filter;
-  std::optional<OperandInfo<T>> bias = std::nullopt;
-  Vector<T> expected;
-
-  void Test(
-      MLGraphTest& helper,
-      V8TestingScope& scope,
-      MLGraphBuilder* builder,
-      MLConvTranspose2dOptions* options = MLConvTranspose2dOptions::Create()) {
-    // Build the graph.
-    auto* input_operand =
-        BuildInput(builder, "input", input.dimensions, input.data_type,
-                   scope.GetExceptionState());
-    auto* filter_operand =
-        BuildConstant(builder, filter.dimensions, filter.data_type,
-                      filter.values, scope.GetExceptionState());
-    if (bias) {
-      options->setBias(BuildConstant(
-          builder, bias.value().dimensions, bias.value().data_type,
-          bias.value().values, scope.GetExceptionState()));
-    }
-    auto* output_operand = BuildConvTranspose2d(scope, builder, input_operand,
-                                                filter_operand, options);
-    auto [graph, error_name, error_message] =
-        helper.BuildGraph(scope, builder, {{"output", output_operand}});
-    ASSERT_THAT(graph, testing::NotNull());
-
-    // Compute the graph.
-    MLNamedArrayBufferViews inputs(
-        {{"input",
-          CreateArrayBufferViewForOperand(input_operand, input.values)}});
-    MLNamedArrayBufferViews outputs(
-        {{"output", CreateArrayBufferViewForOperand(output_operand)}});
-    std::tie(error_name, error_message) =
-        helper.ComputeGraph(scope, graph, inputs, outputs);
-    EXPECT_TRUE(error_name.IsNull());
-    auto results = GetArrayBufferViewValues<T>(outputs[0].second);
-    EXPECT_EQ(results, expected);
-  }
-};
-
-TEST_P(MLGraphTest, ConvTranspose2dTest) {
-  V8TestingScope scope;
-  auto* builder =
-      CreateMLGraphBuilder(scope.GetExecutionContext(), scope.GetScriptState(),
-                           scope.GetExceptionState());
-  {
-    // Test convTranspose2d operator for nhwc input layout and ohwi filter
-    // layout.
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kOhwi);
-    ConvTranspose2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 3, 3, 1},
-                  .values = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 3, 3, 1},
-                   .values = {1.0, 3.0, 5.0, 7.0, 9.0, 2.0, 4.0, 6.0, 8.0}},
-        .expected = {1.0,  5.0,   14.0,  19.0,  15.0,  11.0,  40.0,
-                     82.0, 74.0,  36.0,  39.0,  114.0, 195.0, 165.0,
-                     81.0, 65.0,  163.0, 235.0, 173.0, 66.0,  28.0,
-                     74.0, 140.0, 118.0, 72.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test fused convTranspose2d operator for nhwc input layout and ohwi filter
-    // layout, fusing with bias operand and relu activation.
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kOhwi);
-    options->setActivation(builder->relu(scope.GetExceptionState()));
-    ConvTranspose2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 3, 3, 1},
-                  .values = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {3, 3, 3, 1},
-                   .values = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
-                              9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0,
-                              1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}},
-        .bias =
-            OperandInfo<float>{.data_type = V8MLOperandDataType::Enum::kFloat32,
-                               .dimensions = {3},
-                               .values = {-6000.0, -7000.0, 8000.0}},
-        .expected = {0.0, 0.0, 8001.0, 0.0, 0.0, 8004.0, 0.0, 0.0, 8010.0,
-                     0.0, 0.0, 8012.0, 0.0, 0.0, 8009.0, 0.0, 0.0, 8008.0,
-                     0.0, 0.0, 8026.0, 0.0, 0.0, 8056.0, 0.0, 0.0, 8054.0,
-                     0.0, 0.0, 8036.0, 0.0, 0.0, 8030.0, 0.0, 0.0, 8084.0,
-                     0.0, 0.0, 8165.0, 0.0, 0.0, 8144.0, 0.0, 0.0, 8090.0,
-                     0.0, 0.0, 8056.0, 0.0, 0.0, 8134.0, 0.0, 0.0, 8236.0,
-                     0.0, 0.0, 8186.0, 0.0, 0.0, 8108.0, 0.0, 0.0, 8049.0,
-                     0.0, 0.0, 8112.0, 0.0, 0.0, 8190.0, 0.0, 0.0, 8144.0,
-                     0.0, 0.0, 8081.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test convTranspose2d operator by setting padding=1.
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kOhwi);
-    options->setPadding({1, 1, 1, 1});
-    ConvTranspose2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 5, 5, 1},
-                  .values = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
-                             1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
-                             1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 3, 3, 1},
-                   .values = {1.0, 3.0, 5.0, 7.0, 9.0, 2.0, 4.0, 6.0, 8.0}},
-        .expected = {48.0,  100.0, 127.0, 145.0, 101.0, 126.0, 186.0,
-                     231.0, 213.0, 132.0, 132.0, 249.0, 285.0, 267.0,
-                     153.0, 156.0, 231.0, 213.0, 177.0, 147.0, 129.0,
-                     217.0, 217.0, 199.0, 95.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test convTranspose2d operator by setting strides=2, padding=1.
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kOhwi);
-    options->setStrides({2, 2});
-    options->setPadding({1, 1, 1, 1});
-    ConvTranspose2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 3, 3, 1},
-                  .values = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 3, 3, 1},
-                   .values = {1.0, 3.0, 5.0, 7.0, 9.0, 2.0, 4.0, 6.0, 8.0}},
-        .expected = {9.0,   16.0, 18.0, 25.0, 27.0, 18.0, 41.0, 27.0, 59.0,
-                     36.0,  36.0, 43.0, 45.0, 52.0, 54.0, 45.0, 95.0, 54.0,
-                     113.0, 63.0, 63.0, 70.0, 72.0, 79.0, 81.0}}
-        .Test(*this, scope, builder, options);
-  }
-  {
-    // Test convTranspose2d by setting outputSizes={1, 8, 8, 1}.
-    auto* options = MLConvTranspose2dOptions::Create();
-    options->setInputLayout(V8MLInputOperandLayout::Enum::kNhwc);
-    options->setFilterLayout(
-        V8MLConvTranspose2dFilterOperandLayout::Enum::kOhwi);
-    options->setStrides({2, 2});
-    options->setOutputSizes({8, 8});
-    ConvTranspose2dTester<float>{
-        .input = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                  .dimensions = {1, 3, 3, 1},
-                  .values = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0}},
-        .filter = {.data_type = V8MLOperandDataType::Enum::kFloat32,
-                   .dimensions = {1, 3, 3, 1},
-                   .values = {1.0, 3.0, 5.0, 7.0, 9.0, 2.0, 4.0, 6.0, 8.0}},
-        .expected = {1.0,  3.0,  7.0,  6.0,  13.0,  9.0,  15.0, 0.0,
-                     7.0,  9.0,  16.0, 18.0, 25.0,  27.0, 6.0,  0.0,
-                     8.0,  18.0, 41.0, 27.0, 59.0,  36.0, 54.0, 0.0,
-                     28.0, 36.0, 43.0, 45.0, 52.0,  54.0, 12.0, 0.0,
-                     23.0, 45.0, 95.0, 54.0, 113.0, 63.0, 93.0, 0.0,
-                     49.0, 63.0, 70.0, 72.0, 79.0,  81.0, 18.0, 0.0,
-                     28.0, 42.0, 88.0, 48.0, 100.0, 54.0, 72.0, 0.0,
-                     0.0,  0.0,  0.0,  0.0,  0.0,   0.0,  0.0,  0.0}}
-        .Test(*this, scope, builder, options);
-  }
-}
-
-template <typename T>
 struct GemmTester {
   OperandInfo<T> a;
   OperandInfo<T> b;
diff --git a/third_party/blink/renderer/modules/model_execution/exception_helpers.cc b/third_party/blink/renderer/modules/model_execution/exception_helpers.cc
index 65f1d4f..51c9c81 100644
--- a/third_party/blink/renderer/modules/model_execution/exception_helpers.cc
+++ b/third_party/blink/renderer/modules/model_execution/exception_helpers.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/modules/model_execution/exception_helpers.h"
 
+#include "third_party/blink/public/mojom/model_execution/model_session.mojom-shared.h"
+#include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/platform/bindings/exception_code.h"
 
 namespace blink {
@@ -19,4 +21,59 @@
       DOMException::GetErrorName(DOMExceptionCode::kOperationError)));
 }
 
+DOMException* ConvertModelStreamingResponseErrorToDOMException(
+    ModelStreamingResponseStatus error) {
+  switch (error) {
+    case ModelStreamingResponseStatus::kErrorUnknown:
+      return DOMException::Create(
+          "An unknown error occurred.",
+          DOMException::GetErrorName(DOMExceptionCode::kUnknownError));
+    case ModelStreamingResponseStatus::kErrorInvalidRequest:
+      return DOMException::Create(
+          "The request was invalid.",
+          DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError));
+    case ModelStreamingResponseStatus::kErrorRequestThrottled:
+      return DOMException::Create(
+          "The request was throttled.",
+          DOMException::GetErrorName(DOMExceptionCode::kQuotaExceededError));
+    case ModelStreamingResponseStatus::kErrorPermissionDenied:
+      return DOMException::Create(
+          "A user permission error occurred, such as not signed-in or not "
+          "allowed to execute model.",
+          DOMException::GetErrorName(DOMExceptionCode::kNotAllowedError));
+    case ModelStreamingResponseStatus::kErrorGenericFailure:
+      return DOMException::Create(
+          "Other generic failure occurred.",
+          DOMException::GetErrorName(DOMExceptionCode::kUnknownError));
+    case ModelStreamingResponseStatus::kErrorRetryableError:
+      return DOMException::Create(
+          "A retryable error occurred in the server.",
+          DOMException::GetErrorName(DOMExceptionCode::kNotReadableError));
+    case ModelStreamingResponseStatus::kErrorNonRetryableError:
+      return DOMException::Create(
+          "A non-retryable error occurred in the server.",
+          DOMException::GetErrorName(DOMExceptionCode::kNotReadableError));
+    case ModelStreamingResponseStatus::kErrorUnsupportedLanguage:
+      return DOMException::Create(
+          "The language was unsupported.",
+          DOMException::GetErrorName(DOMExceptionCode::kNotSupportedError));
+    case ModelStreamingResponseStatus::kErrorFiltered:
+      return DOMException::Create(
+          "The execution yielded a bad response.",
+          DOMException::GetErrorName(DOMExceptionCode::kNotReadableError));
+    case ModelStreamingResponseStatus::kErrorDisabled:
+      return DOMException::Create(
+          "The response was disabled.",
+          DOMException::GetErrorName(DOMExceptionCode::kNotReadableError));
+    case ModelStreamingResponseStatus::kErrorCancelled:
+      return DOMException::Create(
+          "The request was cancelled.",
+          DOMException::GetErrorName(DOMExceptionCode::kAbortError));
+    case ModelStreamingResponseStatus::kOngoing:
+    case ModelStreamingResponseStatus::kComplete:
+      NOTREACHED();
+      return nullptr;
+  }
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/model_execution/exception_helpers.h b/third_party/blink/renderer/modules/model_execution/exception_helpers.h
index 9a5edda..880fdef7 100644
--- a/third_party/blink/renderer/modules/model_execution/exception_helpers.h
+++ b/third_party/blink/renderer/modules/model_execution/exception_helpers.h
@@ -5,15 +5,21 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MODEL_EXECUTION_EXCEPTION_HELPERS_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_MODEL_EXECUTION_EXCEPTION_HELPERS_H_
 
+#include "third_party/blink/public/mojom/model_execution/model_session.mojom-blink-forward.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 
 namespace blink {
 
+using mojom::blink::ModelStreamingResponseStatus;
+
 void ThrowInvalidContextException(ExceptionState& exception_state);
 
 void RejectPromiseWithInternalError(ScriptPromiseResolverBase* resolver);
 
+DOMException* ConvertModelStreamingResponseErrorToDOMException(
+    ModelStreamingResponseStatus error);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_MODEL_EXECUTION_EXCEPTION_HELPERS_H_
diff --git a/third_party/blink/renderer/modules/model_execution/model_generic_session.cc b/third_party/blink/renderer/modules/model_execution/model_generic_session.cc
index 3643214..860f7c8 100644
--- a/third_party/blink/renderer/modules/model_execution/model_generic_session.cc
+++ b/third_party/blink/renderer/modules/model_execution/model_generic_session.cc
@@ -30,41 +30,6 @@
 
 using mojom::blink::ModelStreamingResponseStatus;
 
-// TODO(crbug.com/1520700): update this to different DOMException once the
-// list finalized.
-const char* ConvertModelStreamingResponseErrorToString(
-    ModelStreamingResponseStatus error) {
-  switch (error) {
-    case ModelStreamingResponseStatus::kErrorUnknown:
-      return "Unknown error.";
-    case ModelStreamingResponseStatus::kErrorInvalidRequest:
-      return "The request was invalid.";
-    case ModelStreamingResponseStatus::kErrorRequestThrottled:
-      return "The request was throttled.";
-    case ModelStreamingResponseStatus::kErrorPermissionDenied:
-      return "User permission errors such as not signed-in or not allowed to "
-             "execute model.";
-    case ModelStreamingResponseStatus::kErrorGenericFailure:
-      return "Other generic failures.";
-    case ModelStreamingResponseStatus::kErrorRetryableError:
-      return "Retryable error occurred in server.";
-    case ModelStreamingResponseStatus::kErrorNonRetryableError:
-      return "Non-retryable error occurred in server.";
-    case ModelStreamingResponseStatus::kErrorUnsupportedLanguage:
-      return "Unsupported.";
-    case ModelStreamingResponseStatus::kErrorFiltered:
-      return "Bad response.";
-    case ModelStreamingResponseStatus::kErrorDisabled:
-      return "Response was disabled.";
-    case ModelStreamingResponseStatus::kErrorCancelled:
-      return "The request was cancelled.";
-    case ModelStreamingResponseStatus::kOngoing:
-    case ModelStreamingResponseStatus::kComplete:
-      NOTREACHED();
-      return "";
-  }
-}
-
 // Implementation of blink::mojom::blink::ModelStreamingResponder that
 // handles the streaming output of the model execution, and returns the full
 // result through a promise.
@@ -107,7 +72,8 @@
       if (status == ModelStreamingResponseStatus::kComplete) {
         resolver_->Resolve(response_);
       } else {
-        resolver_->Reject(ConvertModelStreamingResponseErrorToString(status));
+        resolver_->Reject(
+            ConvertModelStreamingResponseErrorToDOMException(status));
       }
       // Record the per execution metrics and run the complete callback.
       base::UmaHistogramCounts1M(
@@ -189,9 +155,8 @@
       if (status == ModelStreamingResponseStatus::kComplete) {
         Controller()->Close();
       } else {
-        Controller()->Error(MakeGarbageCollected<DOMException>(
-            DOMExceptionCode::kNotReadableError,
-            ConvertModelStreamingResponseErrorToString(status)));
+        Controller()->Error(
+            ConvertModelStreamingResponseErrorToDOMException(status));
       }
       // Record the per execution metrics and run the complete callback.
       base::UmaHistogramCounts1M(
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
index ceecd88..1100c30 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
@@ -189,6 +189,10 @@
   // Returns true iff we have LCPP hint data for the fetch context.
   virtual bool DoesLCPPHaveAnyHintData() { return false; }
 
+  // Returns true iff we have LCP element locator hint data for the fetch
+  // context.
+  virtual bool DoesLCPPHaveLcpElementLocatorHintData() { return false; }
+
   // Returns the origin of the top frame in the document or the dedicated
   // worker. This returns nullptr for Shared Workers and Service Workers.
   virtual scoped_refptr<const SecurityOrigin> GetTopFrameOrigin() const {
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 58fb3aa..0cb5c2bd 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -666,6 +666,19 @@
     }
   }
 
+  // The following code disables AdjustImagePriority when there is LCPP
+  // LcpElementLocator hint data. The reason why not to early return from this
+  // function is that we want to record UMA with following
+  // MaybeRecordBoostImagePriorityReason() function even when we disables
+  // AdjustImagePriority.
+  if (base::FeatureList::IsEnabled(features::kLCPCriticalPathPredictor) &&
+      features::kLCPCriticalPathAdjustImageLoadPriority.Get() &&
+      features::kLCPCriticalPathAdjustImageLoadPriorityOverrideFirstNBoost
+          .Get() &&
+      context_->DoesLCPPHaveLcpElementLocatorHintData()) {
+    new_priority = priority_so_far;
+  }
+
   // Only records HTTP family URLs (e.g. Exclude data URLs).
   if (resource_request.Url().ProtocolIsInHTTPFamily()) {
     MaybeRecordBoostImagePriorityReason(priority_so_far != new_priority,
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 7ae026c..a1fd2e1 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1558,6 +1558,10 @@
       status: "test",
     },
     {
+      // crbug.com/333630754
+      name: "FasterMinContent",
+    },
+    {
       name: "FastPositionIterator",
       // Not enabled due to a RTL issue. crbug.com/1421016.
     },
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index e51f3b5..4114736 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -2814,7 +2814,6 @@
 crbug.com/626703 external/wpt/css/css-text/text-justify/text-justify-word-separators.html [ Failure ]
 crbug.com/626703 external/wpt/svg/pservers/reftests/gradient-color-interpolation.svg [ Failure ]
 crbug.com/626703 virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/gpu/arg_min_max.https.any.html [ Skip Timeout ]
-crbug.com/626703 virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/gpu/arg_min_max.https.any.worker.html [ Crash ]
 crbug.com/626703 external/wpt/css/css-page/page-orientation-on-landscape-001-print.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-page/page-orientation-on-portrait-001-print.html [ Failure ]
 crbug.com/626703 external/wpt/css/css-page/page-orientation-on-square-001-print.html [ Failure ]
@@ -7260,43 +7259,33 @@
 crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/empty-ruby-text-container-abs.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/line-break-around-ruby-001.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/line-spacing.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/nested-ruby-pairing-001.html [ Crash Failure Timeout ]
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/ruby-align-001.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/ruby-align-001a.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/ruby-align-002.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/ruby-align-002a.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/ruby-base-container-float.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/ruby-bidi-003.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/ruby-intra-level-whitespace-001.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/external/wpt/css/css-ruby/ruby-line-breaking-002.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/after-doesnt-crash.html [ Crash Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/before-doesnt-crash.html [ Crash Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/float-overhang-from-ruby-text.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/line-break-ruby.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/nested-ruby.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/overhang-horizontal.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/overhang-vertical.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/percentage-height-child-crash.html [ Crash ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-illegal-7.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-illegal-combined.html [ Crash Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-position-modern-japanese-fonts.html [ Crash Failure ]
+crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-position-modern-japanese-fonts.html [ Failure ]
+crbug.com/324111880 virtual/ruby-lb/fast/ruby/rubyDOM-insert-text2.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/select-ruby.html [ Failure ]
 # RubyLineBreakable; NeedsRebaseline
+crbug.com/324111880 virtual/ruby-lb/fast/ruby/after-doesnt-crash.html [ Failure ]
+crbug.com/324111880 virtual/ruby-lb/fast/ruby/before-doesnt-crash.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/float-object-doesnt-crash.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/modify-positioned-ruby-text-crash.html [ Failure ]
+crbug.com/324111880 virtual/ruby-lb/fast/ruby/percentage-height-child-crash.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/position-after.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-column-break.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-columns-spans.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-columns.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-empty-rt.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-first-letter.html [ Failure ]
+crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-illegal-7.html [ Failure ]
+crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-illegal-combined.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-length.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-text-before-after-content.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/ruby-trailing.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/rubyDOM-insert-rt-block-1.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/rubyDOM-insert-rt-block-2.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/rubyDOM-insert-rt-block-3.html [ Failure ]
-crbug.com/324111880 virtual/ruby-lb/fast/ruby/rubyDOM-insert-text2.html [ Failure ]
 crbug.com/324111880 virtual/ruby-lb/fast/ruby/split-ruby-column-percentage-height-descendant.html [ Failure ]
 
 # line-clamp
@@ -7389,3 +7378,7 @@
 # Gardener 2024-04-11
 crbug.com/333739617 [ Mac ] fast/peerconnection/RTCEncodedVideoFrameMetadata-timestamp.html [ Timeout ]
 crbug.com/333501264 virtual/fenced-frame-mparch/external/wpt/fenced-frame/setting-null-config-navigates-to-about-blank.https.html [ Failure Pass ]
+
+# Gardener 2024-04-16
+crbug.com/335003887 [ Mac14 ] virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/gpu/arg_min_max.https.any.worker.html [ Failure Pass ]
+crbug.com/335003887 [ Win11 ] virtual/webnn-service-with-gpu/external/wpt/webnn/conformance_tests/gpu/arg_min_max.https.any.worker.html [ Failure Pass ]
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-invalid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-invalid.html
index 2056055f..b93a48fb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-invalid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-invalid.html
@@ -115,13 +115,33 @@
 @font-palette-values --A {
     override-colors: 0 canvas;
 }
+
+/* 19 */
+@font-palette-values --A {
+    override-colors: 0 currentcolor;
+}
+
+/* 20 */
+@font-palette-values --A {
+    override-colors: 0 light-dark(white, black);
+}
+
+/* 21 */
+@font-palette-values --A {
+    override-colors: 0 color-mix(in lch, red, canvas);
+}
+
+/* 22 */
+@font-palette-values --A {
+    override-colors: 0 light-dark(white, currentcolor);
+}
 </style>
 </head>
 <body>
 <script>
 let rules = document.getElementById("style").sheet.cssRules;
 test(function() {
-    assert_equals(rules.length, 19);
+    assert_equals(rules.length, 23);
 });
 
 test(function() {
@@ -283,6 +303,34 @@
     assert_equals(rule.basePalette, "");
     assert_equals(rule.overrideColors, "");
 });
+
+test(function() {
+    let text = rules[19].cssText;
+    let rule = rules[19];
+    assert_equals(text.indexOf("override-colors"), -1);
+    assert_equals(rule.overrideColors, "");
+});
+
+test(function() {
+    let text = rules[20].cssText;
+    let rule = rules[20];
+    assert_equals(text.indexOf("override-colors"), -1);
+    assert_equals(rule.overrideColors, "");
+});
+
+test(function() {
+    let text = rules[21].cssText;
+    let rule = rules[21];
+    assert_equals(text.indexOf("override-colors"), -1);
+    assert_equals(rule.overrideColors, "");
+});
+
+test(function() {
+    let text = rules[22].cssText;
+    let rule = rules[22];
+    assert_equals(text.indexOf("override-colors"), -1);
+    assert_equals(rule.overrideColors, "");
+});
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-valid.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-valid.html
index 3c0c0626..99fceff2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-valid.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-palette-values-valid.html
@@ -103,6 +103,11 @@
 @font-palette-values --P {
     font-family: foo, bar, baz;
 }
+
+/* 17 */
+@font-palette-values --Q {
+    override-colors: 0 color-mix(in lch, red, blue);
+}
 </style>
 </head>
 <body>
@@ -385,6 +390,14 @@
     assert_equals(rule.basePalette, "");
     assert_equals(rule.overrideColors, "");
 });
+
+test(function() {
+    let rule = rules[17];
+    assert_equals(rule.name, "--Q");
+    assert_equals(rule.fontFamily, "");
+    assert_equals(rule.basePalette, "");
+    assert_equals(rule.overrideColors, "0 color-mix(in lch, red, blue)");
+});
 </script>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-insertion-005-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-insertion-005-ref.html
index 03e5cab..79a19130 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-insertion-005-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-insertion-005-ref.html
@@ -11,3 +11,12 @@
 <p><rb>a</rb><rt>x</rt><rb>b</rb><rt>y</rt></p>
 <p><rbc><span>a</span><rb></rb>b</rbc><rt>x</rt><rt></rt><rt>y</rt></p>
 <p><rb>a</rb><rb></rb><rb>b</rb><rtc><span>x</span><rt></rt>y</rtc></p>
+
+<p>'a' and 'b c' should be paired with 'x' and 'y z' repectively:</p>
+<p><ruby>a <rt>x</rt><span style="display:block">b</span> c<rt>y z</ruby></p>
+
+<p>'a b' and 'c' should be paired with 'x y' and 'z' repectively:</p>
+<p><ruby>a <span style="display:block">b</span> <rt>x y</rt><span>c</span><rt>z</ruby></p>
+
+<p>'a b' and 'c d' should be paired with 'w x' and 'y z'  repectively:</p>
+<p><ruby>a <span style="display:block">b</span> <rt>w x</rt><span>c</span> <span style="display:block">d</span><rt>y z</ruby>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-insertion-005.html b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-insertion-005.html
index a684d66..66f7dbd 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-insertion-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-insertion-005.html
@@ -20,3 +20,12 @@
 <p><rbc><span data-insert="after" data-tag="rb">a</span>b</rbc><rt>x</rt><rt></rt><rt>y</rt></p>
 <!--     pseudo ruby text -->
 <p><rb>a</rb><rb></rb><rb>b</rb><rtc><span data-insert="after" data-tag="rt">x</span>y</rtc></p>
+
+<p>'a' and 'b c' should be paired with 'x' and 'y z' repectively:</p>
+<p><ruby>a <span style="display:block" data-insert="before" data-tag="rt" data-text="x">b</span> c<rt>y z</ruby></p>
+
+<p>'a b' and 'c' should be paired with 'x y' and 'z' repectively:</p>
+<p><ruby>a <span style="display:block">b</span> <span data-insert="before" data-tag="rt" data-text="x y">c</span><rt>z</ruby></p>
+
+<p>'a b' and 'c d' should be paired with 'w x' and 'y z'  repectively:</p>
+<p><ruby>a <span style="display:block">b</span> <span data-insert="before" data-tag="rt" data-text="w x">c</span> <span style="display:block">d</span><rt>y z</ruby>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-removal-003-ref.html b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-removal-003-ref.html
index 0067c014..113598e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-removal-003-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-removal-003-ref.html
@@ -18,3 +18,8 @@
 
 <p><rbc>ab</rbc><rt>xy</rt></p>
 <p><rb>ab</rb><rtc style="letter-spacing: 1px">xy</rtc></p>
+
+<p>'a b c' should be paried with 'x y z':</p>
+<p><ruby>a b <span style="display:block">c</span><rt>x y z</ruby>
+<p><ruby>a <span style="display:block">b</span> c<rt>x y z</ruby>
+<p><ruby><span style="display:block">a</span> b <span style="display:block">c</span><rt>x y z</ruby>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-removal-003.html b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-removal-003.html
index d35b2b9..ff6c0b4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-removal-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-ruby/ruby-dynamic-removal-003.html
@@ -31,3 +31,9 @@
 <!--     pseudo ruby text -->
 <!-- letter-spacing is added here to avoid fuzzy on Windows. See bug 1111891 -->
 <p><rb>ab</rb><rtc style="letter-spacing: 1px">x<rt class="remove"></rt>y</rtc></p>
+
+<p>'a b c' should be paried with 'x y z':</p>
+<!-- merge -->
+<p><ruby>a <rt class="remove">w</rt><span>b</span> <span style="display:block">c</span><rt>x y z</ruby>
+<p><ruby>a <span style="display:block">b</span> <rt class="remove">w</rt><span>c</span><rt>x y z</ruby>
+<p><ruby><span style="display:block">a</span> <rt class="remove">w</rt><span>b</span> <span style="display:block">c</span><rt>x y z</ruby>
diff --git a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js
index ffc9c2c..44d5f14 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js
@@ -55,3 +55,482 @@
   const filter = builder.input('filter', kExampleFilterDescriptor);
   assert_throws_js(TypeError, () => builder.conv2d(input, filter, options));
 }, '[conv2d] throw if activation option is from another builder');
+
+const tests = [
+  {
+    name: '[conv2d] Test with default options.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+  },
+  {
+    name: '[conv2d] Test with padding.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [1, 1, 1, 1],
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 5, 5]}
+  },
+  {
+    name: '[conv2d] Test with strides and padding.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [1, 1, 1, 1],
+      strides: [2, 2],
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+  },
+  {
+    name: '[conv2d] Test with strides and asymmetric padding.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 4, 2]},
+    options: {
+      padding: [1, 2, 0, 1],
+      strides: [2, 2],
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+  },
+  {
+    name: '[conv2d] Test depthwise conv2d by setting groups to input channels.',
+    input: {dataType: 'float32', dimensions: [1, 4, 2, 2]},
+    filter: {dataType: 'float32', dimensions: [4, 1, 2, 2]},
+    options: {
+      groups: 4,
+    },
+    output: {dataType: 'float32', dimensions: [1, 4, 1, 1]}
+  },
+  {
+    name:
+        '[conv2d] Test depthwise conv2d with groups, inputLayout="nhwc" and filterLayout="ihwo".',
+    input: {dataType: 'float32', dimensions: [1, 2, 2, 4]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 2, 4]},
+    options: {
+      groups: 4,
+      inputLayout: 'nhwc',
+      filterLayout: 'ihwo',
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 1, 4]}
+  },
+  {
+    name:
+        '[conv2d] Test with dilations, inputLayout="nhwc" and filterLayout="ihwo".',
+    input: {dataType: 'float32', dimensions: [1, 65, 65, 1]},
+    filter: {dataType: 'float32', dimensions: [1, 3, 3, 1]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'ihwo',
+      dilations: [4, 4],
+    },
+    output: {dataType: 'float32', dimensions: [1, 57, 57, 1]}
+  },
+  {
+    name: '[conv2d] Test with inputLayout="nchw" and filterLayout="oihw".',
+    input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      inputLayout: 'nchw',
+      filterLayout: 'oihw',
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+  },
+  {
+    name: '[conv2d] Test with inputLayout="nchw" and filterLayout="hwio".',
+    input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+    options: {
+      inputLayout: 'nchw',
+      filterLayout: 'hwio',
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+  },
+  {
+    name: '[conv2d] Test with inputLayout="nchw" and filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+    options: {
+      inputLayout: 'nchw',
+      filterLayout: 'ohwi',
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+  },
+  {
+    name: '[conv2d] Test with inputLayout="nchw" and filterLayout="ihwo".',
+    input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+    options: {
+      inputLayout: 'nchw',
+      filterLayout: 'ihwo',
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+  },
+  {
+    name: '[conv2d] Test with inputLayout="nhwc" and filterLayout="oihw".',
+    input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'oihw',
+    },
+    output: {dataType: 'float32', dimensions: [1, 3, 3, 1]}
+  },
+  {
+    name: '[conv2d] Test with inputLayout="nhwc" and filterLayout="hwio".',
+    input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+    filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'hwio',
+    },
+    output: {dataType: 'float32', dimensions: [1, 3, 3, 1]}
+  },
+  {
+    name: '[conv2d] Test with inputLayout="nhwc" and filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'ohwi',
+    },
+    output: {dataType: 'float32', dimensions: [1, 3, 3, 1]}
+  },
+  {
+    name: '[conv2d] Test with inputLayout="nhwc" and filterLayout="ihwo".',
+    input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+    filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'ihwo',
+    },
+    output: {dataType: 'float32', dimensions: [1, 3, 3, 1]}
+  },
+  {
+    name:
+        '[conv2d] Throw if the output operand\'s number of elements is too large.',
+    input: {
+      dataType: 'float32',
+      dimensions: [1, 1, kMaxUnsignedLong / 2, kMaxUnsignedLong / 2]
+    },
+    filter: {dataType: 'float32', dimensions: [8, 1, 1, 1]},
+  },
+  {
+    name: '[conv2d] Throw if the input is not a 4-D tensor.',
+    input: {dataType: 'float32', dimensions: [1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 2, 1]},
+  },
+  {
+    name: '[conv2d] Throw if the filter is not a 4-D tensor.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [2, 2]},
+  },
+  {
+    name:
+        '[conv2d] Throw if the filter data type doesn\'t match the input data type.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'int32', dimensions: [1, 1, 2, 2]},
+  },
+  {
+    name: '[conv2d] Throw if the length of padding is not 4.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      padding: [2, 2],
+    },
+  },
+  {
+    name: '[conv2d] Throw if the length of strides is not 2.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      strides: [2],
+    },
+  },
+  {
+    name: '[conv2d] Throw if strideHeight is smaller than 1.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      strides: [0, 1],
+    },
+  },
+  {
+    name: '[conv2d] Throw if strideWidth is smaller than 1.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      strides: [1, 0],
+    },
+  },
+  {
+    name: '[conv2d] Throw if the length of dilations is not 2.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      dilations: [1],
+    },
+  },
+  {
+    name: '[conv2d] Throw if dilationHeight is smaller than 1.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      dilations: [0, 1],
+    },
+  },
+  {
+    name: '[conv2d] Throw if dilationWidth is smaller than 1.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      dilations: [1, 0],
+    },
+  },
+  {
+    name: '[conv2d] Throw if inputChannels % groups is not 0.',
+    input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      groups: 3,
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels.',
+    input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      groups: 2,
+    },
+  },
+  {
+    name: '[conv2d] Throw if the groups is smaller than 1.',
+    input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      groups: 0,
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw due to overflow when calculating the effective filter height.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 434983, 2]},
+    options: {
+      dilations: [328442, 1],
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw due to overflow when calculating the effective filter width.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 234545]},
+    options: {
+      dilations: [2, 843452],
+    },
+  },
+  {
+    name: '[conv2d] Throw due to overflow when dilation height is too large.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      dilations: [kMaxUnsignedLong, 1],
+    },
+  },
+  {
+    name: '[conv2d] Throw due to overflow when dilation width is too large.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      dilations: [1, kMaxUnsignedLong],
+    },
+  },
+  {
+    name: '[conv2d] Throw due to underflow when calculating the output height.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 4, 2]},
+    options: {
+      dilations: [4, 1],
+      padding: [1, 1, 1, 1],
+      strides: [2, 2],
+    },
+  },
+  {
+    name: '[conv2d] Throw due to underflow when calculating the output width.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 8]},
+    options: {
+      dilations: [1, 4],
+      padding: [1, 1, 1, 1],
+      strides: [2, 2],
+    },
+  },
+  {
+    name: '[conv2d] Throw if the bias is not a 1-D tensor.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      bias: {dataType: 'float32', dimensions: [1, 2]},
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="oihw".',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      bias: {dataType: 'float32', dimensions: [2]},
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="hwio".',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [2, 2, 1, 1]},
+    options: {
+      bias: {dataType: 'float32', dimensions: [2]},
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 2, 1]},
+    options: {
+      bias: {dataType: 'float32', dimensions: [2]},
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="ihwo".',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 2, 1]},
+    options: {
+      bias: {dataType: 'float32', dimensions: [2]},
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if the bias data type doesn\'t match input data type.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      bias: {dataType: 'int32', dimensions: [1]},
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nchw" and filterLayout="oihw".',
+    input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      inputLayout: 'nchw',
+      filterLayout: 'oihw',
+      groups: 2,
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nchw" and filterLayout="hwio".',
+    input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+    options: {
+      inputLayout: 'nchw',
+      filterLayout: 'hwio',
+      groups: 2,
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nchw" and filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+    options: {
+      inputLayout: 'nchw',
+      filterLayout: 'ohwi',
+      groups: 2,
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nchw" and filterLayout="ihwo".',
+    input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+    options: {
+      inputLayout: 'nchw',
+      filterLayout: 'ihwo',
+      groups: 2,
+    },
+
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nhwc" and filterLayout="oihw".',
+    input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'oihw',
+      groups: 2,
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nhwc" and filterLayout="hwio".',
+    input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+    filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'hwio',
+      groups: 2,
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nhwc" and filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'ohwi',
+      groups: 2,
+    },
+  },
+  {
+    name:
+        '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nhwc" and filterLayout="ihwo".',
+    input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+    filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+    options: {
+      inputLayout: 'nhwc',
+      filterLayout: 'ihwo',
+      groups: 2,
+    },
+  },
+];
+
+tests.forEach(
+    test => promise_test(async t => {
+      const input = builder.input(
+          'input',
+          {dataType: test.input.dataType, dimensions: test.input.dimensions});
+      const filter = builder.input(
+          'filter',
+          {dataType: test.filter.dataType, dimensions: test.filter.dimensions});
+
+      if (test.options && test.options.bias) {
+        test.options.bias = builder.input('bias', {
+          dataType: test.options.bias.dataType,
+          dimensions: test.options.bias.dimensions
+        });
+      }
+
+      if (test.output) {
+        const output = builder.conv2d(input, filter, test.options);
+        assert_equals(output.dataType(), test.output.dataType);
+        assert_array_equals(output.shape(), test.output.dimensions);
+      } else {
+        assert_throws_js(
+            TypeError, () => builder.conv2d(input, filter, test.options));
+      }
+    }, test.name));
diff --git a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/convTranspose2d.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/convTranspose2d.https.any.js
index c14f445b..3c3c49566 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/convTranspose2d.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/convTranspose2d.https.any.js
@@ -57,3 +57,467 @@
   assert_throws_js(
       TypeError, () => builder.convTranspose2d(input, filter, options));
 }, '[convTranspose2d] throw if activation option is from another builder');
+
+const tests = [
+  {
+    name: '[convTranspose2d] Test with default options.',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    output: {dataType: 'float32', dimensions: [1, 1, 5, 5]}
+  },
+  {
+    name:
+        '[convTranspose2d] Test with inputLayout="nchw" and filterLayout="hwoi".',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+    options: {
+      filterLayout: 'hwoi',
+      inputLayout: 'nchw',
+    },
+    output: {dataType: 'float32', dimensions: [1, 2, 5, 5]}
+  },
+  {
+    name:
+        '[convTranspose2d] Test with inputLayout="nchw" and filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+    options: {
+      filterLayout: 'ohwi',
+      inputLayout: 'nchw',
+    },
+    output: {dataType: 'float32', dimensions: [1, 2, 5, 5]}
+  },
+  {
+    name:
+        '[convTranspose2d] Test with inputLayout="nhwc" and filterLayout="iohw".',
+    input: {dataType: 'float32', dimensions: [1, 3, 3, 1]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      filterLayout: 'iohw',
+      inputLayout: 'nhwc',
+    },
+    output: {dataType: 'float32', dimensions: [1, 5, 5, 2]}
+  },
+  {
+    name:
+        '[convTranspose2d] Test with inputLayout="nhwc" and filterLayout="hwoi".',
+    input: {dataType: 'float32', dimensions: [1, 3, 3, 1]},
+    filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+    options: {
+      filterLayout: 'hwoi',
+      inputLayout: 'nhwc',
+    },
+    output: {dataType: 'float32', dimensions: [1, 5, 5, 2]}
+  },
+  {
+    name:
+        '[convTranspose2d] Test with inputLayout="nhwc" and filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 3, 3, 1]},
+    filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+    options: {
+      filterLayout: 'ohwi',
+      inputLayout: 'nhwc',
+    },
+    output: {dataType: 'float32', dimensions: [1, 5, 5, 2]}
+  },
+  {
+    name: '[convTranspose2d] Test with strides=[3, 2], outputSizes=[10, 8].',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      strides: [3, 2],
+      outputSizes: [10, 8],
+    },
+    output: {dataType: 'float32', dimensions: [1, 2, 10, 8]}
+  },
+  {
+    name: '[convTranspose2d] Test with strides=[3, 2], outputPadding=[1, 1].',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      strides: [3, 2],
+      outputPadding: [1, 1],
+    },
+    output: {dataType: 'float32', dimensions: [1, 2, 10, 8]}
+  },
+  {
+    name: '[convTranspose2d] Test with padding=1.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [1, 1, 1, 1],
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 5, 5]}
+  },
+  {
+    name: '[convTranspose2d] Test with padding=1, groups=3.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [1, 1, 1, 1],
+      groups: 3,
+    },
+    output: {dataType: 'float32', dimensions: [1, 3, 5, 5]}
+  },
+  {
+    name: '[convTranspose2d] Test with strides=2.',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      strides: [2, 2],
+    },
+    output: {dataType: 'float32', dimensions: [1, 2, 7, 7]}
+  },
+  {
+    name: '[convTranspose2d] Test with strides=2 and padding=1.',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [1, 1, 1, 1],
+      strides: [2, 2],
+    },
+    output: {dataType: 'float32', dimensions: [1, 1, 5, 5]}
+  },
+  {
+    name:
+        '[convTranspose2d] Test when the output sizes are explicitly specified, the output padding values are ignored though padding value is not smaller than stride along the same axis.',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      outputPadding: [3, 3],
+      strides: [3, 2],
+      outputSizes: [10, 8],
+    },
+    output: {dataType: 'float32', dimensions: [1, 2, 10, 8]}
+  },
+  {
+    name: '[convTranspose2d] Throw if the input is not a 4-D tensor.',
+    input: {dataType: 'float32', dimensions: [1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+  },
+  {
+    name: '[convTranspose2d] Throw if the filter is not a 4-D tensor.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [2, 2]},
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the filter data type doesn\'t match the input data type.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'int32', dimensions: [1, 1, 2, 2]},
+  },
+  {
+    name: '[convTranspose2d] Throw if the length of padding is not 4.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      padding: [2, 2],
+    },
+  },
+  {
+    name: '[convTranspose2d] Throw if the length of strides is not 2.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      strides: [2],
+    },
+  },
+  {
+    name: '[convTranspose2d] Throw if one stride value is smaller than 1.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      strides: [1, 0],
+    },
+  },
+  {
+    name: '[convTranspose2d] Throw if the length of dilations is not 2.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      dilations: [1],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the one dilation value is smaller than 1.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      dilations: [1, 0],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nchw" and filterLayout="iohw".',
+    input: {dataType: 'float32', dimensions: [1, 3, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      filterLayout: 'iohw',
+      inputLayout: 'nchw',
+      groups: 1,
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nchw" and filterLayout="hwoi".',
+    input: {dataType: 'float32', dimensions: [1, 3, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [3, 1, 2, 1]},
+    options: {
+      filterLayout: 'hwoi',
+      inputLayout: 'nchw',
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nchw" and filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+    options: {
+      filterLayout: 'ohwi',
+      inputLayout: 'nchw',
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nhwc" and filterLayout="iohw".',
+    input: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      filterLayout: 'iohw',
+      inputLayout: 'nhwc',
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the input channels is not equal to the filter input channels inputLayout="nhwc" and filterLayout="hwoi".',
+    input: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+    filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+    options: {
+      filterLayout: 'hwoi',
+      inputLayout: 'nhwc',
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nhwc" and filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+    filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+    options: {
+      filterLayout: 'ohwi',
+      inputLayout: 'nhwc',
+    },
+  },
+  {
+    name: '[convTranspose2d] Throw if output channels is too large.',
+    input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [4, 2, 2, 2]},
+    options: {
+      groups: kMaxUnsignedLong,
+    },
+  },
+  {
+    name: '[convTranspose2d] Throw if the groups is smaller than 1.',
+    input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      groups: 0,
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw due to overflow when calculating the effective filter height.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 434983, 2]},
+    options: {
+      dilations: [328443, 1],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw due to overflow when calculating the effective filter width.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 234545]},
+    options: {
+      dilations: [2, 843452],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw due to overflow when dilation height is too large.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 2]},
+    options: {
+      dilations: [kMaxUnsignedLong, 1],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw due to overflow when dilation width is too large.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 2]},
+    options: {
+      dilations: [1, kMaxUnsignedLong],
+    },
+  },
+  {
+    name: '[convTranspose2d] Throw if the bias is not a 1-D tensor.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      bias: {dataType: 'float32', dimensions: [1, 2]},
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="iohw".',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      filterLayout: 'iohw',
+      bias: {dataType: 'float32', dimensions: [2]},
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="hwoi".',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [2, 2, 1, 1]},
+    options: {
+      filterLayout: 'hwoi',
+      bias: {dataType: 'float32', dimensions: [2]},
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="ohwi".',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 2, 1]},
+    options: {
+      filterLayout: 'ohwi',
+      bias: {dataType: 'float32', dimensions: [2]},
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the bias data type doesn\'t match input data type.',
+    input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    options: {
+      bias: {dataType: 'int32', dimensions: [1]},
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the outputPadding is not a sequence of length 2.',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      strides: [3, 2],
+      outputPadding: [1, 1, 1, 1],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the outputPadding is not smaller than stride along the width dimension.',
+    input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [0, 0, 3, 3],
+      strides: [2, 2],
+      outputPadding: [0, 2],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the outputPadding is not smaller than stride along the height dimension.',
+    input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [0, 0, 3, 3],
+      strides: [2, 2],
+      outputPadding: [2, 0],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw if the outputSizes is not a sequence of length 2.',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+    options: {
+      strides: [3, 2],
+      outputSizes: [1, 2, 10, 8],
+    },
+  },
+  {
+    name: '[convTranspose2d] Throw if the padding height is too large.',
+    input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [4, 4, 0, 0],
+      strides: [2, 2],
+      outputPadding: [1, 0],
+    },
+  },
+  {
+    name: '[convTranspose2d] Throw if the padding width is too large.',
+    input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [0, 0, 4, 4],
+      strides: [2, 2],
+      outputPadding: [0, 1],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw due to outputSizes values are smaller than the output sizes calculated by not using outputPadding.',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [1, 1, 1, 1],
+      strides: [2, 2],
+      outputSizes: [4, 4],
+      outputPadding: [1, 1],
+    },
+  },
+  {
+    name:
+        '[convTranspose2d] Throw due to outputSizes values are greater than the output sizes calculated by not using outputPadding.',
+    input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+    options: {
+      padding: [1, 1, 1, 1],
+      strides: [2, 2],
+      outputSizes: [6, 8],
+      outputPadding: [1, 1],
+    },
+  },
+];
+
+tests.forEach(
+    test => promise_test(async t => {
+      const input = builder.input(
+          'input',
+          {dataType: test.input.dataType, dimensions: test.input.dimensions});
+      const filter = builder.input(
+          'filter',
+          {dataType: test.filter.dataType, dimensions: test.filter.dimensions});
+
+      if (test.options && test.options.bias) {
+        test.options.bias = builder.input('bias', {
+          dataType: test.options.bias.dataType,
+          dimensions: test.options.bias.dimensions
+        });
+      }
+
+      if (test.output) {
+        const output = builder.convTranspose2d(input, filter, test.options);
+        assert_equals(output.dataType(), test.output.dataType);
+        assert_array_equals(output.shape(), test.output.dimensions);
+      } else {
+        assert_throws_js(
+            TypeError,
+            () => builder.convTranspose2d(input, filter, test.options));
+      }
+    }, test.name));
diff --git a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/matmul.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/matmul.https.any.js
index 03616ddb..6ce0d87 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/matmul.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/matmul.https.any.js
@@ -5,3 +5,109 @@
 'use strict';
 
 validateTwoInputsFromMultipleBuilders('matmul');
+
+const tests = [
+  {
+    name: '[matmul] Throw if first input\'s rank is less than 2',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [2]},
+      b: {dataType: 'float32', dimensions: [2, 2]}
+    }
+  },
+  {
+    name: '[matmul] Throw if second input\'s rank is less than 2',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [2, 2]},
+      b: {dataType: 'float32', dimensions: [2]}
+    }
+  },
+  {
+    name: '[matmul] Test with 2-D input and 4-D input',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [1, 4]},
+      b: {dataType: 'float32', dimensions: [2, 2, 4, 2]}
+    },
+    output: {dataType: 'float32', dimensions: [2, 2, 1, 2]}
+  },
+  {
+    name: '[matmul] Test with 2-D input and 2-D input',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [4, 2]},
+      b: {dataType: 'float32', dimensions: [2, 3]}
+    },
+    output: {dataType: 'float32', dimensions: [4, 3]}
+  },
+  {
+    // batchShape is a clone of inputShape with the spatial dimensions
+    // (last 2 items) removed.
+    name:
+        '[matmul] Test with 3-D input and 3-D input of broadcastable batchShape',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [2, 3, 4]},
+      b: {dataType: 'float32', dimensions: [1, 4, 1]}
+    },
+    output: {dataType: 'float32', dimensions: [2, 3, 1]}
+  },
+  {
+    // batchShape is a clone of inputShape with the spatial dimensions
+    // (last 2 items) removed.
+    name:
+        '[matmul] Test with 4-D input and 3-D input of broadcastable batchShape',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [2, 2, 3, 4]},
+      b: {dataType: 'float32', dimensions: [1, 4, 5]}
+    },
+    output: {dataType: 'float32', dimensions: [2, 2, 3, 5]}
+  },
+  {
+    name: '[matmul] Test with 3-D input and 3-D input',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [2, 3, 4]},
+      b: {dataType: 'float32', dimensions: [2, 4, 5]}
+    },
+    output: {dataType: 'float32', dimensions: [2, 3, 5]}
+  },
+  {
+    name: '[matmul] Throw if data type of two inputs don\'t match',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [2, 3, 4]},
+      b: {dataType: 'int32', dimensions: [2, 4, 5]}
+    }
+  },
+  {
+    name:
+        '[matmul] Throw if columns of first input\'s shape doesn\'t match the rows of second input\'s shape',
+    inputs: {
+      a: {dataType: 'float32', dimensions: /* [rows, columns] */[2, 3]},
+      b: {dataType: 'float32', dimensions: /* [rows, columns] */[2, 4]}
+    },
+  },
+  {
+    // batchShape is a clone of inputShape with the spatial dimensions
+    // (last 2 items) removed.
+    name: '[matmul] Throw if batchShapes aren\'t bidirectionally broadcastable',
+    inputs: {
+      a: {dataType: 'float32', dimensions: [3, 3, 4]},
+      b: {dataType: 'float32', dimensions: [2, 4, 1]}
+    },
+  },
+];
+
+tests.forEach(test => promise_test(async t => {
+                const inputA = builder.input('a', {
+                  dataType: test.inputs.a.dataType,
+                  dimensions: test.inputs.a.dimensions
+                });
+                const inputB = builder.input('b', {
+                  dataType: test.inputs.b.dataType,
+                  dimensions: test.inputs.b.dimensions
+                });
+                if (test.output) {
+                  const output = builder.matmul(inputA, inputB);
+                  assert_equals(output.dataType(), test.output.dataType);
+                  assert_array_equals(output.shape(), test.output.dimensions);
+                } else {
+                  assert_throws_js(
+                      TypeError, () => builder.matmul(inputA, inputB));
+                }
+              }, test.name));
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-1-expected.txt b/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-1-expected.txt
deleted file mode 100644
index e1a6ba5..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-1-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-SUCCESS!
-
-
-
-textnew ruby textblock more textruby text
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-1.html b/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-1.html
deleted file mode 100644
index 661bd94..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-1.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<script>
-function test()
-{
-    if (window.testRunner)
-        testRunner.dumpAsText();
-    document.getElementById("result").firstChild.data = 'SUCCESS!';
-
-    var ruby = document.getElementById('R');
-    var block = document.getElementById('D');
-    var newRT = document.createElement('rt');
-    var newRTText = document.createTextNode('new ruby text');
-    newRT.appendChild(newRTText);
-    ruby.insertBefore(newRT, block);    
-}
-</script>
-</head>
-<!-- Inserting a <rt> element, causing a split of block flow to inline flow and block flow -->
-<!-- As this is a malformed example we don't care about the exact rendering output, only that it doesn't crash -->
-<body onload="test()">
-<div id="result">FAILED!</p>
-<br>
-<br>
-<ruby id="R">text <div id="D">block</div> more text<rt>ruby text</rt></ruby>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-2-expected.txt b/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-2-expected.txt
deleted file mode 100644
index 953fed7..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-2-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-SUCCESS!
-
-
-
-text blocknew ruby textmore textruby text
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-2.html b/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-2.html
deleted file mode 100644
index 020731b..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-2.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<script>
-function test()
-{
-    if (window.testRunner)
-        testRunner.dumpAsText();
-    document.getElementById("result").firstChild.data = 'SUCCESS!';
-
-    var ruby = document.getElementById('R');
-    var span = document.getElementById('S');
-    var newRT = document.createElement('rt');
-    var newRTText = document.createTextNode('new ruby text');
-    newRT.appendChild(newRTText);
-    ruby.insertBefore(newRT, span);    
-}
-</script>
-</head>
-<!-- Inserting a <rt> element, causing a split of block flow to block flow and inline flow -->
-<!-- As this is a malformed example we don't care about the exact rendering output, only that it doesn't crash -->
-<body onload="test()">
-<div id="result">FAILED!</p>
-<br>
-<br>
-<ruby id="R">text <div>block</div> <span id="S">more</span> text<rt>ruby text</rt></ruby>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-3-expected.txt b/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-3-expected.txt
deleted file mode 100644
index 953fed7..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-3-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-SUCCESS!
-
-
-
-text blocknew ruby textmore textruby text
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-3.html b/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-3.html
deleted file mode 100644
index ab52d1aa..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-insert-rt-block-3.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<script>
-function test()
-{
-    if (window.testRunner)
-        testRunner.dumpAsText();
-    document.getElementById("result").firstChild.data = 'SUCCESS!';
-
-    var ruby = document.getElementById('R');
-    var span = document.getElementById('S');
-    var newRT = document.createElement('rt');
-    var newRTText = document.createTextNode('new ruby text');
-    newRT.appendChild(newRTText);
-    ruby.insertBefore(newRT, span);    
-}
-</script>
-</head>
-<!-- Inserting a <rt> element, causing a split of block flow to block flow and block flow -->
-<!-- As this is a malformed example we don't care about the exact rendering output, only that it doesn't crash -->
-<body onload="test()">
-<div id="result">FAILED!</p>
-<br>
-<br>
-<ruby id="R">text <div>block</div> <span id="S">more</span> <div>text</div><rt>ruby text</rt></ruby>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-1-expected.txt b/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-1-expected.txt
deleted file mode 100644
index 6e69928..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-1-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-SUCCESS!
-
-
-
-some textmore text and a blockruby text 2
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-1.html b/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-1.html
deleted file mode 100644
index a1da9e9..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-1.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<script>
-function test()
-{
-    if (window.testRunner)
-        testRunner.dumpAsText();
-    document.getElementById("result").firstChild.data = 'SUCCESS!';
-
-    var ruby = document.getElementById('R');
-    var rt = document.getElementById('RT');
-    ruby.removeChild(rt);    
-}
-</script>
-</head>
-<!-- Removing a <rt> element, causing a merge of inline flow and block flow -->
-<!-- As this is a malformed example we don't care about the exact rendering output, only that it doesn't crash -->
-<body onload="test()">
-<div id="result">FAILED!</p>
-<br>
-<br>
-<ruby id="R">some text<rt id="RT">ruby text 1</rt><span>more text</span> <div>and a block</div><rt>ruby text 2</rt></ruby>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-2-expected.txt b/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-2-expected.txt
deleted file mode 100644
index 299eaa80..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-2-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-SUCCESS!
-
-
-
-text block more textruby text 2
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-2.html b/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-2.html
deleted file mode 100644
index c1e3f137..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-2.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<script>
-function test()
-{
-    if (window.testRunner)
-        testRunner.dumpAsText();
-    document.getElementById("result").firstChild.data = 'SUCCESS!';
-
-    var ruby = document.getElementById('R');
-    var rt = document.getElementById('RT');
-    ruby.removeChild(rt);    
-}
-</script>
-</head>
-<!-- Removing a <rt> element, causing a merge of block flow and inline flow -->
-<!-- As this is a malformed example we don't care about the exact rendering output, only that it doesn't crash -->
-<body onload="test()">
-<div id="result">FAILED!</p>
-<br>
-<br>
-<ruby id="R">text <div>block</div> <rt id="RT">ruby text 1</rt><span>more</span> text<rt>ruby text 2</rt></ruby>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-3-expected.txt b/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-3-expected.txt
deleted file mode 100644
index 299eaa80..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-3-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-SUCCESS!
-
-
-
-text block more textruby text 2
diff --git a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-3.html b/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-3.html
deleted file mode 100644
index 0945826c..0000000
--- a/third_party/blink/web_tests/fast/ruby/rubyDOM-remove-rt-block-3.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-<script>
-function test()
-{
-    if (window.testRunner)
-        testRunner.dumpAsText();
-    document.getElementById("result").firstChild.data = 'SUCCESS!';
-
-    var ruby = document.getElementById('R');
-    var rt = document.getElementById('RT');
-    ruby.removeChild(rt);    
-}
-</script>
-</head>
-<!-- Removing a <rt> element, causing a merge of block flow and block flow -->
-<!-- As this is a malformed example we don't care about the exact rendering output, only that it doesn't crash -->
-<body onload="test()">
-<div id="result">FAILED!</p>
-<br>
-<br>
-<ruby id="R">text <div>block</div> <rt id="RT">ruby text 1</rt><span>more</span> <div>text</div><rt>ruby text 2</rt></ruby>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/http/tests/security/base-url-data-expected.txt b/third_party/blink/web_tests/http/tests/security/base-url-data-expected.txt
index adaa6ad..b375d51 100644
--- a/third_party/blink/web_tests/http/tests/security/base-url-data-expected.txt
+++ b/third_party/blink/web_tests/http/tests/security/base-url-data-expected.txt
@@ -1,4 +1,6 @@
 CONSOLE ERROR: 'data' URLs may not be used as base URLs for a document.
 This is a testharness.js-based test.
+[FAIL] 'data:' is an invalid base URL.
+  assert_equals: expected "data:/,This%20is%20a%20data%20URL." but got "data:/,This is a data URL."
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/http/tests/security/base-url-data.html b/third_party/blink/web_tests/http/tests/security/base-url-data.html
index 9bab9fa..64d4c3d5 100644
--- a/third_party/blink/web_tests/http/tests/security/base-url-data.html
+++ b/third_party/blink/web_tests/http/tests/security/base-url-data.html
@@ -14,7 +14,9 @@
       img.onload = t.step_func_done(_ => {
         assert_equals(img.naturalWidth, 76, "Image loaded correctly.");
         assert_equals(img.src, "http://127.0.0.1:8000/security/resources/abe.png");
-        assert_equals(base.href, 'data:/,This is a data URL.');
+        // Fails unless kStandardCompliantNonSpecialSchemeURLParsing is enabled.
+        // See https://crbug.com/40063064.
+        assert_equals(base.href, 'data:/,This%20is%20a%20data%20URL.');
       });
       img.onerror = t.unreached_func("Image should have loaded.");
 
diff --git a/third_party/blink/web_tests/platform/linux/virtual/url-non-special/http/tests/security/base-url-data-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/url-non-special/http/tests/security/base-url-data-expected.txt
index 168e4aa..adaa6ad 100644
--- a/third_party/blink/web_tests/platform/linux/virtual/url-non-special/http/tests/security/base-url-data-expected.txt
+++ b/third_party/blink/web_tests/platform/linux/virtual/url-non-special/http/tests/security/base-url-data-expected.txt
@@ -1,6 +1,4 @@
 CONSOLE ERROR: 'data' URLs may not be used as base URLs for a document.
 This is a testharness.js-based test.
-[FAIL] 'data:' is an invalid base URL.
-  assert_equals: expected "data:/,This is a data URL." but got "data:/,This%20is%20a%20data%20URL."
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/where.https.any-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/where.https.any-expected.txt
deleted file mode 100644
index bbb8c93a4..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/where.https.any-expected.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] where float32 0D scalars
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 1D constant tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 1D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 2D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 3D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 5D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 0D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 1D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 2D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 3D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 4D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast trueValues 2D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast trueValues 4D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast falseValues 3D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast falseValues 4D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors all broadcast 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/where.https.any.worker-expected.txt b/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/where.https.any.worker-expected.txt
deleted file mode 100644
index bbb8c93a4..0000000
--- a/third_party/blink/web_tests/platform/linux/virtual/webnn-service-without-gpu/external/wpt/webnn/conformance_tests/gpu/where.https.any.worker-expected.txt
+++ /dev/null
@@ -1,37 +0,0 @@
-This is a testharness.js-based test.
-[FAIL] where float32 0D scalars
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 1D constant tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 1D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 2D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 3D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 5D tensors
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 0D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 1D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 2D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 3D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast condition 4D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast trueValues 2D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast trueValues 4D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast falseValues 3D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors only broadcast falseValues 4D to 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-[FAIL] where float32 4D tensors all broadcast 4D
-  promise_test: Unhandled rejection with value: object "NotSupportedError: Failed to execute 'build' on 'MLGraphBuilder': where is not implemented"
-Harness: the test ran to completion.
-
diff --git a/third_party/dawn b/third_party/dawn
index 413dfc0..b5d8926 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 413dfc05c2ba4c5a5d8629b440b17ab9b3ed5c5d
+Subproject commit b5d89266d090eb586b756294ea09e4beb0c06bcb
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index b4ea518..1202967 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit b4ea518eb65f83a0f5c4e8f9625b099ec18c07d8
+Subproject commit 120296784c3ee38e176618378479e2d63ab4deaf
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 8e6c915..4e11c88 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 8e6c915f32a6c98fe6968eace7a2a9a66589a892
+Subproject commit 4e11c8801f7a4e8e9f7b94f6acd4155cbe0a5e57
diff --git a/third_party/rust/chromium_crates_io/Cargo.lock b/third_party/rust/chromium_crates_io/Cargo.lock
index d1baab6..ffe03e96 100644
--- a/third_party/rust/chromium_crates_io/Cargo.lock
+++ b/third_party/rust/chromium_crates_io/Cargo.lock
@@ -285,7 +285,7 @@
 
 [[package]]
 name = "prost-derive"
-version = "0.12.3"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "anyhow",
diff --git a/third_party/rust/chromium_crates_io/supply-chain/audits.toml b/third_party/rust/chromium_crates_io/supply-chain/audits.toml
index 00b1259..e5f924c 100644
--- a/third_party/rust/chromium_crates_io/supply-chain/audits.toml
+++ b/third_party/rust/chromium_crates_io/supply-chain/audits.toml
@@ -724,6 +724,11 @@
 criteria = ["safe-to-run", "does-not-implement-crypto", "ub-risk-0"]
 version = "0.12.3"
 
+[[audits.prost-derive]]
+who = "Adrian Taylor <adetaylor@chromium.org>"
+criteria = ["safe-to-run", "does-not-implement-crypto"]
+delta = "0.12.3 -> 0.12.4"
+
 [[audits.qr_code]]
 who = "Lukasz Anforowicz <lukasza@chromium.org>"
 criteria = ["safe-to-deploy", "does-not-implement-crypto", "ub-risk-0"]
diff --git a/third_party/rust/chromium_crates_io/supply-chain/config.toml b/third_party/rust/chromium_crates_io/supply-chain/config.toml
index db602ef4..cc831cf 100644
--- a/third_party/rust/chromium_crates_io/supply-chain/config.toml
+++ b/third_party/rust/chromium_crates_io/supply-chain/config.toml
@@ -146,7 +146,7 @@
 [policy."proc-macro2:1.0.80"]
 criteria = ["does-not-implement-crypto", "safe-to-deploy", "ub-risk-2"]
 
-[policy."prost-derive:0.12.3"]
+[policy."prost-derive:0.12.4"]
 criteria = ["does-not-implement-crypto", "safe-to-run"]
 
 [policy."prost:0.12.3"]
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/.cargo_vcs_info.json b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/.cargo_vcs_info.json
deleted file mode 100644
index 5e7c6b1b..0000000
--- a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/.cargo_vcs_info.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "git": {
-    "sha1": "907e9f6fbf72262f52333459bbfb27224da1ad72"
-  },
-  "path_in_vcs": "prost-derive"
-}
\ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/.cargo-checksum.json b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/.cargo-checksum.json
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/.cargo-checksum.json
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/.cargo-checksum.json
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/.cargo_vcs_info.json b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/.cargo_vcs_info.json
new file mode 100644
index 0000000..2f45cd3d88
--- /dev/null
+++ b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/.cargo_vcs_info.json
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "38a00d89527728b6d50736d2ede49b5963abc2fd"
+  },
+  "path_in_vcs": "prost-derive"
+}
\ No newline at end of file
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/Cargo.toml b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/Cargo.toml
similarity index 94%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/Cargo.toml
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/Cargo.toml
index 029894d..7e2db46 100644
--- a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/Cargo.toml
+++ b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/Cargo.toml
@@ -11,9 +11,9 @@
 
 [package]
 edition = "2021"
-rust-version = "1.60"
+rust-version = "1.70"
 name = "prost-derive"
-version = "0.12.3"
+version = "0.12.4"
 authors = [
     "Dan Burkert <dan@danburkert.com>",
     "Lucio Franco <luciofranco14@gmail.com>",
@@ -32,7 +32,7 @@
 version = "1.0.1"
 
 [dependencies.itertools]
-version = ">=0.10, <0.12"
+version = ">=0.10, <=0.12"
 features = ["use_alloc"]
 default-features = false
 
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/Cargo.toml.orig b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/Cargo.toml.orig
similarity index 80%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/Cargo.toml.orig
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/Cargo.toml.orig
index 45d5cb464..d4f7b8d2 100644
--- a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/Cargo.toml.orig
+++ b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/Cargo.toml.orig
@@ -1,6 +1,6 @@
 [package]
 name = "prost-derive"
-version = "0.12.3"
+version = "0.12.4"
 authors = [
     "Dan Burkert <dan@danburkert.com>",
     "Lucio Franco <luciofranco14@gmail.com>",
@@ -12,14 +12,14 @@
 readme = "README.md"
 description = "A Protocol Buffers implementation for the Rust Language."
 edition = "2021"
-rust-version = "1.60"
+rust-version = "1.70"
 
 [lib]
 proc_macro = true
 
 [dependencies]
 anyhow = "1.0.1"
-itertools = { version = ">=0.10, <0.12", default-features = false, features = ["use_alloc"] }
+itertools = { version = ">=0.10, <=0.12", default-features = false, features = ["use_alloc"] }
 proc-macro2 = "1"
 quote = "1"
 syn = { version = "2", features = [ "extra-traits" ] }
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/LICENSE b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/LICENSE
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/LICENSE
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/LICENSE
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/README.md b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/README.md
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/README.md
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/README.md
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/group.rs b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/group.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/group.rs
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/group.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/map.rs b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/map.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/map.rs
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/map.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/message.rs b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/message.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/message.rs
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/message.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/mod.rs b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/mod.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/mod.rs
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/mod.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/oneof.rs b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/oneof.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/oneof.rs
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/oneof.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/scalar.rs b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/scalar.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/scalar.rs
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/scalar.rs
diff --git a/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/lib.rs b/third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/lib.rs
similarity index 100%
rename from third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/lib.rs
rename to third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/lib.rs
diff --git a/third_party/rust/prost_derive/v0_12/BUILD.gn b/third_party/rust/prost_derive/v0_12/BUILD.gn
index 3425302..2ac54c8 100644
--- a/third_party/rust/prost_derive/v0_12/BUILD.gn
+++ b/third_party/rust/prost_derive/v0_12/BUILD.gn
@@ -12,21 +12,21 @@
   crate_name = "prost_derive"
   epoch = "0.12"
   crate_type = "proc-macro"
-  crate_root = "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/lib.rs"
+  crate_root = "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/lib.rs"
   sources = [
-    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/group.rs",
-    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/map.rs",
-    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/message.rs",
-    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/mod.rs",
-    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/oneof.rs",
-    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/field/scalar.rs",
-    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/src/lib.rs",
+    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/group.rs",
+    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/map.rs",
+    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/message.rs",
+    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/mod.rs",
+    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/oneof.rs",
+    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/field/scalar.rs",
+    "//third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/src/lib.rs",
   ]
   inputs = []
 
   build_native_rust_unit_tests = false
   edition = "2021"
-  cargo_pkg_version = "0.12.3"
+  cargo_pkg_version = "0.12.4"
   cargo_pkg_authors = "Dan Burkert <dan@danburkert.com>, Lucio Franco <luciofranco14@gmail.com>, Tokio Contributors <team@tokio.rs>"
   cargo_pkg_name = "prost-derive"
   cargo_pkg_description =
diff --git a/third_party/rust/prost_derive/v0_12/README.chromium b/third_party/rust/prost_derive/v0_12/README.chromium
index ec3c0869..16174f7 100644
--- a/third_party/rust/prost_derive/v0_12/README.chromium
+++ b/third_party/rust/prost_derive/v0_12/README.chromium
@@ -1,9 +1,9 @@
 Name: prost-derive
 URL: https://crates.io/crates/prost-derive
 Description: A Protocol Buffers implementation for the Rust Language.
-Version: 0.12.3
+Version: 0.12.4
 Security Critical: no
 Shipped: no
 License: Apache 2.0
-License File: //third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.3/LICENSE
-Revision: 907e9f6fbf72262f52333459bbfb27224da1ad72
+License File: //third_party/rust/chromium_crates_io/vendor/prost-derive-0.12.4/LICENSE
+Revision: 38a00d89527728b6d50736d2ede49b5963abc2fd
diff --git a/third_party/skia b/third_party/skia
index b159229..50ac111 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit b159229f2174800f6655f7b7dbba01d7bd3d5d48
+Subproject commit 50ac1117f1597d57faf9ae5360ad95029c515f97
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index 35d6b77..ea0fb51 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit 35d6b77d10f523580acf18702b28efc2709bc1d2
+Subproject commit ea0fb515f594700d7fc4cbfff49268f596de4984
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index 11efd3b..3f94329 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit 11efd3b4ad23b66ed7aa88e84193833dbe5a7150
+Subproject commit 3f94329188723ae92fc1bdefbcacd659fed2aa8b
diff --git a/third_party/webrtc b/third_party/webrtc
index 181dbeb..d86c0cd 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 181dbebba43f9023754d1f4766e7e4a2516c53a4
+Subproject commit d86c0cdbde7261deefc5771f41a14186bac9fd09
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index a4f4b64..4de9a414 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -18868,6 +18868,8 @@
   <int value="-1035140982" label="ClientSideDetectionModelOnAndroid:enabled"/>
   <int value="-1034344165" label="V8NoTurbo:disabled"/>
   <int value="-1033738911" label="enable-mac-views-dialogs"/>
+  <int value="-1033128341"
+      label="CrOSLateBootCrasProcessorDedicatedThread:enabled"/>
   <int value="-1032884201" label="HeavyAdPrivacyMitigations:enabled"/>
   <int value="-1032545529" label="FileSystemAccessLockingScheme:disabled"/>
   <int value="-1032265414" label="AutocompleteExtendedSuggestions:disabled"/>
@@ -22439,6 +22441,8 @@
   <int value="565406673" label="EnableVirtualKeyboardMdUi:enabled"/>
   <int value="566031886" label="DIPS:disabled"/>
   <int value="567368307" label="enable-experimental-canvas-features"/>
+  <int value="567862349"
+      label="CrOSLateBootCrasProcessorDedicatedThread:disabled"/>
   <int value="569192576" label="UpstreamTrustedReportsFirmware:enabled"/>
   <int value="570445904" label="WGIGamepadTriggerRumble:disabled"/>
   <int value="570469494" label="LoginDetection:disabled"/>
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index 7b73808..d81ea7e3 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -1237,7 +1237,7 @@
 </histogram>
 
 <histogram name="Accessibility.ImageLabels.ModalDialogAccepted"
-    enum="BooleanAccepted" expires_after="2024-08-11">
+    enum="BooleanAccepted" expires_after="2024-10-13">
   <owner>katie@chromium.org</owner>
   <owner>dtseng@chromium.org</owner>
   <summary>
@@ -1637,7 +1637,7 @@
 </histogram>
 
 <histogram name="Accessibility.LiveTranslate.CharactersTranslated"
-    units="count" expires_after="2024-08-14">
+    units="count" expires_after="2024-10-13">
   <owner>evliu@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -1647,7 +1647,7 @@
 </histogram>
 
 <histogram name="Accessibility.LiveTranslate.EnableFrom{Entrypoint}"
-    enum="BooleanEnabled" expires_after="2024-08-14">
+    enum="BooleanEnabled" expires_after="2024-10-13">
   <owner>evliu@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -1663,7 +1663,7 @@
 </histogram>
 
 <histogram name="Accessibility.LiveTranslate.SourceLanguage"
-    enum="LocaleCodeISO639" expires_after="2024-08-14">
+    enum="LocaleCodeISO639" expires_after="2024-10-13">
   <owner>evliu@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
@@ -1673,7 +1673,7 @@
 </histogram>
 
 <histogram name="Accessibility.LiveTranslate.TargetLanguage"
-    enum="LocaleCodeISO639" expires_after="2024-08-14">
+    enum="LocaleCodeISO639" expires_after="2024-10-13">
   <owner>evliu@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index d4626a5..7f9f056 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1041,7 +1041,7 @@
 </histogram>
 
 <histogram name="Android.DownloadManager.OpenSource.Audio"
-    enum="AndroidDownloadOpenSource" expires_after="2024-08-11">
+    enum="AndroidDownloadOpenSource" expires_after="2024-10-13">
   <owner>shaktisahu@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
   <summary>
@@ -1110,7 +1110,7 @@
 </histogram>
 
 <histogram name="Android.DownloadPage.OpenSource"
-    enum="AndroidDownloadOpenSource" expires_after="2024-08-11">
+    enum="AndroidDownloadOpenSource" expires_after="2024-10-13">
   <owner>qinmin@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
   <summary>
@@ -1884,7 +1884,7 @@
 </histogram>
 
 <histogram name="Android.InputDevice.Keyboard.Active" enum="Boolean"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>aishwaryarj@google.com</owner>
   <owner>clank-large-form-factors@google.com</owner>
   <summary>
@@ -1893,7 +1893,7 @@
 </histogram>
 
 <histogram name="Android.InputDevice.Mouse.Active" enum="Boolean"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>aishwaryarj@google.com</owner>
   <owner>clank-large-form-factors@google.com</owner>
   <summary>Emitted when a mouse is connected or disconnected.</summary>
@@ -2311,7 +2311,7 @@
 </histogram>
 
 <histogram name="Android.MultiInstance.MaxWindowLimitExceeded" enum="Boolean"
-    expires_after="2024-06-15">
+    expires_after="2024-10-13">
   <owner>ranjithkagathi@google.com</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -2501,7 +2501,7 @@
 </histogram>
 
 <histogram name="Android.Omnibox.InvalidMatch" enum="MatchResult"
-    expires_after="2024-08-14">
+    expires_after="2024-10-13">
   <owner>ender@chromium.org</owner>
   <owner>chrome-mobile-search@google.com</owner>
   <summary>
@@ -2887,7 +2887,7 @@
 </histogram>
 
 <histogram name="Android.Omnibox.UsedSuggestionFromCache" enum="Boolean"
-    expires_after="2024-08-14">
+    expires_after="2024-10-13">
   <owner>ender@chromium.org</owner>
   <owner>chrome-mobile-search@google.com</owner>
   <summary>
@@ -3673,7 +3673,7 @@
 </histogram>
 
 <histogram name="Android.RestoreTabsOnFRE.ResultActionFirstShow2"
-    enum="RestoreTabsOnFREResultAction" expires_after="2024-08-11">
+    enum="RestoreTabsOnFREResultAction" expires_after="2024-10-13">
   <owner>ckitagawa@chromium.org</owner>
   <owner>bjfong@google.com</owner>
   <owner>fredmello@chromium.org</owner>
@@ -3684,7 +3684,7 @@
 </histogram>
 
 <histogram name="Android.RestoreTabsOnFRE.ResultActionSecondShow2"
-    enum="RestoreTabsOnFREResultAction" expires_after="2024-08-11">
+    enum="RestoreTabsOnFREResultAction" expires_after="2024-10-13">
   <owner>ckitagawa@chromium.org</owner>
   <owner>bjfong@google.com</owner>
   <owner>fredmello@chromium.org</owner>
@@ -4346,7 +4346,7 @@
 </histogram>
 
 <histogram name="Android.TabSwitcher.SetupRecyclerView.Time" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>skavuluru@chromium.org</owner>
   <owner>twellington@chromium.org</owner>
   <owner>clank-large-form-factors@google.com</owner>
@@ -4558,7 +4558,7 @@
 </histogram>
 
 <histogram name="Android.WebContentsState.SavedStateVersion" units="version"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>ellyjones@chromium.org</owner>
   <owner>src/chrome/browser/tab/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/arc/histograms.xml b/tools/metrics/histograms/metadata/arc/histograms.xml
index 7faf9f7..2edc0498 100644
--- a/tools/metrics/histograms/metadata/arc/histograms.xml
+++ b/tools/metrics/histograms/metadata/arc/histograms.xml
@@ -1684,7 +1684,7 @@
 </histogram>
 
 <histogram name="Arc.OptInNetworkErrorAction" enum="ArcOptInNetworkErrorAction"
-    expires_after="2024-08-13">
+    expires_after="2024-10-13">
   <owner>mhasank@google.com</owner>
   <owner>arc-core@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/ash/histograms.xml b/tools/metrics/histograms/metadata/ash/histograms.xml
index 18fac77..ed59586 100644
--- a/tools/metrics/histograms/metadata/ash/histograms.xml
+++ b/tools/metrics/histograms/metadata/ash/histograms.xml
@@ -2520,7 +2520,7 @@
 </histogram>
 
 <histogram name="Ash.Desks.DeskLifetime_{DeskIndex}" units="hr"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>afakhry@chromium.org</owner>
   <owner>janetmac@chromium.org</owner>
   <summary>
@@ -5710,7 +5710,7 @@
 </histogram>
 
 <histogram name="Ash.Overview.Enter.PresentationTime" units="ms"
-    expires_after="2024-08-14">
+    expires_after="2024-10-13">
   <owner>sammiequon@chromium.org</owner>
   <owner>zxdan@chromium.org</owner>
   <owner>chromeos-wm-corexp@google.com</owner>
@@ -5984,7 +5984,7 @@
 </histogram>
 
 <histogram name="Ash.Personalization.App.Duration" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>pzliu@google.com</owner>
   <owner>assistive-eng@google.com</owner>
   <summary>
@@ -5994,7 +5994,7 @@
 </histogram>
 
 <histogram name="Ash.Personalization.DynamicColor.ColorScheme.Settled"
-    enum="PersonalizationDynamicColorColorScheme" expires_after="2024-08-11">
+    enum="PersonalizationDynamicColorColorScheme" expires_after="2024-10-13">
   <owner>ericamlee@chromium.org</owner>
   <owner>assistive-eng@google.com</owner>
   <summary>
@@ -6014,7 +6014,7 @@
 </histogram>
 
 <histogram name="Ash.Personalization.DynamicColor.StaticColor.Settled"
-    enum="PersonalizationDynamicColorStaticColor" expires_after="2024-08-11">
+    enum="PersonalizationDynamicColorStaticColor" expires_after="2024-10-13">
   <owner>ericamlee@chromium.org</owner>
   <owner>assistive-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/autofill/histograms.xml b/tools/metrics/histograms/metadata/autofill/histograms.xml
index 12830c5..adfe215 100644
--- a/tools/metrics/histograms/metadata/autofill/histograms.xml
+++ b/tools/metrics/histograms/metadata/autofill/histograms.xml
@@ -4636,7 +4636,7 @@
 
 <histogram name="Autofill.SharedStorageServerCardDataSetResult"
     enum="AutofillSharedStorageServerCardDataSetResult"
-    expires_after="2024-08-01">
+    expires_after="2024-10-13">
   <owner>nburris@google.com</owner>
   <owner>payments-autofill-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/background/histograms.xml b/tools/metrics/histograms/metadata/background/histograms.xml
index 02a63dc5..2b29758 100644
--- a/tools/metrics/histograms/metadata/background/histograms.xml
+++ b/tools/metrics/histograms/metadata/background/histograms.xml
@@ -186,7 +186,7 @@
 </histogram>
 
 <histogram name="BackgroundSync.Registration.OneShot"
-    enum="BackgroundSyncStatus" expires_after="2024-06-30">
+    enum="BackgroundSyncStatus" expires_after="2024-10-13">
   <owner>nator@chromium.org</owner>
   <owner>rayankans@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index af0edd9..c7bd2ee 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -1350,7 +1350,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.ClosedSheetType.Android" enum="FedCmSheetType"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>npm@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -1466,7 +1466,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.IdpSigninStatus.ClosePopupWindowReason"
-    enum="FedCmClosePopupWindowReason" expires_after="2024-08-11">
+    enum="FedCmClosePopupWindowReason" expires_after="2024-10-13">
   <owner>tanzachary@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -1489,7 +1489,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.IdpSigninStatus.MismatchDialogResult"
-    enum="FedCmMismatchDialogResult" expires_after="2024-08-11">
+    enum="FedCmMismatchDialogResult" expires_after="2024-10-13">
   <owner>tanzachary@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -1499,7 +1499,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.IdpSigninStatus.PopupWindowResult"
-    enum="FedCmPopupWindowResult" expires_after="2024-08-11">
+    enum="FedCmPopupWindowResult" expires_after="2024-10-13">
   <owner>tanzachary@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -1509,7 +1509,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.IdpSigninStatus.ShowPopupWindowResult"
-    enum="FedCmShowPopupWindowResult" expires_after="2024-08-11">
+    enum="FedCmShowPopupWindowResult" expires_after="2024-10-13">
   <owner>tanzachary@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -1826,7 +1826,7 @@
 </histogram>
 
 <histogram name="Blink.FedCm.Timing.MismatchDialogShownDuration" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>tanzachary@chromium.org</owner>
   <owner>web-identity-eng@google.com</owner>
   <summary>
@@ -2013,7 +2013,7 @@
 </histogram>
 
 <histogram name="Blink.FencedFrame.CreationOrNavigationOutcome"
-    enum="FencedFrameCreationOutcome" expires_after="2024-08-08">
+    enum="FencedFrameCreationOutcome" expires_after="2024-10-13">
   <owner>shivanisha@chromium.org</owner>
   <owner>dom@chromium.org</owner>
   <owner>lbrady@google.com</owner>
@@ -2025,7 +2025,7 @@
 </histogram>
 
 <histogram name="Blink.FencedFrame.FailedSandboxLoadInTopLevelFrame"
-    enum="BooleanYesNo" expires_after="2024-08-08">
+    enum="BooleanYesNo" expires_after="2024-10-13">
   <owner>shivanisha@chromium.org</owner>
   <owner>dom@chromium.org</owner>
   <owner>lbrady@google.com</owner>
@@ -2067,7 +2067,7 @@
 </histogram>
 
 <histogram name="Blink.FencedFrame.MandatoryUnsandboxedFlagsSandboxed"
-    enum="WebSandboxFlags" expires_after="2024-08-08">
+    enum="WebSandboxFlags" expires_after="2024-10-13">
   <owner>shivanisha@chromium.org</owner>
   <owner>dom@chromium.org</owner>
   <owner>lbrady@google.com</owner>
@@ -2103,7 +2103,7 @@
 </histogram>
 
 <histogram name="Blink.FetchQueuedPreloadsTime.{FrameType}.{IsInitial}"
-    units="ms" expires_after="2024-08-11">
+    units="ms" expires_after="2024-10-13">
   <owner>cduvall@chromium.org</owner>
   <owner>jam@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
@@ -3765,7 +3765,7 @@
 </histogram>
 
 <histogram name="Blink.ScanAndPreloadTime.{FrameType}.{IsInitial}" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>cduvall@chromium.org</owner>
   <owner>jam@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
@@ -4794,7 +4794,7 @@
 </histogram>
 
 <histogram name="Blink.{CookieOperation}Time" units="ms"
-    expires_after="2024-08-09">
+    expires_after="2024-10-13">
   <owner>cduvall@chromium.org</owner>
   <owner>jam@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/bluetooth/histograms.xml b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
index 79bfeb4..f1185d02 100644
--- a/tools/metrics/histograms/metadata/bluetooth/histograms.xml
+++ b/tools/metrics/histograms/metadata/bluetooth/histograms.xml
@@ -53,7 +53,7 @@
 </variants>
 
 <histogram name="Bluetooth.BlueZ.DBus.{MethodName}.Latency" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>jonmann@chromium.org</owner>
   <owner>chromeos-cross-device-eng@google.com</owner>
   <summary>Tracks the latency of the BlueZ {MethodName} DBus method.</summary>
@@ -1417,7 +1417,7 @@
 
 <histogram
     name="Bluetooth.ChromeOS.FastPair.SavedDevices.GetSavedDevices.Result"
-    enum="BooleanSuccess" expires_after="2024-08-04">
+    enum="BooleanSuccess" expires_after="2024-10-13">
   <owner>jackshira@google.com</owner>
   <owner>dclasson@google.com</owner>
   <owner>brandosocarras@google.com</owner>
@@ -1431,7 +1431,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ChromeOS.FastPair.SavedDevices.Remove.Result"
-    enum="BooleanSuccess" expires_after="2024-06-28">
+    enum="BooleanSuccess" expires_after="2024-10-13">
   <owner>jackshira@google.com</owner>
   <owner>dclasson@google.com</owner>
   <owner>brandosocarras@google.com</owner>
@@ -1445,7 +1445,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ChromeOS.FastPair.SavedDevices.TotalUxLoadTime"
-    units="ms" expires_after="2024-06-28">
+    units="ms" expires_after="2024-10-13">
   <owner>jackshira@google.com</owner>
   <owner>dclasson@google.com</owner>
   <owner>brandosocarras@google.com</owner>
@@ -1465,7 +1465,7 @@
 </histogram>
 
 <histogram name="Bluetooth.ChromeOS.FastPair.SavedDevices.UiEvent"
-    enum="FastPairSavedDevicesUiEvent" expires_after="2024-06-28">
+    enum="FastPairSavedDevicesUiEvent" expires_after="2024-10-13">
   <owner>jackshira@google.com</owner>
   <owner>dclasson@google.com</owner>
   <owner>brandosocarras@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/borealis/histograms.xml b/tools/metrics/histograms/metadata/borealis/histograms.xml
index 8bbd151d..37b3fad6 100644
--- a/tools/metrics/histograms/metadata/borealis/histograms.xml
+++ b/tools/metrics/histograms/metadata/borealis/histograms.xml
@@ -185,7 +185,7 @@
 </histogram>
 
 <histogram name="Borealis.Install.OverallTime2" units="ms"
-    expires_after="2024-08-09">
+    expires_after="2024-10-13">
   <owner>philpearson@google.com</owner>
   <owner>src/chrome/browser/ash/borealis/OWNERS</owner>
   <improvement direction="LOWER_IS_BETTER"/>
@@ -201,7 +201,7 @@
 </histogram>
 
 <histogram name="Borealis.Install.Result" enum="BorealisInstallResult"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>philpearson@google.com</owner>
   <owner>src/chrome/browser/ash/borealis/OWNERS</owner>
   <summary>
@@ -253,7 +253,7 @@
 </histogram>
 
 <histogram name="Borealis.Startup.NumAttempts" enum="BooleanAttempted"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>philpearson@google.com</owner>
   <owner>src/chrome/browser/ash/borealis/OWNERS</owner>
   <summary>Recording every attempt to start Borealis (via the UI).</summary>
@@ -272,7 +272,7 @@
 </histogram>
 
 <histogram name="Borealis.Startup.OverallTime2" units="ms"
-    expires_after="2024-08-09">
+    expires_after="2024-10-13">
   <owner>philpearson@google.com</owner>
   <owner>src/chrome/browser/ash/borealis/OWNERS</owner>
   <improvement direction="LOWER_IS_BETTER"/>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 67c79b4..4223a94 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -62,7 +62,7 @@
 </histogram>
 
 <histogram name="Browser.ChromeOS.HatsStatus" enum="HatsStatus"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>aalsum@chromium.org</owner>
   <owner>chromeos-data-eng@google.com</owner>
   <summary>
@@ -762,7 +762,7 @@
 </histogram>
 
 <histogram name="Browser.PaintPreview.TabService.DiskUsageAtStartup" units="KB"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <summary>
@@ -890,7 +890,7 @@
 </histogram>
 
 <histogram name="Browser.Tabs.TotalIncompleteSwitchDuration3{TabSwitchingType}"
-    units="ms" expires_after="2024-08-04">
+    units="ms" expires_after="2024-10-13">
   <owner>fdoray@chromium.org</owner>
   <owner>jonross@chromium.org</owner>
   <owner>joenotcharles@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/chrome/histograms.xml b/tools/metrics/histograms/metadata/chrome/histograms.xml
index 139ad3e4..d79e809 100644
--- a/tools/metrics/histograms/metadata/chrome/histograms.xml
+++ b/tools/metrics/histograms/metadata/chrome/histograms.xml
@@ -124,7 +124,7 @@
 </histogram>
 
 <histogram name="Chrome.KAnonymityService.TrustTokenGetter.Action"
-    enum="KAnonymityTrustTokenGetterAction" expires_after="M127">
+    enum="KAnonymityTrustTokenGetterAction" expires_after="2024-10-13">
   <owner>behamilton@google.com</owner>
   <owner>pauljensen@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 3d39ce04..d263e869 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -2344,7 +2344,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Printing.TimeCostOfFailedFoomaticShell"
-    units="seconds" expires_after="2024-06-30">
+    units="seconds" expires_after="2024-10-13">
   <owner>pawliczek@chromium.org</owner>
   <owner>bmgordon@chromium.org</owner>
   <owner>project-bolton@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
index d2cd1631..c2dc00a 100644
--- a/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos_settings/histograms.xml
@@ -779,7 +779,7 @@
 </histogram>
 
 <histogram name="ChromeOS.Settings.SnapWindowSuggestions" enum="BooleanToggled"
-    expires_after="2024-08-08">
+    expires_after="2024-10-13">
   <owner>sophiewen@chromium.org</owner>
   <owner>michelefan@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/commerce/histograms.xml b/tools/metrics/histograms/metadata/commerce/histograms.xml
index 118b93cf..c634bae 100644
--- a/tools/metrics/histograms/metadata/commerce/histograms.xml
+++ b/tools/metrics/histograms/metadata/commerce/histograms.xml
@@ -532,7 +532,7 @@
 </histogram>
 
 <histogram name="Commerce.PriceTracking.PriceInsightsSidePanel.{Action}"
-    enum="PriceInsightsPriceBucket" expires_after="2024-08-04">
+    enum="PriceInsightsPriceBucket" expires_after="2024-10-13">
   <owner>yuezhanggg@chromium.org</owner>
   <owner>zhiyuancai@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/companion/histograms.xml b/tools/metrics/histograms/metadata/companion/histograms.xml
index 776503d..aabec2a 100644
--- a/tools/metrics/histograms/metadata/companion/histograms.xml
+++ b/tools/metrics/histograms/metadata/companion/histograms.xml
@@ -160,7 +160,7 @@
 </histogram>
 
 <histogram name="Companion.VisualQuery.Agent.DomImageCount" units="images"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>srna@google.com</owner>
   <owner>pstjuste@google.com</owner>
   <owner>src/chrome/browser/companion/OWNERS</owner>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index ac46ba7..17a8c8fe 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -969,7 +969,7 @@
 </histogram>
 
 <histogram name="CompositorLatency.IpcThread.{LatencyType}"
-    units="microseconds" expires_after="2024-08-11">
+    units="microseconds" expires_after="2024-10-13">
   <owner>jonross@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/content/histograms.xml b/tools/metrics/histograms/metadata/content/histograms.xml
index 3967e55..6694a76 100644
--- a/tools/metrics/histograms/metadata/content/histograms.xml
+++ b/tools/metrics/histograms/metadata/content/histograms.xml
@@ -406,7 +406,7 @@
 </histogram>
 
 <histogram name="ContentSettings.RegularProfile.DefaultBackgroundSyncSetting"
-    enum="ContentSetting" expires_after="2024-08-11">
+    enum="ContentSetting" expires_after="2024-10-13">
   <owner>tungnh@chromium.org</owner>
   <owner>src/components/permissions/PERMISSIONS_OWNERS</owner>
   <summary>
@@ -650,7 +650,7 @@
 
 <histogram
     name="ContentSettings.{RegularProfileFiltered}DefaultRequestDesktopSiteSetting"
-    enum="ContentSetting" expires_after="2024-08-11">
+    enum="ContentSetting" expires_after="2024-10-13">
   <owner>shuyng@google.com</owner>
   <owner>twellington@chromium.org</owner>
   <summary>
@@ -824,7 +824,7 @@
 </histogram>
 
 <histogram name="ContentSuggestions.Feed.ActivityLoggingEnabled" enum="Boolean"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>dewittj@chromium.org</owner>
   <owner>feed@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
index 5bc2cd51..91df4f23 100644
--- a/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
+++ b/tools/metrics/histograms/metadata/custom_tabs/histograms.xml
@@ -364,7 +364,7 @@
 </histogram>
 
 <histogram name="CustomTabs.Minimized.MinimizeSuccess" enum="Boolean"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>sinansahin@google.com</owner>
   <owner>chrome-connective-tissue@google.com</owner>
   <summary>
@@ -385,7 +385,7 @@
 </histogram>
 
 <histogram name="CustomTabs.MinimizedEvents" enum="CustomTabsMinimizedEvents"
-    expires_after="2024-08-04">
+    expires_after="2024-10-13">
   <owner>katzz@google.com</owner>
   <owner>chrome-connective-tissue@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/dev/histograms.xml b/tools/metrics/histograms/metadata/dev/histograms.xml
index 6a6f0ed..dcee2d8 100644
--- a/tools/metrics/histograms/metadata/dev/histograms.xml
+++ b/tools/metrics/histograms/metadata/dev/histograms.xml
@@ -39,7 +39,7 @@
 </histogram>
 
 <histogram name="DevTools.AnimationPlaybackRateChanged"
-    enum="DevToolsAnimationPlaybackRateChanged" expires_after="2024-08-08">
+    enum="DevToolsAnimationPlaybackRateChanged" expires_after="2024-10-13">
   <owner>yangguo@chromium.org</owner>
   <owner>changhaohan@chromium.org</owner>
   <owner>ergunsh@chromium.org</owner>
@@ -47,7 +47,7 @@
 </histogram>
 
 <histogram name="DevTools.AnimationPointDragged"
-    enum="DevToolsAnimationPointDragged" expires_after="2024-08-08">
+    enum="DevToolsAnimationPointDragged" expires_after="2024-10-13">
   <owner>yangguo@chromium.org</owner>
   <owner>changhaohan@chromium.org</owner>
   <owner>ergunsh@chromium.org</owner>
@@ -644,7 +644,7 @@
   </summary>
 </histogram>
 
-<histogram name="DevTools.TraceLoad" units="ms" expires_after="2024-08-12">
+<histogram name="DevTools.TraceLoad" units="ms" expires_after="2024-10-13">
   <owner>paulirish@chromium.org</owner>
   <owner>jacktfranklin@chromium.org</owner>
   <owner>victorporof@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/disk/histograms.xml b/tools/metrics/histograms/metadata/disk/histograms.xml
index 57d01f3..bf8b43e 100644
--- a/tools/metrics/histograms/metadata/disk/histograms.xml
+++ b/tools/metrics/histograms/metadata/disk/histograms.xml
@@ -34,7 +34,7 @@
 </histogram>
 
 <histogram name="DiskCache.0.TotalIOTimeRead" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>jam@chromium.org</owner>
   <owner>swarm-team@google.com</owner>
   <summary>
@@ -45,7 +45,7 @@
 </histogram>
 
 <histogram name="DiskCache.0.TotalIOTimeWrite" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>jam@chromium.org</owner>
   <owner>swarm-team@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index 72f3828..68f1b9c96 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -2401,7 +2401,7 @@
 </histogram>
 
 <histogram name="Enterprise.ManagedScreensaver.Enabled" enum="BooleanEnabled"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>mpetrisor@chromium.org</owner>
   <owner>imprivata-eng@google.com</owner>
   <summary>
@@ -3055,7 +3055,7 @@
 </histogram>
 
 <histogram name="Enterprise.UserPolicy.Count" units="policies"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>vincb@google.com</owner>
   <owner>ftirelo@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/event/histograms.xml b/tools/metrics/histograms/metadata/event/histograms.xml
index 11d2a1f..184443e2 100644
--- a/tools/metrics/histograms/metadata/event/histograms.xml
+++ b/tools/metrics/histograms/metadata/event/histograms.xml
@@ -346,7 +346,7 @@
 </histogram>
 
 <histogram name="Event.Latency.OS2.{EventType}" units="ms"
-    expires_after="2024-08-14">
+    expires_after="2024-10-13">
   <owner>flackr@chromium.org</owner>
   <owner>joenotcharles@google.com</owner>
   <owner>input-dev@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/extensions/histograms.xml b/tools/metrics/histograms/metadata/extensions/histograms.xml
index 37809d6..854cf8a 100644
--- a/tools/metrics/histograms/metadata/extensions/histograms.xml
+++ b/tools/metrics/histograms/metadata/extensions/histograms.xml
@@ -1734,7 +1734,7 @@
 </histogram>
 
 <histogram name="Extensions.ExtensionReenabledRemotely" units="count"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>anunoy@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -3644,7 +3644,7 @@
 </histogram>
 
 <histogram name="Extensions.ManifestVersion2Count.{ManifestLocation}"
-    units="number of extensions" expires_after="2024-08-14">
+    units="number of extensions" expires_after="2024-10-13">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -3657,7 +3657,7 @@
 </histogram>
 
 <histogram name="Extensions.ManifestVersion3Count.{ManifestLocation}"
-    units="number of extensions" expires_after="2024-08-14">
+    units="number of extensions" expires_after="2024-10-13">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
@@ -3670,7 +3670,7 @@
 </histogram>
 
 <histogram name="Extensions.ManifestVersionByLocation.{ManifestLocation}"
-    units="manifest version" expires_after="2024-08-14">
+    units="manifest version" expires_after="2024-10-13">
   <owner>rdevlin.cronin@chromium.org</owner>
   <owner>extensions-core@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/file/histograms.xml b/tools/metrics/histograms/metadata/file/histograms.xml
index 7cb3f1d..00022146 100644
--- a/tools/metrics/histograms/metadata/file/histograms.xml
+++ b/tools/metrics/histograms/metadata/file/histograms.xml
@@ -1526,7 +1526,7 @@
 </histogram>
 
 <histogram name="FileBrowser.Recent.LoadFileSystemProvider" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>simmonsjosh@google.com</owner>
   <owner>src/ui/file_manager/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
index 506620e..2216094 100644
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
@@ -879,7 +879,7 @@
 </histogram>
 
 <histogram name="Gpu.FenceHandle.CloneCountsPerSubmit" units="clones"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>petermcneeley@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
   <summary>
@@ -1188,7 +1188,7 @@
 </histogram>
 
 <histogram name="GPU.PaintOpReader.DeserializationError"
-    enum="PaintOpDeserializationError" expires_after="2024-08-11">
+    enum="PaintOpDeserializationError" expires_after="2024-10-13">
   <owner>junov@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
   <summary>
@@ -1640,7 +1640,7 @@
 </histogram>
 
 <histogram name="GPU.{GraphiteDawnOrWebGPU}.Support.{FeatureName}"
-    enum="Boolean" expires_after="2024-08-11">
+    enum="Boolean" expires_after="2024-10-13">
   <owner>enga@chromium.org</owner>
   <owner>mdb.webgpu-dev-team@google.com</owner>
   <summary>
@@ -1715,7 +1715,7 @@
 </histogram>
 
 <histogram name="Viz.BeginFrameSource.Accuracy.AverageDelta2"
-    units="microseconds" expires_after="2024-08-11">
+    units="microseconds" expires_after="2024-10-13">
   <owner>magchen@chromium.org</owner>
   <owner>ccameron@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
@@ -1845,7 +1845,7 @@
 </histogram>
 
 <histogram name="Viz.FileDescriptorTracking.{FdStat}" units="units"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>petermcneeley@chromium.org</owner>
   <owner>graphics-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/history/histograms.xml b/tools/metrics/histograms/metadata/history/histograms.xml
index 91a8e74..9a2d243 100644
--- a/tools/metrics/histograms/metadata/history/histograms.xml
+++ b/tools/metrics/histograms/metadata/history/histograms.xml
@@ -1452,7 +1452,7 @@
 </histogram>
 
 <histogram name="History.DatabaseBasicMetricsTime" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>sophiechang@chromium.org</owner>
   <owner>tommycli@chromium.org</owner>
   <owner>treib@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/ios/histograms.xml b/tools/metrics/histograms/metadata/ios/histograms.xml
index e7b74650..1a40866c 100644
--- a/tools/metrics/histograms/metadata/ios/histograms.xml
+++ b/tools/metrics/histograms/metadata/ios/histograms.xml
@@ -120,7 +120,7 @@
 </variants>
 
 <histogram name="IOS.Allocator.ShimInstalled" enum="Boolean"
-    expires_after="2024-08-08">
+    expires_after="2024-10-13">
   <owner>rohitrao@chromium.org</owner>
   <owner>justincohen@chromium.org</owner>
   <summary>
@@ -170,7 +170,7 @@
 </histogram>
 
 <histogram name="IOS.BackgroundTimeBeforeColdStart" units="minutes"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>thegreenfrog@chromium.org</owner>
   <owner>bling-team@google.com</owner>
   <summary>
@@ -2094,7 +2094,7 @@
 
 <histogram
     name="IOS.Omnibox.Promo.SelectedPosition.{Context}.{OmniboxDeviceSwitcherResult}"
-    enum="OmniboxPromoSelectedPositions" expires_after="2024-08-11">
+    enum="OmniboxPromoSelectedPositions" expires_after="2024-10-13">
   <owner>christianxu@chromium.org</owner>
   <owner>bling-team@google.com</owner>
   <summary>
@@ -2736,7 +2736,7 @@
 </histogram>
 
 <histogram name="IOS.PushNotification.ChimeDeviceRegistration"
-    enum="BooleanSuccess" expires_after="2024-08-11">
+    enum="BooleanSuccess" expires_after="2024-10-13">
   <owner>ajuma@google.com</owner>
   <owner>danieltwhite@google.com</owner>
   <summary>
@@ -2849,7 +2849,7 @@
 <histogram
     name="IOS.PushNotification.NotificationSettingsAuthorizationStatus.ByProvider"
     enum="PushNotificationSettingsAuthorizationStatus"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>guiperez@google.com</owner>
   <owner>chrome-sherlock@google.com</owner>
   <summary>
@@ -4255,7 +4255,7 @@
 </histogram>
 
 <histogram name="IOS.WhatsNew.InstructionsShown" enum="WhatsNewType"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>cheickcisse@google.com</owner>
   <owner>bling-mony-pod@google.com</owner>
   <summary>
@@ -4265,7 +4265,7 @@
 </histogram>
 
 <histogram name="IOS.WhatsNew.ItemsClickedCount" units="count"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>cheickcisse@google.com</owner>
   <owner>bling-mony-pod@google.com</owner>
   <summary>The number of clicked What's New items per impression</summary>
diff --git a/tools/metrics/histograms/metadata/kiosk/histograms.xml b/tools/metrics/histograms/metadata/kiosk/histograms.xml
index a689af0e..330e743 100644
--- a/tools/metrics/histograms/metadata/kiosk/histograms.xml
+++ b/tools/metrics/histograms/metadata/kiosk/histograms.xml
@@ -162,7 +162,7 @@
 </histogram>
 
 <histogram name="Kiosk.Extensions.InstallTimedOut" enum="BooleanYesNo"
-    expires_after="2024-08-01">
+    expires_after="2024-10-13">
   <owner>yixie@chromium.org</owner>
   <owner>chromeos-kiosk-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/login/histograms.xml b/tools/metrics/histograms/metadata/login/histograms.xml
index 26d30b3..707dd56 100644
--- a/tools/metrics/histograms/metadata/login/histograms.xml
+++ b/tools/metrics/histograms/metadata/login/histograms.xml
@@ -86,7 +86,7 @@
 </histogram>
 
 <histogram name="Login.DevicePolicyState" enum="DevicePoliciesState"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>igorcov@chromium.org</owner>
   <owner>chromeos-commercial-remote-management@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/magic_stack/histograms.xml b/tools/metrics/histograms/metadata/magic_stack/histograms.xml
index 85065b1..f360e1b 100644
--- a/tools/metrics/histograms/metadata/magic_stack/histograms.xml
+++ b/tools/metrics/histograms/metadata/magic_stack/histograms.xml
@@ -52,7 +52,7 @@
 </variants>
 
 <histogram name="MagicStack.Clank.Settings.{ToggleState}" enum="ModuleType"
-    expires_after="2024-06-20">
+    expires_after="2024-10-13">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
@@ -131,7 +131,7 @@
 
 <histogram
     name="MagicStack.Clank.{ModuleDelegateHost}.ContextMenu.RemoveModule."
-    enum="ModuleType" expires_after="2024-06-20">
+    enum="ModuleType" expires_after="2024-10-13">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
@@ -277,7 +277,7 @@
 
 <histogram
     name="MagicStack.Clank.{ModuleDelegateHost}.Module.FetchDataTimeoutType."
-    enum="ModuleType" expires_after="2024-06-20">
+    enum="ModuleType" expires_after="2024-10-13">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
@@ -332,7 +332,7 @@
 </histogram>
 
 <histogram name="MagicStack.Clank.{ModuleDelegateHost}.Module.TopImpression."
-    enum="ModuleType" expires_after="2024-06-20">
+    enum="ModuleType" expires_after="2024-10-13">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
@@ -397,7 +397,7 @@
 </histogram>
 
 <histogram name="MagicStack.Clank.{ModuleDelegateHost}.NotScrollable"
-    units="count" expires_after="2024-06-20">
+    units="count" expires_after="2024-10-13">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
@@ -424,7 +424,7 @@
 
 <histogram
     name="MagicStack.Clank.{ModuleDelegateHost}.Segmentation.FetchRankingResultsDurationMs"
-    units="ms" expires_after="2024-06-20">
+    units="ms" expires_after="2024-10-13">
   <owner>hanxi@chromium.org</owner>
   <owner>xinyiji@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/media/histograms.xml b/tools/metrics/histograms/metadata/media/histograms.xml
index 112e159..62f3df5 100644
--- a/tools/metrics/histograms/metadata/media/histograms.xml
+++ b/tools/metrics/histograms/metadata/media/histograms.xml
@@ -317,7 +317,7 @@
 </histogram>
 
 <histogram name="Media.AImageReaderGLOwner.AcquireImageResult"
-    enum="MediaStatus" expires_after="2024-08-11">
+    enum="MediaStatus" expires_after="2024-10-13">
   <owner>vikassoni@chromium.org</owner>
   <owner>media-dev-uma@chromium.org</owner>
   <summary>
@@ -5742,7 +5742,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Device.SupportedPixelFormat"
-    enum="VideoPixelFormatUnion" expires_after="2024-08-11">
+    enum="VideoPixelFormatUnion" expires_after="2024-10-13">
   <owner>eshr@google.com</owner>
   <owner>handellm@google.com</owner>
   <owner>atadres@chromium.org</owner>
@@ -5996,7 +5996,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Win.Device.CaptureBeginTime.Interval"
-    units="ms" expires_after="2024-06-30">
+    units="ms" expires_after="2024-10-13">
   <owner>ilnik@google.com</owner>
   <owner>video-cmi-mpp@google.com</owner>
   <summary>
@@ -6007,7 +6007,7 @@
 </histogram>
 
 <histogram name="Media.VideoCapture.Win.Device.CaptureBeginTime.MFTimeOffset"
-    units="ms" expires_after="2024-06-30">
+    units="ms" expires_after="2024-10-13">
   <owner>ilnik@google.com</owner>
   <owner>video-cmi-mpp@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/memory/histograms.xml b/tools/metrics/histograms/metadata/memory/histograms.xml
index 934627e..d2051a5 100644
--- a/tools/metrics/histograms/metadata/memory/histograms.xml
+++ b/tools/metrics/histograms/metadata/memory/histograms.xml
@@ -1633,7 +1633,7 @@
 </histogram>
 
 <histogram name="Memory.ParkableString.DiskWriteTime.5min" units="ms"
-    expires_after="2024-06-30">
+    expires_after="2024-10-13">
   <owner>lizeb@chromium.org</owner>
   <owner>pasko@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/mobile/histograms.xml b/tools/metrics/histograms/metadata/mobile/histograms.xml
index 2870e77..9826bb5 100644
--- a/tools/metrics/histograms/metadata/mobile/histograms.xml
+++ b/tools/metrics/histograms/metadata/mobile/histograms.xml
@@ -91,7 +91,7 @@
 </histogram>
 
 <histogram name="Mobile.ContextMenu.CopyImage" enum="ContextMenuIOSCopyImage"
-    expires_after="2024-03-10">
+    expires_after="2025-03-10">
   <owner>djean@chromium.org</owner>
   <owner>gambard@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/navigation/histograms.xml b/tools/metrics/histograms/metadata/navigation/histograms.xml
index 8be9e33..8efccca 100644
--- a/tools/metrics/histograms/metadata/navigation/histograms.xml
+++ b/tools/metrics/histograms/metadata/navigation/histograms.xml
@@ -1040,7 +1040,7 @@
 
 <histogram
     name="Navigation.MainFrame.NewNavigation.IgnoreRestore.IsHTTPOrHTTPS.{DurationFromTo}.Time2"
-    units="ms" expires_after="2024-08-04">
+    units="ms" expires_after="2024-10-13">
   <owner>chikamune@chromium.org</owner>
   <owner>yyanagisawa@chromium.org</owner>
   <owner>kouhei@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/net/histograms.xml b/tools/metrics/histograms/metadata/net/histograms.xml
index b35c9b4..1a8b05c1 100644
--- a/tools/metrics/histograms/metadata/net/histograms.xml
+++ b/tools/metrics/histograms/metadata/net/histograms.xml
@@ -341,7 +341,7 @@
 </histogram>
 
 <histogram name="HttpCache.AddTransactionToEntry" units="ms"
-    expires_after="2024-08-13">
+    expires_after="2024-10-13">
   <owner>bashi@chromium.org</owner>
   <owner>net-dev@chromium.org</owner>
   <summary>
@@ -1579,7 +1579,7 @@
 </histogram>
 
 <histogram name="Net.DNS.UpgradeConfig.DotUpgradeSucceeded" enum="Boolean"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>horo@chromium.org</owner>
   <owner>src/net/dns/OWNERS</owner>
   <summary>
@@ -1603,7 +1603,7 @@
 </histogram>
 
 <histogram name="Net.DNS.UpgradeConfig.Ineligible.DohSpecified" enum="Boolean"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>horo@chromium.org</owner>
   <owner>src/net/dns/OWNERS</owner>
   <summary>
@@ -1624,7 +1624,7 @@
 </histogram>
 
 <histogram name="Net.DNS.UpgradeConfig.InsecureUpgradeSucceeded" enum="Boolean"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>horo@chromium.org</owner>
   <owner>src/net/dns/OWNERS</owner>
   <summary>
@@ -2234,7 +2234,7 @@
 </histogram>
 
 <histogram name="Net.NetworkTransaction{HostType}.RetryReason"
-    enum="HttpNetworkTransactionRetryReason" expires_after="2024-07-07">
+    enum="HttpNetworkTransactionRetryReason" expires_after="2024-10-13">
   <owner>horo@chromium.org</owner>
   <owner>src/net/OWNERS</owner>
   <summary>Log the reason when HttpNetworkTransaction retries.</summary>
@@ -2291,7 +2291,7 @@
 </histogram>
 
 <histogram name="Net.QuicActiveSessionCount.{GoingAwayReason}" units="sesions"
-    expires_after="2024-07-01">
+    expires_after="2024-10-13">
   <owner>nidhijaju@chromium.org</owner>
   <owner>blink-network-stack@google.com</owner>
   <summary>
@@ -3747,7 +3747,7 @@
 </histogram>
 
 <histogram name="Net.QuicSession.NumPingsSent" units="pings"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>renjietang@chromium.org</owner>
   <owner>src/net/quic/OWNERS</owner>
   <summary>
@@ -4682,7 +4682,7 @@
 </histogram>
 
 <histogram name="Net.Reporting.HeaderType" enum="NetReportingHeaderType"
-    expires_after="2024-07-01">
+    expires_after="2024-10-13">
   <owner>rodneyding@google.com</owner>
   <owner>src/net/reporting/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index 77744405..50abf91 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -2023,7 +2023,7 @@
 </histogram>
 
 <histogram name="NewTabPage.WallpaperSearch.SetRecentThemeProcessingLatency"
-    units="ms" expires_after="2024-08-11">
+    units="ms" expires_after="2024-10-13">
   <owner>tiborg@chromium.org</owner>
   <owner>rtatum@google.com</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/omnibox/histograms.xml b/tools/metrics/histograms/metadata/omnibox/histograms.xml
index eb67d2b..331fa5e9 100644
--- a/tools/metrics/histograms/metadata/omnibox/histograms.xml
+++ b/tools/metrics/histograms/metadata/omnibox/histograms.xml
@@ -1507,7 +1507,7 @@
 </histogram>
 
 <histogram name="Omnibox.SearchEngineType{Population}"
-    enum="OmniboxSearchEngineType" expires_after="2024-08-08">
+    enum="OmniboxSearchEngineType" expires_after="2024-10-13">
   <owner>jdonnelly@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <owner>chrome-omnibox-team@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/optimization/histograms.xml b/tools/metrics/histograms/metadata/optimization/histograms.xml
index c02155e4..946ea72b 100644
--- a/tools/metrics/histograms/metadata/optimization/histograms.xml
+++ b/tools/metrics/histograms/metadata/optimization/histograms.xml
@@ -264,7 +264,7 @@
 </variants>
 
 <histogram name="OptimizationGuide.AccessTokenHelper.Result"
-    enum="OptimizationGuideAccessTokenResult" expires_after="2024-08-11">
+    enum="OptimizationGuideAccessTokenResult" expires_after="2024-10-13">
   <owner>rajendrant@chromium.org</owner>
   <owner>sophiechang@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index 096cfded..03831bc 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -1418,7 +1418,7 @@
 </histogram>
 
 <histogram name="Ads.InterestGroup.ServerAuction.EndToEndTime" units="ms"
-    expires_after="2024-08-04">
+    expires_after="2024-10-13">
   <owner>behamilton@google.com</owner>
   <owner>pauljensen@chromium.org</owner>
   <owner>privacy-sandbox-dev@chromium.org</owner>
@@ -1972,7 +1972,7 @@
   <summary>Records whenever a Blimp tab toggles visibility.</summary>
 </histogram>
 
-<histogram name="BlueZ.AdapterLost" units="seconds" expires_after="2024-08-11">
+<histogram name="BlueZ.AdapterLost" units="seconds" expires_after="2024-10-13">
   <owner>mcchou@chromium.org</owner>
   <owner>chromeos-bt-platform-sw-core@google.com</owner>
   <summary>
@@ -3518,7 +3518,7 @@
 </histogram>
 
 <histogram name="Conversions.ExtraReportDelay2" units="ms"
-    expires_after="2024-07-14">
+    expires_after="2024-10-13">
   <owner>apaseltiner@chromium.org</owner>
   <owner>johnidel@chromium.org</owner>
   <owner>csharrison@chromium.org</owner>
@@ -5114,7 +5114,7 @@
 
 <histogram name="Eche.Connection.Result.FailureReason"
     enum="SecureChannelConnectionAttemptFailureReason"
-    expires_after="2024-07-10">
+    expires_after="2024-10-13">
   <owner>jonmann@chromium.org</owner>
   <owner>chromeos-cross-device-eng@google.com</owner>
   <summary>
@@ -5192,7 +5192,7 @@
 </histogram>
 
 <histogram name="Eche.StreamEvent.ConnectionFail" enum="ConnectionFailReason"
-    expires_after="2024-07-10">
+    expires_after="2024-10-13">
   <owner>crisrael@google.com</owner>
   <owner>exo-core-eng@google.com</owner>
   <owner>chromeos-cross-device-eng@google.com</owner>
@@ -6094,7 +6094,7 @@
 </histogram>
 
 <histogram name="FirstRun.LaunchSource" enum="FirstRunLaunchSource"
-    expires_after="2024-08-04">
+    expires_after="2024-10-13">
   <owner>jlebel@chromium.org</owner>
   <owner>olivierrobin@chromium.org</owner>
   <summary>
@@ -6581,7 +6581,7 @@
 </histogram>
 
 <histogram name="ImportantFile.FileReplaceRetryCount" units="attempt count"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>etienneb@chromium.org</owner>
   <owner>grt@chromium.org</owner>
   <summary>
@@ -8944,7 +8944,7 @@
 </histogram>
 
 <histogram name="PushMessaging.UnregistrationStatus"
-    enum="PushUnregistrationStatus" expires_after="2024-08-11">
+    enum="PushUnregistrationStatus" expires_after="2024-10-13">
   <owner>peter@chromium.org</owner>
   <owner>knollr@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/page/histograms.xml b/tools/metrics/histograms/metadata/page/histograms.xml
index 149c943..5c8a0a5c 100644
--- a/tools/metrics/histograms/metadata/page/histograms.xml
+++ b/tools/metrics/histograms/metadata/page/histograms.xml
@@ -381,7 +381,7 @@
 </histogram>
 
 <histogram name="PageLoad.Clients.Ads.HeavyAds.DisallowedByBlocklist"
-    enum="BooleanBlocked" expires_after="2024-08-11">
+    enum="BooleanBlocked" expires_after="2024-10-13">
   <owner>johnidel@chromium.org</owner>
   <owner>jkarlin@chromium.org</owner>
   <owner>chrome-ads-histograms@google.com</owner>
@@ -2706,7 +2706,7 @@
 
 <histogram name="PageLoad.Internal.SoftNavigationFromStartInvalidTiming"
     enum="SoftNavigationFromStartInvalidTimingReasons"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>haoliuk@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 3b7cd770..ea9a9fe 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -245,7 +245,7 @@
 </histogram>
 
 <histogram name="KeyboardAccessory.GenerationDialogChoice.{GenerationType}"
-    enum="GenerationDialogChoice" expires_after="2024-08-11">
+    enum="GenerationDialogChoice" expires_after="2024-10-13">
   <owner>ioanap@chromium.org</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -574,7 +574,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AccountStoreCredentialsAfterOptIn"
-    units="credentials" expires_after="2024-08-11">
+    units="credentials" expires_after="2024-10-13">
   <owner>treib@chromium.org</owner>
   <owner>mamir@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/performance_manager/histograms.xml b/tools/metrics/histograms/metadata/performance_manager/histograms.xml
index a4111f1..fc5f16f 100644
--- a/tools/metrics/histograms/metadata/performance_manager/histograms.xml
+++ b/tools/metrics/histograms/metadata/performance_manager/histograms.xml
@@ -347,7 +347,7 @@
 </histogram>
 
 <histogram name="PerformanceManager.TabRevisitTracker.TimeToClose2"
-    units="seconds" expires_after="2024-08-09">
+    units="seconds" expires_after="2024-10-13">
   <owner>anthonyvd@chromium.org</owner>
   <owner>chrome-catan@google.com</owner>
   <summary>
@@ -357,7 +357,7 @@
 </histogram>
 
 <histogram name="PerformanceManager.TabRevisitTracker.TimeToRevisit2"
-    units="seconds" expires_after="2024-08-09">
+    units="seconds" expires_after="2024-10-13">
   <owner>anthonyvd@chromium.org</owner>
   <owner>chrome-catan@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/permissions/histograms.xml b/tools/metrics/histograms/metadata/permissions/histograms.xml
index 341c846..3857213 100644
--- a/tools/metrics/histograms/metadata/permissions/histograms.xml
+++ b/tools/metrics/histograms/metadata/permissions/histograms.xml
@@ -1307,7 +1307,7 @@
 </histogram>
 
 <histogram name="SiteEngagementService.EngagementType"
-    enum="SiteEngagementServiceEngagementType" expires_after="2024-08-11">
+    enum="SiteEngagementServiceEngagementType" expires_after="2024-10-13">
   <owner>calamity@chromium.org</owner>
   <owner>dominickn@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/platform/histograms.xml b/tools/metrics/histograms/metadata/platform/histograms.xml
index 6853d42d..582d70e 100644
--- a/tools/metrics/histograms/metadata/platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/platform/histograms.xml
@@ -247,7 +247,7 @@
 </histogram>
 
 <histogram name="Platform.DetachableBase.ActivePercent" units="%"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>fshao@chromium.org</owner>
   <owner>phoenixshen@chromium.org</owner>
   <owner>chromeos-kukui@google.com</owner>
@@ -440,7 +440,7 @@
 </histogram>
 
 <histogram name="Platform.DlcService.InstallResult"
-    enum="DlcService.InstallResult" expires_after="2024-09-01">
+    enum="DlcService.InstallResult" expires_after="2025-02-01">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
@@ -450,7 +450,7 @@
 </histogram>
 
 <histogram name="Platform.DlcService.UninstallResult"
-    enum="DlcService.UninstallResult" expires_after="2024-04-28">
+    enum="DlcService.UninstallResult" expires_after="2025-02-01">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
@@ -818,7 +818,7 @@
 
 <histogram
     name="Platform.Libhwsec.RetryAction.PinWeaverManager.{ReplayOperationType}{ReplayEntryType}"
-    enum="HwsecRetryActionEnum" expires_after="2024-08-11">
+    enum="HwsecRetryActionEnum" expires_after="2024-10-13">
   <owner>chensa@google.com</owner>
   <owner>cros-hwsec-userland-eng+uma@google.com</owner>
   <summary>
@@ -1606,7 +1606,7 @@
 </histogram>
 
 <histogram name="Platform.Segmentation.FeatureLevel" units="level number"
-    expires_after="2024-08-09">
+    expires_after="2024-10-13">
   <owner>gwendal@chromium.org</owner>
   <owner>iby@chromium.org</owner>
   <owner>chromeos-data-eng@google.com</owner>
@@ -1618,7 +1618,7 @@
 </histogram>
 
 <histogram name="Platform.Segmentation.ScopeLevel"
-    enum="FeatureManagementScopeLevel" expires_after="2024-08-09">
+    enum="FeatureManagementScopeLevel" expires_after="2024-10-13">
   <owner>gwendal@chromium.org</owner>
   <owner>iby@chromium.org</owner>
   <owner>chromeos-data-eng@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/power/histograms.xml b/tools/metrics/histograms/metadata/power/histograms.xml
index ecbc2168..f28af8c 100644
--- a/tools/metrics/histograms/metadata/power/histograms.xml
+++ b/tools/metrics/histograms/metadata/power/histograms.xml
@@ -1620,7 +1620,7 @@
 </histogram>
 
 <histogram name="Power.PowerButtonPressInTabletMode"
-    enum="PowerButtonPressType" expires_after="2024-08-11">
+    enum="PowerButtonPressType" expires_after="2024-10-13">
   <owner>minch@chromium.org</owner>
   <owner>xdai@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/printing/histograms.xml b/tools/metrics/histograms/metadata/printing/histograms.xml
index 1536bb2..416cc37 100644
--- a/tools/metrics/histograms/metadata/printing/histograms.xml
+++ b/tools/metrics/histograms/metadata/printing/histograms.xml
@@ -145,7 +145,7 @@
 </histogram>
 
 <histogram name="Printing.CUPS.IppAttributesSuccess" enum="BooleanSuccess"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>bmgordon@chromium.org</owner>
   <owner>cros-printing-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/privacy/histograms.xml b/tools/metrics/histograms/metadata/privacy/histograms.xml
index 38037d3..11e2c53e 100644
--- a/tools/metrics/histograms/metadata/privacy/histograms.xml
+++ b/tools/metrics/histograms/metadata/privacy/histograms.xml
@@ -646,7 +646,7 @@
 </histogram>
 
 <histogram name="PrivacySandbox.AggregationService.Storage.Sql.CreationTime2"
-    units="ms" expires_after="2024-08-11">
+    units="ms" expires_after="2024-10-13">
   <owner>alexmt@chromium.org</owner>
   <owner>linnan@chromium.org</owner>
   <summary>
@@ -658,7 +658,7 @@
 </histogram>
 
 <histogram name="PrivacySandbox.AggregationService.Storage.Sql.Error"
-    enum="SqliteLoggedResultCode" expires_after="2024-08-11">
+    enum="SqliteLoggedResultCode" expires_after="2024-10-13">
   <owner>dmcardle@chromium.org</owner>
   <owner>apaseltiner@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/profile/histograms.xml b/tools/metrics/histograms/metadata/profile/histograms.xml
index 658ea06..dc61c79 100644
--- a/tools/metrics/histograms/metadata/profile/histograms.xml
+++ b/tools/metrics/histograms/metadata/profile/histograms.xml
@@ -209,7 +209,7 @@
 </histogram>
 
 <histogram name="Profile.Incognito.MovedToBackgroundAfterDuration"
-    units="minutes" expires_after="2024-08-11">
+    units="minutes" expires_after="2024-10-13">
   <owner>arabm@google.com</owner>
   <owner>chrome-incognito@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/readaloud/histograms.xml b/tools/metrics/histograms/metadata/readaloud/histograms.xml
index fecd2ab..6bf89d2 100644
--- a/tools/metrics/histograms/metadata/readaloud/histograms.xml
+++ b/tools/metrics/histograms/metadata/readaloud/histograms.xml
@@ -100,7 +100,7 @@
 </histogram>
 
 <histogram name="ReadAloud.HighlightingEnabled" enum="BooleanEnabled"
-    expires_after="2024-08-04">
+    expires_after="2024-10-13">
   <owner>andreaxg@google.com</owner>
   <owner>basiaz@google.com</owner>
   <owner>iwells@chromium.org</owner>
@@ -242,7 +242,7 @@
 </histogram>
 
 <histogram name="ReadAloud.SpeedChange" enum="ReadAloudSpeed"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>andreaxg@google.com</owner>
   <owner>basiaz@google.com</owner>
   <owner>iwells@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/renderer/histograms.xml b/tools/metrics/histograms/metadata/renderer/histograms.xml
index f42fbc47..c7489ef 100644
--- a/tools/metrics/histograms/metadata/renderer/histograms.xml
+++ b/tools/metrics/histograms/metadata/renderer/histograms.xml
@@ -470,7 +470,7 @@
 </histogram>
 
 <histogram name="Renderer.PaintPreview.Capture.MainFrameBlinkCaptureDuration"
-    units="ms" expires_after="2024-08-11">
+    units="ms" expires_after="2024-10-13">
   <owner>ckitagawa@chromium.org</owner>
   <owner>fredmello@chromium.org</owner>
   <owner>chrome-fdt@google.com</owner>
@@ -526,7 +526,7 @@
 </histogram>
 
 <histogram name="Renderer.Preload.UnusedResource" enum="BooleanSuccess"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>iclelland@chromium.org</owner>
   <owner>speed-metrics-dev@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
index b48f96f..62bee6d 100644
--- a/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
+++ b/tools/metrics/histograms/metadata/safe_browsing/histograms.xml
@@ -254,7 +254,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.BrowserThrottle.CheckerOnIOLifetime" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>thefrog@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -579,7 +579,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.ClientSidePhishingDetection.AllowlistMatchResult"
-    enum="SafeBrowsingAllowlistAsyncMatch" expires_after="2024-08-11">
+    enum="SafeBrowsingAllowlistAsyncMatch" expires_after="2024-10-13">
   <owner>thefrog@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -2086,7 +2086,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.AllowlistSizeTooSmall"
-    enum="BooleanUnavailable" expires_after="2024-08-11">
+    enum="BooleanUnavailable" expires_after="2024-10-13">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -2451,7 +2451,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.RT.Response.VerdictType.{UserPopulation}"
-    enum="SafeBrowsingRTLookupResponseVerdictType" expires_after="2024-08-11">
+    enum="SafeBrowsingRTLookupResponseVerdictType" expires_after="2024-10-13">
   <owner>zackhan@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -3029,7 +3029,7 @@
 </histogram>
 
 <histogram name="SafeBrowsing.V4ProcessPartialUpdate.RemovalsHashesCount"
-    units="entries" expires_after="2024-08-11">
+    units="entries" expires_after="2024-10-13">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml
index 63d847f..3139ee3 100644
--- a/tools/metrics/histograms/metadata/sb_client/histograms.xml
+++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -266,7 +266,7 @@
 </histogram>
 
 <histogram name="SBClientDownload.FileAnalysisDuration{Analysis}" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -418,7 +418,7 @@
 
 <histogram
     name="SBClientDownload.Warning.DownloadIsHttps.{DangerType}.{Action}"
-    enum="BooleanHttps" expires_after="2024-08-11">
+    enum="BooleanHttps" expires_after="2024-10-13">
   <owner>xinghuilu@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
@@ -695,7 +695,7 @@
 </histogram>
 
 <histogram name="SBClientPhishing.ModelDynamicUpdateSuccess"
-    enum="BooleanSuccess" expires_after="2024-08-11">
+    enum="BooleanSuccess" expires_after="2024-10-13">
   <owner>andysjlim@chromium.org</owner>
   <owner>chrome-counter-abuse-alerts@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/search/histograms.xml b/tools/metrics/histograms/metadata/search/histograms.xml
index 96682bd..4cebc916 100644
--- a/tools/metrics/histograms/metadata/search/histograms.xml
+++ b/tools/metrics/histograms/metadata/search/histograms.xml
@@ -218,7 +218,7 @@
 </histogram>
 
 <histogram name="Search.ChoiceScreenSelectedEngineIndex" units="index"
-    expires_after="2024-08-04">
+    expires_after="2024-10-13">
   <owner>dgn@chromium.org</owner>
   <owner>chrome-waffle-eng@google.com</owner>
   <summary>
@@ -241,7 +241,7 @@
 </histogram>
 
 <histogram name="Search.ChoiceScreenShowedEngineAt.Index{Index}"
-    enum="OmniboxSearchEngineType" expires_after="2024-08-04">
+    enum="OmniboxSearchEngineType" expires_after="2024-10-13">
   <owner>dgn@chromium.org</owner>
   <owner>chrome-waffle-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/settings/histograms.xml b/tools/metrics/histograms/metadata/settings/histograms.xml
index 05dee347..6cb41ef1 100644
--- a/tools/metrics/histograms/metadata/settings/histograms.xml
+++ b/tools/metrics/histograms/metadata/settings/histograms.xml
@@ -53,7 +53,7 @@
 </variants>
 
 <histogram name="Settings.AdvancedSpellcheck.OnStartup2" enum="BooleanEnabled"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>harrisonsean@chromium.org</owner>
   <owner>chrome-privacy-controls@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/signin/histograms.xml b/tools/metrics/histograms/metadata/signin/histograms.xml
index eacfd355..04803d8d 100644
--- a/tools/metrics/histograms/metadata/signin/histograms.xml
+++ b/tools/metrics/histograms/metadata/signin/histograms.xml
@@ -42,7 +42,7 @@
 
 <histogram
     name="Signin.AccountCapabilities.GetFromSystemLibraryDuration{Caller}"
-    units="ms" expires_after="2024-08-11">
+    units="ms" expires_after="2024-10-13">
   <owner>fernandex@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <owner>triploblastic@chromium.org</owner>
@@ -118,7 +118,7 @@
 </histogram>
 
 <histogram name="Signin.AccountCapabilities.{Priority}.FetchDuration.{Result}"
-    units="ms" expires_after="2024-08-11">
+    units="ms" expires_after="2024-10-13">
   <owner>alexilin@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -143,7 +143,7 @@
 </histogram>
 
 <histogram name="Signin.AccountCapabilities.{Priority}.FetchResult"
-    enum="AccountCapabilitiesFetchResult" expires_after="2024-08-11">
+    enum="AccountCapabilitiesFetchResult" expires_after="2024-10-13">
   <owner>alexilin@chromium.org</owner>
   <owner>msarda@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
@@ -324,7 +324,7 @@
 
 <histogram
     name="Signin.AccountTracker.RefreshAccountInfo.IsAlreadyTrackingAccount"
-    enum="Boolean" expires_after="2024-08-11">
+    enum="Boolean" expires_after="2024-10-13">
   <owner>msarda@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
   <summary>
@@ -532,7 +532,7 @@
 
 <histogram
     name="Signin.BoundSessionCredentials.Covered{Type}RequestWasDeferred"
-    enum="BooleanWasDeferred" expires_after="2024-08-11">
+    enum="BooleanWasDeferred" expires_after="2024-10-13">
   <owner>alexilin@chromium.org</owner>
   <owner>msalama@chromium.org</owner>
   <owner>chrome-signin-team@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/stability/histograms.xml b/tools/metrics/histograms/metadata/stability/histograms.xml
index fd8c2464..cf42a03 100644
--- a/tools/metrics/histograms/metadata/stability/histograms.xml
+++ b/tools/metrics/histograms/metadata/stability/histograms.xml
@@ -192,7 +192,7 @@
 </histogram>
 
 <histogram name="Stability.ChildFrameCrash.ShownAfterCrashingReason"
-    enum="ShownAfterCrashingReason" expires_after="2024-08-09">
+    enum="ShownAfterCrashingReason" expires_after="2024-10-13">
   <owner>alexmos@chromium.org</owner>
   <owner>boliu@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
@@ -204,7 +204,7 @@
 </histogram>
 
 <histogram name="Stability.ChildFrameCrash.TabMarkedForReload"
-    enum="BooleanMarkedForReload" expires_after="2024-08-09">
+    enum="BooleanMarkedForReload" expires_after="2024-10-13">
   <owner>alexmos@chromium.org</owner>
   <owner>boliu@chromium.org</owner>
   <summary>
@@ -214,7 +214,7 @@
 </histogram>
 
 <histogram name="Stability.ChildFrameCrash.TabMarkedForReload.Visibility"
-    enum="FrameVisibility" expires_after="2024-08-09">
+    enum="FrameVisibility" expires_after="2024-10-13">
   <owner>alexmos@chromium.org</owner>
   <owner>boliu@chromium.org</owner>
   <summary>
@@ -225,7 +225,7 @@
 </histogram>
 
 <histogram name="Stability.ChildFrameCrash.Visibility" enum="CrashVisibility"
-    expires_after="2024-08-09">
+    expires_after="2024-10-13">
   <owner>alexmos@chromium.org</owner>
   <owner>boliu@chromium.org</owner>
   <owner>lukasza@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/storage/histograms.xml b/tools/metrics/histograms/metadata/storage/histograms.xml
index e8af214..1b8410b 100644
--- a/tools/metrics/histograms/metadata/storage/histograms.xml
+++ b/tools/metrics/histograms/metadata/storage/histograms.xml
@@ -945,7 +945,7 @@
 </histogram>
 
 <histogram name="Storage.SharedStorage.Document.Timing.Append" units="ms"
-    expires_after="2024-07-31">
+    expires_after="2024-10-13">
   <owner>cammie@chromium.org</owner>
   <owner>yaoxia@chromium.org</owner>
   <owner>chrome-ads-histograms@google.com</owner>
@@ -1249,7 +1249,7 @@
 </histogram>
 
 <histogram name="Storage.SharedStorage.Worklet.Timing.Delete" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>cammie@chromium.org</owner>
   <owner>yaoxia@chromium.org</owner>
   <owner>chrome-ads-histograms@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/sync/histograms.xml b/tools/metrics/histograms/metadata/sync/histograms.xml
index b9e40a4d..47afeba 100644
--- a/tools/metrics/histograms/metadata/sync/histograms.xml
+++ b/tools/metrics/histograms/metadata/sync/histograms.xml
@@ -373,7 +373,7 @@
 </histogram>
 
 <histogram name="Sync.ConfigureTime_{ConfigurationType}.{Result}" units="ms"
-    expires_after="2024-08-09">
+    expires_after="2024-10-13">
   <owner>victorvianna@google.com</owner>
   <component>Services&gt;Sync</component>
   <summary>
@@ -780,7 +780,7 @@
 </histogram>
 
 <histogram name="Sync.InvalidBookmarkSpecifics"
-    enum="InvalidBookmarkSpecificsError" expires_after="2024-06-23">
+    enum="InvalidBookmarkSpecificsError" expires_after="2024-10-13">
   <owner>mastiz@chromium.org</owner>
   <owner>rushans@google.com</owner>
   <component>Services&gt;Sync</component>
@@ -1429,7 +1429,7 @@
 </histogram>
 
 <histogram name="Sync.SharingMessage.CommitResult"
-    enum="SyncSharingMessageCommitErrorCode" expires_after="2024-08-04">
+    enum="SyncSharingMessageCommitErrorCode" expires_after="2024-10-13">
   <owner>rushans@google.com</owner>
   <owner>treib@chromium.org</owner>
   <component>Services&gt;Sync</component>
@@ -1955,7 +1955,7 @@
 </histogram>
 
 <histogram name="Sync.TrustedVaultJavascriptAddRecoveryMethodIsIncognito"
-    enum="BooleanIncognito" expires_after="2024-06-30">
+    enum="BooleanIncognito" expires_after="2024-10-13">
   <owner>mmoskvitin@google.com</owner>
   <owner>mastiz@chromium.org</owner>
   <component>Services&gt;Sync</component>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index ed9f31b..7d086a5 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -421,7 +421,7 @@
 </histogram>
 
 <histogram name="Tab.PerceivedRestoreTime" units="ms"
-    expires_after="2024-08-04">
+    expires_after="2024-10-13">
   <owner>ckitagawa@chromium.org</owner>
   <owner>dtrainor@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
@@ -547,7 +547,7 @@
 </histogram>
 
 <histogram name="Tab.SwitchedToForegroundAge" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>dtrainor@chromium.org</owner>
   <owner>ckitagawa@chroimum.org</owner>
   <summary>
@@ -2270,7 +2270,7 @@
   </summary>
 </histogram>
 
-<histogram name="Tabs.TabState.LoadTime" units="ms" expires_after="2024-06-30">
+<histogram name="Tabs.TabState.LoadTime" units="ms" expires_after="2024-10-13">
   <owner>yusufo@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <owner>dtrainor@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/uma/histograms.xml b/tools/metrics/histograms/metadata/uma/histograms.xml
index 6ebdc5a..83d08fd 100644
--- a/tools/metrics/histograms/metadata/uma/histograms.xml
+++ b/tools/metrics/histograms/metadata/uma/histograms.xml
@@ -954,7 +954,7 @@
 </histogram>
 
 <histogram name="UMA.TruncatedEvents.UserAction" units="events"
-    expires_after="2024-10-06">
+    expires_after="2024-10-13">
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/update_engine/histograms.xml b/tools/metrics/histograms/metadata/update_engine/histograms.xml
index b35ab5f..8b142aaa 100644
--- a/tools/metrics/histograms/metadata/update_engine/histograms.xml
+++ b/tools/metrics/histograms/metadata/update_engine/histograms.xml
@@ -319,7 +319,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.Check.TargetVersion"
-    enum="UpdateEngineChromeOsVersionPrefix" expires_after="2024-08-11">
+    enum="UpdateEngineChromeOsVersionPrefix" expires_after="2024-10-13">
   <owner>mpolzer@google.com</owner>
   <owner>chromeos-commercial-remote-management@google.com</owner>
   <summary>
@@ -503,7 +503,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.InstallDateProvisioningSource"
-    enum="UpdateEngineInstallDateProvisioningSource" expires_after="2024-08-11">
+    enum="UpdateEngineInstallDateProvisioningSource" expires_after="2024-10-13">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
@@ -704,7 +704,7 @@
 </histogram>
 
 <histogram name="UpdateEngine.SuccessfulUpdate.RebootCount" units="count"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>kimjae@chromium.org</owner>
   <owner>chromeos-core-services@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/v8/histograms.xml b/tools/metrics/histograms/metadata/v8/histograms.xml
index 3b0993d1..782095d 100644
--- a/tools/metrics/histograms/metadata/v8/histograms.xml
+++ b/tools/metrics/histograms/metadata/v8/histograms.xml
@@ -1183,16 +1183,6 @@
   <summary>Reason a mark-compact garbage collection was started in V8.</summary>
 </histogram>
 
-<histogram name="V8.GCMarkingSum" units="ms" expires_after="2024-04-28">
-  <owner>mlippautz@chromium.org</owner>
-  <owner>v8-memory-sheriffs@google.com</owner>
-  <summary>
-    Sum of all durations of all marking phases (incremental and non-incremental)
-    within one V8 garbage collection cycle. Reported once per garbage collection
-    at the end.
-  </summary>
-</histogram>
-
 <histogram name="V8.LiftoffBailoutReasons" enum="LiftoffBailoutReason"
     expires_after="2024-09-15">
   <owner>ecmziegler@chromium.org</owner>
@@ -1217,7 +1207,7 @@
 </histogram>
 
 <histogram name="V8.LocalWindowProxy.InitializeTime" units="ms"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>nidhijaju@chromium.org</owner>
   <owner>chrome-loading@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/web_core/histograms.xml b/tools/metrics/histograms/metadata/web_core/histograms.xml
index 5866c68..7c8f0eee 100644
--- a/tools/metrics/histograms/metadata/web_core/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_core/histograms.xml
@@ -113,7 +113,7 @@
 </histogram>
 
 <histogram name="WebCore.Fullscreen.LockStateAtEntryViaApi"
-    enum="FullscreenLockState" expires_after="2024-08-11">
+    enum="FullscreenLockState" expires_after="2024-10-13">
   <owner>takumif@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
@@ -126,7 +126,7 @@
 </histogram>
 
 <histogram name="WebCore.Fullscreen.LockStateAtEntryViaBrowserUi"
-    enum="FullscreenLockState" expires_after="2024-08-11">
+    enum="FullscreenLockState" expires_after="2024-10-13">
   <owner>takumif@chromium.org</owner>
   <owner>openscreen-eng@google.com</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/web_rtc/histograms.xml b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
index 9c82aaf..5a78016 100644
--- a/tools/metrics/histograms/metadata/web_rtc/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_rtc/histograms.xml
@@ -1460,7 +1460,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.ThermalState" enum="ThermalState"
-    expires_after="2024-08-11">
+    expires_after="2024-10-13">
   <owner>eshr@google.com</owner>
   <owner>hbos@chromium.org</owner>
   <summary>
@@ -1667,7 +1667,7 @@
 </histogram>
 
 <histogram name="WebRTC.Stun.Integrity.{StunPacketType}"
-    enum="WebRtcStunIntegrityOutcome" expires_after="2024-08-11">
+    enum="WebRtcStunIntegrityOutcome" expires_after="2024-10-13">
   <owner>hta@chromium.org</owner>
   <owner>webrtc-dev@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index d63c801..6e176e29 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,8 +5,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v44.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "6a2bbb73d020b83fc7b916f5645f171328348087",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/3f2b2b6385d24f80239a7c49d464a06292fbd078/trace_processor_shell.exe"
+            "hash": "b14b8bb20644ed79958bed962481c7ef43430db6",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/6191dd39ee994efe60df79204ebd311f32470a52/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "d8e27d961be1db97db098c6826017aec0397ecfd",
@@ -21,8 +21,8 @@
             "full_remote_path": "perfetto-luci-artifacts/v44.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "9116d187b986a165ff18eced9c43d1df8b4f22d2",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/3f2b2b6385d24f80239a7c49d464a06292fbd078/trace_processor_shell"
+            "hash": "250c949ae64d5b57131f7536618ae8f39812840e",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/6191dd39ee994efe60df79204ebd311f32470a52/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/web_dev_style/js_checker.py b/tools/web_dev_style/js_checker.py
index 86d34a27..1bda403 100644
--- a/tools/web_dev_style/js_checker.py
+++ b/tools/web_dev_style/js_checker.py
@@ -38,10 +38,6 @@
         '    // <if expr="chromeos">\n' +
         "    // </if>\n")
 
-  def DebuggerCheck(self, i, line):
-    return self.RegexCheck(i, line, r"^\s*(debugger);",
-                           "Debugger statements should be removed")
-
   def EndJsDocCommentCheck(self, i, line):
     msg = "End JSDoc comments with */ instead of **/"
     def _check(regex):
@@ -139,7 +135,6 @@
                 self.BindThisCheck(i, line),
                 self.ChromeSendCheck(i, line),
                 self.CommentIfAndIncludeCheck(i, line),
-                self.DebuggerCheck(i, line),
                 self.EndJsDocCommentCheck(i, line),
                 self.ExtraDotInGenericCheck(i, line),
                 self.InheritDocCheck(i, line),
diff --git a/tools/web_dev_style/js_checker_test.py b/tools/web_dev_style/js_checker_test.py
index 8941958d..2692ffe 100755
--- a/tools/web_dev_style/js_checker_test.py
+++ b/tools/web_dev_style/js_checker_test.py
@@ -25,36 +25,6 @@
 
     self.checker = js_checker.JSChecker(MockInputApi(), MockOutputApi())
 
-  def ShouldFailDebuggerCheck(self, line):
-    error = self.checker.DebuggerCheck(1, line)
-    self.assertNotEqual("", error, "Should be flagged as style error: " + line)
-    highlight = test_util.GetHighlight(line, error).strip()
-    self.assertTrue(highlight.startswith("debugger"))
-
-  def ShouldPassDebuggerCheck(self, line):
-    self.assertEqual("", self.checker.DebuggerCheck(1, line),
-                     "Should not be flagged as style error: " + line)
-
-  def testDebuggerFails(self):
-    lines = [
-        "debugger;",
-        "  debugger;",
-    ]
-    for line in lines:
-      self.ShouldFailDebuggerCheck(line)
-
-  def testDebuggerPasses(self):
-    lines = [
-        "// Test that it works in the debugger",
-        "  // Test that it works in the debugger",
-        "debugger.debug(func);",
-        "  debugger.debug(func);",
-        "console.log('debugger enabled');",
-        "  console.log('debugger enabled');",
-    ]
-    for line in lines:
-      self.ShouldPassDebuggerCheck(line)
-
   def ShouldFailBindThisCheck(self, line):
     error = self.checker.BindThisCheck(1, line)
     self.assertNotEqual("", error, "Should be flagged as style error: " + line)
diff --git a/ui/aura/test/test_screen.cc b/ui/aura/test/test_screen.cc
index bad1abcb..bfb05ec2 100644
--- a/ui/aura/test/test_screen.cc
+++ b/ui/aura/test/test_screen.cc
@@ -7,6 +7,7 @@
 #include <stdint.h>
 
 #include "base/check_op.h"
+#include "base/containers/adapters.h"
 #include "build/build_config.h"
 #include "ui/aura/env.h"
 #include "ui/aura/window.h"
@@ -148,10 +149,37 @@
   return GetWindowAtScreenPoint(GetCursorScreenPoint()) == window;
 }
 
+gfx::NativeWindow TestScreen::GetWindowForPoint(Window* window,
+                                                const gfx::Point& local_point) {
+  DCHECK(window);
+  if (!window->IsVisible()) {
+    return nullptr;
+  }
+
+  if (!window->HitTest(local_point)) {
+    return nullptr;
+  }
+
+  for (Window* child : base::Reversed(window->children())) {
+    gfx::Point point_in_child_coords(local_point);
+    Window::ConvertPointToTarget(window, child, &point_in_child_coords);
+    Window* match = GetWindowForPoint(child, point_in_child_coords);
+    if (match) {
+      return match;
+    }
+  }
+  return window;
+}
+
 gfx::NativeWindow TestScreen::GetWindowAtScreenPoint(const gfx::Point& point) {
   if (!host_ || !host_->window())
     return nullptr;
-  return host_->window()->GetEventHandlerForPoint(point);
+
+  // GetWindowAtScreenPoint() is designed to return a visible window that
+  // contains the given point within its bounds. Using GetEventHandlerForPoint()
+  // can lead to null returns for windows that don't have an event handler, such
+  // as content_window_ in DesktopNativeWidgetAura.
+  return GetWindowForPoint(host_->window(), point);
 }
 
 gfx::NativeWindow TestScreen::GetLocalProcessWindowAtPoint(
diff --git a/ui/aura/test/test_screen.h b/ui/aura/test/test_screen.h
index 74082eb..9c43d37 100644
--- a/ui/aura/test/test_screen.h
+++ b/ui/aura/test/test_screen.h
@@ -45,6 +45,8 @@
   void SetWorkAreaInsets(const gfx::Insets& insets);
 
  protected:
+  static gfx::NativeWindow GetWindowForPoint(Window* window,
+                                             const gfx::Point& local_point);
   gfx::Transform GetRotationTransform() const;
   gfx::Transform GetUIScaleTransform() const;
 
diff --git a/ui/aura/window.h b/ui/aura/window.h
index fd8e7a5..dbcdfd34 100644
--- a/ui/aura/window.h
+++ b/ui/aura/window.h
@@ -577,6 +577,7 @@
   friend class ScopedWindowEventTargetingBlocker;
   friend class WindowTargeter;
   friend class test::WindowTestApi;
+  friend class TestScreen;
 
   // Handles registering FrameSinkId hierarchy for SetEmbedFrameSinkId() and
   // CreateLayerTreeFrameSink().
diff --git a/ui/aura/window_event_dispatcher.cc b/ui/aura/window_event_dispatcher.cc
index ab5f160..17ece5e 100644
--- a/ui/aura/window_event_dispatcher.cc
+++ b/ui/aura/window_event_dispatcher.cc
@@ -144,7 +144,7 @@
 void WindowEventDispatcher::OnMouseEventsEnableStateChanged(bool enabled) {
   // Send entered / exited so that visual state can be updated to match
   // mouse events state.
-  PostSynthesizeMouseMove();
+  PostSynthesizeMouseMove(window());
   // TODO(mazda): Add code to disable mouse events when |enabled| == false.
 }
 
@@ -233,7 +233,7 @@
       if (pending_synthesize_mouse_move) {
         // Schedule a synthesized mouse move event when there is no held mouse
         // move and we should generate one.
-        PostSynthesizeMouseMove();
+        PostSynthesizeMouseMove(window());
       }
     }
   }
@@ -259,7 +259,7 @@
 
   // Synthesize a mouse move in case the cursor's location in root coordinates
   // changed but its position in WindowTreeHost coordinates did not.
-  PostSynthesizeMouseMove();
+  PostSynthesizeMouseMove(window());
 }
 
 void WindowEventDispatcher::OnPostNotifiedWindowDestroying(Window* window) {
@@ -699,7 +699,7 @@
     return;
 
   if (window->ContainsPointInRoot(GetLastMouseLocationInRoot()))
-    PostSynthesizeMouseMove();
+    PostSynthesizeMouseMove(window);
 
   // Hiding the window releases capture which can implicitly destroy the window
   // so the window may no longer be valid after this call.
@@ -738,7 +738,7 @@
          new_bounds_in_root.Contains(last_mouse_location)) ||
         (new_bounds_in_root.Contains(last_mouse_location) &&
          new_bounds_in_root.origin() != old_bounds_in_root.origin())) {
-      PostSynthesizeMouseMove();
+      PostSynthesizeMouseMove(window);
     }
   }
 }
@@ -829,7 +829,7 @@
   return dispatch_details;
 }
 
-void WindowEventDispatcher::PostSynthesizeMouseMove() {
+void WindowEventDispatcher::PostSynthesizeMouseMove(Window* window) {
   // No one should care where the real mouse is when this flag is on. So there
   // is no need to send a synthetic mouse move here.
   if (ui::PlatformEventSource::ShouldIgnoreNativePlatformEvents())
@@ -837,6 +837,26 @@
 
   if (synthesize_mouse_move_ || in_shutdown_)
     return;
+
+#if BUILDFLAG(IS_WIN)
+  // Gets the window at the current cursor point.
+  gfx::Point cursor_point =
+      display::Screen::GetScreen()->GetCursorScreenPoint();
+  gfx::NativeWindow window_under_cursor =
+      display::Screen::GetScreen()->GetWindowAtScreenPoint(cursor_point);
+
+  ConvertPointFromScreen(&cursor_point);
+  // If the mouse cursor is within the |window|, but |window_under_cursor| is
+  // null, it means another program's window is occluding ours. And also, if
+  // |window_under_cursor| doesn't belong to ours then we do not synthesize a
+  // mouse move event.
+  if (window->ContainsPointInRoot(cursor_point) &&
+      (!window_under_cursor ||
+       !host_->window()->Contains(window_under_cursor))) {
+    return;
+  }
+#endif
+
   synthesize_mouse_move_ = true;
   base::SingleThreadTaskRunner::GetCurrentDefault()->PostNonNestableTask(
       FROM_HERE,
@@ -851,7 +871,7 @@
     return;
   if (window->IsVisible() &&
       window->ContainsPointInRoot(GetLastMouseLocationInRoot())) {
-    PostSynthesizeMouseMove();
+    PostSynthesizeMouseMove(window);
   }
 }
 
diff --git a/ui/aura/window_event_dispatcher.h b/ui/aura/window_event_dispatcher.h
index e49ca608..98c88c4 100644
--- a/ui/aura/window_event_dispatcher.h
+++ b/ui/aura/window_event_dispatcher.h
@@ -269,7 +269,7 @@
 
   // Posts a task to send synthesized mouse move event if there is no a pending
   // task.
-  void PostSynthesizeMouseMove();
+  void PostSynthesizeMouseMove(Window* window);
 
   // Creates and dispatches synthesized mouse move event using the current mouse
   // location.
diff --git a/ui/aura/window_tree_host_platform.cc b/ui/aura/window_tree_host_platform.cc
index d70f84f1..c7649b7 100644
--- a/ui/aura/window_tree_host_platform.cc
+++ b/ui/aura/window_tree_host_platform.cc
@@ -354,6 +354,18 @@
 int64_t WindowTreeHostPlatform::OnStateUpdate(
     const PlatformWindowDelegate::State& old,
     const PlatformWindowDelegate::State& latest) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  // Notify the fullscreen type change before the window state change to reflect
+  // the immersive status at OnWindowStateChanged.
+  if (old.fullscreen_type != latest.fullscreen_type) {
+    OnFullscreenTypeChanged(old.fullscreen_type, latest.fullscreen_type);
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+  if (old.window_state != latest.window_state) {
+    OnWindowStateChanged(old.window_state, latest.window_state);
+  }
+
   if (old.bounds_dip != latest.bounds_dip || old.size_px != latest.size_px ||
       old.window_scale != latest.window_scale) {
     bool origin_changed = old.bounds_dip.origin() != latest.bounds_dip.origin();
@@ -364,7 +376,7 @@
     compositor()->SetExternalPageScaleFactor(latest.raster_scale);
   }
 
-  bool needs_frame = latest.ProducesFrameOnUpdateFrom(old);
+  bool needs_frame = latest.WillProduceFrameOnUpdateFrom(old);
   if (old.occlusion_state != latest.occlusion_state &&
       NativeWindowOcclusionTracker::
           IsNativeWindowOcclusionTrackingAlwaysEnabled(this)) {
diff --git a/ui/file_manager/integration_tests/file_manager/file_display.ts b/ui/file_manager/integration_tests/file_manager/file_display.ts
index a7e8a14e..f6a10b3 100644
--- a/ui/file_manager/integration_tests/file_manager/file_display.ts
+++ b/ui/file_manager/integration_tests/file_manager/file_display.ts
@@ -972,3 +972,63 @@
   // Check: the toolbar read-only indicator should not be visible.
   await remoteCall.waitForElement(appId, '#read-only-indicator[hidden]');
 }
+
+/**
+ * Tests that when local files are disabled, we navigate to default set by the
+ * policy, e.g. Drive after unmounting a USB.
+ */
+export async function fileDisplayLocalFilesDisabledUnmountRemovable() {
+  // Mount Drive and Downloads.
+  await sendTestMessage({name: 'mountDrive'});
+  await sendTestMessage({name: 'mountDownloads'});
+  // Ensure two volumes are mounted.
+  await remoteCall.waitForVolumesCount(2);
+
+  // Enable SkyVault, this should unmount Downloads.
+  await sendTestMessage({name: 'setupSkyVault'});
+  await remoteCall.waitForVolumesCount(1);
+
+  // Open Files app without specifying the initial directory/root.
+  const appId = await remoteCall.openNewWindow(null, null);
+  chrome.test.assertTrue(!!appId, 'failed to open new window');
+
+  // Confirm that the Files App opened in Google Drive, as set by the policy.
+  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/My Drive');
+
+  // Mount USB volume in the Downloads window.
+  await sendTestMessage({name: 'mountFakeUsb'});
+
+  // Wait for the USB mount and click to open the USB volume.
+  const directoryTree = await DirectoryTreePageObject.create(appId);
+  await directoryTree.selectItemByType('removable');
+  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/fake-usb');
+
+  // Unmount the USB.
+  await sendTestMessage({name: 'unmountUsb'});
+
+  // We should navigate to My Drive.
+  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/My Drive');
+}
+
+/**
+ * Tests that disabling local storage while in a local folder navigates away to
+ * the default set by the policy, e.g. Drive.
+ */
+export async function fileDisplayLocalFilesDisableInMyFiles() {
+  // Mount Drive and Downloads.
+  await sendTestMessage({name: 'mountDrive'});
+  await sendTestMessage({name: 'mountDownloads'});
+
+  // Open Files app without specifying the initial directory/root.
+  const appId = await remoteCall.openNewWindow(null, null);
+  chrome.test.assertTrue(!!appId, 'failed to open new window');
+
+  // Confirm that the Files App opened in MyFiles.
+  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/My files');
+
+  // Disable local storage.
+  await sendTestMessage({name: 'setupSkyVault'});
+
+  // We should navigate to Drive.
+  await remoteCall.waitUntilCurrentDirectoryIsChanged(appId, '/My Drive');
+}
diff --git a/ui/ozone/platform/wayland/host/wayland_popup.cc b/ui/ozone/platform/wayland/host/wayland_popup.cc
index f9e6427..9098236 100644
--- a/ui/ozone/platform/wayland/host/wayland_popup.cc
+++ b/ui/ozone/platform/wayland/host/wayland_popup.cc
@@ -80,7 +80,7 @@
 
   auto bounds_dip =
       wl::TranslateWindowBoundsToParentDIP(this, xdg_parent_window);
-  bounds_dip.Inset(GetDecorationInsetsInDIP());
+  bounds_dip.Inset(delegate()->CalculateInsetsInDIP(GetPlatformWindowState()));
 
   ShellPopupParams params;
   params.bounds = bounds_dip;
@@ -196,7 +196,8 @@
   if (shell_popup_ && old_bounds_dip != bounds_dip) {
     auto bounds_dip_in_parent =
         wl::TranslateWindowBoundsToParentDIP(this, xdg_parent_window);
-    bounds_dip_in_parent.Inset(GetDecorationInsetsInDIP());
+    bounds_dip_in_parent.Inset(
+        delegate()->CalculateInsetsInDIP(GetPlatformWindowState()));
 
     // If Wayland moved the popup (for example, a dnd arrow icon), schedule
     // redraw as Aura doesn't do that for moved surfaces. If redraw has not been
@@ -237,8 +238,12 @@
           pending_bounds_dip, xdg_parent_window->GetBoundsInDIP()) +
       xdg_parent_window->GetWindowGeometryOffsetInDIP();
 
-  // Bounds are in the geometry space. Need to add decoration insets backs.
-  const auto insets = GetDecorationInsetsInDIP();
+  // Bounds are in the geometry space. Need to add decoration insets backs. Note
+  // that the window state for WaylandPopup is always `kUnknown` now, but we
+  // check `pending_configure_state_.window_state` to make it consistent.
+  const auto insets = delegate()->CalculateInsetsInDIP(
+      pending_configure_state_.window_state.value_or(
+          PlatformWindowState::kUnknown));
   pending_configure_state_.bounds_dip->Inset(-insets);
   pending_configure_state_.size_px =
       delegate()->ConvertRectToPixels(pending_bounds_dip).size();
@@ -336,13 +341,13 @@
   return shell_popup() ? shell_popup()->IsConfigured() : false;
 }
 
-void WaylandPopup::SetWindowGeometry(gfx::Size size_dip) {
+void WaylandPopup::SetWindowGeometry(
+    const PlatformWindowDelegate::State& state) {
   if (!shell_popup_) {
     return;
   }
-  const auto insets = GetDecorationInsetsInDIP();
-  gfx::Rect geometry_dip(size_dip);
-  geometry_dip.Inset(insets);
+  gfx::Rect geometry_dip(state.bounds_dip.size());
+  geometry_dip.Inset(delegate()->CalculateInsetsInDIP(state.window_state));
   shell_popup_->SetWindowGeometry(geometry_dip);
 }
 
diff --git a/ui/ozone/platform/wayland/host/wayland_popup.h b/ui/ozone/platform/wayland/host/wayland_popup.h
index 124b727..a318166 100644
--- a/ui/ozone/platform/wayland/host/wayland_popup.h
+++ b/ui/ozone/platform/wayland/host/wayland_popup.h
@@ -48,7 +48,7 @@
   bool OnInitialize(PlatformWindowInitProperties properties,
                     PlatformWindowDelegate::State* state) override;
   WaylandPopup* AsWaylandPopup() override;
-  void SetWindowGeometry(gfx::Size size_dip) override;
+  void SetWindowGeometry(const PlatformWindowDelegate::State& state) override;
   void UpdateWindowMask() override;
   void PropagateBufferScale(float new_scale) override;
   void ShowTooltip(const std::u16string& text,
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
index 86f93a3..2ed65ef 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
@@ -71,7 +71,6 @@
 WaylandToplevelWindow::WaylandToplevelWindow(PlatformWindowDelegate* delegate,
                                              WaylandConnection* connection)
     : WaylandWindow(delegate, connection),
-      state_(PlatformWindowState::kNormal),
       screen_coordinates_enabled_(kDefaultScreenCoordinateEnabled) {
   // Set a class property key, which allows |this| to be used for interactive
   // events, e.g. move or resize.
@@ -109,7 +108,7 @@
 #endif
   shell_toplevel_->SetTitle(window_title_);
   SetSizeConstraints();
-  TriggerStateChanges();
+  TriggerStateChanges(GetPlatformWindowState());
   SetUpShellIntegration();
   OnDecorationModeChanged();
 
@@ -204,7 +203,8 @@
 bool WaylandToplevelWindow::IsVisible() const {
   // X and Windows return true if the window is minimized. For consistency, do
   // the same.
-  return !!shell_toplevel_ || state_ == PlatformWindowState::kMinimized;
+  return !!shell_toplevel_ ||
+         GetPlatformWindowState() == PlatformWindowState::kMinimized;
 }
 
 void WaylandToplevelWindow::SetTitle(const std::u16string& title) {
@@ -236,10 +236,11 @@
   if (fullscreen) {
     new_state = PlatformWindowState::kFullScreen;
     display_id = target_display_id;
-  } else if (previous_state_ == PlatformWindowState::kMaximized)
-    new_state = previous_state_;
-  else
+  } else if (previously_maximized_) {
+    new_state = PlatformWindowState::kMaximized;
+  } else {
     new_state = PlatformWindowState::kNormal;
+  }
 
   SetWindowState(new_state, display_id);
 }
@@ -272,9 +273,14 @@
     // TODO(crbug.com/1293740): Verify that the claim about a window initialized
     // as a minimized window cannot ack configure. If not
     // `IsSurfaceConfigured()` condition can be removed.
-    previous_state_ = state_;
-    state_ = PlatformWindowState::kMinimized;
-    delegate()->OnWindowStateChanged(previous_state_, state_);
+    //
+    // TODO(crbug.com/40276379): Use `GetLatestRequestedState().window_state`
+    // instead once the window state becomes async.
+    auto previous_state = applied_state().window_state;
+    previously_maximized_ = previous_state == PlatformWindowState::kMaximized;
+    ForceApplyWindowStateDoNotUse(PlatformWindowState::kMinimized);
+    delegate()->OnWindowStateChanged(previous_state,
+                                     PlatformWindowState::kMinimized);
   }
 }
 
@@ -295,7 +301,7 @@
 }
 
 PlatformWindowState WaylandToplevelWindow::GetPlatformWindowState() const {
-  return state_;
+  return applied_state().window_state;
 }
 
 std::optional<std::string> WaylandToplevelWindow::TakeActivationToken() const {
@@ -548,32 +554,32 @@
     const WindowStates& window_states) {
   VLOG(1) << "Wayland XDG/Aura toplevel configure: states="
           << window_states.ToString();
-  // Store the old state to propagte state changes if Wayland decides to change
-  // the state to something else.
-  PlatformWindowState old_state = state_;
+
+  PlatformWindowState window_state = PlatformWindowState::kUnknown;
   if ((!SupportsConfigureMinimizedState() &&
-       state_ == PlatformWindowState::kMinimized &&
+       GetLatestRequestedState().window_state ==
+           PlatformWindowState::kMinimized &&
        !window_states.is_activated) ||
       window_states.is_minimized) {
-    state_ = PlatformWindowState::kMinimized;
+    window_state = PlatformWindowState::kMinimized;
   } else if (window_states.is_fullscreen) {
-    state_ = PlatformWindowState::kFullScreen;
+    window_state = PlatformWindowState::kFullScreen;
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   } else if (window_states.is_pinned_fullscreen) {
-    state_ = PlatformWindowState::kPinnedFullscreen;
+    window_state = PlatformWindowState::kPinnedFullscreen;
   } else if (window_states.is_trusted_pinned_fullscreen) {
-    state_ = PlatformWindowState::kTrustedPinnedFullscreen;
+    window_state = PlatformWindowState::kTrustedPinnedFullscreen;
 #endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
   } else if (window_states.is_maximized) {
-    state_ = PlatformWindowState::kMaximized;
+    window_state = PlatformWindowState::kMaximized;
   } else if (window_states.is_snapped_primary) {
-    state_ = PlatformWindowState::kSnappedPrimary;
+    window_state = PlatformWindowState::kSnappedPrimary;
   } else if (window_states.is_snapped_secondary) {
-    state_ = PlatformWindowState::kSnappedSecondary;
+    window_state = PlatformWindowState::kSnappedSecondary;
   } else if (window_states.is_floated) {
-    state_ = PlatformWindowState::kFloated;
+    window_state = PlatformWindowState::kFloated;
   } else {
-    state_ = PlatformWindowState::kNormal;
+    window_state = PlatformWindowState::kNormal;
   }
 
   // No matter what mode we have, the display id doesn't matter at this time
@@ -594,23 +600,9 @@
           : (IsPinnedOrFullscreen(window_states)
                  ? PlatformFullscreenType::kPlain
                  : PlatformFullscreenType::kNone);
-  if (fullscreen_type_ != fullscreen_type) {
-    // The fullscreen state change has finished and we we need to inform the
-    // browser/app that the transition is done.
-    delegate()->OnFullscreenTypeChanged(fullscreen_type_, fullscreen_type);
-    fullscreen_type_ = fullscreen_type;
-  }
+  pending_configure_state_.fullscreen_type = fullscreen_type;
 #endif
 
-  // Should skip notifying OnWindowStateChanged() when there are incoming
-  // responses for the window show state requests to avoid notifying more than
-  // once.
-  // TODO(crbug.com/1502744): Implement notification logic correctly.
-  const bool skip_window_state_changed_notification =
-      (requested_window_show_state_count_ > 0);
-  if (requested_window_show_state_count_)
-    requested_window_show_state_count_--;
-
   // Update state before notifying delegate.
   const bool did_active_change = is_active_ != window_states.is_activated;
   is_active_ = window_states.is_activated;
@@ -625,6 +617,8 @@
   }
 #endif  // IS_LINUX || IS_CHROMEOS_LACROS
 
+  pending_configure_state_.window_state = window_state;
+
   // Width or height set to 0 means that we should decide on width and height by
   // ourselves, but we don't want to set them to anything else. Use restored
   // bounds size or the current bounds iff the current state is normal (neither
@@ -638,12 +632,12 @@
       pending_configure_state_.bounds_dip.value_or(gfx::Rect()));
   if (width_dip > 1 && height_dip > 1) {
     bounds_dip.SetRect(x, y, width_dip, height_dip);
-    const auto insets = GetDecorationInsetsInDIP();
-    if (ShouldSetBounds(state_) && !insets.IsEmpty()) {
+    const auto& insets = delegate()->CalculateInsetsInDIP(window_state);
+    if (ShouldSetBounds(window_state) && !insets.IsEmpty()) {
       bounds_dip.Inset(-insets);
       bounds_dip.set_origin({x, y});
     }
-  } else if (ShouldSetBounds(state_)) {
+  } else if (ShouldSetBounds(window_state)) {
     bounds_dip = !restored_bounds_dip().IsEmpty() ? restored_bounds_dip()
                                                   : GetBoundsInDIP();
   }
@@ -659,17 +653,12 @@
   // We reset `restored_bounds_dip_` if the window is normal, snapped or floated
   // state, or update it to the applied bounds if we don't have any meaningful
   // value stored.
-  if (ShouldSetBounds(state_)) {
+  if (ShouldSetBounds(window_state)) {
     SetRestoredBoundsInDIP({});
   } else if (GetRestoredBoundsInDIP().IsEmpty()) {
     SetRestoredBoundsInDIP(GetBoundsInDIP());
   }
 
-  if (old_state != state_ && !skip_window_state_changed_notification) {
-    previous_state_ = old_state;
-    delegate()->OnWindowStateChanged(previous_state_, state_);
-  }
-
   if (did_active_change) {
     if (active_bubble()) {
       ActivateBubble(is_active_ ? active_bubble() : nullptr);
@@ -713,6 +702,8 @@
 bool WaylandToplevelWindow::OnInitialize(
     PlatformWindowInitProperties properties,
     PlatformWindowDelegate::State* state) {
+  state->window_state = PlatformWindowState::kNormal;
+
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   auto token = base::UnguessableToken::Create();
   window_unique_id_ =
@@ -760,17 +751,18 @@
   return shell_toplevel() ? shell_toplevel()->IsConfigured() : false;
 }
 
-void WaylandToplevelWindow::SetWindowGeometry(gfx::Size size_dip) {
+void WaylandToplevelWindow::SetWindowGeometry(
+    const PlatformWindowDelegate::State& state) {
   DCHECK(connection()->SupportsSetWindowGeometry());
 
   if (!shell_toplevel_)
     return;
 
-  gfx::Rect geometry_dip(size_dip);
+  gfx::Rect geometry_dip(state.bounds_dip.size());
 
-  const auto insets = GetDecorationInsetsInDIP();
-  if (state_ == PlatformWindowState::kNormal && !insets.IsEmpty()) {
-    geometry_dip.Inset(insets);
+  auto insets_dip = delegate()->CalculateInsetsInDIP(state.window_state);
+  if (!insets_dip.IsEmpty()) {
+    geometry_dip.Inset(insets_dip);
 
     // Shrinking the bounds by the decoration insets might result in empty
     // bounds. For the reasons already explained in WaylandWindow::Initialize(),
@@ -1064,9 +1056,8 @@
 
 void WaylandToplevelWindow::Unpin() {
   if (SupportsConfigurePinnedState()) {
-    auto new_state = previous_state_ == PlatformWindowState::kMaximized
-                         ? previous_state_
-                         : PlatformWindowState::kNormal;
+    auto new_state = previously_maximized_ ? PlatformWindowState::kMaximized
+                                           : PlatformWindowState::kNormal;
     SetWindowState(new_state, display::kInvalidDisplayId);
   } else {
     if (auto* zaura_surface = GetZAuraSurface()) {
@@ -1083,20 +1074,6 @@
 
 void WaylandToplevelWindow::DumpState(std::ostream& out) const {
   WaylandWindow::DumpState(out);
-#if BUILDFLAG(IS_CHROMEOS_LACROS)
-  out << ", fullscreen_type=";
-  switch (fullscreen_type_) {
-    case PlatformFullscreenType::kNone:
-      out << "not fullscreen";
-      break;
-    case PlatformFullscreenType::kPlain:
-      out << "plain fullscreen";
-      break;
-    case PlatformFullscreenType::kImmersive:
-      out << "immersive fullscreen";
-      break;
-  }
-#endif
   out << ", title=" << window_title_
       << ", is_active=" << ToBoolString(is_active_)
       << ", restore_session_id=" << restore_session_id_;
@@ -1131,76 +1108,97 @@
   workspace_extension_delegate_ = delegate;
 }
 
-void WaylandToplevelWindow::TriggerStateChanges() {
-  if (!shell_toplevel_)
-    return;
-
-  // Call UnSetMaximized only if current state is normal. Otherwise, if the
-  // current state is fullscreen and the previous is maximized, calling
-  // UnSetMaximized may result in wrong restored window position that clients
-  // are not allowed to know about.
-  if (state_ == PlatformWindowState::kMinimized) {
-    LOG(FATAL) << "Should not be called with kMinimized state";
-  } else if (state_ == PlatformWindowState::kFullScreen) {
-    shell_toplevel_->SetFullscreen(
-        GetWaylandOutputForDisplayId(fullscreen_display_id_));
-  } else if (state_ == PlatformWindowState::kPinnedFullscreen ||
-             state_ == PlatformWindowState::kTrustedPinnedFullscreen) {
-    if (auto* zaura_surface = GetZAuraSurface()) {
-      zaura_surface->SetPin(state_ ==
-                            PlatformWindowState::kTrustedPinnedFullscreen);
+void WaylandToplevelWindow::TriggerStateChanges(
+    PlatformWindowState window_state) {
+  if (shell_toplevel_) {
+    // Call UnSetMaximized only if current state is normal. Otherwise, if the
+    // current state is fullscreen and the previous is maximized, calling
+    // UnSetMaximized may result in wrong restored window position that clients
+    // are not allowed to know about.
+    if (window_state == PlatformWindowState::kMinimized) {
+      LOG(FATAL) << "Should not be called with kMinimized state";
+    } else if (window_state == PlatformWindowState::kFullScreen) {
+      shell_toplevel_->SetFullscreen(
+          GetWaylandOutputForDisplayId(fullscreen_display_id_));
+    } else if (window_state == PlatformWindowState::kPinnedFullscreen ||
+               window_state == PlatformWindowState::kTrustedPinnedFullscreen) {
+      if (auto* zaura_surface = GetZAuraSurface()) {
+        zaura_surface->SetPin(window_state ==
+                              PlatformWindowState::kTrustedPinnedFullscreen);
+      }
+    } else if (GetLatestRequestedState().window_state ==
+               PlatformWindowState::kFullScreen) {
+      shell_toplevel_->UnSetFullscreen();
+    } else if (GetLatestRequestedState().window_state ==
+                   PlatformWindowState::kPinnedFullscreen ||
+               GetLatestRequestedState().window_state ==
+                   PlatformWindowState::kTrustedPinnedFullscreen) {
+      if (auto* zaura_surface = GetZAuraSurface()) {
+        zaura_surface->UnsetPin();
+      }
+    } else if (window_state == PlatformWindowState::kMaximized) {
+      shell_toplevel_->SetMaximized();
+    } else if (window_state == PlatformWindowState::kNormal) {
+      shell_toplevel_->UnSetMaximized();
     }
-  } else if (previous_state_ == PlatformWindowState::kFullScreen) {
-    shell_toplevel_->UnSetFullscreen();
-  } else if (previous_state_ == PlatformWindowState::kPinnedFullscreen ||
-             previous_state_ == PlatformWindowState::kTrustedPinnedFullscreen) {
-    if (auto* zaura_surface = GetZAuraSurface()) {
-      zaura_surface->UnsetPin();
-    }
-  } else if (state_ == PlatformWindowState::kMaximized) {
-    shell_toplevel_->SetMaximized();
-  } else if (state_ == PlatformWindowState::kNormal) {
-    shell_toplevel_->UnSetMaximized();
   }
 
-  delegate()->OnWindowStateChanged(previous_state_, state_);
+  // Update the window state of the applied state before calling
+  // OnWindowStateChanged so it can be used to pick up the new window state. We
+  // cannot request state here because the bounds is not yet synchronized with
+  // window state. Requesting the state will trigger SetWindowGeometry with the
+  // current bounds + insets, so it has a risk to set geometry aligning with the
+  // client side window state while the server side has not yet configured it.
+  // This behavior is not necessarily a problem, but it causes the failure on
+  // weston.
+  // TODO(crbug.com/40276379): Remove this once this is async.
+  auto previous_state = applied_state().window_state;
+  ForceApplyWindowStateDoNotUse(window_state);
+  delegate()->OnWindowStateChanged(previous_state, window_state);
   connection()->Flush();
 }
 
-void WaylandToplevelWindow::SetWindowState(PlatformWindowState state,
+void WaylandToplevelWindow::SetWindowState(PlatformWindowState window_state,
                                            int64_t target_display_id) {
-  CHECK_NE(state, PlatformWindowState::kMinimized);
+  CHECK_NE(window_state, PlatformWindowState::kMinimized);
 
-  if (ShouldTriggerStateChange(state, target_display_id)) {
-    // We don't want to update the previous state, for cases like fullscreening
-    // to a different output while already in fullscreen, so we can still
-    // restore back to the previous non-fullscreen state.
-    if (state_ != state) {
-      previous_state_ = state_;
-      state_ = state;
+  if (ShouldTriggerStateChange(window_state, target_display_id)) {
+    // TODO(crbug.com/40276379): Use `GetLatestRequestedState().window_state`
+    // instead once the window state becomes async.
+    auto previous_state = applied_state().window_state;
+
+    // We want to remember whether it was previously maximized, for cases like
+    // fullscreening to a different output while already in fullscreen, so we
+    // can still restore back to the previous non-fullscreen state.
+    if (previous_state != window_state) {
+      previously_maximized_ = previous_state == PlatformWindowState::kMaximized;
     }
+
     // Remember the display id if we are going to fullscreen - otherwise reset.
-    fullscreen_display_id_ = (state_ == PlatformWindowState::kFullScreen)
+    fullscreen_display_id_ = (window_state == PlatformWindowState::kFullScreen)
                                  ? target_display_id
                                  : display::kInvalidDisplayId;
-    // Tracks this window show state change request, coming from the Browser.
-    requested_window_show_state_count_++;
 
-    TriggerStateChanges();
+    TriggerStateChanges(window_state);
   }
 }
 
 bool WaylandToplevelWindow::ShouldTriggerStateChange(
-    PlatformWindowState state,
+    PlatformWindowState window_state,
     int64_t target_display_id) const {
   // Allow the state transition if the state is different.
-  if (state_ != state) {
+  //
+  // The latest requested state from the client is stored as
+  // `applied_state().window_state` so use it as a previous state.
+  // TODO(crbug.com/40276379): Use `GetLatestRequestedState().window_state`
+  // instead once the window state becomes async.
+  if (applied_state().window_state != window_state) {
     return true;
   }
 
   // Allow the state transition if the state is fullscreen and the screen has
   // changed to something explicit - or different.
-  if (state == PlatformWindowState::kFullScreen &&
+  if (window_state == PlatformWindowState::kFullScreen &&
       target_display_id != display::kInvalidDisplayId &&
       target_display_id != fullscreen_display_id_) {
     return true;
diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
index 2561b08d..012fa5fe 100644
--- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.h
@@ -91,7 +91,7 @@
   bool OnInitialize(PlatformWindowInitProperties properties,
                     PlatformWindowDelegate::State* state) override;
   bool IsActive() const override;
-  void SetWindowGeometry(gfx::Size size_dip) override;
+  void SetWindowGeometry(const PlatformWindowDelegate::State& state) override;
   bool IsScreenCoordinatesEnabled() const override;
   bool SupportsConfigureMinimizedState() const override;
   bool SupportsConfigurePinnedState() const override;
@@ -211,7 +211,7 @@
 
   void UpdateSystemModal();
 
-  void TriggerStateChanges();
+  void TriggerStateChanges(PlatformWindowState window_state);
 
   // Sets the new window `state` to the window. `target_display_id` gets ignored
   // unless the state is `PlatformWindowState::kFullscreen`.
@@ -256,10 +256,11 @@
   // Wrappers around shell surface.
   std::unique_ptr<ShellToplevelWrapper> shell_toplevel_;
 
-  // Contains the current state of the window.
-  PlatformWindowState state_ = PlatformWindowState::kUnknown;
-  // Contains the previous state of the window.
-  PlatformWindowState previous_state_ = PlatformWindowState::kUnknown;
+  // True if it's maximized before requesting the window state change from the
+  // client.
+  // TODO(b/328109805): Move this logic to server side on Lacros.
+  bool previously_maximized_ = false;
+
   // The display ID to switch to in case the state is `kFullscreen`.
   int64_t fullscreen_display_id_ = display::kInvalidDisplayId;
 
@@ -271,10 +272,6 @@
   bool is_active_ = false;
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
-  // This is used to detect fullscreen type changes from the Aura side
-  // to inform Lacros clients from the asynchronous task completion.
-  PlatformFullscreenType fullscreen_type_ = PlatformFullscreenType::kNone;
-
   // The flag that indicates the last requested immersive fullscreen status from
   // SetImmersiveFullscreenStatue to detect the immersive status changes. Set to
   // null if it had never been called.
@@ -309,16 +306,6 @@
   std::optional<std::vector<gfx::Rect>> opaque_region_px_;
   std::optional<std::vector<gfx::Rect>> input_region_px_;
 
-  // Tracks how many the window show state requests by made by the Browser
-  // are currently being processed by the Wayland Compositor. In practice,
-  // each individual increment corresponds to an explicit window show state
-  // change request, and gets a response by the Compositor.
-  //
-  // This mechanism allows Ozone/Wayland to filter out notifying the delegate
-  // (PlatformWindowDelegate) more than once, for the same window show state
-  // change.
-  uint32_t requested_window_show_state_count_ = 0;
-
   // Information used by the compositor to restore the window state upon
   // creation.
   int32_t restore_session_id_ = 0;
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 1925f95..cb640d8 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -363,7 +363,7 @@
 void WaylandWindow::Show(bool inactive) {
   // Initially send the window geometry. After this, we only update window
   // geometry when the value in latched_state_ updates.
-  SetWindowGeometry(latched_state_.bounds_dip.size());
+  SetWindowGeometry(latched_state_);
   frame_manager_->MaybeProcessPendingFrame();
 }
 
@@ -414,9 +414,6 @@
       << ", restore_bounds_dip=" << restored_bounds_dip_.ToString()
       << ", overlay_delegation="
       << (wayland_overlay_delegation_enabled_ ? "enabled" : "disabled");
-  if (frame_insets_px_) {
-    out << ", frame_insets=" << frame_insets_px_->ToString();
-  }
   if (has_touch_focus_) {
     out << ", has_touch_focus";
   }
@@ -599,19 +596,6 @@
   NOTIMPLEMENTED_LOG_ONCE();
 }
 
-void WaylandWindow::SetDecorationInsets(const gfx::Insets* insets_px) {
-  // TODO(crbug.com/1395267): Add window geometry to WaylandWindow::State.
-  if ((!frame_insets_px_ && !insets_px) ||
-      (frame_insets_px_ && insets_px && *frame_insets_px_ == *insets_px)) {
-    return;
-  }
-  if (insets_px) {
-    frame_insets_px_ = *insets_px;
-  } else {
-    frame_insets_px_ = std::nullopt;
-  }
-}
-
 void WaylandWindow::SetWindowIcons(const gfx::ImageSkia& window_icon,
                                    const gfx::ImageSkia& app_icon) {
   NOTIMPLEMENTED_LOG_ONCE();
@@ -886,6 +870,7 @@
   }
 
   PlatformWindowDelegate::State state;
+  state.window_state = PlatformWindowState::kUnknown;
   state.bounds_dip = properties.bounds;
 
   // Make sure we don't store empty bounds, or else later on we might send an
@@ -922,8 +907,6 @@
 
   connection_->window_manager()->AddWindow(GetWidget(), this);
 
-  SetDecorationInsets(&properties.frame_insets_px);
-
   if (!OnInitialize(std::move(properties), &state)) {
     return false;
   }
@@ -954,23 +937,13 @@
   return true;
 }
 
-void WaylandWindow::SetWindowGeometry(gfx::Size size_dip) {}
+void WaylandWindow::SetWindowGeometry(
+    const PlatformWindowDelegate::State& state) {}
 
 gfx::Vector2d WaylandWindow::GetWindowGeometryOffsetInDIP() const {
-  if (!frame_insets_px_.has_value()) {
-    return {};
-  }
-
-  auto scale = applied_state().window_scale;
-  return {static_cast<int>(frame_insets_px_->left() / scale),
-          static_cast<int>(frame_insets_px_->top() / scale)};
-}
-
-gfx::Insets WaylandWindow::GetDecorationInsetsInDIP() const {
-  auto scale = latched_state().window_scale;
-  return frame_insets_px_.has_value()
-             ? gfx::ScaleToRoundedInsets(*frame_insets_px_, 1.f / scale)
-             : gfx::Insets{};
+  const auto& insets_dip =
+      delegate()->CalculateInsetsInDIP(GetPlatformWindowState());
+  return {insets_dip.left(), insets_dip.top()};
 }
 
 WaylandWindow* WaylandWindow::GetRootParentWindow() {
@@ -1276,6 +1249,15 @@
   // For values not specified in pending_configure_state_, use the latest
   // requested values.
   auto state = GetLatestRequestedState();
+
+  if (pending_configure_state_.window_state.has_value()) {
+    state.window_state = pending_configure_state_.window_state.value();
+  }
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (pending_configure_state_.fullscreen_type.has_value()) {
+    state.fullscreen_type = pending_configure_state_.fullscreen_type.value();
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
   if (pending_configure_state_.bounds_dip.has_value()) {
     state.bounds_dip = pending_configure_state_.bounds_dip.value();
   }
@@ -1346,7 +1328,17 @@
   // latched state, because in flight configure requests are only removed on
   // latch.
   if (in_flight_requests_.empty()) {
-    DCHECK_EQ(applied_state_, latched_state_);
+    // Currently, we have a hack that overrides `applied_state_.window_state`
+    // when the window state change is requested from the client. In such case,
+    // `applied_state_` may take a different window state value from
+    // `latched_state_` when the server side sends configure event.
+    // TODO(crbug.com/40276379): Check window state is equal between
+    // `applied_state_` and `latched_state_` as well.
+    auto applied_state_copy = applied_state_;
+    // Override `applied_state_.window_state` as the same value as
+    // `latched_state_` to exclude `window_state` from equivalence check.
+    applied_state_copy.window_state = latched_state_.window_state;
+    CHECK_EQ(applied_state_copy, latched_state_);
   }
 
   // Adjust state values if necessary.
@@ -1361,9 +1353,6 @@
   state.size_px = gfx::ScaleToEnclosingRectIgnoringError(
                       gfx::Rect(state.bounds_dip.size()), state.window_scale)
                       .size();
-  // This will ensure that if insets at the time of the request changed, a new
-  // frame is produced when the state is applied.
-  state.insets = GetDecorationInsetsInDIP();
 
   StateRequest req{.state = state, .serial = serial};
   if (in_flight_requests_.empty()) {
@@ -1498,8 +1487,6 @@
   // Latch the most up to date state we have a frame back for.
   auto old_state = latched_state_;
   latched_state_ = req.state;
-  auto old_latched_insets = latched_insets_;
-  latched_insets_ = GetDecorationInsetsInDIP();
 
   // Update the geometry if the bounds are different or the window scale has
   // been changed or if the insets have changed since the last latched request.
@@ -1508,14 +1495,13 @@
   // the device scale factor known from the display. It can be different from
   // the one that the |latch_state_.window_scale| has. As a result, the geometry
   // is set with wrong values as Wayland requires them to be in DIP.
+  // TODO(crbug.com/328011220): Investigate whether we can remove
+  // `window_scale` check from here.
   if (req.state.bounds_dip.size() != old_state.bounds_dip.size() ||
       req.state.window_scale != old_state.window_scale ||
-      // If insets change that is a geometry change even when the bounds or
-      // scale remain the same. The updated insets may not be known at the time
-      // of the request, hence the need to check this if there are changes in
-      // insets since it latched the last time.
-      old_latched_insets != latched_insets_) {
-    SetWindowGeometry(req.state.bounds_dip.size());
+      delegate()->CalculateInsetsInDIP(req.state.window_state) !=
+          delegate()->CalculateInsetsInDIP(old_state.window_state)) {
+    SetWindowGeometry(req.state);
   }
   UpdateWindowMask();
   if (req.serial != -1) {
@@ -1580,4 +1566,14 @@
   }
 }
 
+PlatformWindowDelegate::State WaylandWindow::GetLatestRequestedState() const {
+  return in_flight_requests_.empty() ? applied_state_
+                                     : in_flight_requests_.back().state;
+}
+
+void WaylandWindow::ForceApplyWindowStateDoNotUse(
+    PlatformWindowState window_state) {
+  applied_state_.window_state = window_state;
+}
+
 }  // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h
index 915b8f9d..930f364b 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -114,6 +114,7 @@
   }
   WaylandWindow* parent_window() const { return parent_window_; }
   PlatformWindowDelegate* delegate() { return delegate_; }
+  const PlatformWindowDelegate* delegate() const { return delegate_; }
 
   gfx::AcceleratedWidget GetWidget() const;
 
@@ -219,7 +220,6 @@
   gfx::Rect GetRestoredBoundsInDIP() const override;
   bool ShouldWindowContentsBeTransparent() const override;
   void SetAspectRatio(const gfx::SizeF& aspect_ratio) override;
-  void SetDecorationInsets(const gfx::Insets* insets_px) override;
   void SetWindowIcons(const gfx::ImageSkia& window_icon,
                       const gfx::ImageSkia& app_icon) override;
   void SizeConstraintsChanged() override;
@@ -330,14 +330,11 @@
   virtual void OnDragSessionClose(ui::mojom::DragOperation operation);
 
   // Sets the window geometry.
-  virtual void SetWindowGeometry(gfx::Size size_dip);
+  virtual void SetWindowGeometry(const PlatformWindowDelegate::State& state);
 
   // Returns the offset of the window geometry within the window surface.
   gfx::Vector2d GetWindowGeometryOffsetInDIP() const;
 
-  // Returns the effective decoration insets.
-  gfx::Insets GetDecorationInsetsInDIP() const;
-
   // Returns a root parent window within the same hierarchy.
   WaylandWindow* GetRootParentWindow();
 
@@ -480,10 +477,17 @@
   // Returns the next state that will be applied, or the currently applied state
   // if there are no later unapplied states. This is used when updating a single
   // property (e.g. window scale) without wanting to modify the others.
-  PlatformWindowDelegate::State GetLatestRequestedState() const {
-    return in_flight_requests_.empty() ? applied_state_
-                                       : in_flight_requests_.back().state;
-  }
+  PlatformWindowDelegate::State GetLatestRequestedState() const;
+
+  // Sets given `window_state` to `applied_state_` so that it reflects the
+  // client side window state change immediately, Not that
+  // `applied_state_.window_state` is the source of truth as a window state.
+  // This should be called only when the client side requests the new window
+  // state and it is expected to become the same when the server side
+  // configures.
+  // DO NOT USE THIS unless it's really needed and okay to use.
+  // TODO(crbug.com/40276379): Remove this.
+  void ForceApplyWindowStateDoNotUse(PlatformWindowState window_state);
 
   bool HasInFlightRequestsForStateForTesting() const {
     return !in_flight_requests_.empty();
@@ -492,6 +496,10 @@
   // PendingConfigureState describes the content of a configure sent from the
   // wayland server.
   struct PendingConfigureState {
+    std::optional<PlatformWindowState> window_state;
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    std::optional<PlatformFullscreenType> fullscreen_type;
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
     std::optional<gfx::Rect> bounds_dip;
     std::optional<gfx::Size> size_px;
     std::optional<float> raster_scale;
@@ -626,12 +634,6 @@
   scoped_refptr<BitmapCursor> cursor_;
 #endif
 
-  // Margins between edges of the surface and the window geometry (i.e., the
-  // area of the window that is visible to the user as the actual window).  The
-  // areas outside the geometry are used to draw client-side window decorations.
-  // TODO(crbug.com/1306688): Use DIP for frame insets.
-  std::optional<gfx::Insets> frame_insets_px_;
-
   bool has_touch_focus_ = false;
   // The UI scale may be forced through the command line, which means that it
   // replaces the default value that is equal to the natural device scale.
@@ -711,9 +713,6 @@
   // server. See the comments on applied_state_ for further explanation.
   PlatformWindowDelegate::State latched_state_;
 
-  // Stores the insets in DIP at the time of the last latched state.
-  gfx::Insets latched_insets_;
-
   // In-flight state requests. Once a frame comes from the GPU
   // process with the appropriate viz sequence number, ack_configure request
   // with |serial| will be sent to the Wayland compositor if needed.
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 9b6b77e..345f077e 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -515,152 +515,6 @@
   wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
 }
 
-// Checks that decoration insets do not change final bounds and that
-// WaylandToplevelWindow::HandleToplevelConfigure does correct rounding when
-// some sides of insets divides by 2 with remainder.
-TEST_P(WaylandWindowTest, SetDecorationInsets) {
-  constexpr gfx::Rect kNormalBounds{956, 556};
-  constexpr auto kHiDpiScale = 2;
-  const BoundsChange kHiDpiBounds{false};
-
-  window_->SetBoundsInDIP(kNormalBounds);
-
-  auto state = InitializeWlArrayWithActivatedState();
-
-  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
-    wl::TestOutput* output = server->output();
-    // Send the window to |output|.
-    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
-    ASSERT_TRUE(surface);
-    wl_surface_send_enter(surface->resource(), output->resource());
-  });
-
-  // Set insets for normal DPI.
-  const auto kDecorationInsets = gfx::Insets::TLBR(24, 28, 32, 28);
-  auto bounds_with_insets = kNormalBounds;
-  bounds_with_insets.Inset(kDecorationInsets);
-  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
-  PostToServerAndWait([id = surface_id_, bounds_with_insets](
-                          wl::TestWaylandServerThread* server) {
-    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
-    ASSERT_TRUE(surface);
-    wl::MockXdgSurface* xdg_surface = surface->xdg_surface();
-    EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds_with_insets));
-  });
-  window_->SetDecorationInsets(&kDecorationInsets);
-  AdvanceFrameToCurrent(window_.get(), delegate_);
-  VerifyAndClearExpectations();
-
-  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
-  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
-    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
-    ASSERT_TRUE(surface);
-    wl::MockXdgSurface* xdg_surface = surface->xdg_surface();
-    // No update to the window geometry.
-    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
-  });
-
-  SendConfigureEvent(surface_id_, bounds_with_insets.size(), state);
-  AdvanceFrameToCurrent(window_.get(), delegate_);
-  VerifyAndClearExpectations();
-
-  // Change scale.  This is the only time when we expect the pixel position to
-  // change.
-  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kHiDpiBounds))).Times(1);
-  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
-    auto* output = server->output();
-    output->SetScale(kHiDpiScale);
-    output->SetDeviceScaleFactor(kHiDpiScale);
-    output->Flush();
-  });
-
-  // Pretend we are already rendering using new scale.
-  window_->root_surface()->set_surface_buffer_scale(kHiDpiScale);
-
-  // Set new insets so that rounding does not result in integer.
-  constexpr auto kDecorationInsets_2x = gfx::Insets::TLBR(48, 55, 63, 55);
-  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
-    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
-    ASSERT_TRUE(surface);
-    wl::MockXdgSurface* xdg_surface = surface->xdg_surface();
-    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(28, 24, 900, 500)))
-        .Times(1);
-  });
-
-  window_->SetDecorationInsets(&kDecorationInsets_2x);
-  AdvanceFrameToCurrent(window_.get(), delegate_);
-  VerifyAndClearExpectations();
-
-  // Now send configure events many times - bounds mustn't change.
-  for (size_t i = 0; i < 10; i++) {
-    EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
-    PostToServerAndWait(
-        [id = surface_id_](wl::TestWaylandServerThread* server) {
-          wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
-          ASSERT_TRUE(surface);
-          wl::MockXdgSurface* xdg_surface = surface->xdg_surface();
-          // No update to the window geometry.
-          EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
-        });
-    SendConfigureEvent(surface_id_, bounds_with_insets.size(), state);
-    AdvanceFrameToCurrent(window_.get(), delegate_);
-  }
-  VerifyAndClearExpectations();
-}
-
-// Checks that geometry is set when decoration insets change even when bounds or
-// scale don't.
-TEST_P(WaylandWindowTest, OnlyChangeDecorationInsets) {
-  // The bounds never change throughout this test
-  constexpr gfx::Rect kBounds{980, 1188};
-
-  window_->SetBoundsInDIP(kBounds);
-
-  auto state = InitializeWlArrayWithActivatedState();
-
-  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
-    wl::TestOutput* output = server->output();
-    // Send the window to |output|.
-    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
-    ASSERT_TRUE(surface);
-    wl_surface_send_enter(surface->resource(), output->resource());
-  });
-
-  const auto kInitialInsets = gfx::Insets::TLBR(20, 36, 52, 36);
-  auto bounds_with_insets = kBounds;
-  bounds_with_insets.Inset(kInitialInsets);
-  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
-  PostToServerAndWait([id = surface_id_, bounds_with_insets](
-                          wl::TestWaylandServerThread* server) {
-    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
-    ASSERT_TRUE(surface);
-    wl::MockXdgSurface* xdg_surface = surface->xdg_surface();
-    EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds_with_insets));
-  });
-  window_->SetDecorationInsets(&kInitialInsets);
-  AdvanceFrameToCurrent(window_.get(), delegate_);
-  VerifyAndClearExpectations();
-
-  const auto kNewInsets = gfx::Insets::TLBR(10, 10, 10, 10);
-  bounds_with_insets = kBounds;
-  bounds_with_insets.Inset(kNewInsets);
-  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
-  PostToServerAndWait([id = surface_id_, bounds_with_insets](
-                          wl::TestWaylandServerThread* server) {
-    wl::MockSurface* surface = server->GetObject<wl::MockSurface>(id);
-    ASSERT_TRUE(surface);
-    wl::MockXdgSurface* xdg_surface = surface->xdg_surface();
-    EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds_with_insets));
-  });
-
-  // Change insets here so that these are detected when a new state is requested
-  // from the server.
-  window_->SetDecorationInsets(&kNewInsets);
-  SendConfigureEvent(surface_id_, bounds_with_insets.size(), state);
-  AdvanceFrameToCurrent(window_.get(), delegate_);
-  VerifyAndClearExpectations();
-}
-
 #if BUILDFLAG(IS_LINUX)
 // Checks that when the window gets some of its edges tiled, it notifies the
 // delegate appropriately.
@@ -844,6 +698,7 @@
 
   window_->HandleToplevelConfigure(kMaximizedBounds.width(),
                                    kMaximizedBounds.height(), window_states);
+  window_->HandleSurfaceConfigure(2);
 
   EXPECT_EQ(PlatformWindowState::kMaximized, window_->GetPlatformWindowState());
   EXPECT_EQ(window_->GetRestoredBoundsInDIP(), kNormalBounds);
@@ -882,10 +737,7 @@
   // calls) invoke SetWindowGeometry(), but that should not happen during the
   // change of the window state.
   // See https://crbug.com/1223005.
-  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _))
-      .Times(1)
-      .WillOnce(
-          testing::Invoke([this]() { window_->SetDecorationInsets({}); }));
+  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
   window_->Maximize();
   SendConfigureEvent(surface_id_, kMaximizedBounds.size(), active_maximized);
   AdvanceFrameToCurrent(window_.get(), delegate_);
@@ -928,10 +780,7 @@
   // calls) invoke SetWindowGeometry(), but that should not happen during the
   // change of the window state.
   // See https://crbug.com/1223005.
-  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _))
-      .Times(1)
-      .WillOnce(
-          testing::Invoke([this]() { window_->SetDecorationInsets({}); }));
+  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
   EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0);
   EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
@@ -948,6 +797,110 @@
   VerifyAndClearExpectations();
 }
 
+TEST_P(WaylandWindowTest, MaximizeAndRestoreWithInsets) {
+  constexpr gfx::Rect kNormalBounds{510, 310};
+  constexpr gfx::Insets kNormalInsets(5);
+
+  constexpr gfx::Rect kMaximizedBounds{800, 600};
+  constexpr gfx::Insets kMaximizedInsets(0);
+
+  // Make sure the window has normal state initially.
+  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
+  window_->SetBoundsInDIP(gfx::Rect(kNormalBounds.size()));
+  EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
+  AdvanceFrameToCurrent(window_.get(), delegate_);
+  VerifyAndClearExpectations();
+
+  // Deactivate the surface.
+  auto empty_state = MakeStateArray({});
+  SendConfigureEvent(surface_id_, {0, 0}, empty_state);
+  AdvanceFrameToCurrent(window_.get(), delegate_);
+
+  auto maximized_geometry = kMaximizedBounds;
+  maximized_geometry.Inset(kMaximizedInsets);
+  auto active_maximized = MakeStateArray(
+      {XDG_TOPLEVEL_STATE_ACTIVATED, XDG_TOPLEVEL_STATE_MAXIMIZED});
+  PostToServerAndWait([id = surface_id_, bounds = maximized_geometry](
+                          wl::TestWaylandServerThread* server) {
+    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
+    ASSERT_TRUE(mock_surface);
+    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
+    EXPECT_CALL(*xdg_surface->xdg_toplevel(), SetMaximized());
+    EXPECT_CALL(*xdg_surface, SetWindowGeometry(gfx::Rect(bounds.size())));
+  });
+  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
+  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
+  // Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
+  // the real delegate.  Its OnWindowStateChanged() may (through some chain of
+  // calls) invoke SetWindowGeometry(), but that should not happen during the
+  // change of the window state.
+  // See https://crbug.com/1223005.
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
+      .WillRepeatedly(Return(kMaximizedInsets));
+  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
+  window_->Maximize();
+  SendConfigureEvent(surface_id_, maximized_geometry.size(), active_maximized);
+  AdvanceFrameToCurrent(window_.get(), delegate_);
+  VerifyAndClearExpectations();
+
+  auto inactive_maximized = MakeStateArray({XDG_TOPLEVEL_STATE_MAXIMIZED});
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
+      .WillRepeatedly(Return(kMaximizedInsets));
+  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
+    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
+    ASSERT_TRUE(mock_surface);
+    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
+    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
+  });
+  EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)));
+  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
+  SendConfigureEvent(surface_id_, maximized_geometry.size(),
+                     inactive_maximized);
+  AdvanceFrameToCurrent(window_.get(), delegate_);
+  VerifyAndClearExpectations();
+
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
+      .WillRepeatedly(Return(kMaximizedInsets));
+  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
+    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
+    ASSERT_TRUE(mock_surface);
+    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
+    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
+  });
+  EXPECT_CALL(delegate_, OnActivationChanged(Eq(true)));
+  EXPECT_CALL(delegate_, OnBoundsChanged(_)).Times(0);
+  SendConfigureEvent(surface_id_, maximized_geometry.size(), active_maximized);
+  AdvanceFrameToCurrent(window_.get(), delegate_);
+  VerifyAndClearExpectations();
+
+  auto normal_geometry = kNormalBounds;
+  normal_geometry.Inset(kNormalInsets);
+  // Emulate a piece of behaviour of BrowserDesktopWindowTreeHostLinux, which is
+  // the real delegate.  Its OnWindowStateChanged() may (through some chain of
+  // calls) invoke SetWindowGeometry(), but that should not happen during the
+  // change of the window state.
+  // See https://crbug.com/1223005.
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
+      .WillRepeatedly(Return(kNormalInsets));
+  EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
+  EXPECT_CALL(delegate_, OnActivationChanged(_)).Times(0);
+  EXPECT_CALL(delegate_, OnBoundsChanged(Eq(kDefaultBoundsChange)));
+  PostToServerAndWait([id = surface_id_, bounds = normal_geometry](
+                          wl::TestWaylandServerThread* server) {
+    wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
+    ASSERT_TRUE(mock_surface);
+    wl::MockXdgSurface* xdg_surface = mock_surface->xdg_surface();
+    EXPECT_CALL(*xdg_surface->xdg_toplevel(), UnsetMaximized());
+    EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds));
+  });
+  window_->Restore();
+  // Reinitialize wl_array, which removes previous old states.
+  auto active = InitializeWlArrayWithActivatedState();
+  SendConfigureEvent(surface_id_, {0, 0}, active);
+  AdvanceFrameToCurrent(window_.get(), delegate_);
+  VerifyAndClearExpectations();
+}
+
 // Tests the event sequence where a minimize request is initiated by the client.
 TEST_P(WaylandWindowTest, ClientInitiatedMinimize) {
   if (!window_->SupportsConfigureMinimizedState()) {
@@ -1103,11 +1056,14 @@
 }
 
 TEST_P(WaylandWindowTest, SetFullscreenAndRestore) {
+  constexpr gfx::Rect kNormalBounds{500, 300};
+  constexpr gfx::Rect kFullscreenBounds{800, 600};
+
   // Make sure the window is initialized to normal state from the beginning.
   EXPECT_EQ(PlatformWindowState::kNormal, window_->GetPlatformWindowState());
 
   wl::ScopedWlArray states = InitializeWlArrayWithActivatedState();
-  SendConfigureEvent(surface_id_, {0, 0}, states);
+  SendConfigureEvent(surface_id_, kNormalBounds.size(), states);
 
   states.AddStateToWlArray(XDG_TOPLEVEL_STATE_FULLSCREEN);
 
@@ -1121,10 +1077,10 @@
   window_->SetFullscreen(true, display::kInvalidDisplayId);
   // Make sure than WaylandWindow manually handles fullscreen states. Check the
   // comment in the WaylandWindow::SetFullscreen.
+  VerifyAndClearExpectations();
+  SendConfigureEvent(surface_id_, kFullscreenBounds.size(), states);
   EXPECT_EQ(window_->GetPlatformWindowState(),
             PlatformWindowState::kFullScreen);
-  VerifyAndClearExpectations();
-  SendConfigureEvent(surface_id_, {0, 0}, states);
 
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
     wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
@@ -1135,11 +1091,10 @@
 
   EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
   window_->Restore();
-  EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
   VerifyAndClearExpectations();
   // Reinitialize wl_array, which removes previous old states.
   states = InitializeWlArrayWithActivatedState();
-  SendConfigureEvent(surface_id_, {0, 0}, states);
+  SendConfigureEvent(surface_id_, kNormalBounds.size(), states);
   EXPECT_EQ(window_->GetPlatformWindowState(), PlatformWindowState::kNormal);
 }
 
@@ -1169,7 +1124,13 @@
         server->GetObject<wl::MockSurface>(surface_id);
     EXPECT_FALSE(mock_surface->xdg_surface());
   });
-  EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
+
+  // We must receive a state change after SetFullscreen.
+  EXPECT_CALL(delegate,
+              OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
+                                   Eq(PlatformWindowState::kFullScreen)))
+      .Times(1);
+
   window->SetFullscreen(true, display::kInvalidDisplayId);
   // The state of the window must already be fullscreen one.
   EXPECT_EQ(window->GetPlatformWindowState(), PlatformWindowState::kFullScreen);
@@ -1178,17 +1139,9 @@
 
   Mock::VerifyAndClearExpectations(&delegate);
 
-  // We must receive a state change after Show is called.
-  EXPECT_CALL(delegate,
-              OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
-                                   Eq(PlatformWindowState::kFullScreen)))
-      .Times(1);
-
   // Show and Activate the surface.
   window->Show(false);
 
-  Mock::VerifyAndClearExpectations(&delegate);
-
   // We mustn't receive any state changes if that does not differ from the last
   // state.
   EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
@@ -1226,7 +1179,12 @@
         server->GetObject<wl::MockSurface>(surface_id);
     EXPECT_FALSE(mock_surface->xdg_surface());
   });
-  EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
+
+  // We must receive a state change after Show is called.
+  EXPECT_CALL(delegate,
+              OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
+                                   Eq(PlatformWindowState::kMaximized)))
+      .Times(1);
 
   window->Maximize();
   // The state of the window must already be fullscreen one.
@@ -1236,17 +1194,9 @@
 
   Mock::VerifyAndClearExpectations(&delegate);
 
-  // We must receive a state change after Show is called.
-  EXPECT_CALL(delegate,
-              OnWindowStateChanged(Eq(PlatformWindowState::kNormal),
-                                   Eq(PlatformWindowState::kMaximized)))
-      .Times(1);
-
   // Show the window now.
   window->Show(false);
 
-  Mock::VerifyAndClearExpectations(&delegate);
-
   // Window show state should be already up to date, so delegate is not
   // notified.
   EXPECT_CALL(delegate, OnWindowStateChanged(_, _)).Times(0);
@@ -1272,8 +1222,8 @@
   // Set nonzero insets and ensure that they are only used when the window has
   // normal state.
   // See https://crbug.com/1274629
-  window_->SetDecorationInsets(&kInsets);
-
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
+      .WillRepeatedly(Return(gfx::Insets()));
   EXPECT_CALL(delegate_,
               OnWindowStateChanged(_, Eq(PlatformWindowState::kMaximized)))
       .Times(1);
@@ -1292,6 +1242,8 @@
   VerifyAndClearExpectations();
 
   // Unmaximize
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
+      .WillRepeatedly(Return(kInsets));
   EXPECT_CALL(delegate_,
               OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
       .Times(1);
@@ -1319,6 +1271,8 @@
                                       PlatformFullscreenType::kPlain))
       .Times(1);
 #endif
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kFullScreen))
+      .WillRepeatedly(Return(gfx::Insets()));
   EXPECT_CALL(delegate_,
               OnWindowStateChanged(_, Eq(PlatformWindowState::kFullScreen)))
       .Times(1);
@@ -1339,6 +1293,8 @@
                                                  PlatformFullscreenType::kNone))
       .Times(1);
 #endif
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
+      .WillRepeatedly(Return(kInsets));
   EXPECT_CALL(delegate_,
               OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
       .Times(1);
@@ -1360,6 +1316,8 @@
   VerifyAndClearExpectations();
 
   // Now, maximize, fullscreen and restore.
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kMaximized))
+      .WillRepeatedly(Return(gfx::Insets()));
   EXPECT_CALL(delegate_,
               OnWindowStateChanged(_, Eq(PlatformWindowState::kMaximized)))
       .Times(1);
@@ -1381,6 +1339,8 @@
                                       PlatformFullscreenType::kPlain))
       .Times(1);
 #endif
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kFullScreen))
+      .WillRepeatedly(Return(gfx::Insets()));
   EXPECT_CALL(delegate_,
               OnWindowStateChanged(_, Eq(PlatformWindowState::kFullScreen)))
       .Times(1);
@@ -1401,6 +1361,8 @@
                                                  PlatformFullscreenType::kNone))
       .Times(1);
 #endif
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
+      .WillRepeatedly(Return(kInsets));
   EXPECT_CALL(delegate_,
               OnWindowStateChanged(_, Eq(PlatformWindowState::kNormal)))
       .Times(1);
@@ -2382,6 +2344,8 @@
   auto bounds_with_insets = kMainWindowBounds;
   bounds_with_insets.Inset(kMainWindowInsets);
   EXPECT_CALL(delegate_, OnBoundsChanged(_));
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
+      .WillRepeatedly(Return(kMainWindowInsets));
   PostToServerAndWait([id = surface_id_, bounds_with_insets](
                           wl::TestWaylandServerThread* server) {
     wl::MockSurface* mock_surface = server->GetObject<wl::MockSurface>(id);
@@ -2390,7 +2354,8 @@
     EXPECT_CALL(*xdg_surface, SetWindowGeometry(bounds_with_insets));
   });
   window_->SetBoundsInDIP(kMainWindowBounds);
-  window_->SetDecorationInsets(&kMainWindowInsets);
+  SendConfigureEvent(surface_id_, bounds_with_insets.size(),
+                     MakeStateArray({XDG_TOPLEVEL_STATE_ACTIVATED}));
   AdvanceFrameToCurrent(window_.get(), delegate_);
   VerifyAndClearExpectations();
 
@@ -4847,6 +4812,7 @@
     window_->HandleSurfaceConfigure(3);
     EXPECT_EQ(window_->GetPlatformWindowState(),
               PlatformWindowState::kMinimized);
+    EXPECT_EQ(gfx::Rect(), window_->GetBoundsInDIP());
     VerifyAndClearExpectations();
   } else {
     EXPECT_CALL(delegate_, OnWindowStateChanged(_, _)).Times(1);
@@ -4863,19 +4829,20 @@
     // It must be still the same minimized state.
     EXPECT_EQ(window_->GetPlatformWindowState(),
               PlatformWindowState::kMinimized);
-  }
+    EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());
 
-  // The window geometry has to be set to the current bounds of the window for
-  // minimized state.
-  EXPECT_EQ(gfx::Rect(800, 600), window_->GetBoundsInDIP());
-  PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
-    auto* surface = server->GetObject<wl::MockSurface>(id);
-    auto* xdg_surface = surface->xdg_surface();
-    EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
-  });
-  // Send one additional empty configuration event for minimized state.
-  // (which means the surface is not maximized, fullscreen or activated)
-  SendConfigureEvent(surface_id_, {0, 0}, wl::ScopedWlArray({}));
+    // The window geometry has to be set to the current bounds of the window for
+    // minimized state.
+    PostToServerAndWait(
+        [id = surface_id_](wl::TestWaylandServerThread* server) {
+          auto* surface = server->GetObject<wl::MockSurface>(id);
+          auto* xdg_surface = surface->xdg_surface();
+          EXPECT_CALL(*xdg_surface, SetWindowGeometry(_)).Times(0);
+        });
+    // Send one additional empty configuration event for minimized state.
+    // (which means the surface is not maximized, fullscreen or activated)
+    SendConfigureEvent(surface_id_, {0, 0}, wl::ScopedWlArray({}));
+  }
 }
 
 class BlockableWaylandToplevelWindow : public WaylandToplevelWindow {
diff --git a/ui/ozone/platform/wayland/test/mock_wayland_platform_window_delegate.cc b/ui/ozone/platform/wayland/test/mock_wayland_platform_window_delegate.cc
index 93993945..b98fd65 100644
--- a/ui/ozone/platform/wayland/test/mock_wayland_platform_window_delegate.cc
+++ b/ui/ozone/platform/wayland/test/mock_wayland_platform_window_delegate.cc
@@ -35,6 +35,16 @@
 int64_t MockWaylandPlatformWindowDelegate::OnStateUpdate(
     const PlatformWindowDelegate::State& old,
     const PlatformWindowDelegate::State& latest) {
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  if (old.fullscreen_type != latest.fullscreen_type) {
+    OnFullscreenTypeChanged(old.fullscreen_type, latest.fullscreen_type);
+  }
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+  if (old.window_state != latest.window_state) {
+    OnWindowStateChanged(old.window_state, latest.window_state);
+  }
+
   if (old.bounds_dip != latest.bounds_dip || old.size_px != latest.size_px ||
       old.window_scale != latest.window_scale) {
     bool origin_changed = old.bounds_dip.origin() != latest.bounds_dip.origin();
@@ -45,7 +55,7 @@
     OnOcclusionStateChanged(latest.occlusion_state);
   }
 
-  if (!latest.ProducesFrameOnUpdateFrom(old)) {
+  if (!latest.WillProduceFrameOnUpdateFrom(old)) {
     return -1;
   }
 
diff --git a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
index a9509d4..8c87622 100644
--- a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
@@ -1577,6 +1577,9 @@
   PlatformWindowInitProperties properties;
   properties.type = PlatformWindowType::kWindow;
   properties.bounds = kNormalBounds;
+  gfx::Insets insets;
+  EXPECT_CALL(delegate_, CalculateInsetsInDIP(PlatformWindowState::kNormal))
+      .WillRepeatedly(testing::Return(insets));
   auto window = WaylandWindow::Create(&delegate_, connection_.get(),
                                       std::move(properties));
   ASSERT_TRUE(window);
@@ -1590,8 +1593,6 @@
   window->SetRestoredBoundsInDIP(kRestoredBounds);
   wl::SyncDisplay(connection_->display_wrapper(), *connection_->display());
 
-  gfx::Insets insets;
-  window->SetDecorationInsets(&insets);
   window->Show(false);
 
   const uint32_t surface_id = window->root_surface()->get_surface_id();
diff --git a/ui/ozone/platform/x11/x11_window.cc b/ui/ozone/platform/x11/x11_window.cc
index 55c1e9f..5765eec9 100644
--- a/ui/ozone/platform/x11/x11_window.cc
+++ b/ui/ozone/platform/x11/x11_window.cc
@@ -738,14 +738,6 @@
   // save this one for later too.
   should_maximize_after_map_ = !window_mapped_in_client_;
 
-  // Some WMs keep respecting the frame extents even if the window is maximised.
-  // Remove the insets when maximising.  The extents will be set again when the
-  // window is restored to normal state.
-  // See https://crbug.com/1260821
-  if (CanSetDecorationInsets()) {
-    SetDecorationInsets(nullptr);
-  }
-
   SetWMSpecState(true, x11::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
                  x11::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
 }
@@ -1105,26 +1097,6 @@
   return connection_->WmSupportsHint(x11::GetAtom("_GTK_FRAME_EXTENTS"));
 }
 
-void X11Window::SetDecorationInsets(const gfx::Insets* insets_px) {
-  auto atom = x11::GetAtom("_GTK_FRAME_EXTENTS");
-  if (!insets_px) {
-    connection_->DeleteProperty(xwindow_, atom);
-    return;
-  }
-
-  // Insets must be zero when the window state is not normal nor unknown.
-  CHECK(GetPlatformWindowState() == PlatformWindowState::kNormal ||
-        GetPlatformWindowState() == PlatformWindowState::kUnknown ||
-        *insets_px == gfx::Insets(0));
-
-  connection_->SetArrayProperty(
-      xwindow_, atom, x11::Atom::CARDINAL,
-      std::vector<uint32_t>{static_cast<uint32_t>(insets_px->left()),
-                            static_cast<uint32_t>(insets_px->right()),
-                            static_cast<uint32_t>(insets_px->top()),
-                            static_cast<uint32_t>(insets_px->bottom())});
-}
-
 void X11Window::SetOpaqueRegion(
     std::optional<std::vector<gfx::Rect>> region_px) {
   auto atom = x11::GetAtom("_NET_WM_OPAQUE_REGION");
@@ -1427,6 +1399,29 @@
                             base::Unretained(platform_window_delegate())));
 }
 
+void X11Window::UpdateDecorationInsets() {
+  auto atom = x11::GetAtom("_GTK_FRAME_EXTENTS");
+  auto insets_dip =
+      platform_window_delegate_->CalculateInsetsInDIP(GetPlatformWindowState());
+
+  if (insets_dip.IsEmpty()) {
+    connection_->DeleteProperty(xwindow_, atom);
+    return;
+  }
+
+  // Insets must be zero when the window state is not normal nor unknown.
+  CHECK(GetPlatformWindowState() == PlatformWindowState::kNormal ||
+        GetPlatformWindowState() == PlatformWindowState::kUnknown);
+
+  auto insets_px = platform_window_delegate_->ConvertInsetsToPixels(insets_dip);
+  connection_->SetArrayProperty(
+      xwindow_, atom, x11::Atom::CARDINAL,
+      std::vector<uint32_t>{static_cast<uint32_t>(insets_px.left()),
+                            static_cast<uint32_t>(insets_px.right()),
+                            static_cast<uint32_t>(insets_px.top()),
+                            static_cast<uint32_t>(insets_px.bottom())});
+}
+
 void X11Window::OnXWindowStateChanged() {
   // Determine the new window state information to be propagated to the client.
   // Note that the order of checks is important here, because window can have
@@ -1490,6 +1485,9 @@
     auto old_state = state_;
     state_ = new_state;
     platform_window_delegate_->OnWindowStateChanged(old_state, state_);
+    if (CanSetDecorationInsets()) {
+      UpdateDecorationInsets();
+    }
   }
 
   WindowTiledEdges tiled_state = GetTiledState();
diff --git a/ui/ozone/platform/x11/x11_window.h b/ui/ozone/platform/x11/x11_window.h
index 970946c..b0dc5aa 100644
--- a/ui/ozone/platform/x11/x11_window.h
+++ b/ui/ozone/platform/x11/x11_window.h
@@ -118,7 +118,6 @@
   void SizeConstraintsChanged() override;
   void SetOpacity(float opacity) override;
   bool CanSetDecorationInsets() const override;
-  void SetDecorationInsets(const gfx::Insets* insets_px) override;
   void SetOpaqueRegion(
       std::optional<std::vector<gfx::Rect>> region_px) override;
   void SetInputRegion(std::optional<std::vector<gfx::Rect>> region_px) override;
@@ -167,6 +166,8 @@
   FRIEND_TEST_ALL_PREFIXES(X11WindowTest,
                            ToggleMinimizePropogateToPlatformWindowDelegate);
 
+  void UpdateDecorationInsets();
+
   // PlatformEventDispatcher:
   bool CanDispatchEvent(const PlatformEvent& event) override;
   uint32_t DispatchEvent(const PlatformEvent& event) override;
diff --git a/ui/ozone/test/mock_platform_window_delegate.h b/ui/ozone/test/mock_platform_window_delegate.h
index abfde546..8f95d8e 100644
--- a/ui/ozone/test/mock_platform_window_delegate.h
+++ b/ui/ozone/test/mock_platform_window_delegate.h
@@ -25,6 +25,8 @@
 
   ~MockPlatformWindowDelegate() override;
 
+  MOCK_CONST_METHOD1(CalculateInsetsInDIP,
+                     gfx::Insets(PlatformWindowState window_state));
   MOCK_METHOD1(OnBoundsChanged, void(const BoundsChange& change));
   MOCK_METHOD1(OnDamageRect, void(const gfx::Rect& damaged_region));
   MOCK_METHOD1(DispatchEvent, void(Event* event));
diff --git a/ui/platform_window/platform_window.cc b/ui/platform_window/platform_window.cc
index 7220192..1171b27a 100644
--- a/ui/platform_window/platform_window.cc
+++ b/ui/platform_window/platform_window.cc
@@ -6,7 +6,6 @@
 
 #include <string>
 
-#include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace ui {
@@ -59,8 +58,6 @@
   return false;
 }
 
-void PlatformWindow::SetDecorationInsets(const gfx::Insets* insets_px) {}
-
 void PlatformWindow::SetOpaqueRegion(
     std::optional<std::vector<gfx::Rect>> region_px) {}
 
diff --git a/ui/platform_window/platform_window.h b/ui/platform_window/platform_window.h
index c55f403..8fecce04 100644
--- a/ui/platform_window/platform_window.h
+++ b/ui/platform_window/platform_window.h
@@ -20,7 +20,6 @@
 
 namespace gfx {
 class ImageSkia;
-class Insets;
 class Point;
 class Rect;
 class SizeF;
@@ -177,12 +176,6 @@
   // specifying the decoration insets.
   virtual bool CanSetDecorationInsets() const;
 
-  // Lets the WM know which portion of the window is the frame decoration.  The
-  // WM may use this to eg. snap windows to each other starting where the window
-  // begins rather than starting where the shadow begins.  If |insets_px| is
-  // nullptr, then any existing insets will be reset.
-  virtual void SetDecorationInsets(const gfx::Insets* insets_px);
-
   // Sets a hint for the compositor so it can avoid unnecessarily redrawing
   // occluded portions of windows.  If |region_px| is nullopt or empty, then any
   // existing region will be reset.
diff --git a/ui/platform_window/platform_window_delegate.cc b/ui/platform_window/platform_window_delegate.cc
index 9afc628..c37954aa 100644
--- a/ui/platform_window/platform_window_delegate.cc
+++ b/ui/platform_window/platform_window_delegate.cc
@@ -20,26 +20,34 @@
          state == PlatformWindowState::kTrustedPinnedFullscreen;
 }
 
-bool PlatformWindowDelegate::State::ProducesFrameOnUpdateFrom(
+bool PlatformWindowDelegate::State::WillProduceFrameOnUpdateFrom(
     const State& old) const {
-  // Changing the bounds origin won't produce a new frame. Anything else will,
-  // except for the occlusion state. We do not check that here since there isn't
-  // enough information to determine if it will produce a frame, as it depends
-  // on whether native occlusion is enabled and if the ui compositor changes
-  // visibility.
-  return old.bounds_dip.size() != bounds_dip.size() || old.size_px != size_px ||
-         old.window_scale != window_scale || old.raster_scale != raster_scale ||
-         old.insets != insets;
+  // Changing the bounds origin or fullscreen type will not produce a new frame.
+  // Anything else will produce a frame, except for the occlusion state. We do
+  // not check that here since there isn't enough information to determine if
+  // it will produce a frame, as it depends on whether native occlusion is
+  // enabled and if the ui compositor changes visibility.
+  // Note: Changing the window state produces a new frame as
+  // OnWindowStateChanged will schedule relayout even without the bounds change.
+  // On the other hand, the fullscreen type change will not schedule relayout
+  // and does not affect producing the frame.
+  return old.window_state != window_state ||
+         old.bounds_dip.size() != bounds_dip.size() || old.size_px != size_px ||
+         old.window_scale != window_scale || old.raster_scale != raster_scale;
 }
 
 std::string PlatformWindowDelegate::State::ToString() const {
   std::stringstream result;
   result << "State {";
-  result << "bounds_dip = " << bounds_dip.ToString();
+  result << "window_state = " << static_cast<int>(window_state);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+  result << ", fullscreen_type = " << static_cast<int>(fullscreen_type);
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+  result << ", bounds_dip = " << bounds_dip.ToString();
   result << ", size_px = " << size_px.ToString();
   result << ", window_scale = " << window_scale;
   result << ", raster_scale = " << raster_scale;
-  result << ", insets = " << insets.ToString();
+  result << ", occlusion_state = " << static_cast<int>(occlusion_state);
   result << "}";
   return result.str();
 }
@@ -48,6 +56,11 @@
 
 PlatformWindowDelegate::~PlatformWindowDelegate() = default;
 
+gfx::Insets PlatformWindowDelegate::CalculateInsetsInDIP(
+    PlatformWindowState window_state) const {
+  return gfx::Insets();
+}
+
 #if BUILDFLAG(IS_LINUX)
 void PlatformWindowDelegate::OnWindowTiledStateChanged(
     WindowTiledEdges new_tiled_edges) {}
@@ -125,6 +138,11 @@
   return gfx::PointF(screen_in_pixels);
 }
 
+gfx::Insets PlatformWindowDelegate::ConvertInsetsToPixels(
+    const gfx::Insets& insets_dip) const {
+  return insets_dip;
+}
+
 void PlatformWindowDelegate::DisableNativeWindowOcclusion() {}
 
 }  // namespace ui
diff --git a/ui/platform_window/platform_window_delegate.h b/ui/platform_window/platform_window_delegate.h
index 469db3e..a83d4d52 100644
--- a/ui/platform_window/platform_window_delegate.h
+++ b/ui/platform_window/platform_window_delegate.h
@@ -12,15 +12,11 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "ui/base/ui_base_types.h"
+#include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 
-#if BUILDFLAG(IS_FUCHSIA)
-#include "ui/gfx/geometry/insets.h"
-#endif  // BUILDFLAG(IS_FUCHSIA)
-
 namespace gfx {
-class Rect;
 class Size;
 class PointF;
 }  // namespace gfx
@@ -116,35 +112,49 @@
   // This is used by OnStateChanged and currently only by ozone/wayland.
   struct COMPONENT_EXPORT(PLATFORM_WINDOW) State {
     bool operator==(const State& rhs) const {
-      return std::tie(bounds_dip, size_px, window_scale, raster_scale, insets,
+      return std::tie(window_state,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+                      fullscreen_type,
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+                      bounds_dip, size_px, window_scale, raster_scale,
                       occlusion_state) ==
-             std::tie(rhs.bounds_dip, rhs.size_px, rhs.window_scale,
-                      rhs.raster_scale, rhs.insets, rhs.occlusion_state);
+             std::tie(rhs.window_state,
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+                      rhs.fullscreen_type,
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+                      rhs.bounds_dip, rhs.size_px, rhs.window_scale,
+                      rhs.raster_scale, rhs.occlusion_state);
     }
 
-    // Bounds in DIP.
+    // Current platform window state.
+    PlatformWindowState window_state = PlatformWindowState::kUnknown;
+
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+    // Current platform fullscreen type.
+    PlatformFullscreenType fullscreen_type = PlatformFullscreenType::kNone;
+#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)
+
+    // Bounds in DIP. The origin of `bounds_dip` does not affect whether it
+    // produces a new frame or not. Only the size of `bounds_dip` does.
     gfx::Rect bounds_dip;
+
     // Size in pixels. Note that it's required to keep information in both DIP
     // and pixels since it is not always possible to convert between them.
     gfx::Size size_px;
+
     // Current scale factor of the output where the window is located at.
     float window_scale = 1.0;
-    // TODO(crbug.com/1395267): Add window states here.
 
     // Scale to raster the window at.
     float raster_scale = 1.0;
 
-    // Insets in DIP. Used in platforms where window decorations are drawn by
-    // the client.
-    gfx::Insets insets;
-
     // Occlusion state
     PlatformWindowOcclusionState occlusion_state =
         PlatformWindowOcclusionState::kUnknown;
 
-    // Returns true if updating from the given State |old| to this state
+    // Returns true if updating from the given State `old` to this state
     // should produce a frame.
-    bool ProducesFrameOnUpdateFrom(const State& old) const;
+    bool WillProduceFrameOnUpdateFrom(const State& old) const;
 
     std::string ToString() const;
   };
@@ -152,6 +162,10 @@
   PlatformWindowDelegate();
   virtual ~PlatformWindowDelegate();
 
+  // Calculates the insets in dip based on the window state.
+  virtual gfx::Insets CalculateInsetsInDIP(
+      PlatformWindowState window_state) const;
+
   virtual void OnBoundsChanged(const BoundsChange& change) = 0;
 
   // Note that |damaged_region| is in the platform-window's coordinates, in
@@ -264,15 +278,19 @@
   // Called when tooltip is hidden on server.
   virtual void OnTooltipHiddenOnServer();
 
-  // Convert gfx::Rect in pixels to DIP in screen, and vice versa.
+  // Converts gfx::Rect in pixels to DIP in screen, and vice versa.
   virtual gfx::Rect ConvertRectToPixels(const gfx::Rect& rect_in_dp) const;
   virtual gfx::Rect ConvertRectToDIP(const gfx::Rect& rect_in_pixels) const;
 
-  // Convert gfx::Point in screen pixels to dip in the window's local
+  // Converts gfx::Point in screen pixels to dip in the window's local
   // coordinate.
   virtual gfx::PointF ConvertScreenPointToLocalDIP(
       const gfx::Point& screen_in_pixels) const;
 
+  // Converts gfx::Insets in DIP to pixels.
+  virtual gfx::Insets ConvertInsetsToPixels(
+      const gfx::Insets& insets_dip) const;
+
   // Disables native window occlusion.
   virtual void DisableNativeWindowOcclusion();
 };
diff --git a/ui/platform_window/platform_window_init_properties.h b/ui/platform_window/platform_window_init_properties.h
index 2af60f26..37ffb70a 100644
--- a/ui/platform_window/platform_window_init_properties.h
+++ b/ui/platform_window/platform_window_init_properties.h
@@ -76,9 +76,6 @@
   PlatformWindowType type = PlatformWindowType::kWindow;
   // Sets the desired initial bounds. Can be empty.
   gfx::Rect bounds;
-  // Sets the frame insets. Can be empty.
-  // TODO(crbug.com/1306688): Use DIP for frame insets.
-  gfx::Insets frame_insets_px;
   // Tells PlatformWindow which native widget its parent holds. It is usually
   // used to find a parent from internal list of PlatformWindows.
   gfx::AcceleratedWidget parent_widget = gfx::kNullAcceleratedWidget;
diff --git a/ui/views/controls/menu/menu_host.cc b/ui/views/controls/menu/menu_host.cc
index 05e2952..b42556c 100644
--- a/ui/views/controls/menu/menu_host.cc
+++ b/ui/views/controls/menu/menu_host.cc
@@ -19,6 +19,8 @@
 #include "ui/base/ui_base_types.h"
 #include "ui/compositor/compositor.h"
 #include "ui/events/gestures/gesture_recognizer.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/views/controls/menu/menu_controller.h"
 #include "ui/views/controls/menu/menu_host_root_view.h"
@@ -142,11 +144,6 @@
                                        : gfx::NativeWindow();
   params.bounds = init_params.bounds;
 
-#if BUILDFLAG(IS_OZONE)
-  params.frame_insets =
-      submenu_->GetScrollViewContainer()->outside_border_insets();
-#endif
-
 #if defined(USE_AURA)
   params.init_properties_container.SetProperty(aura::client::kOwnedWindowAnchor,
                                                init_params.owned_window_anchor);
@@ -358,6 +355,15 @@
                     : Widget::GetPrimaryWindowWidget();
 }
 
+gfx::Insets MenuHost::GetCustomInsetsInDIP() const {
+#if BUILDFLAG(IS_OZONE)
+  if (submenu_) {
+    return submenu_->GetScrollViewContainer()->outside_border_insets();
+  }
+#endif  // BUILDFLAG(IS_OZONE)
+  return gfx::Insets();
+}
+
 void MenuHost::OnWidgetDestroying(Widget* widget) {
   DCHECK_EQ(GetOwner(), widget);
   owner_observation_.Reset();
diff --git a/ui/views/controls/menu/menu_host.h b/ui/views/controls/menu/menu_host.h
index 7e92e72..fc1d5fc 100644
--- a/ui/views/controls/menu/menu_host.h
+++ b/ui/views/controls/menu/menu_host.h
@@ -12,10 +12,14 @@
 #include "base/scoped_observation.h"
 #include "build/build_config.h"
 #include "ui/base/owned_window_anchor.h"
-#include "ui/gfx/geometry/rect.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/widget/widget_observer.h"
 
+namespace gfx {
+class Insets;
+class Rect;
+}  // namespace gfx
+
 namespace views {
 
 class MenuControllerTest;
@@ -99,6 +103,7 @@
   void OnDragWillStart() override;
   void OnDragComplete() override;
   Widget* GetPrimaryWindowWidget() override;
+  gfx::Insets GetCustomInsetsInDIP() const override;
 
   // WidgetObserver:
   void OnWidgetDestroying(Widget* widget) override;
diff --git a/ui/views/test/widget_test.cc b/ui/views/test/widget_test.cc
index 1b9bb494..7caed7ab 100644
--- a/ui/views/test/widget_test.cc
+++ b/ui/views/test/widget_test.cc
@@ -21,6 +21,8 @@
     BUILDFLAG(IS_CHROMEOS_LACROS)
 
 #include "ui/views/test/test_desktop_screen_ozone.h"
+#elif BUILDFLAG(IS_WIN)
+#include "ui/views/widget/desktop_aura/desktop_screen_win.h"
 #endif
 
 namespace views::test {
@@ -154,12 +156,14 @@
 #if (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CASTOS)) || \
     BUILDFLAG(IS_CHROMEOS_LACROS)
   screen_ = views::test::TestDesktopScreenOzone::Create();
+#elif BUILDFLAG(IS_WIN)
+  screen_ = std::make_unique<views::DesktopScreenWin>();
 #endif
   DesktopWidgetTest::SetUp();
 }
 
 #if (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CASTOS)) || \
-    BUILDFLAG(IS_CHROMEOS_LACROS)
+    BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_WIN)
 void DesktopWidgetTestInteractive::TearDown() {
   DesktopWidgetTest::TearDown();
   screen_.reset();
diff --git a/ui/views/test/widget_test.h b/ui/views/test/widget_test.h
index ff46bb3..37cc265b 100644
--- a/ui/views/test/widget_test.h
+++ b/ui/views/test/widget_test.h
@@ -21,7 +21,7 @@
 #include "ui/views/widget/widget_observer.h"
 
 #if (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CASTOS)) || \
-    BUILDFLAG(IS_CHROMEOS_LACROS)
+    BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_WIN)
 
 #include "ui/display/screen.h"
 #endif
@@ -181,7 +181,7 @@
   void SetUp() override;
 
 #if (BUILDFLAG(IS_LINUX) && !BUILDFLAG(IS_CASTOS)) || \
-    BUILDFLAG(IS_CHROMEOS_LACROS)
+    BUILDFLAG(IS_CHROMEOS_LACROS) || BUILDFLAG(IS_WIN)
   void TearDown() override;
   std::unique_ptr<display::Screen> screen_;
 #endif
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
index b305f5c2..7727cc1 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
@@ -131,8 +131,7 @@
 }
 
 ui::PlatformWindowInitProperties ConvertWidgetInitParamsToInitProperties(
-    const Widget::InitParams& params,
-    float device_scale_factor) {
+    const Widget::InitParams& params) {
   ui::PlatformWindowInitProperties properties;
   properties.type = GetPlatformWindowType(params.type);
   properties.accept_events = params.accept_events;
@@ -171,9 +170,6 @@
     }
   }
   properties.inhibit_keyboard_shortcuts = params.inhibit_keyboard_shortcuts;
-
-  properties.frame_insets_px =
-      gfx::ScaleToCeiledInsets(params.frame_insets, device_scale_factor);
 #endif
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -271,7 +267,7 @@
     GetContentWindow()->SetProperty(aura::client::kAnimationsDisabledKey, true);
 
   ui::PlatformWindowInitProperties properties =
-      ConvertWidgetInitParamsToInitProperties(params, device_scale_factor());
+      ConvertWidgetInitParamsToInitProperties(params);
   AddAdditionalInitProperties(params, &properties);
 
 #if BUILDFLAG(IS_CHROMEOS)
@@ -899,6 +895,11 @@
   }
 }
 
+gfx::Insets DesktopWindowTreeHostPlatform::CalculateInsetsInDIP(
+    ui::PlatformWindowState window_state) const {
+  return GetWidget()->GetCustomInsetsInDIP();
+}
+
 void DesktopWindowTreeHostPlatform::OnClosed() {
   open_windows().remove(GetAcceleratedWidget());
   wm::SetWindowMoveClient(window(), nullptr);
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
index e3686175..d116850 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h
@@ -152,6 +152,8 @@
                                      bool visible) override;
 
   // PlatformWindowDelegate:
+  gfx::Insets CalculateInsetsInDIP(
+      ui::PlatformWindowState window_state) const override;
   void OnClosed() override;
   void OnWindowStateChanged(ui::PlatformWindowState old_state,
                             ui::PlatformWindowState new_state) override;
diff --git a/ui/views/widget/widget.cc b/ui/views/widget/widget.cc
index d958581d..5f16bddd 100644
--- a/ui/views/widget/widget.cc
+++ b/ui/views/widget/widget.cc
@@ -34,6 +34,8 @@
 #include "ui/display/screen.h"
 #include "ui/events/event.h"
 #include "ui/events/event_utils.h"
+#include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/views/controls/menu/menu_controller.h"
 #include "ui/views/drag_controller.h"
@@ -697,6 +699,10 @@
   return GetRestoredBounds().size();
 }
 
+gfx::Insets Widget::GetCustomInsetsInDIP() const {
+  return gfx::Insets();
+}
+
 void Widget::CenterWindow(const gfx::Size& size) {
   if (native_widget_)
     native_widget_->CenterWindow(size);
diff --git a/ui/views/widget/widget.h b/ui/views/widget/widget.h
index 0d373af..7519d49 100644
--- a/ui/views/widget/widget.h
+++ b/ui/views/widget/widget.h
@@ -28,7 +28,6 @@
 #include "ui/color/color_provider_utils.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/events/event_source.h"
-#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/native_theme/native_theme.h"
 #include "ui/native_theme/native_theme_observer.h"
@@ -42,6 +41,7 @@
 }
 
 namespace gfx {
+class Insets;
 class Point;
 class Rect;
 }  // namespace gfx
@@ -460,10 +460,6 @@
     // window should request the wayland compositor to send key events,
     // even if it matches with the compositor's keyboard shortcuts.
     bool inhibit_keyboard_shortcuts = false;
-
-    // Specifies the insets of the Widget. Default is empty, which means no
-    // insets are to be set.
-    gfx::Insets frame_insets;
 #endif
 
     // Directly sets the NativeTheme used by the Widget. Providing the
@@ -675,6 +671,10 @@
   // Retrieves the restored size for the window.
   gfx::Size GetSize() const;
 
+  // Returns the insets that each widget implementation can customize. It
+  // returns empty insets by default.
+  virtual gfx::Insets GetCustomInsetsInDIP() const;
+
   // Sizes the window to the specified size and centers it.
   void CenterWindow(const gfx::Size& size);
 
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc
index 18a0c1e..3402b59 100644
--- a/ui/views/widget/widget_interactive_uitest.cc
+++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -1412,6 +1412,83 @@
   EXPECT_TRUE(widget_window->CanFocus());
 }
 
+class SyntheticMouseMoveCounter : public ui::EventHandler {
+ public:
+  explicit SyntheticMouseMoveCounter(Widget* widget) : widget_(widget) {
+    widget_->GetNativeWindow()->AddPreTargetHandler(this);
+  }
+
+  SyntheticMouseMoveCounter(const SyntheticMouseMoveCounter&) = delete;
+  SyntheticMouseMoveCounter& operator=(const SyntheticMouseMoveCounter&) =
+      delete;
+
+  ~SyntheticMouseMoveCounter() override {
+    widget_->GetNativeWindow()->RemovePreTargetHandler(this);
+  }
+
+  // ui::EventHandler:
+  void OnMouseEvent(ui::MouseEvent* event) override {
+    if (event->type() == ui::ET_MOUSE_MOVED && event->IsSynthesized()) {
+      ++count_;
+    }
+  }
+
+  int num_synthetic_mouse_moves() const { return count_; }
+
+ private:
+  int count_ = 0;
+  raw_ptr<Widget> widget_;
+};
+
+#if BUILDFLAG(ENABLE_DESKTOP_AURA)
+TEST_F(DesktopWidgetTestInteractive,
+       DoNotSynthesizeMouseMoveOnVisibilityChangeIfOccluded) {
+  // Create a top-level widget.
+  WidgetAutoclosePtr widget_below(CreateTopLevelPlatformDesktopWidget());
+  widget_below->SetBounds(gfx::Rect(300, 300));
+  widget_below->Show();
+
+  // Dispatch a mouse event to place cursor inside window bounds.
+  base::RunLoop run_loop;
+  ui_controls::SendMouseMoveNotifyWhenDone(150, 150, run_loop.QuitClosure());
+  run_loop.Run();
+
+  // Create a child widget.
+  UniqueWidgetPtr child = std::make_unique<Widget>();
+  Widget::InitParams child_params =
+      CreateParams(Widget::InitParams::TYPE_WINDOW);
+  child_params.parent = widget_below->GetNativeView();
+  child_params.context = widget_below->GetNativeWindow();
+  child->Init(std::move(child_params));
+  child->SetBounds(gfx::Rect(300, 300));
+  child->Show();
+  base::RunLoop().RunUntilIdle();
+
+  SyntheticMouseMoveCounter counter_below(widget_below.get());
+  EXPECT_EQ(0, counter_below.num_synthetic_mouse_moves());
+
+  // Update the child window's visibility. This should trigger a synthetic
+  // mouse move event.
+  child->Hide();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1, counter_below.num_synthetic_mouse_moves());
+
+  // Occlude the existing widget with a new top-level widget.
+  WidgetAutoclosePtr widget_above(CreateTopLevelPlatformDesktopWidget());
+  widget_above->SetBounds(gfx::Rect(300, 300));
+  widget_above->Show();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(widget_above->AsWidget()->IsStackedAbove(
+      widget_below->AsWidget()->GetNativeView()));
+
+  // Update the child window's visibility again, but this should not trigger a
+  // synthetic mouse move event, since there's another widget under the cursor.
+  child->Show();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1, counter_below.num_synthetic_mouse_moves());
+}
+#endif  // BUILDFLAG(ENABLE_DESKTOP_AURA)
 #endif  // BUILDFLAG(IS_WIN)
 
 #if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)