diff --git a/.gitmodules b/.gitmodules
index 804076fb..e6441a1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -858,10 +858,6 @@
 	path = remoting/tools/internal
 	url = https://chrome-internal.googlesource.com/chrome/remoting/tools/internal
 	gclient-condition = checkout_src_internal
-[submodule "services/shape_detection/internal"]
-	path = services/shape_detection/internal
-	url = https://chrome-internal.googlesource.com/chrome/services/shape_detection
-	gclient-condition = checkout_src_internal
 [submodule "signing_keys"]
 	path = signing_keys
 	url = https://chrome-internal.googlesource.com/clank/apptestkey
diff --git a/AUTHORS b/AUTHORS
index 9dae142..148d35c1 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -272,6 +272,7 @@
 Chanyong Moon <dev.chanyongmoon@gmail.com>
 Chaobin Zhang <zhchbin@gmail.com>
 Charles Vaughn <cvaughn@gmail.com>
+Chenhao Diao <diaochenhao@gmail.com>
 Cheng Zhao <zcbenz@gmail.com>
 Cheng Yu <yuzichengcode@gmail.com>
 Chenguang Shao <chenguangshao1@gmail.com>
@@ -909,6 +910,7 @@
 Lisha Guo <lisha.guo@intel.com>
 Lizhi Fan <lizhi.fan@samsung.com>
 Lloyd Huang <bzkirto@gmail.com>
+Loay Ghreeb <loayahmed655@gmail.com>
 Loo Rong Jie <loorongjie@gmail.com>
 Lorenzo Stoakes <lstoakes@gmail.com>
 Lu Guanqun <guanqun.lu@gmail.com>
diff --git a/DEPS b/DEPS
index 55e7235..60ffbb1d 100644
--- a/DEPS
+++ b/DEPS
@@ -295,7 +295,7 @@
   # 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': '81737b465ba8c0c5686ebf867a1adc05eecdc951',
+  'src_internal_revision': '26795f31e4ce2586e12dfb1897cbd87573277cff',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
@@ -307,7 +307,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': '649514930e65cb7a9baa489092431010df5e8b4a',
+  'angle_revision': '9e629bbb0f732b071f3c7ca6d13b4692b02f6cef',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -371,7 +371,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
-  'crossbench_revision': '1fcf64fb04e2c8477aeda4ad6eaea8ac1ae9422d',
+  'crossbench_revision': '76223aed1ca101a8c6ceabf50b2d912424860f74',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -387,7 +387,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '5d9af4427b5620b138701f9e9a12f70860cdae4d',
+  'devtools_frontend_revision': '89f560fc68315d20519c66ed54d61a368308d034',
   # 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.
@@ -411,7 +411,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': '168d54a050758eb88e25757111b850bb1e97ed90',
+  'dawn_revision': '6a294b931ac942984bdeb350d608c93c0fef8cae',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -515,11 +515,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling llvm-libc
   # and whatever else without interference from each other.
-  'llvm_libc_revision':    '66ae6e2bd8e09c7a20b8b623b7a0249a5262f87c',
+  'llvm_libc_revision':    'e09972e0948f01cace57c08ba82b61fc171c739c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling llvm-libc
   # and whatever else without interference from each other.
-  'compiler_rt_revision': '0ceada7e63a869692256f956cc22869007469c55',
+  'compiler_rt_revision': '3b1c4b0e4fee85b10a2272117315d520c607a328',
 
   # If you change this, also update the libc++ revision in
   # //buildtools/deps_revisions.gni.
@@ -1448,7 +1448,7 @@
       'packages': [
         {
           'package': 'chromium/third_party/updater/chromium_win_arm64',
-          'version': 'version:2@1476005',
+          'version': 'ksRzLnqewvz7P-YMX2e8mxZuDI1hgPtLNCuAXIisXhoC',
         },
       ],
   },
@@ -1572,7 +1572,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    'ceeb89e0f8224ca96ca05598cf0cfc1a7b652963',
+    '87625a3482c6c67f2ae49c6ed62f44a8ba86a900',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1731,7 +1731,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': 'dMDoulfjeBxh9IzbcSAvcwijRIqO4KgMa2lhE_dgUWYC',
+          'version': 'X7GtTJlh84B4DuULla3Iwb6leLYXIQjpohJ2yqazhdgC',
       },
     ],
     'condition': 'checkout_android and non_git_source',
@@ -2067,7 +2067,7 @@
 
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '3f633ff2f98baa7bd2563b710bc4168691eda7bb',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9a172103022289fcd1374c4d12d0192204343f59',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -2926,7 +2926,7 @@
       'dep_type': 'cipd',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@4e2ba677d4d655a0b0d69b596f1c076ee0d4b516',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@316ed08fbc9a673e93c8960324cad4801e24a5dd',
   'src/third_party/glslang/src': '{chromium_git}/external/github.com/KhronosGroup/glslang@21b4e37133868b3a50ef15fc027ecd6d3a52c875',
   'src/third_party/spirv-cross/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Cross@b8fcf307f1f347089e3c46eb4451d27f32ebc8d3',
   'src/third_party/spirv-headers/src': '{chromium_git}/external/github.com/KhronosGroup/SPIRV-Headers@2a611a970fdbc41ac2e3e328802aed9985352dca',
@@ -2935,7 +2935,7 @@
   'src/third_party/vulkan-loader/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Loader@dc6786e527cf8cefd244318f546b3b2ec26e84f4',
   'src/third_party/vulkan-tools/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Tools@d671923090e4dc74c0ebdb10c6e09fa0826e1fe9',
   'src/third_party/vulkan-utility-libraries/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-Utility-Libraries@54c9baf20802a13279e23fa4cb0528dd5cf16064',
-  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@7cee394fda75d7b33d52b06b9e637abeb23c991c',
+  'src/third_party/vulkan-validation-layers/src': '{chromium_git}/external/github.com/KhronosGroup/Vulkan-ValidationLayers@4b207eefa09f304a06c237fcf4913304e0b7d8d1',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + '56300b29fbfcc693ee6609ddad3fdd5b7a449a21',
@@ -2974,7 +2974,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'c01b768bce4a143e152c1870b6ba99ea6267d2b0',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '97733807d3c40aeb165cf2fbe4137f23c92446ea',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '2a8d4a83f751286302ce34573409ad75cc318508',
 
   'src/third_party/webpagereplay':
     Var('chromium_git') + '/webpagereplay.git' + '@' + Var('webpagereplay_revision'),
@@ -4677,7 +4677,7 @@
 
   'src/components/autofill/core/browser/form_parsing/internal_resources': {
       'url': Var('chrome_git') + '/chrome/components/autofill_regex_patterns.git' + '@' +
-        'b0d7376c49e618262acef6723c9dbdc7c41d31e8',
+        '103ef5ee6ebd7611908d3685faf5dc42d79b569d',
       'condition': 'checkout_src_internal',
   },
 
@@ -4736,7 +4736,7 @@
 
   'src/components/test/data/autofill/heuristics-json/internal': {
       'url': Var('chrome_git') + '/chrome/test/autofill/structured_forms.git' + '@' +
-        '059ed8f0e91c6377aded5f0b3826ffe6f8717ab0',
+        'adc0282015eb4a9fa58b09adb2e1dcd75a522b7f',
       'condition': 'checkout_chromium_autofill_test_dependencies',
   },
 
@@ -4794,12 +4794,6 @@
       'condition': 'checkout_src_internal',
   },
 
-  'src/services/shape_detection/internal': {
-      'url': Var('chrome_git') + '/chrome/services/shape_detection.git' + '@' +
-        '8fd3ed03363d2155b038613ff3e2d094a2ad98a3',
-      'condition': 'checkout_src_internal',
-  },
-
   'src/signing_keys': {
       'url': Var('chrome_git') + '/clank/apptestkey.git' + '@' +
         '5138e684915721cbccbb487ec0764ed05650fcd0',
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index dcbce77..a3156b3 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -216,7 +216,7 @@
 // Enables or disables Boca OnTask mute ARC audio requests on ChromeOS.
 BASE_FEATURE(kBocaOnTaskMuteArcAudio,
              "BocaOnTaskMuteArcAudio",
-             base::FEATURE_ENABLED_BY_DEFAULT);
+             base::FEATURE_DISABLED_BY_DEFAULT);
 
 // Enables or disables the Boca OnTask pod on ChromeOS.
 BASE_FEATURE(kBocaOnTaskPod, "BocaOnTaskPod", base::FEATURE_ENABLED_BY_DEFAULT);
diff --git a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
index dc57e4b04..3fca572 100644
--- a/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
+++ b/base/android/java/src/org/chromium/base/process_launcher/ChildProcessConnection.java
@@ -132,6 +132,57 @@
         int COUNT = 4;
     }
 
+    /**
+     * Count the ChildServiceConnectionDelegate.onServiceConnected callback.
+     *
+     * <p>This is to detect the service binding restart. If the counter is more than 1, it means the
+     * service is restarted (e.g. due to LMK, crash).
+     *
+     * <p>onServiceDisconnectedOnLauncherThread() unbinds all service bindings when the child
+     * process dies to prevent the service from restarting. However there is race and the child
+     * process can restart. The metrics "Android.ChildProcessConnection.OnServiceConnectedCounts" is
+     * to understand how much restarts happen in practice.
+     *
+     * <p>This is expected to be used for waived service binding which is bound once for the service
+     * lifetime. retireAndCreateFallbackBindings() unbinds the waived binding, but
+     * CountOnServiceConnectedDecorator will be recreated at createBindings() in the method.
+     */
+    private static class CountOnServiceConnectedDecorator
+            implements ChildServiceConnectionDelegate {
+        private final ChildServiceConnectionDelegate mDelegate;
+        private final Handler mLauncherHandler;
+
+        private int mCountOnServiceConnected;
+
+        CountOnServiceConnectedDecorator(
+                ChildServiceConnectionDelegate delegate, Handler launcherHandler) {
+            mDelegate = delegate;
+            mLauncherHandler = launcherHandler;
+        }
+
+        @Override
+        public void onServiceConnected(final IBinder service) {
+            mDelegate.onServiceConnected(service);
+            if (mLauncherHandler.getLooper() == Looper.myLooper()) {
+                incrementCount();
+                return;
+            }
+            mLauncherHandler.post(() -> incrementCount());
+        }
+
+        @Override
+        public void onServiceDisconnected() {
+            mDelegate.onServiceDisconnected();
+        }
+
+        private void incrementCount() {
+            mCountOnServiceConnected += 1;
+            RecordHistogram.recordCount100Histogram(
+                    "Android.ChildProcessConnection.OnServiceConnectedCounts",
+                    mCountOnServiceConnected);
+        }
+    }
+
     private static class ChildProcessMismatchException extends RuntimeException {
         ChildProcessMismatchException(String msg) {
             super(msg);
@@ -480,7 +531,7 @@
                 mConnectionFactory.createConnection(
                         mBindIntent,
                         mDefaultBindFlags | Context.BIND_WAIVE_PRIORITY,
-                        mConnectionDelegate,
+                        new CountOnServiceConnectedDecorator(mConnectionDelegate, mLauncherHandler),
                         mInstanceName);
     }
 
diff --git a/base/containers/auto_spanification_helper.h b/base/containers/auto_spanification_helper.h
index 80595101..155d50d 100644
--- a/base/containers/auto_spanification_helper.h
+++ b/base/containers/auto_spanification_helper.h
@@ -7,6 +7,7 @@
 
 #include <array>
 
+#include "base/containers/span.h"
 #include "base/numerics/checked_math.h"
 
 namespace base {
@@ -23,6 +24,43 @@
   return sizeof(Element) * N;
 }
 
+// Modifies the input span by removing its first element (if not empty)
+// and returns the modified span.
+// Used to rewrite pre-increment (++ptr).
+// WARNING: This helper is intended to be used only by the auto spanification
+// tool. Do not use this helper outside of the tool. Usage should usually be
+// replaced with `base::span::(const_)iterator`.
+template <typename T>
+span<T> PreIncrementSpan(span<T>& span_ref) {
+  static_assert(
+      span<T>::extent == dynamic_extent,
+      "PreIncrementSpan requires a dynamic-extent span (base::span<T>)");
+  // An iterator that is at the end is expressed as an empty span and it shall
+  // not be incremented.
+  CHECK(!span_ref.empty());
+  span_ref = span_ref.template subspan<1u>();
+  return span_ref;
+}
+
+// Returns a copy of the input span *before* modification, and then
+// modifies the input span by removing its first element (if not empty).
+// Used to rewrite post-increment (ptr++).
+// WARNING: This helper is intended to be used only by the auto spanification
+// tool. Do not use this helper outside of the tool. Usage should usually be
+// replaced with `base::span::(const_)iterator`.
+template <typename T>
+span<T> PostIncrementSpan(span<T>& span_ref) {
+  static_assert(
+      span<T>::extent == dynamic_extent,
+      "PostIncrementSpan requires a dynamic-extent span (base::span<T>)");
+  // An iterator that is at the end is expressed as an empty span and it shall
+  // not be incremented.
+  CHECK(!span_ref.empty());
+  span<T> original_span = span_ref;
+  span_ref = span_ref.template subspan<1u>();
+  return original_span;
+}
+
 }  // namespace base
 
 namespace base::spanification_internal {
diff --git a/base/containers/auto_spanification_helper_unittest.cc b/base/containers/auto_spanification_helper_unittest.cc
index 22675f2..c8eb8645 100644
--- a/base/containers/auto_spanification_helper_unittest.cc
+++ b/base/containers/auto_spanification_helper_unittest.cc
@@ -9,8 +9,106 @@
 
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace base {
+namespace {
+
+TEST(AutoSpanificationIncrementTest, PreIncrementSpan) {
+  std::vector<int> data = {1, 2, 3, 4, 5};
+  span<int> s(data);
+
+  span<int> result = PreIncrementSpan(s);
+  EXPECT_THAT(s, testing::ElementsAre(2, 3, 4, 5));
+
+  EXPECT_EQ(result.data(), s.data());
+  EXPECT_EQ(result.size(), s.size());
+}
+
+TEST(AutoSpanificationIncrementTest, PreIncrementSingleElementSpan) {
+  std::vector<int> single_element_data = {42};
+  span<int> s(single_element_data);
+
+  span<int> result = PreIncrementSpan(s);
+
+  EXPECT_TRUE(s.empty());
+  EXPECT_EQ(s.size(), 0u);
+
+  EXPECT_TRUE(result.empty());
+  EXPECT_EQ(result.size(), 0u);
+}
+
+TEST(AutoSpanificationIncrementTest, PreIncrementEmptySpan) {
+  std::vector<int> empty_data;
+  span<int> s(empty_data);
+
+  // An iterator that is at the end is expressed as an empty span and it shall
+  // not be incremented. Expect a CHECK failure when trying to pre-increment an
+  // empty span.
+  ASSERT_DEATH_IF_SUPPORTED({ PreIncrementSpan(s); }, "");
+}
+
+TEST(AutoSpanificationIncrementTest, PreIncrementConstSpan) {
+  const std::vector<int> data = {1, 2, 3, 4, 5};
+  span<const int> s(data);
+
+  span<const int> result = PreIncrementSpan(s);
+
+  EXPECT_THAT(s, testing::ElementsAre(2, 3, 4, 5));
+
+  EXPECT_EQ(result.data(), s.data());
+  EXPECT_EQ(result.size(), s.size());
+}
+
+TEST(AutoSpanificationIncrementTest, PostIncrementSpan) {
+  std::vector<int> data = {1, 2, 3, 4, 5};
+  span<int> s(data);
+
+  span<int> result = PostIncrementSpan(s);
+
+  EXPECT_THAT(result, testing::ElementsAre(1, 2, 3, 4, 5));
+  EXPECT_THAT(s, testing::ElementsAre(2, 3, 4, 5));
+}
+
+TEST(AutoSpanificationIncrementTest, PostIncrementSingleElementSpan) {
+  std::vector<int> single_element_data = {42};
+  span<int> s(single_element_data);
+
+  span<int> result = PostIncrementSpan(s);
+
+  EXPECT_EQ(result.size(), 1u);
+  EXPECT_EQ(result[0], 42);
+
+  EXPECT_TRUE(s.empty());
+  EXPECT_EQ(s.size(), 0u);
+}
+
+TEST(AutoSpanificationIncrementTest, PostIncrementEmptySpan) {
+  std::vector<int> empty_data;
+  span<int> s(empty_data);
+
+  // An iterator that is at the end is expressed as an empty span and it shall
+  // not be incremented. Expect a CHECK failure when trying to post-increment an
+  // empty span.
+  ASSERT_DEATH_IF_SUPPORTED({ PostIncrementSpan(s); }, "");
+}
+
+TEST(AutoSpanificationIncrementTest, PostIncrementConstSpan) {
+  const std::vector<int> data = {1, 2, 3, 4, 5};
+  span<const int> s(data);
+
+  span<const int> result = PostIncrementSpan(s);
+
+  EXPECT_THAT(result, testing::ElementsAre(1, 2, 3, 4, 5));
+
+  EXPECT_EQ(s.size(), 4u);
+  EXPECT_THAT(s, testing::ElementsAre(2, 3, 4, 5));
+}
+
+}  // namespace
+}  // namespace base
+
 namespace base::internal::spanification {
 
 namespace {
diff --git a/base/memory/weak_ptr.cc b/base/memory/weak_ptr.cc
index a1c1690..0eee119 100644
--- a/base/memory/weak_ptr.cc
+++ b/base/memory/weak_ptr.cc
@@ -83,11 +83,14 @@
     : flag_(MakeRefCounted<WeakReference::Flag>()) {}
 
 WeakReferenceOwner::~WeakReferenceOwner() {
-  flag_->Invalidate();
+  if (flag_) {
+    flag_->Invalidate();
+  }
 }
 
 WeakReference WeakReferenceOwner::GetRef() const {
 #if DCHECK_IS_ON()
+  DCHECK(flag_);
   // If we hold the last reference to the Flag then detach the SequenceChecker.
   if (!HasRefs()) {
     flag_->DetachFromSequence();
@@ -98,12 +101,20 @@
 }
 
 void WeakReferenceOwner::Invalidate() {
+  DCHECK(flag_);
   flag_->Invalidate();
   flag_ = MakeRefCounted<WeakReference::Flag>();
 }
 
+void WeakReferenceOwner::InvalidateAndDoom() {
+  DCHECK(flag_);
+  flag_->Invalidate();
+  flag_.reset();
+}
+
 void WeakReferenceOwner::BindToCurrentSequence() {
 #if DCHECK_IS_ON()
+  DCHECK(flag_);
   flag_->BindToCurrentSequence();
 #endif
 }
diff --git a/base/memory/weak_ptr.h b/base/memory/weak_ptr.h
index ce64a9b..032f597 100644
--- a/base/memory/weak_ptr.h
+++ b/base/memory/weak_ptr.h
@@ -163,6 +163,7 @@
   bool HasRefs() const { return !flag_->HasOneRef(); }
 
   void Invalidate();
+  void InvalidateAndDoom();
   void BindToCurrentSequence();
 
  private:
@@ -402,18 +403,24 @@
         weak_reference_owner_.GetRef(), reinterpret_cast<T*>(ptr_));
   }
 
-  // Call this method to invalidate all existing weak pointers.
+  // Invalidates all existing weak pointers.
   void InvalidateWeakPtrs() {
     DCHECK(ptr_);
     weak_reference_owner_.Invalidate();
   }
 
-  // Call this method to determine if any weak pointers exist.
-  bool HasWeakPtrs() const {
+  // Invalidates all existing weak pointers, and makes the factory unusable
+  // (cannot call GetWeakPtr after this). This is more efficient than
+  // InvalidateWeakPtrs().
+  void InvalidateWeakPtrsAndDoom() {
     DCHECK(ptr_);
-    return weak_reference_owner_.HasRefs();
+    weak_reference_owner_.InvalidateAndDoom();
+    ptr_ = 0;
   }
 
+  // Call this method to determine if any weak pointers exist.
+  bool HasWeakPtrs() const { return ptr_ && weak_reference_owner_.HasRefs(); }
+
   // Rebind the factory to the current sequence. This allows creating an object
   // and associated weak pointers on a different thread from the one they are
   // used on.
diff --git a/base/memory/weak_ptr_unittest.cc b/base/memory/weak_ptr_unittest.cc
index 3fd68d84..c4dfba33 100644
--- a/base/memory/weak_ptr_unittest.cc
+++ b/base/memory/weak_ptr_unittest.cc
@@ -424,6 +424,22 @@
   EXPECT_FALSE(factory.HasWeakPtrs());
 }
 
+TEST(WeakPtrTest, InvalidateWeakPtrsAndDoom) {
+  int data;
+  WeakPtrFactory<int> factory(&data);
+  WeakPtr<int> ptr = factory.GetWeakPtr();
+  EXPECT_EQ(&data, ptr.get());
+  EXPECT_TRUE(factory.HasWeakPtrs());
+  factory.InvalidateWeakPtrsAndDoom();
+  EXPECT_EQ(nullptr, ptr.get());
+  EXPECT_FALSE(factory.HasWeakPtrs());
+
+  EXPECT_DCHECK_DEATH({
+    // Cannot get a WeakPtr from a doomed factory.
+    WeakPtr<int> other_ptr = factory.GetWeakPtr();
+  });
+}
+
 // Tests that WasInvalidated() is true only for invalidated WeakPtrs (not
 // nullptr) and doesn't DCHECK (e.g. because of a dereference attempt).
 TEST(WeakPtrTest, WasInvalidatedByFactoryDestruction) {
diff --git a/base/nix/xdg_util.cc b/base/nix/xdg_util.cc
index de66c5b..e0251b4 100644
--- a/base/nix/xdg_util.cc
+++ b/base/nix/xdg_util.cc
@@ -143,6 +143,9 @@
       if (value == "LXQt") {
         return DESKTOP_ENVIRONMENT_LXQT;
       }
+      if (value == "COSMIC") {
+        return DESKTOP_ENVIRONMENT_COSMIC;
+      }
     }
   }
 
@@ -215,6 +218,8 @@
       return "UKUI";
     case DESKTOP_ENVIRONMENT_LXQT:
       return "LXQT";
+    case DESKTOP_ENVIRONMENT_COSMIC:
+      return "COSMIC";
   }
   return nullptr;
 }
diff --git a/base/nix/xdg_util.h b/base/nix/xdg_util.h
index 85b012be..0232fb0 100644
--- a/base/nix/xdg_util.h
+++ b/base/nix/xdg_util.h
@@ -41,6 +41,7 @@
   DESKTOP_ENVIRONMENT_UNITY = 9,
   DESKTOP_ENVIRONMENT_XFCE = 10,
   DESKTOP_ENVIRONMENT_LXQT = 11,
+  DESKTOP_ENVIRONMENT_COSMIC = 13,
 };
 
 // Values based on valid types indicated in:
diff --git a/base/nix/xdg_util_unittest.cc b/base/nix/xdg_util_unittest.cc
index 6474a85..d8d02ca 100644
--- a/base/nix/xdg_util_unittest.cc
+++ b/base/nix/xdg_util_unittest.cc
@@ -53,6 +53,7 @@
 const char* const kXdgDesktopUnity = "Unity";
 const char* const kXdgDesktopUnity7 = "Unity:Unity7";
 const char* const kXdgDesktopUnity8 = "Unity:Unity8";
+const char* const kXdgDesktopCosmic = "COSMIC";
 const char* const kKDESessionKDE5 = "5";
 const char* const kKDESessionKDE6 = "6";
 
@@ -333,6 +334,15 @@
   EXPECT_EQ(DESKTOP_ENVIRONMENT_UNITY, GetDesktopEnvironment(&getter));
 }
 
+TEST(XDGUtilTest, GetXdgDesktopCosmic) {
+  MockEnvironment getter;
+  EXPECT_CALL(getter, GetVar(_)).WillRepeatedly(Return(std::nullopt));
+  EXPECT_CALL(getter, GetVar(StrEq(kXdgCurrentDesktopEnvVar)))
+      .WillOnce(Return(kXdgDesktopCosmic));
+
+  EXPECT_EQ(DESKTOP_ENVIRONMENT_COSMIC, GetDesktopEnvironment(&getter));
+}
+
 TEST(XDGUtilTest, GetXdgSessiontypeUnset) {
   MockEnvironment getter;
   EXPECT_CALL(getter, GetVar(_)).WillRepeatedly(Return(std::nullopt));
diff --git a/base/task/sequence_manager/delayed_task_handle_delegate.cc b/base/task/sequence_manager/delayed_task_handle_delegate.cc
index cc2e355..a59f75b 100644
--- a/base/task/sequence_manager/delayed_task_handle_delegate.cc
+++ b/base/task/sequence_manager/delayed_task_handle_delegate.cc
@@ -4,6 +4,7 @@
 
 #include "base/task/sequence_manager/delayed_task_handle_delegate.h"
 
+#include "base/features.h"
 #include "base/task/sequence_manager/task_queue_impl.h"
 
 namespace base::sequence_manager::internal {
@@ -32,7 +33,11 @@
     return;
   }
 
-  weak_ptr_factory_.InvalidateWeakPtrs();
+  if (features::IsReducePPMsEnabled()) {
+    weak_ptr_factory_.InvalidateWeakPtrsAndDoom();
+  } else {
+    weak_ptr_factory_.InvalidateWeakPtrs();
+  }
 
   // If the task is still inside the heap, then it can be removed directly.
   if (heap_handle_.IsValid()) {
@@ -61,7 +66,12 @@
   DCHECK(IsValid());
   // The task must be removed from the heap before running it.
   DCHECK(!heap_handle_.IsValid());
-  weak_ptr_factory_.InvalidateWeakPtrs();
+
+  if (features::IsReducePPMsEnabled()) {
+    weak_ptr_factory_.InvalidateWeakPtrsAndDoom();
+  } else {
+    weak_ptr_factory_.InvalidateWeakPtrs();
+  }
 }
 
 }  // namespace base::sequence_manager::internal
diff --git a/build/install-build-deps.py b/build/install-build-deps.py
index 6b1236a5..412962f 100755
--- a/build/install-build-deps.py
+++ b/build/install-build-deps.py
@@ -85,14 +85,16 @@
   parser.add_argument(
       "--nacl",
       action="store_true",
-      help="Enable installation of prerequisites for building NaCl",
-  )
+      # Deprecated flag retained as functional for backward compatibility:
+      # Enable installation of nacl dependencies
+      help=argparse.SUPPRESS)
   parser.add_argument(
       "--no-nacl",
       action="store_false",
       dest="nacl",
-      help="Disable installation of prerequisites for building NaCl",
-  )
+      # Deprecated flag retained as functional for backward compatibility:
+      # Enable installation of nacl dependencies
+      help=argparse.SUPPRESS)
   parser.add_argument(
       "--backwards-compatible",
       action="store_true",
@@ -202,7 +204,7 @@
 
 
 def apt_update(options):
-  if options.lib32 or options.nacl:
+  if options.lib32 or options.backwards_compatible:
     subprocess.check_call(["sudo", "dpkg", "--add-architecture", "i386"])
   subprocess.check_call(["sudo", "apt-get", "update"])
 
@@ -327,7 +329,8 @@
     packages.append("binutils-mips64el-linux-gnuabi64")
 
   # 64-bit systems need a minimum set of 32-bit compat packages for the
-  # pre-built NaCl binaries.
+  # pre-built NaCl binaries or Android SDK
+  # See https://developer.android.com/sdk/installing/index.html?pkg=tools
   if "ELF 64-bit" in subprocess.check_output(["file", "-L",
                                               "/sbin/init"]).decode():
     # ARM64 may not support these.
@@ -588,6 +591,45 @@
       "ttf-kochi-mincho",
       "ttf-mscorefonts-installer",
       "xfonts-mathml",
+
+      # for NaCl
+      "g++-mingw-w64-i686",
+      "lib32z1-dev",
+      "libasound2:i386",
+      "libcap2:i386",
+      "libelf-dev:i386",
+      "libfontconfig1:i386",
+      "libglib2.0-0:i386",
+      "libgpm2:i386",
+      "libncurses5:i386",
+      "libnss3:i386",
+      "libpango-1.0-0:i386",
+      "libssl-dev:i386",
+      "libtinfo-dev",
+      "libtinfo-dev:i386",
+      "libtool",
+      "libudev1:i386",
+      "libuuid1:i386",
+      "libxcomposite1:i386",
+      "libxcursor1:i386",
+      "libxdamage1:i386",
+      "libxi6:i386",
+      "libxrandr2:i386",
+      "libxss1:i386",
+      "libxtst6:i386",
+      "texinfo",
+      "xvfb",
+
+      # Packages to build NaCl, its toolchains, and its ports.
+      "ant",
+      "autoconf",
+      "bison",
+      "cmake",
+      "gawk",
+      "intltool",
+      "libtinfo5",
+      "xutils-dev",
+      "xsltproc",
   ]
 
   if package_exists("python-is-python2"):
@@ -615,6 +657,15 @@
   else:
     packages.append("apache2-bin")
 
+  # for NaCl.
+  # Prefer lib32ncurses5-dev to match libncurses5:i386 if it exists.
+  # In some Ubuntu releases, lib32ncurses5-dev is a transition package to
+  # lib32ncurses-dev, so use that as a fallback.
+  if package_exists("lib32ncurses5-dev"):
+    packages.append("lib32ncurses5-dev")
+  else:
+    packages.append("lib32ncurses-dev")
+
   php_versions = [
       ("php8.1-cgi", "libapache2-mod-php8.1"),
       ("php8.0-cgi", "libapache2-mod-php8.0"),
@@ -665,72 +716,6 @@
   return packages
 
 
-def nacl_list(options):
-  if not options.nacl:
-    print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies.",
-          file=sys.stderr)
-    return []
-
-  packages = [
-      "g++-mingw-w64-i686",
-      "lib32z1-dev",
-      "libasound2:i386",
-      "libcap2:i386",
-      "libelf-dev:i386",
-      "libfontconfig1:i386",
-      "libglib2.0-0:i386",
-      "libgpm2:i386",
-      "libncurses5:i386",
-      "libnss3:i386",
-      "libpango-1.0-0:i386",
-      "libssl-dev:i386",
-      "libtinfo-dev",
-      "libtinfo-dev:i386",
-      "libtool",
-      "libudev1:i386",
-      "libuuid1:i386",
-      "libxcomposite1:i386",
-      "libxcursor1:i386",
-      "libxdamage1:i386",
-      "libxi6:i386",
-      "libxrandr2:i386",
-      "libxss1:i386",
-      "libxtst6:i386",
-      "texinfo",
-      "xvfb",
-      # Packages to build NaCl, its toolchains, and its ports.
-      "ant",
-      "autoconf",
-      "bison",
-      "cmake",
-      "gawk",
-      "intltool",
-      "libtinfo5",
-      "xutils-dev",
-      "xsltproc",
-  ]
-
-  for package in packages:
-    if not package_exists(package):
-      print("Skipping NaCl, NaCl toolchain, NaCl ports dependencies because %s "
-            "is not available" % package,
-            file=sys.stderr)
-      return []
-
-  print("Including NaCl, NaCl toolchain, NaCl ports dependencies.",
-        file=sys.stderr)
-
-  # Prefer lib32ncurses5-dev to match libncurses5:i386 if it exists.
-  # In some Ubuntu releases, lib32ncurses5-dev is a transition package to
-  # lib32ncurses-dev, so use that as a fallback.
-  if package_exists("lib32ncurses5-dev"):
-    packages.append("lib32ncurses5-dev")
-  else:
-    packages.append("lib32ncurses-dev")
-
-  return packages
-
-
 # Packages suffixed with t64 are "transition packages" and should be preferred.
 def maybe_append_t64(package):
   name = package.split(":")
@@ -781,7 +766,7 @@
 
 def package_list(options):
   packages = (dev_list() + lib_list() + dbg_list(options) +
-              lib32_list(options) + arm_list(options) + nacl_list(options) +
+              lib32_list(options) + arm_list(options) +
               backwards_compatible_list(options))
   packages = [maybe_append_t64(package) for package in set(packages)]
 
diff --git a/cc/BUILD.gn b/cc/BUILD.gn
index 22beb28b..b6a7608d 100644
--- a/cc/BUILD.gn
+++ b/cc/BUILD.gn
@@ -186,8 +186,6 @@
     "metrics/compositor_timing_history.h",
     "metrics/custom_metrics_recorder.cc",
     "metrics/custom_metrics_recorder.h",
-    "metrics/dropped_frame_counter.cc",
-    "metrics/dropped_frame_counter.h",
     "metrics/event_latency_tracing_recorder.cc",
     "metrics/event_latency_tracing_recorder.h",
     "metrics/event_latency_tracker.cc",
@@ -794,7 +792,6 @@
     "metrics/compositor_frame_reporter_unittest.cc",
     "metrics/compositor_frame_reporting_controller_unittest.cc",
     "metrics/compositor_timing_history_unittest.cc",
-    "metrics/dropped_frame_counter_unittest.cc",
     "metrics/event_metrics_unittest.cc",
     "metrics/events_metrics_manager_unittest.cc",
     "metrics/frame_info_unittest.cc",
diff --git a/cc/layers/picture_layer_impl_perftest.cc b/cc/layers/picture_layer_impl_perftest.cc
index 9af545d..bfa7e3cd 100644
--- a/cc/layers/picture_layer_impl_perftest.cc
+++ b/cc/layers/picture_layer_impl_perftest.cc
@@ -77,7 +77,7 @@
     do {
       std::unique_ptr<TilingSetRasterQueueAll> queue =
           TilingSetRasterQueueAll::Create(
-              pending_layer_->picture_layer_tiling_set(), false, true);
+              pending_layer_->picture_layer_tiling_set(), true);
       ASSERT_TRUE(queue);
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
@@ -103,7 +103,7 @@
     do {
       std::unique_ptr<TilingSetRasterQueueAll> queue =
           TilingSetRasterQueueAll::Create(
-              pending_layer_->picture_layer_tiling_set(), false, true);
+              pending_layer_->picture_layer_tiling_set(), true);
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc
index 65faa31..07a6358 100644
--- a/cc/layers/picture_layer_impl_unittest.cc
+++ b/cc/layers/picture_layer_impl_unittest.cc
@@ -1504,7 +1504,7 @@
 
   std::unique_ptr<TilingSetRasterQueueAll> queue =
       TilingSetRasterQueueAll::Create(
-          pending_layer()->picture_layer_tiling_set(), false, false);
+          pending_layer()->picture_layer_tiling_set(), false);
   EXPECT_TRUE(queue);
   for (; !queue->IsEmpty(); queue->Pop()) {
     const PrioritizedTile& prioritized_tile = queue->Top();
@@ -2933,7 +2933,7 @@
   int high_res_now_tiles = 0u;
   std::unique_ptr<TilingSetRasterQueueAll> queue =
       TilingSetRasterQueueAll::Create(
-          pending_layer()->picture_layer_tiling_set(), false, false);
+          pending_layer()->picture_layer_tiling_set(), false);
   EXPECT_TRUE(queue);
   while (!queue->IsEmpty()) {
     PrioritizedTile prioritized_tile = queue->Top();
@@ -3005,7 +3005,7 @@
   unique_tiles.clear();
   high_res_tile_count = 0u;
   queue = TilingSetRasterQueueAll::Create(
-      pending_layer()->picture_layer_tiling_set(), false, false);
+      pending_layer()->picture_layer_tiling_set(), false);
   EXPECT_TRUE(queue);
   while (!queue->IsEmpty()) {
     PrioritizedTile prioritized_tile = queue->Top();
@@ -3042,7 +3042,7 @@
   }
 
   queue = TilingSetRasterQueueAll::Create(
-      pending_layer()->picture_layer_tiling_set(), true, false);
+      pending_layer()->picture_layer_tiling_set(), false);
   EXPECT_TRUE(queue);
   EXPECT_TRUE(queue->IsEmpty());
 }
@@ -3907,7 +3907,7 @@
   int unoccluded_tile_count = 0;
   std::unique_ptr<TilingSetRasterQueueAll> queue =
       TilingSetRasterQueueAll::Create(
-          pending_layer()->picture_layer_tiling_set(), false, false);
+          pending_layer()->picture_layer_tiling_set(), false);
   EXPECT_TRUE(queue);
   while (!queue->IsEmpty()) {
     PrioritizedTile prioritized_tile = queue->Top();
@@ -3939,7 +3939,7 @@
 
   unoccluded_tile_count = 0;
   queue = TilingSetRasterQueueAll::Create(
-      pending_layer()->picture_layer_tiling_set(), false, false);
+      pending_layer()->picture_layer_tiling_set(), false);
   EXPECT_TRUE(queue);
   while (!queue->IsEmpty()) {
     PrioritizedTile prioritized_tile = queue->Top();
@@ -3964,7 +3964,7 @@
 
   unoccluded_tile_count = 0;
   queue = TilingSetRasterQueueAll::Create(
-      pending_layer()->picture_layer_tiling_set(), false, false);
+      pending_layer()->picture_layer_tiling_set(), false);
   EXPECT_TRUE(queue);
   while (!queue->IsEmpty()) {
     PrioritizedTile prioritized_tile = queue->Top();
diff --git a/cc/layers/tile_display_layer_impl_unittest.cc b/cc/layers/tile_display_layer_impl_unittest.cc
index 56ccd06c..44b448c 100644
--- a/cc/layers/tile_display_layer_impl_unittest.cc
+++ b/cc/layers/tile_display_layer_impl_unittest.cc
@@ -22,9 +22,8 @@
 
   auto render_pass = viz::CompositorRenderPass::Create();
   AppendQuadsData data;
-  layer.AppendQuads(
-      AppendQuadsContext{DRAW_MODE_RESOURCELESS_SOFTWARE, {}, false},
-      render_pass.get(), &data);
+  layer.AppendQuads(AppendQuadsContext{DRAW_MODE_SOFTWARE, {}, false},
+                    render_pass.get(), &data);
 
   EXPECT_EQ(render_pass->quad_list.size(), 0u);
 }
@@ -52,9 +51,8 @@
 
   auto render_pass = viz::CompositorRenderPass::Create();
   AppendQuadsData data;
-  raw_layer->AppendQuads(
-      AppendQuadsContext{DRAW_MODE_RESOURCELESS_SOFTWARE, {}, false},
-      render_pass.get(), &data);
+  raw_layer->AppendQuads(AppendQuadsContext{DRAW_MODE_SOFTWARE, {}, false},
+                         render_pass.get(), &data);
 
   EXPECT_EQ(render_pass->quad_list.size(), 1u);
   EXPECT_EQ(render_pass->quad_list.front()->rect, kLayerRect);
@@ -69,7 +67,8 @@
       kLayerColor);
 }
 
-TEST_F(TileDisplayLayerImplTest, NonEmptyTilingResultsInPictureQuad) {
+TEST_F(TileDisplayLayerImplTest,
+       NonEmptyTilingWithResourceResultsInPictureQuad) {
   constexpr gfx::Size kLayerBounds(1300, 1900);
   constexpr gfx::Rect kLayerRect(kLayerBounds);
   constexpr float kOpacity = 1.0;
@@ -103,9 +102,8 @@
 
   auto render_pass = viz::CompositorRenderPass::Create();
   AppendQuadsData data;
-  raw_layer->AppendQuads(
-      AppendQuadsContext{DRAW_MODE_RESOURCELESS_SOFTWARE, {}, false},
-      render_pass.get(), &data);
+  raw_layer->AppendQuads(AppendQuadsContext{DRAW_MODE_SOFTWARE, {}, false},
+                         render_pass.get(), &data);
 
   EXPECT_EQ(render_pass->quad_list.size(), 1u);
   EXPECT_EQ(render_pass->quad_list.front()->rect, kLayerRect);
@@ -120,6 +118,54 @@
             false);
 }
 
+TEST_F(TileDisplayLayerImplTest,
+       NonEmptyTilingWithColorResultsInSolidColorQuad) {
+  constexpr gfx::Size kLayerBounds(1300, 1900);
+  constexpr gfx::Rect kLayerRect(kLayerBounds);
+  constexpr float kOpacity = 1.0;
+  constexpr SkColor4f kTileColor = SkColors::kRed;
+
+  auto layer = std::make_unique<TileDisplayLayerImpl>(
+      CHECK_DEREF(host_impl()->active_tree()), /*id=*/42);
+  auto* raw_layer = layer.get();
+  host_impl()->active_tree()->AddLayer(std::move(layer));
+
+  // For the production code to actually append a quad, the layer must have
+  // non-zero size and not be completely transparent.
+  raw_layer->SetBounds(kLayerBounds);
+  raw_layer->draw_properties().visible_layer_rect = kLayerRect;
+  raw_layer->draw_properties().opacity = kOpacity;
+
+  auto& tiling = raw_layer->GetOrCreateTilingFromScaleKey(1.0);
+  tiling.SetTileSize(kLayerBounds);
+  tiling.SetTilingRect(kLayerRect);
+
+  tiling.SetTileContents(TileIndex{0, 0}, kTileColor, /*update_damage=*/true);
+
+  SetupRootProperties(host_impl()->active_tree()->root_layer());
+
+  auto render_pass = viz::CompositorRenderPass::Create();
+  AppendQuadsData data;
+  raw_layer->AppendQuads(AppendQuadsContext{DRAW_MODE_SOFTWARE, {}, false},
+                         render_pass.get(), &data);
+
+  EXPECT_EQ(render_pass->quad_list.size(), 1u);
+  EXPECT_EQ(render_pass->quad_list.front()->rect, kLayerRect);
+  EXPECT_EQ(render_pass->quad_list.front()->visible_rect, kLayerRect);
+  EXPECT_EQ(render_pass->quad_list.front()->shared_quad_state->opacity,
+            kOpacity);
+  EXPECT_EQ(render_pass->quad_list.front()->material,
+            viz::DrawQuad::Material::kSolidColor);
+  EXPECT_EQ(
+      viz::SolidColorDrawQuad::MaterialCast(render_pass->quad_list.front())
+          ->color,
+      kTileColor);
+  EXPECT_EQ(
+      viz::SolidColorDrawQuad::MaterialCast(render_pass->quad_list.front())
+          ->force_anti_aliasing_off,
+      false);
+}
+
 class TileDisplayLayerImplWithEdgeAADisabledTest
     : public TileDisplayLayerImplTest {
  public:
@@ -163,9 +209,8 @@
 
   auto render_pass = viz::CompositorRenderPass::Create();
   AppendQuadsData data;
-  raw_layer->AppendQuads(
-      AppendQuadsContext{DRAW_MODE_RESOURCELESS_SOFTWARE, {}, false},
-      render_pass.get(), &data);
+  raw_layer->AppendQuads(AppendQuadsContext{DRAW_MODE_SOFTWARE, {}, false},
+                         render_pass.get(), &data);
 
   EXPECT_EQ(render_pass->quad_list.size(), 1u);
   EXPECT_EQ(viz::TileDrawQuad::MaterialCast(render_pass->quad_list.front())
diff --git a/cc/metrics/begin_main_frame_metrics.h b/cc/metrics/begin_main_frame_metrics.h
index 830a578..68427d3f 100644
--- a/cc/metrics/begin_main_frame_metrics.h
+++ b/cc/metrics/begin_main_frame_metrics.h
@@ -25,8 +25,8 @@
   base::TimeDelta paint;
   base::TimeDelta composite_commit;
   base::TimeDelta update_layers;
-  // True if we should measure smoothness in TotalFrameCounter and
-  // DroppedFrameCounter. Currently true when first contentful paint is done.
+  // True if we should measure smoothness in TotalFrameCounter.
+  // Currently true when first contentful paint is done.
   bool should_measure_smoothness = false;
 
   BeginMainFrameMetrics();
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc
index 00a4bba..31cd4da 100644
--- a/cc/metrics/compositor_frame_reporter.cc
+++ b/cc/metrics/compositor_frame_reporter.cc
@@ -30,7 +30,6 @@
 #include "base/tracing/protos/chrome_track_event.pbzero.h"
 #include "cc/base/rolling_time_delta_history.h"
 #include "cc/metrics/custom_metrics_recorder.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/metrics/event_latency_tracing_recorder.h"
 #include "cc/metrics/event_latency_tracker.h"
 #include "cc/metrics/event_metrics.h"
@@ -515,7 +514,6 @@
       smooth_thread_(smooth_thread),
       layer_tree_host_id_(layer_tree_host_id),
       global_trackers_(trackers) {
-  DCHECK(global_trackers_.dropped_frame_counter);
   DCHECK(global_trackers_.frame_sorter);
   if (global_trackers_.frame_sorter->first_contentful_paint_received()) {
     global_trackers_.frame_sorter->AddNewFrame(args);
@@ -890,15 +888,6 @@
     ReportPaintMetric();
   }
 
-  if (TestReportType(FrameReportType::kDroppedFrame)) {
-    global_trackers_.dropped_frame_counter->AddDroppedFrame();
-  } else {
-    if (has_partial_update_) {
-      global_trackers_.dropped_frame_counter->AddPartialFrame();
-    } else {
-      global_trackers_.dropped_frame_counter->AddGoodFrame();
-    }
-  }
   global_trackers_.frame_sorter->AddFrameInfoToBuffer(frame_info);
   if (global_trackers_.frame_sorter->first_contentful_paint_received()) {
     // Delegates call to DFC->OnEndFrame.
diff --git a/cc/metrics/compositor_frame_reporter.h b/cc/metrics/compositor_frame_reporter.h
index b286c27..78e2ac9 100644
--- a/cc/metrics/compositor_frame_reporter.h
+++ b/cc/metrics/compositor_frame_reporter.h
@@ -37,7 +37,6 @@
 }
 
 namespace cc {
-class DroppedFrameCounter;
 class EventLatencyTracker;
 class FrameSorter;
 class LatencyUkmReporter;
@@ -45,7 +44,6 @@
 struct GlobalMetricsTrackers {
   // RAW_PTR_EXCLUSION: Renderer performance: visible in sampling profiler
   // stacks.
-  RAW_PTR_EXCLUSION DroppedFrameCounter* dropped_frame_counter = nullptr;
   RAW_PTR_EXCLUSION LatencyUkmReporter* latency_ukm_reporter = nullptr;
   RAW_PTR_EXCLUSION FrameSequenceTrackerCollection* frame_sequence_trackers =
       nullptr;
diff --git a/cc/metrics/compositor_frame_reporter_unittest.cc b/cc/metrics/compositor_frame_reporter_unittest.cc
index 3c18797..1bcc037 100644
--- a/cc/metrics/compositor_frame_reporter_unittest.cc
+++ b/cc/metrics/compositor_frame_reporter_unittest.cc
@@ -16,7 +16,6 @@
 #include "base/test/simple_test_tick_clock.h"
 #include "base/time/time.h"
 #include "cc/metrics/compositor_frame_reporting_controller.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/metrics/event_metrics.h"
 #include "components/viz/common/frame_timing_details.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -195,8 +194,7 @@
   }
 
   std::unique_ptr<CompositorFrameReporter> CreatePipelineReporter() {
-    GlobalMetricsTrackers trackers{&dropped_frame_counter_,
-                                   nullptr,
+    GlobalMetricsTrackers trackers{nullptr,
                                    nullptr,
                                    nullptr,
                                    nullptr,
@@ -228,7 +226,6 @@
   // and destroyed after that.
   base::SimpleTestTickClock test_tick_clock_;
 
-  DroppedFrameCounter dropped_frame_counter_;
   FrameSorter frame_sorter_;
   std::unique_ptr<CompositorFrameReporter> pipeline_reporter_;
 
diff --git a/cc/metrics/compositor_frame_reporting_controller.cc b/cc/metrics/compositor_frame_reporting_controller.cc
index 2e159dd..62e686c 100644
--- a/cc/metrics/compositor_frame_reporting_controller.cc
+++ b/cc/metrics/compositor_frame_reporting_controller.cc
@@ -9,7 +9,6 @@
 #include "base/debug/dump_without_crashing.h"
 #include "base/metrics/histogram_macros.h"
 #include "cc/metrics/compositor_frame_reporter.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/metrics/frame_sequence_tracker_collection.h"
 #include "cc/metrics/latency_ukm_reporter.h"
 #include "cc/metrics/scroll_jank_dropped_frame_tracker.h"
@@ -66,10 +65,6 @@
   predictor_jank_tracker_->set_scroll_jank_ukm_reporter(nullptr);
   scroll_jank_dropped_frame_tracker_->set_scroll_jank_ukm_reporter(nullptr);
   if (global_trackers_.frame_sorter) {
-    if (global_trackers_.dropped_frame_counter) {
-      global_trackers_.frame_sorter->RemoveObserver(
-          global_trackers_.dropped_frame_counter);
-    }
     if (global_trackers_.frame_sequence_trackers) {
       global_trackers_.frame_sorter->RemoveObserver(
           global_trackers_.frame_sequence_trackers);
@@ -854,16 +849,4 @@
   }
 }
 
-void CompositorFrameReportingController::SetDroppedFrameCounter(
-    DroppedFrameCounter* counter) {
-  if (global_trackers_.dropped_frame_counter && global_trackers_.frame_sorter) {
-    global_trackers_.frame_sorter->RemoveObserver(
-        global_trackers_.dropped_frame_counter);
-  }
-  if (global_trackers_.frame_sorter) {
-    global_trackers_.frame_sorter->AddObserver(counter);
-  }
-  global_trackers_.dropped_frame_counter = counter;
-}
-
 }  // namespace cc
diff --git a/cc/metrics/compositor_frame_reporting_controller.h b/cc/metrics/compositor_frame_reporting_controller.h
index e9e6a0c2..3908918a2 100644
--- a/cc/metrics/compositor_frame_reporting_controller.h
+++ b/cc/metrics/compositor_frame_reporting_controller.h
@@ -32,7 +32,6 @@
 }
 
 namespace cc {
-class DroppedFrameCounter;
 class EventLatencyTracker;
 struct BeginMainFrameMetrics;
 struct FrameInfo;
@@ -107,17 +106,6 @@
     global_trackers_.frame_sorter = frame_sorter;
   }
 
-  void SetDroppedFrameCounter(DroppedFrameCounter* counter);
-
-  void ClearDroppedFrameCounter() {
-    if (global_trackers_.frame_sorter &&
-        global_trackers_.dropped_frame_counter) {
-      global_trackers_.frame_sorter->RemoveObserver(
-          global_trackers_.dropped_frame_counter);
-    }
-    global_trackers_.dropped_frame_counter = nullptr;
-  }
-
   void SetFrameSequenceTrackerCollection(
       FrameSequenceTrackerCollection* frame_sequence_trackers) {
     if (global_trackers_.frame_sorter) {
diff --git a/cc/metrics/compositor_frame_reporting_controller_unittest.cc b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
index 33cb503..16b0663 100644
--- a/cc/metrics/compositor_frame_reporting_controller_unittest.cc
+++ b/cc/metrics/compositor_frame_reporting_controller_unittest.cc
@@ -15,7 +15,6 @@
 #include "base/test/simple_test_tick_clock.h"
 #include "base/test/test_trace_processor.h"
 #include "base/time/time.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/metrics/event_metrics.h"
 #include "cc/metrics/frame_sequence_metrics.h"
 #include "cc/metrics/frame_sequence_tracker_collection.h"
@@ -118,14 +117,13 @@
 class CompositorFrameReportingControllerTest : public testing::Test {
  public:
   CompositorFrameReportingControllerTest()
-      : current_id_(1, 1), tracker_collection_(false, &dropped_counter_) {
+      : current_id_(1, 1), tracker_collection_(false) {
     test_tick_clock_.SetNowTicks(base::TimeTicks::Now());
     reporting_controller_.set_tick_clock(&test_tick_clock_);
     args_ = SimulateBeginFrameArgs(current_id_);
     reporting_controller_.SetFrameSorter(&frame_sorter_);
     reporting_controller_.SetFrameSequenceTrackerCollection(
         &tracker_collection_);
-    reporting_controller_.SetDroppedFrameCounter(&dropped_counter_);
   }
 
   // The following functions simulate the actions that would
@@ -345,7 +343,6 @@
   viz::FrameTokenGenerator current_token_;
   FrameSorter frame_sorter_;
   FrameSequenceTrackerCollection tracker_collection_;
-  DroppedFrameCounter dropped_counter_;
   TestCompositorFrameReportingController reporting_controller_;
   ::base::test::TracingEnvironment tracing_environment_;
 };
@@ -1458,7 +1455,6 @@
 
   reporting_controller_.ResetReporters();
   reporting_controller_.ClearFrameSequenceTrackerCollection();
-  reporting_controller_.ClearDroppedFrameCounter();
   reporting_controller_.SetFrameSorter(nullptr);
 }
 
@@ -1520,7 +1516,6 @@
 
   reporting_controller_.ResetReporters();
   reporting_controller_.ClearFrameSequenceTrackerCollection();
-  reporting_controller_.ClearDroppedFrameCounter();
   reporting_controller_.SetFrameSorter(nullptr);
 }
 
@@ -1549,7 +1544,6 @@
   // Stop requesting frames, skip over a few frames, and submit + present
   // another frame. There should no new dropped frames.
   frame_sorter_.Reset(/*reset_fcp=*/true);
-  dropped_counter_.Reset();
   reporting_controller_.OnStoppedRequestingBeginFrames();
   for (uint32_t i = 0; i < kSkipFrames; ++i)
     IncrementCurrentId();
@@ -1560,7 +1554,6 @@
 
   reporting_controller_.ResetReporters();
   reporting_controller_.ClearFrameSequenceTrackerCollection();
-  reporting_controller_.ClearDroppedFrameCounter();
   reporting_controller_.SetFrameSorter(nullptr);
 }
 
@@ -1649,7 +1642,6 @@
   tracker_collection_.StartSequence(
       FrameSequenceTrackerType::kCompositorAnimation);
   EXPECT_EQ(tracker_collection_.GetSmoothThread(), thread_type_compositor);
-  dropped_counter_.OnFirstContentfulPaintReceived();
   frame_sorter_.OnFirstContentfulPaintReceived();
 
   // Submit and present two compositor frames.
@@ -1671,7 +1663,6 @@
   EXPECT_EQ(3u + kSkipFrames_1, frame_sorter_.total_frames());
   EXPECT_EQ(0u, frame_sorter_.total_partial());
   EXPECT_EQ(kSkipFrames_1, frame_sorter_.total_dropped());
-  EXPECT_EQ(kSkipFrames_1, dropped_counter_.total_smoothness_dropped());
 
   // Now skip over a few frames which are not affecting smoothness.
   tracker_collection_.StopSequence(
@@ -1685,7 +1676,6 @@
   EXPECT_EQ(4u + kSkipFrames_1 + kSkipFrames_2, frame_sorter_.total_frames());
   EXPECT_EQ(0u, frame_sorter_.total_partial());
   EXPECT_EQ(kSkipFrames_1 + kSkipFrames_2, frame_sorter_.total_dropped());
-  EXPECT_EQ(kSkipFrames_1, dropped_counter_.total_smoothness_dropped());
 
   // Now skip over a few frames more frames which are affecting smoothness.
   tracker_collection_.StartSequence(
@@ -1700,8 +1690,6 @@
   EXPECT_EQ(0u, frame_sorter_.total_partial());
   EXPECT_EQ(kSkipFrames_1 + kSkipFrames_2 + kSkipFrames_3,
             frame_sorter_.total_dropped());
-  EXPECT_EQ(kSkipFrames_1 + kSkipFrames_3,
-            dropped_counter_.total_smoothness_dropped());
 }
 
 TEST_F(CompositorFrameReportingControllerTest,
@@ -1793,10 +1781,7 @@
       FrameSequenceTrackerType::kMainThreadAnimation);
   EXPECT_EQ(tracker_collection_.GetSmoothThread(), thread_type_main);
 
-  dropped_counter_.OnFirstContentfulPaintReceived();
   frame_sorter_.OnFirstContentfulPaintReceived();
-  dropped_counter_.SetTimeFirstContentfulPaintReceivedForTesting(
-      args_.frame_time);
 
   SimulateBeginMainFrame();
   reporting_controller_.OnFinishImplFrame(current_id_);
@@ -1831,17 +1816,11 @@
 
   // There are two frames with partial updates
   EXPECT_EQ(2u, frame_sorter_.total_partial());
-  // Which one is accompanied with new main thread update so only one affects
-  // smoothness
-  EXPECT_EQ(1u, dropped_counter_.total_smoothness_dropped());
 }
 
 TEST_F(CompositorFrameReportingControllerTest,
        NoUpdateCompositorWithJankyMain) {
-  dropped_counter_.OnFirstContentfulPaintReceived();
   frame_sorter_.OnFirstContentfulPaintReceived();
-  dropped_counter_.SetTimeFirstContentfulPaintReceivedForTesting(
-      args_.frame_time);
 
   // Start a new frame and take it all the way to start the frame on the main
   // thread (i.e. 'begin main frame').
diff --git a/cc/metrics/compositor_timing_history_unittest.cc b/cc/metrics/compositor_timing_history_unittest.cc
index 6e4f011d..0483a5b 100644
--- a/cc/metrics/compositor_timing_history_unittest.cc
+++ b/cc/metrics/compositor_timing_history_unittest.cc
@@ -9,7 +9,6 @@
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "cc/base/features.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace cc {
@@ -47,7 +46,6 @@
   TestCompositorTimingHistory timing_history_;
   base::TimeTicks now_;
   uint64_t sequence_number = 0;
-  DroppedFrameCounter dropped_counter;
 
   viz::BeginFrameArgs GetFakeBeginFrameArg(bool on_critical_path = true) {
     viz::BeginFrameArgs args = viz::BeginFrameArgs();
diff --git a/cc/metrics/dropped_frame_counter.cc b/cc/metrics/dropped_frame_counter.cc
deleted file mode 100644
index 9a9639ca..0000000
--- a/cc/metrics/dropped_frame_counter.cc
+++ /dev/null
@@ -1,420 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "cc/metrics/dropped_frame_counter.h"
-
-#include <algorithm>
-#include <array>
-#include <cmath>
-#include <iterator>
-
-#include "base/functional/bind.h"
-#include "base/metrics/histogram.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/trace_event/trace_event.h"
-#include "build/chromeos_buildflags.h"
-#include "cc/base/features.h"
-#include "cc/metrics/custom_metrics_recorder.h"
-#include "cc/metrics/frame_sorter.h"
-#include "cc/metrics/ukm_smoothness_data.h"
-
-namespace cc {
-namespace {
-
-const base::TimeDelta kDefaultSlidingWindowInterval = base::Seconds(1);
-
-// The start ranges of each bucket, up to but not including the start of the
-// next bucket. The last bucket contains the remaining values.
-constexpr std::array<double, 7> kBucketBounds = {0, 3, 6, 12, 25, 50, 75};
-
-}  // namespace
-
-using SlidingWindowHistogram = DroppedFrameCounter::SlidingWindowHistogram;
-
-void SlidingWindowHistogram::AddPercentDroppedFrame(
-    double percent_dropped_frame,
-    size_t count) {
-  DCHECK_GE(percent_dropped_frame, 0.0);
-  DCHECK_GE(100.0, percent_dropped_frame);
-  histogram_bins_[static_cast<int>(std::round(percent_dropped_frame))] += count;
-  total_count_ += count;
-}
-
-uint32_t SlidingWindowHistogram::GetPercentDroppedFramePercentile(
-    double percentile) const {
-  if (total_count_ == 0)
-    return 0;
-  DCHECK_GE(percentile, 0.0);
-  DCHECK_GE(1.0, percentile);
-  int current_index = 100;  // Last bin in historgam
-  uint32_t skipped_counter = histogram_bins_[current_index];  // Last bin values
-  double samples_to_skip = ((1 - percentile) * total_count_);
-  // We expect this method to calculate higher end percentiles such 95 and as a
-  // result we count from the last bin to find the correct bin.
-  while (skipped_counter < samples_to_skip && current_index > 0) {
-    current_index--;
-    skipped_counter += histogram_bins_[current_index];
-  }
-  return current_index;
-}
-
-double SlidingWindowHistogram::GetPercentDroppedFrameVariance() const {
-  double sum = 0;
-  size_t bin_count = sizeof(histogram_bins_) / sizeof(uint32_t);
-  for (size_t i = 0; i < bin_count; ++i) {
-    sum += histogram_bins_[i] * i;
-  }
-
-  // Don't calculate if count is 1 or less. Avoid divide by zero.
-  if (total_count_ <= 1)
-    return 0;
-
-  double average = sum / total_count_;
-  sum = 0;  // Sum is reset to be used for variance calculation
-
-  for (size_t i = 0; i < bin_count; ++i) {
-    sum += histogram_bins_[i] * (i - average) * (i - average);
-    // histogram_bins_[i] is the number of PDFs which were in the range of
-    // [i,i+1) so i is used as the actual value which is repeated for
-    // histogram_bins_[i] times.
-  }
-
-  return sum / (total_count_ - 1);
-}
-
-std::vector<double> SlidingWindowHistogram::GetPercentDroppedFrameBuckets()
-    const {
-  if (total_count_ == 0)
-    return std::vector<double>(std::size(kBucketBounds), 0);
-  std::vector<double> buckets(std::size(kBucketBounds));
-  for (size_t i = 0; i < std::size(kBucketBounds); ++i) {
-    buckets[i] =
-        static_cast<double>(smoothness_buckets_[i]) * 100 / total_count_;
-  }
-  return buckets;
-}
-
-void SlidingWindowHistogram::Clear() {
-  std::fill(std::begin(histogram_bins_), std::end(histogram_bins_), 0);
-  std::fill(std::begin(smoothness_buckets_), std::end(smoothness_buckets_), 0);
-  total_count_ = 0;
-}
-
-std::ostream& SlidingWindowHistogram::Dump(std::ostream& stream) const {
-  for (size_t i = 0; i < std::size(histogram_bins_); ++i) {
-    stream << i << ": " << histogram_bins_[i] << std::endl;
-  }
-  return stream << "Total: " << total_count_;
-}
-
-std::ostream& operator<<(
-    std::ostream& stream,
-    const DroppedFrameCounter::SlidingWindowHistogram& histogram) {
-  return histogram.Dump(stream);
-}
-
-DroppedFrameCounter::DroppedFrameCounter() = default;
-DroppedFrameCounter::~DroppedFrameCounter() = default;
-
-uint32_t DroppedFrameCounter::GetAverageThroughput() const {
-  size_t good_frames = 0;
-  for (auto it = End(); it; --it) {
-    if (**it == kFrameStateComplete || **it == kFrameStatePartial)
-      ++good_frames;
-  }
-  double throughput = 100. * good_frames / ring_buffer_.BufferSize();
-  return static_cast<uint32_t>(throughput);
-}
-
-void DroppedFrameCounter::AddGoodFrame() {
-  ring_buffer_.SaveToBuffer(kFrameStateComplete);
-  ++total_frames_;
-}
-
-void DroppedFrameCounter::AddPartialFrame() {
-  ring_buffer_.SaveToBuffer(kFrameStatePartial);
-  ++total_frames_;
-  ++total_partial_;
-}
-
-void DroppedFrameCounter::AddDroppedFrame() {
-  ring_buffer_.SaveToBuffer(kFrameStateDropped);
-  ++total_frames_;
-  ++total_dropped_;
-}
-
-// Start with flushing the frames in frame_sorter ignoring the currently
-// pending frames, so all callers should call frame_sorter_.Reset();
-// prior to this function.
-// TODO(crbug.com/409093076): Remove all uses of this function.
-void DroppedFrameCounter::ResetPendingFrames(base::TimeTicks timestamp) {
-  // Before resetting the pending frames, update the measurements for the
-  // sliding windows.
-  if (!latest_sliding_window_start_.is_null()) {
-    const auto report_until = timestamp - kDefaultSlidingWindowInterval;
-    // Report the sliding window metrics for frames that have already been
-    // completed (and some of which may have been dropped).
-    while (!sliding_window_.empty()) {
-      const auto& args = sliding_window_.front().first;
-      if (args.frame_time > report_until)
-        break;
-      PopSlidingWindow();
-    }
-    if (sliding_window_.empty()) {
-      DCHECK_EQ(
-          dropped_frame_count_in_window_[SmoothnessStrategy::kDefaultStrategy],
-          0u);
-      DCHECK_EQ(dropped_frame_count_in_window_
-                    [SmoothnessStrategy::kCompositorFocusedStrategy],
-                0u);
-    }
-
-    // Report no dropped frames for the sliding windows spanning the rest of the
-    // time.
-    if (latest_sliding_window_start_ < report_until) {
-      const auto difference = report_until - latest_sliding_window_start_;
-      const size_t count =
-          std::ceil(difference / latest_sliding_window_interval_);
-      if (count > 0) {
-        sliding_window_histogram_[SmoothnessStrategy::kDefaultStrategy]
-            .AddPercentDroppedFrame(0., count);
-        sliding_window_histogram_
-            [SmoothnessStrategy::kCompositorFocusedStrategy]
-                .AddPercentDroppedFrame(0., count);
-      }
-    }
-  }
-
-  dropped_frame_count_in_window_.fill(0);
-  sliding_window_ = {};
-  latest_sliding_window_start_ = {};
-  latest_sliding_window_interval_ = {};
-}
-
-void DroppedFrameCounter::EnableReportForUI() {
-  report_for_ui_ = true;
-}
-
-void DroppedFrameCounter::OnEndFrame(const viz::BeginFrameArgs& args,
-                                     const FrameInfo& frame_info) {
-  const bool is_dropped = frame_info.IsDroppedAffectingSmoothness();
-  if (!args.interval.is_zero())
-    total_frames_in_window_ = kDefaultSlidingWindowInterval / args.interval;
-
-  // Don't measure smoothness for frames that start before FCP is received, or
-  // that have already been reported as dropped.
-  if (is_dropped && first_contentful_paint_received_ &&
-      args.frame_time >= time_first_contentful_paint_received_) {
-    ++total_smoothness_dropped_;
-
-    if (!report_for_ui_) {
-      ReportFrames();
-    }
-  }
-
-  // Report frames on every frame for UI. And this needs to happen after
-  // `frame_sorter_.AddFrameResult` so that the current ending frame is included
-  // in the sliding window.
-  if (report_for_ui_) {
-    ReportFramesOnEveryFrameForUI();
-  }
-}
-
-void DroppedFrameCounter::ReportFrames() {
-  DCHECK(!report_for_ui_);
-
-  const auto total_frames = total_frames_;
-  TRACE_EVENT2("cc,benchmark", "SmoothnessDroppedFrame", "total", total_frames,
-               "smoothness", total_smoothness_dropped_);
-  if (sliding_window_max_percent_dropped_ !=
-      last_reported_metrics_.max_window) {
-    UMA_HISTOGRAM_PERCENTAGE(
-        "Graphics.Smoothness.MaxPercentDroppedFrames_1sWindow",
-        sliding_window_max_percent_dropped_);
-    last_reported_metrics_.max_window = sliding_window_max_percent_dropped_;
-  }
-
-  if (ukm_smoothness_data_ && total_frames > 0) {
-    UkmSmoothnessData smoothness_data;
-    smoothness_data.avg_smoothness =
-        static_cast<double>(total_smoothness_dropped_) * 100 / total_frames;
-    smoothness_data.median_smoothness =
-        SlidingWindowMedianPercentDropped(SmoothnessStrategy::kDefaultStrategy);
-    smoothness_data.compositor_focused_median =
-        SlidingWindowMedianPercentDropped(
-            SmoothnessStrategy::kCompositorFocusedStrategy);
-    ukm_smoothness_data_->Write(smoothness_data);
-  }
-}
-
-void DroppedFrameCounter::ReportFramesOnEveryFrameForUI() {
-  DCHECK(report_for_ui_);
-}
-
-void DroppedFrameCounter::SetUkmSmoothnessDestination(
-    UkmSmoothnessDataShared* smoothness_data) {
-  ukm_smoothness_data_ = smoothness_data;
-}
-
-// Start with flushing the frames in frame_sorter ignoring the currently
-// pending frames, so all callers should call frame_sorter_.Reset();
-// prior to invoking this function.
-// TODO(crbug.com/409093076): Remove all uses of this function.
-void DroppedFrameCounter::Reset() {
-  total_frames_ = 0;
-  total_partial_ = 0;
-  total_dropped_ = 0;
-  total_smoothness_dropped_ = 0;
-  sliding_window_max_percent_dropped_ = 0;
-  dropped_frame_count_in_window_.fill(0);
-  first_contentful_paint_received_ = false;
-  sliding_window_ = {};
-  latest_sliding_window_start_ = {};
-  sliding_window_histogram_[SmoothnessStrategy::kDefaultStrategy].Clear();
-  sliding_window_histogram_[SmoothnessStrategy::kCompositorFocusedStrategy]
-      .Clear();
-  ring_buffer_.Clear();
-  last_reported_metrics_ = {};
-  sliding_window_current_percent_dropped_.reset();
-}
-
-base::TimeDelta DroppedFrameCounter::ComputeCurrentWindowSize() const {
-  if (sliding_window_.empty())
-    return {};
-  return sliding_window_.back().first.frame_time +
-         sliding_window_.back().first.interval -
-         sliding_window_.front().first.frame_time;
-}
-
-void DroppedFrameCounter::AddSortedFrame(const viz::BeginFrameArgs& args,
-                                         const FrameInfo& frame_info) {
-  // Entirely disregard the frames with interval larger than the window --
-  // these are violating the assumptions in the below code and should
-  // only occur with external frame control, where dropped frame stats
-  // are not relevant.
-  if (args.interval >= kDefaultSlidingWindowInterval) {
-    return;
-  }
-
-  sliding_window_.emplace(args, frame_info);
-  UpdateDroppedFrameCountInWindow(frame_info, 1);
-
-  const bool is_dropped = frame_info.IsDroppedAffectingSmoothness();
-  if (!in_dropping_ && is_dropped) {
-    TRACE_EVENT_BEGIN("cc,benchmark,latency", "DroppedFrameDuration",
-                      perfetto::Track(reinterpret_cast<uint64_t>(this),
-                                      perfetto::ThreadTrack::Current()),
-                      args.frame_time);
-    in_dropping_ = true;
-  } else if (in_dropping_ && !is_dropped) {
-    TRACE_EVENT_END("cc,benchmark,latency" /* "DroppedFrameDuration" */,
-                    perfetto::Track(reinterpret_cast<uint64_t>(this),
-                                    perfetto::ThreadTrack::Current()),
-                    args.frame_time);
-    in_dropping_ = false;
-  }
-
-  OnEndFrame(args, frame_info);
-
-  if (ComputeCurrentWindowSize() < kDefaultSlidingWindowInterval) {
-    return;
-  }
-
-  DCHECK_GE(
-      dropped_frame_count_in_window_[SmoothnessStrategy::kDefaultStrategy], 0u);
-  DCHECK_GE(
-      sliding_window_.size(),
-      dropped_frame_count_in_window_[SmoothnessStrategy::kDefaultStrategy]);
-
-  while (ComputeCurrentWindowSize() > kDefaultSlidingWindowInterval) {
-    PopSlidingWindow();
-  }
-  DCHECK(!sliding_window_.empty());
-}
-
-void DroppedFrameCounter::PopSlidingWindow() {
-  const auto removed_args = sliding_window_.front().first;
-  const auto removed_frame_info = sliding_window_.front().second;
-  UpdateDroppedFrameCountInWindow(removed_frame_info, -1);
-  sliding_window_.pop();
-  if (sliding_window_.empty())
-    return;
-
-  // Don't count the newest element if it is outside the current window.
-  const auto& newest_args = sliding_window_.back().first;
-  const auto newest_was_dropped =
-      sliding_window_.back().second.IsDroppedAffectingSmoothness();
-
-  uint32_t invalidated_frames = 0;
-  if (ComputeCurrentWindowSize() > kDefaultSlidingWindowInterval &&
-      newest_was_dropped) {
-    invalidated_frames++;
-  }
-
-  // If two consecutive 'completed' frames are far apart from each other (in
-  // time), then report the 'dropped frame count' for the sliding window(s) in
-  // between. Note that the window-size still needs to be at least
-  // kDefaultSlidingWindowInterval.
-  const auto max_sliding_window_start =
-      newest_args.frame_time - kDefaultSlidingWindowInterval;
-  const auto max_difference = newest_args.interval * 1.5;
-  const auto& remaining_oldest_args = sliding_window_.front().first;
-  const auto last_timestamp =
-      std::min(remaining_oldest_args.frame_time, max_sliding_window_start);
-  const auto difference = last_timestamp - removed_args.frame_time;
-  const size_t count = difference > max_difference
-                           ? std::ceil(difference / newest_args.interval)
-                           : 1;
-
-  uint32_t dropped =
-      dropped_frame_count_in_window_[SmoothnessStrategy::kDefaultStrategy] -
-      invalidated_frames;
-  const double percent_dropped_frame =
-      std::min((dropped * 100.0) / total_frames_in_window_, 100.0);
-  sliding_window_histogram_[SmoothnessStrategy::kDefaultStrategy]
-      .AddPercentDroppedFrame(percent_dropped_frame, count);
-
-  uint32_t dropped_compositor =
-      dropped_frame_count_in_window_
-          [SmoothnessStrategy::kCompositorFocusedStrategy] -
-      invalidated_frames;
-  double percent_dropped_frame_compositor =
-      std::min((dropped_compositor * 100.0) / total_frames_in_window_, 100.0);
-  sliding_window_histogram_[SmoothnessStrategy::kCompositorFocusedStrategy]
-      .AddPercentDroppedFrame(percent_dropped_frame_compositor, count);
-
-  sliding_window_current_percent_dropped_ = percent_dropped_frame;
-
-  latest_sliding_window_start_ = last_timestamp;
-  latest_sliding_window_interval_ = remaining_oldest_args.interval;
-}
-
-void DroppedFrameCounter::UpdateDroppedFrameCountInWindow(
-    const FrameInfo& frame_info,
-    int count) {
-  if (frame_info.IsDroppedAffectingSmoothness()) {
-    DCHECK_GE(
-        dropped_frame_count_in_window_[SmoothnessStrategy::kDefaultStrategy] +
-            count,
-        0u);
-    dropped_frame_count_in_window_[SmoothnessStrategy::kDefaultStrategy] +=
-        count;
-  }
-  if (frame_info.WasSmoothCompositorUpdateDropped()) {
-    DCHECK_GE(dropped_frame_count_in_window_
-                      [SmoothnessStrategy::kCompositorFocusedStrategy] +
-                  count,
-              0u);
-    dropped_frame_count_in_window_
-        [SmoothnessStrategy::kCompositorFocusedStrategy] += count;
-  }
-}
-
-void DroppedFrameCounter::OnFirstContentfulPaintReceived() {
-  DCHECK(!first_contentful_paint_received_);
-  first_contentful_paint_received_ = true;
-  time_first_contentful_paint_received_ = base::TimeTicks::Now();
-}
-
-}  // namespace cc
diff --git a/cc/metrics/dropped_frame_counter.h b/cc/metrics/dropped_frame_counter.h
deleted file mode 100644
index c5e3ac9b..0000000
--- a/cc/metrics/dropped_frame_counter.h
+++ /dev/null
@@ -1,211 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CC_METRICS_DROPPED_FRAME_COUNTER_H_
-#define CC_METRICS_DROPPED_FRAME_COUNTER_H_
-
-#include <stddef.h>
-
-#include <array>
-#include <map>
-#include <optional>
-#include <queue>
-#include <utility>
-#include <vector>
-
-#include "base/containers/ring_buffer.h"
-#include "base/functional/callback_forward.h"
-#include "base/memory/raw_ptr.h"
-#include "base/time/time.h"
-#include "cc/base/features.h"
-#include "cc/cc_export.h"
-#include "cc/metrics/frame_info.h"
-#include "cc/metrics/frame_sorter.h"
-#include "cc/metrics/ukm_smoothness_data.h"
-
-namespace cc {
-
-// This class maintains a counter for produced/dropped frames, and can be used
-// to estimate the recent throughput.
-class CC_EXPORT DroppedFrameCounter : public FrameSorterObserver {
- public:
-  enum FrameState {
-    kFrameStateDropped,
-    kFrameStatePartial,
-    kFrameStateComplete
-  };
-
-  enum SmoothnessStrategy {
-    kDefaultStrategy,  // All threads and interactions are considered equal.
-    kScrollFocusedStrategy,  // Scroll interactions has the highest priority.
-    kMainFocusedStrategy,    // Reports dropped frames with main thread updates.
-    kCompositorFocusedStrategy,  // Reports dropped frames with compositor
-    // thread updates.
-    kStrategyCount
-  };
-
-  class CC_EXPORT SlidingWindowHistogram {
-   public:
-    void AddPercentDroppedFrame(double percent_dropped_frame, size_t count = 1);
-    uint32_t GetPercentDroppedFramePercentile(double percentile) const;
-    double GetPercentDroppedFrameVariance() const;
-    std::vector<double> GetPercentDroppedFrameBuckets() const;
-    void Clear();
-    std::ostream& Dump(std::ostream& stream) const;
-
-    uint32_t total_count() const { return total_count_; }
-
-   private:
-    std::array<uint32_t, 101> histogram_bins_ = {0};
-    std::array<uint32_t, 7> smoothness_buckets_ = {0};
-    uint32_t total_count_ = 0;
-  };
-
-  DroppedFrameCounter();
-  ~DroppedFrameCounter() override;
-
-  DroppedFrameCounter(const DroppedFrameCounter&) = delete;
-  DroppedFrameCounter& operator=(const DroppedFrameCounter&) = delete;
-
-  size_t frame_history_size() const { return ring_buffer_.BufferSize(); }
-  size_t total_frames() const { return total_frames_; }
-  size_t total_dropped() const { return total_dropped_; }
-  size_t total_partial() const { return total_partial_; }
-  size_t total_smoothness_dropped() const { return total_smoothness_dropped_; }
-
-  uint32_t GetAverageThroughput() const;
-
-  typedef base::RingBuffer<FrameState, 180> RingBufferType;
-  RingBufferType::Iterator Begin() const { return ring_buffer_.Begin(); }
-  // `End()` points to the last `FrameState`, not past it.
-  RingBufferType::Iterator End() const { return ring_buffer_.End(); }
-
-  void AddGoodFrame();
-  void AddPartialFrame();
-  void AddDroppedFrame();
-  void ReportFrames();
-  void ReportFramesOnEveryFrameForUI();
-
-  void SetUkmSmoothnessDestination(UkmSmoothnessDataShared* smoothness_data);
-  void OnFirstContentfulPaintReceived();
-
-  // Reset is used on navigation, which resets frame statistics as well as
-  // frame sorter.
-  void Reset();
-
-  // ResetPendingFrames is used when we need to keep track of frame statistics,
-  // but should no longer wait for the pending frames (e.g. connection to
-  // gpu-process was reset, or the page became invisible, etc.). The pending
-  // frames are not considered to be dropped.
-  void ResetPendingFrames(base::TimeTicks timestamp);
-
-  // Enable dropped frame report for ui::Compositor..
-  void EnableReportForUI();
-
-  void SetTimeFirstContentfulPaintReceivedForTesting(
-      base::TimeTicks time_fcp_received) {
-    DCHECK(first_contentful_paint_received_);
-    time_first_contentful_paint_received_ = time_fcp_received;
-  }
-
-  double sliding_window_max_percent_dropped() const {
-    return sliding_window_max_percent_dropped_;
-  }
-
-  std::optional<double> max_percent_dropped_After_1_sec() const {
-    return sliding_window_max_percent_dropped_After_1_sec_;
-  }
-
-  std::optional<double> max_percent_dropped_After_2_sec() const {
-    return sliding_window_max_percent_dropped_After_2_sec_;
-  }
-
-  std::optional<double> max_percent_dropped_After_5_sec() const {
-    return sliding_window_max_percent_dropped_After_5_sec_;
-  }
-
-  uint32_t SlidingWindowMedianPercentDropped(
-      SmoothnessStrategy strategy) const {
-    DCHECK_GT(SmoothnessStrategy::kStrategyCount, strategy);
-    return sliding_window_histogram_[strategy].GetPercentDroppedFramePercentile(
-        0.5);
-  }
-
-  double SlidingWindowPercentDroppedVariance(
-      SmoothnessStrategy strategy) const {
-    DCHECK_GT(SmoothnessStrategy::kStrategyCount, strategy);
-    return sliding_window_histogram_[strategy].GetPercentDroppedFrameVariance();
-  }
-
-  const SlidingWindowHistogram* GetSlidingWindowHistogram(
-      SmoothnessStrategy strategy) const {
-    DCHECK_GT(SmoothnessStrategy::kStrategyCount, strategy);
-    return &sliding_window_histogram_[strategy];
-  }
-
-  double sliding_window_current_percent_dropped() const {
-    return sliding_window_current_percent_dropped_.value_or(0);
-  }
-
-  bool first_contentful_paint_received() {
-    return first_contentful_paint_received_;
-  }
-
- private:
-  void AddSortedFrame(const viz::BeginFrameArgs& args,
-                      const FrameInfo& frame_info) override;
-  virtual void OnEndFrame(const viz::BeginFrameArgs& args,
-                          const FrameInfo& frame_info);
-  base::TimeDelta ComputeCurrentWindowSize() const;
-
-  void PopSlidingWindow();
-
-  // Adds count to dropped_frame_count_in_window_ of each strategy.
-  void UpdateDroppedFrameCountInWindow(const FrameInfo& frame_info, int count);
-
-  std::queue<std::pair<const viz::BeginFrameArgs, FrameInfo>> sliding_window_;
-  std::array<uint32_t, SmoothnessStrategy::kStrategyCount>
-      dropped_frame_count_in_window_ = {0};
-  double total_frames_in_window_ = 60.0;
-  std::array<SlidingWindowHistogram, SmoothnessStrategy::kStrategyCount>
-      sliding_window_histogram_;
-
-  base::TimeTicks latest_sliding_window_start_;
-  base::TimeDelta latest_sliding_window_interval_;
-
-  RingBufferType ring_buffer_;
-  size_t total_frames_ = 0;
-  size_t total_partial_ = 0;
-  size_t total_dropped_ = 0;
-  size_t total_smoothness_dropped_ = 0;
-  bool first_contentful_paint_received_ = false;
-  double sliding_window_max_percent_dropped_ = 0;
-  std::optional<double> sliding_window_max_percent_dropped_After_1_sec_;
-  std::optional<double> sliding_window_max_percent_dropped_After_2_sec_;
-  std::optional<double> sliding_window_max_percent_dropped_After_5_sec_;
-  base::TimeTicks time_first_contentful_paint_received_;
-  raw_ptr<UkmSmoothnessDataShared> ukm_smoothness_data_ = nullptr;
-
-  struct {
-    double max_window = 0;
-    double p95_window = 0;
-  } last_reported_metrics_;
-
-  bool report_for_ui_ = false;
-  std::optional<double> sliding_window_current_percent_dropped_;
-
-  // Sets to true on a newly dropped frame and stays true as long as the frames
-  // that follow are dropped. Reset when a frame is presented. It is used to
-  // generate asynchronous trace events that cover the duration of consecutive
-  // dropped frames
-  bool in_dropping_ = false;
-};
-
-CC_EXPORT std::ostream& operator<<(
-    std::ostream&,
-    const DroppedFrameCounter::SlidingWindowHistogram&);
-
-}  // namespace cc
-
-#endif  // CC_METRICS_DROPPED_FRAME_COUNTER_H_
diff --git a/cc/metrics/dropped_frame_counter_unittest.cc b/cc/metrics/dropped_frame_counter_unittest.cc
deleted file mode 100644
index 36d15bf..0000000
--- a/cc/metrics/dropped_frame_counter_unittest.cc
+++ /dev/null
@@ -1,650 +0,0 @@
-// Copyright 2020 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "cc/metrics/dropped_frame_counter.h"
-
-#include <cstdlib>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/memory/raw_ptr.h"
-#include "base/synchronization/lock.h"
-#include "base/synchronization/waitable_event.h"
-#include "base/time/default_tick_clock.h"
-#include "base/time/time.h"
-#include "build/chromeos_buildflags.h"
-#include "cc/animation/animation_host.h"
-#include "cc/base/features.h"
-#include "cc/metrics/compositor_frame_reporting_controller.h"
-#include "cc/metrics/custom_metrics_recorder.h"
-#include "cc/metrics/frame_sorter.h"
-#include "cc/test/fake_content_layer_client.h"
-#include "cc/test/fake_frame_info.h"
-#include "cc/test/fake_picture_layer.h"
-#include "cc/test/layer_tree_test.h"
-
-namespace cc {
-namespace {
-
-using SmoothnessStrategy = DroppedFrameCounter::SmoothnessStrategy;
-
-FrameInfo CreateStubFrameInfo(bool is_dropped) {
-  return CreateFakeFrameInfo(is_dropped
-                                 ? FrameInfo::FrameFinalState::kDropped
-                                 : FrameInfo::FrameFinalState::kPresentedAll);
-}
-
-class TestCustomMetricsRecorder : public CustomMetricRecorder {
- public:
-  TestCustomMetricsRecorder() = default;
-  ~TestCustomMetricsRecorder() override = default;
-
-  // CustomMetricRecorder:
-  void ReportPercentDroppedFramesInOneSecondWindow2(double percent) override {
-    ++report_count_;
-    last_percent_dropped_frames_ = percent;
-  }
-  void ReportEventLatency(
-      std::vector<EventLatencyTracker::LatencyData> latencies) override {}
-
-  void Reset() {
-    report_count_ = 0u;
-    last_percent_dropped_frames_ = 0;
-  }
-
-  int report_count() const { return report_count_; }
-
-  double last_percent_dropped_frames() const {
-    return last_percent_dropped_frames_;
-  }
-
- private:
-  int report_count_ = 0u;
-  double last_percent_dropped_frames_ = 0;
-};
-
-class DroppedFrameCounterTestBase : public LayerTreeTest {
- public:
-  DroppedFrameCounterTestBase() = default;
-  ~DroppedFrameCounterTestBase() override = default;
-
-  virtual void SetUpTestConfigAndExpectations() = 0;
-
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    settings->commit_to_active_tree = false;
-  }
-
-  void SetupTree() override {
-    LayerTreeTest::SetupTree();
-
-    Layer* root_layer = layer_tree_host()->root_layer();
-    scroll_layer_ = FakePictureLayer::Create(&client_);
-    // Set up the layer so it always has something to paint.
-    scroll_layer_->set_always_update_resources(true);
-    scroll_layer_->SetBounds({3, 3});
-    client_.set_bounds({3, 3});
-    root_layer->AddChild(scroll_layer_);
-  }
-
-  void RunTest(CompositorMode mode) override {
-    SetUpTestConfigAndExpectations();
-    LayerTreeTest::RunTest(mode);
-  }
-
-  void BeginTest() override {
-    ASSERT_GT(config_.animation_frames, 0u);
-
-    // Start with requesting main-frames.
-    PostSetNeedsCommitToMainThread();
-  }
-
-  void AfterTest() override {
-    EXPECT_GE(total_frames_, config_.animation_frames);
-    // It is possible to drop even more frame than what the test expects (e.g.
-    // in slower machines, slower builds such as asan/tsan builds, etc.), since
-    // the test does not strictly control both threads and deadlines. Therefore,
-    // it is not possible to check for strict equality here.
-    EXPECT_LE(expect_.min_partial, partial_);
-    EXPECT_LE(expect_.min_dropped, dropped_);
-    EXPECT_LE(expect_.min_dropped_smoothness, dropped_smoothness_);
-  }
-
-  // Compositor thread function overrides:
-  void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl,
-                                  const viz::BeginFrameArgs& args,
-                                  bool has_damage) override {
-    if (TestEnded())
-      return;
-
-    // Request a re-draw, and set a non-empty damage region (otherwise the
-    // draw is aborted with 'no damage').
-    host_impl->SetNeedsRedraw();
-    host_impl->SetViewportDamage(gfx::Rect(0, 0, 10, 20));
-
-    if (skip_main_thread_next_frame_) {
-      skip_main_thread_next_frame_ = false;
-    } else {
-      // Request update from the main-thread too.
-      host_impl->SetNeedsCommit();
-    }
-  }
-
-  void DrawLayersOnThread(LayerTreeHostImpl* host_impl) override {
-    // If the main-thread is blocked, then unblock it once the compositor thread
-    // has already drawn a frame.
-    base::WaitableEvent* wait = nullptr;
-    {
-      base::AutoLock lock(wait_lock_);
-      wait = wait_;
-    }
-
-    if (wait) {
-      // When the main-thread blocks during a frame, skip the main-thread for
-      // the next frame, so that the main-thread can be in sync with the
-      // compositor thread again.
-      skip_main_thread_next_frame_ = true;
-      wait->Signal();
-    }
-  }
-
-  void DidReceivePresentationTimeOnThread(
-      LayerTreeHostImpl* host_impl,
-      uint32_t frame_token,
-      const gfx::PresentationFeedback& feedback) override {
-    ++presented_frames_;
-    if (presented_frames_ < config_.animation_frames)
-      return;
-
-    auto* dropped_frame_counter =
-        host_impl->dropped_frame_counter_for_testing();
-    DCHECK(dropped_frame_counter);
-
-    total_frames_ = dropped_frame_counter->total_frames();
-    partial_ = dropped_frame_counter->total_partial();
-    dropped_ = dropped_frame_counter->total_dropped();
-    dropped_smoothness_ = dropped_frame_counter->total_smoothness_dropped();
-    EndTest();
-  }
-
-  // Main-thread function overrides:
-  void BeginMainFrame(const viz::BeginFrameArgs& args) override {
-    if (TestEnded())
-      return;
-
-    bool should_wait = false;
-    if (config_.should_drop_main_every > 0) {
-      should_wait =
-          args.frame_id.sequence_number % config_.should_drop_main_every == 0;
-    }
-
-    if (should_wait) {
-      base::WaitableEvent wait{base::WaitableEvent::ResetPolicy::MANUAL,
-                               base::WaitableEvent::InitialState::NOT_SIGNALED};
-      {
-        base::AutoLock lock(wait_lock_);
-        wait_ = &wait;
-      }
-      wait.Wait();
-      {
-        base::AutoLock lock(wait_lock_);
-        wait_ = nullptr;
-      }
-    }
-
-    // Make some changes so that the main-thread needs to push updates to the
-    // compositor thread (i.e. force a commit).
-    auto const bounds = scroll_layer_->bounds();
-    scroll_layer_->SetBounds({bounds.width(), bounds.height() + 1});
-    if (config_.should_register_main_thread_animation) {
-      animation_host()->SetAnimationCounts(1);
-      animation_host()->SetCurrentFrameHadRaf(true);
-      animation_host()->SetNextFrameHasPendingRaf(true);
-    }
-  }
-
- protected:
-  // The test configuration options. This is set before the test starts, and
-  // remains unchanged after that. So it is safe to read these fields from
-  // either threads.
-  struct TestConfig {
-    uint32_t should_drop_main_every = 0;
-    uint32_t animation_frames = 0;
-    bool should_register_main_thread_animation = false;
-  } config_;
-
-  // The test expectations. This is set before the test starts, and
-  // remains unchanged after that. So it is safe to read these fields from
-  // either threads.
-  struct TestExpectation {
-    uint32_t min_partial = 0;
-    uint32_t min_dropped = 0;
-    uint32_t min_dropped_smoothness = 0;
-  } expect_;
-
- private:
-  // Set up a dummy picture layer so that every begin-main frame requires a
-  // commit (without the dummy layer, the main-thread never has to paint, which
-  // causes an early 'no damage' abort of the main-frame.
-  FakeContentLayerClient client_;
-  scoped_refptr<FakePictureLayer> scroll_layer_;
-
-  // This field is used only on the compositor thread to track how many frames
-  // have been processed.
-  uint32_t presented_frames_ = 0;
-
-  // The |wait_| event is used when the test wants to deliberately force the
-  // main-thread to block while processing begin-main-frames.
-  base::Lock wait_lock_;
-  raw_ptr<base::WaitableEvent> wait_ = nullptr;
-
-  // These fields are populated in the compositor thread when the desired number
-  // of frames have been processed. These fields are subsequently compared
-  // against the expectation after the test ends.
-  uint32_t total_frames_ = 0;
-  uint32_t partial_ = 0;
-  uint32_t dropped_ = 0;
-  uint32_t dropped_smoothness_ = 0;
-
-  bool skip_main_thread_next_frame_ = false;
-};
-
-class DroppedFrameCounterNoDropTest : public DroppedFrameCounterTestBase {
- public:
-  ~DroppedFrameCounterNoDropTest() override = default;
-
-  void SetUpTestConfigAndExpectations() override {
-    config_.animation_frames = 28;
-    config_.should_register_main_thread_animation = false;
-
-    expect_.min_partial = 0;
-    expect_.min_dropped = 0;
-    expect_.min_dropped_smoothness = 0;
-  }
-};
-
-MULTI_THREAD_TEST_F(DroppedFrameCounterNoDropTest);
-
-class DroppedFrameCounterMainDropsNoSmoothness
-    : public DroppedFrameCounterTestBase {
- public:
-  ~DroppedFrameCounterMainDropsNoSmoothness() override = default;
-
-  void SetUpTestConfigAndExpectations() override {
-    config_.animation_frames = 28;
-    config_.should_drop_main_every = 5;
-    config_.should_register_main_thread_animation = false;
-
-    expect_.min_partial = 5;
-    expect_.min_dropped_smoothness = 0;
-  }
-};
-
-// TODO(crbug.com/40144326) Disabled for flakiness.
-// MULTI_THREAD_TEST_F(DroppedFrameCounterMainDropsNoSmoothness);
-
-class DroppedFrameCounterMainDropsSmoothnessTest
-    : public DroppedFrameCounterTestBase {
- public:
-  ~DroppedFrameCounterMainDropsSmoothnessTest() override = default;
-
-  void SetUpTestConfigAndExpectations() override {
-    config_.animation_frames = 28;
-    config_.should_drop_main_every = 5;
-    config_.should_register_main_thread_animation = true;
-
-    expect_.min_partial = 5;
-    expect_.min_dropped_smoothness = 5;
-  }
-};
-
-// TODO(crbug.com/40144326) Disabled for flakiness.
-// MULTI_THREAD_TEST_F(DroppedFrameCounterMainDropsSmoothnessTest);
-
-class DroppedFrameCounterTest : public testing::Test {
- public:
-  explicit DroppedFrameCounterTest(SmoothnessStrategy smoothness_strategy =
-                                       SmoothnessStrategy::kDefaultStrategy)
-      : smoothness_strategy_(smoothness_strategy) {
-    dropped_frame_counter_ = std::make_unique<DroppedFrameCounter>();
-    dropped_frame_counter_->OnFirstContentfulPaintReceived();
-    frame_sorter_.AddObserver(dropped_frame_counter_.get());
-  }
-  ~DroppedFrameCounterTest() override = default;
-
-  // For each boolean in frame_states produces a frame
-  void SimulateFrameSequence(std::vector<bool> frame_states, int repeat) {
-    for (int i = 0; i < repeat; i++) {
-      for (auto is_dropped : frame_states) {
-        viz::BeginFrameArgs args_ = SimulateBeginFrameArgs();
-        if (dropped_frame_counter_->first_contentful_paint_received()) {
-          frame_sorter_.AddNewFrame(args_);
-          frame_sorter_.AddFrameResult(args_, CreateStubFrameInfo(is_dropped));
-        }
-        sequence_number_++;
-        frame_time_ += interval_;
-      }
-    }
-  }
-
-  // Make a sequence of frame states where the first |dropped_frames| out of
-  // |total_frames| are dropped.
-  std::vector<bool> MakeFrameSequence(int dropped_frames, int total_frames) {
-    std::vector<bool> frame_states(total_frames, false);
-    for (int i = 0; i < dropped_frames; i++) {
-      frame_states[i] = true;
-    }
-    return frame_states;
-  }
-
-  std::vector<viz::BeginFrameArgs> SimulatePendingFrame(int repeat) {
-    std::vector<viz::BeginFrameArgs> args(repeat);
-    for (int i = 0; i < repeat; i++) {
-      args[i] = SimulateBeginFrameArgs();
-      if (dropped_frame_counter_->first_contentful_paint_received()) {
-        frame_sorter_.AddNewFrame(args[i]);
-      }
-      sequence_number_++;
-      frame_time_ += interval_;
-    }
-    return args;
-  }
-
-  // Simulate a main and impl thread update on the same frame.
-  void SimulateForkedFrame(bool main_dropped, bool impl_dropped) {
-    viz::BeginFrameArgs args_ = SimulateBeginFrameArgs();
-    if (dropped_frame_counter_->first_contentful_paint_received()) {
-      frame_sorter_.AddNewFrame(args_);
-      frame_sorter_.AddNewFrame(args_);
-    }
-    // End the 'main thread' arm of the fork.
-    auto main_info = CreateStubFrameInfo(main_dropped);
-    main_info.main_thread_response = FrameInfo::MainThreadResponse::kIncluded;
-    frame_sorter_.AddFrameResult(args_, main_info);
-
-    // End the 'compositor thread' arm of the fork.
-    auto impl_info = CreateStubFrameInfo(impl_dropped);
-    impl_info.main_thread_response = FrameInfo::MainThreadResponse::kMissing;
-    frame_sorter_.AddFrameResult(args_, impl_info);
-
-    sequence_number_++;
-    frame_time_ += interval_;
-  }
-
-  void AdvancetimeByIntervals(int interval_count) {
-    frame_time_ += interval_ * interval_count;
-  }
-
-  double PercentDroppedFrameMedian() {
-    return dropped_frame_counter_->SlidingWindowMedianPercentDropped(
-        smoothness_strategy_);
-  }
-
-  double PercentDroppedFrameVariance() {
-    return dropped_frame_counter_->SlidingWindowPercentDroppedVariance(
-        smoothness_strategy_);
-  }
-
-  const DroppedFrameCounter::SlidingWindowHistogram*
-  GetSlidingWindowHistogram() {
-    return dropped_frame_counter_->GetSlidingWindowHistogram(
-        smoothness_strategy_);
-  }
-
-  double GetTotalFramesInWindow() { return base::Seconds(1) / interval_; }
-
-  void SetInterval(base::TimeDelta interval) { interval_ = interval; }
-
-  base::TimeTicks GetNextFrameTime() const { return frame_time_ + interval_; }
-
- public:
-  std::unique_ptr<DroppedFrameCounter> dropped_frame_counter_;
-  FrameSorter frame_sorter_;
-
- private:
-  uint64_t sequence_number_ = 1;
-  uint64_t source_id_ = 1;
-  raw_ptr<const base::TickClock> tick_clock_ =
-      base::DefaultTickClock::GetInstance();
-  base::TimeTicks frame_time_ = tick_clock_->NowTicks();
-  base::TimeDelta interval_ = base::Microseconds(16667);  // 16.667 ms
-
-  SmoothnessStrategy smoothness_strategy_;
-
-  viz::BeginFrameArgs SimulateBeginFrameArgs() {
-    viz::BeginFrameId current_id_(source_id_, sequence_number_);
-    viz::BeginFrameArgs args = viz::BeginFrameArgs();
-    args.frame_id = current_id_;
-    args.frame_time = frame_time_;
-    args.interval = interval_;
-    return args;
-  }
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-// Test class that supports parameterized tests for each of the different
-// SmoothnessStrategy.
-
-class SmoothnessStrategyDroppedFrameCounterTest
-    : public DroppedFrameCounterTest,
-      public testing::WithParamInterface<SmoothnessStrategy> {
- public:
-  SmoothnessStrategyDroppedFrameCounterTest()
-      : DroppedFrameCounterTest(GetParam()) {}
-  ~SmoothnessStrategyDroppedFrameCounterTest() override = default;
-  SmoothnessStrategyDroppedFrameCounterTest(
-      const SmoothnessStrategyDroppedFrameCounterTest&) = delete;
-  SmoothnessStrategyDroppedFrameCounterTest& operator=(
-      const SmoothnessStrategyDroppedFrameCounterTest&) = delete;
-};
-
-std::vector<SmoothnessStrategy> GetSmoothnessStrategyParams() {
-  return std::vector<SmoothnessStrategy>{
-      SmoothnessStrategy::kDefaultStrategy,
-      SmoothnessStrategy::kCompositorFocusedStrategy};
-}
-
-std::string SmoothnessStrategyToString(const SmoothnessStrategy& s) {
-  if (s == SmoothnessStrategy::kDefaultStrategy) {
-    return "DefaultStrategy";
-  } else if (s == SmoothnessStrategy::kCompositorFocusedStrategy) {
-    return "CompositorFocusedStrategy";
-  }
-  return "INVALID";
-}
-
-INSTANTIATE_TEST_SUITE_P(,
-                         SmoothnessStrategyDroppedFrameCounterTest,
-                         ::testing::ValuesIn(GetSmoothnessStrategyParams()),
-                         [](auto& param) {
-                           return SmoothnessStrategyToString(param.param);
-                         });
-
-TEST_P(SmoothnessStrategyDroppedFrameCounterTest, SimplePattern1) {
-  // 2 out of every 3 frames are dropped (In total 80 frames out of 120).
-  SimulateFrameSequence({true, true, true, false, true, false}, 20);
-
-  // The max is the following window:
-  //    16 * <sequence> + {true, true, true, false
-  EXPECT_EQ(PercentDroppedFrameMedian(), 65);
-  EXPECT_LE(PercentDroppedFrameVariance(), 1);
-}
-
-TEST_P(SmoothnessStrategyDroppedFrameCounterTest, SimplePattern2) {
-  // 1 out of every 5 frames are dropped (In total 24 frames out of 120).
-  SimulateFrameSequence({false, false, false, false, true}, 24);
-
-  // 20th bucket, and as a result 95th percentile is also 20.
-  EXPECT_EQ(PercentDroppedFrameMedian(), 20);
-  EXPECT_LE(PercentDroppedFrameVariance(), 1);
-}
-
-TEST_P(SmoothnessStrategyDroppedFrameCounterTest, IncompleteWindow) {
-  // There are only 5 frames submitted, so Max, 95pct, median and variance
-  // should report zero.
-  SimulateFrameSequence({false, false, false, false, true}, 1);
-  EXPECT_EQ(PercentDroppedFrameMedian(), 0);
-  EXPECT_LE(PercentDroppedFrameVariance(), 1);
-}
-
-TEST_F(DroppedFrameCounterTest, NoCrashForIntervalLargerThanWindow) {
-  SetInterval(base::Milliseconds(1000));
-  SimulateFrameSequence({false, false}, 1);
-}
-
-TEST_P(SmoothnessStrategyDroppedFrameCounterTest, Percentile95WithIdleFrames) {
-  // Test scenario:
-  //  . 4s of 20% dropped frames.
-  //  . 96s of idle time.
-  // The 96%ile dropped-frame metric should be 0.
-
-  // Set an interval that rounds up nicely with 1 second.
-  constexpr auto kInterval = base::Milliseconds(10);
-  constexpr int kFps = base::Seconds(1).IntDiv(kInterval);
-  static_assert(
-      kFps % 5 == 0,
-      "kFps must be a multiple of 5 because this test depends on it.");
-  SetInterval(kInterval);
-
-  const auto* histogram = GetSlidingWindowHistogram();
-
-  // First 4 seconds with 20% dropped frames.
-  SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 4);
-  EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
-
-  // Then no frames are added for 97s. Note that this 1s more than 96 seconds,
-  // because the last second remains in the sliding window.
-  AdvancetimeByIntervals(kFps * 97);
-
-  // A single frame to flush the pipeline.
-  SimulateFrameSequence({false}, 1);
-
-  EXPECT_EQ(histogram->total_count(), 100u * kFps);
-  EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.96), 0u);
-  EXPECT_GT(histogram->GetPercentDroppedFramePercentile(0.97), 0u);
-}
-
-TEST_P(SmoothnessStrategyDroppedFrameCounterTest,
-       Percentile95WithIdleFramesWhileHidden) {
-  // The test scenario is the same as |Percentile95WithIdleFrames| test:
-  //  . 4s of 20% dropped frames.
-  //  . 96s of idle time.
-  // However, the 96s of idle time happens *after* the page becomes invisible
-  // (e.g. after a tab-switch). In this case, the idle time *should not*
-  // contribute to the sliding window.
-
-  // Set an interval that rounds up nicely with 1 second.
-  constexpr auto kInterval = base::Milliseconds(10);
-  constexpr int kFps = base::Seconds(1).IntDiv(kInterval);
-  static_assert(
-      kFps % 5 == 0,
-      "kFps must be a multiple of 5 because this test depends on it.");
-  SetInterval(kInterval);
-
-  const auto* histogram = GetSlidingWindowHistogram();
-
-  // First 4 seconds with 20% dropped frames.
-  SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 4);
-  EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
-
-  // Hide the page (thus resetting the pending frames), then idle for 96s before
-  // producing a single frame.
-  dropped_frame_counter_->ResetPendingFrames(GetNextFrameTime());
-  AdvancetimeByIntervals(kFps * 97);
-
-  // A single frame to flush the pipeline.
-  SimulateFrameSequence({false}, 1);
-
-  EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
-}
-
-TEST_P(SmoothnessStrategyDroppedFrameCounterTest,
-       Percentile95WithIdleFramesThenHide) {
-  // The test scenario is the same as |Percentile95WithIdleFramesWhileHidden|:
-  //  . 4s of 20% dropped frames.
-  //  . 96s of idle time.
-  // However, the 96s of idle time happens *before* the page becomes invisible
-  // (e.g. after a tab-switch). In this case, the idle time *should*
-  // contribute to the sliding window.
-
-  // Set an interval that rounds up nicely with 1 second.
-  constexpr auto kInterval = base::Milliseconds(10);
-  constexpr int kFps = base::Seconds(1).IntDiv(kInterval);
-  static_assert(
-      kFps % 5 == 0,
-      "kFps must be a multiple of 5 because this test depends on it.");
-  SetInterval(kInterval);
-
-  const auto* histogram = GetSlidingWindowHistogram();
-
-  // First 4 seconds with 20% dropped frames.
-  SimulateFrameSequence({false, false, false, false, true}, (kFps / 5) * 4);
-  EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.95), 20u);
-
-  // Idle for 96s before hiding the page.
-  AdvancetimeByIntervals(kFps * 97);
-  dropped_frame_counter_->ResetPendingFrames(GetNextFrameTime());
-  AdvancetimeByIntervals(kFps * 97);
-
-  // A single frame to flush the pipeline.
-  SimulateFrameSequence({false}, 1);
-
-  EXPECT_EQ(histogram->GetPercentDroppedFramePercentile(0.96), 0u);
-  EXPECT_GT(histogram->GetPercentDroppedFramePercentile(0.97), 0u);
-}
-
-TEST_F(DroppedFrameCounterTest, FramesInFlightWhenFcpReceived) {
-  // Start five frames in flight.
-  std::vector<viz::BeginFrameArgs> pending_frames = SimulatePendingFrame(5);
-
-  // Set that FCP was received after the third frame starts, but before it ends.
-  base::TimeTicks time_fcp_sent =
-      pending_frames[2].frame_time + pending_frames[2].interval / 2;
-  dropped_frame_counter_->SetTimeFirstContentfulPaintReceivedForTesting(
-      time_fcp_sent);
-
-  // End each of the frames as dropped. The first three should not count for
-  // smoothness, only the last two.
-  for (const auto& frame : pending_frames) {
-    frame_sorter_.AddFrameResult(frame, CreateStubFrameInfo(true));
-  }
-  EXPECT_EQ(dropped_frame_counter_->total_smoothness_dropped(), 2u);
-}
-
-TEST_F(DroppedFrameCounterTest, ForkedCompositorFrameReporter) {
-  // Run different combinations of main and impl threads dropping, make sure
-  // only one frame is counted as dropped each time.
-  SimulateForkedFrame(false, false);
-  EXPECT_EQ(dropped_frame_counter_->total_smoothness_dropped(), 0u);
-
-  SimulateForkedFrame(true, false);
-  EXPECT_EQ(dropped_frame_counter_->total_smoothness_dropped(), 1u);
-
-  SimulateForkedFrame(false, true);
-  EXPECT_EQ(dropped_frame_counter_->total_smoothness_dropped(), 2u);
-
-  SimulateForkedFrame(true, true);
-  EXPECT_EQ(dropped_frame_counter_->total_smoothness_dropped(), 3u);
-}
-
-class DroppedFrameCounterLegacyMetricsTest : public DroppedFrameCounterTest {
- public:
-  DroppedFrameCounterLegacyMetricsTest();
-  ~DroppedFrameCounterLegacyMetricsTest() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-DroppedFrameCounterLegacyMetricsTest::DroppedFrameCounterLegacyMetricsTest() {
-  frame_sorter_.RemoveObserver(dropped_frame_counter_.get());
-  dropped_frame_counter_ = std::make_unique<DroppedFrameCounter>();
-  frame_sorter_.Reset(/*reset_fcp=*/true);
-  dropped_frame_counter_->OnFirstContentfulPaintReceived();
-  frame_sorter_.OnFirstContentfulPaintReceived();
-  frame_sorter_.AddObserver(dropped_frame_counter_.get());
-}
-
-}  // namespace
-}  // namespace cc
diff --git a/cc/metrics/frame_sequence_tracker_collection.cc b/cc/metrics/frame_sequence_tracker_collection.cc
index ddd96ca..4a4b4a0 100644
--- a/cc/metrics/frame_sequence_tracker_collection.cc
+++ b/cc/metrics/frame_sequence_tracker_collection.cc
@@ -11,7 +11,6 @@
 #include "base/memory/ptr_util.h"
 #include "base/time/time.h"
 #include "cc/metrics/compositor_frame_reporting_controller.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/metrics/frame_info.h"
 #include "cc/metrics/frame_sequence_metrics.h"
 #include "cc/metrics/frame_sequence_tracker.h"
@@ -32,10 +31,8 @@
 }  // namespace
 
 FrameSequenceTrackerCollection::FrameSequenceTrackerCollection(
-    bool is_single_threaded,
-    DroppedFrameCounter* dropped_frame_counter)
-    : is_single_threaded_(is_single_threaded),
-      dropped_frame_counter_(dropped_frame_counter) {}
+    bool is_single_threaded)
+    : is_single_threaded_(is_single_threaded) {}
 
 FrameSequenceTrackerCollection::~FrameSequenceTrackerCollection() {
   CleanUp();
@@ -222,9 +219,6 @@
 
   auto tracker = std::move(frame_trackers_[key]);
   active_trackers_.reset(static_cast<size_t>(tracker->type()));
-  if (dropped_frame_counter_) {
-    dropped_frame_counter_->ReportFrames();
-  }
 
   if (tracker->metrics()->GetEffectiveThread() == ThreadType::kCompositor) {
     DCHECK_GT(compositor_thread_driving_smoothness_, 0u);
diff --git a/cc/metrics/frame_sequence_tracker_collection.h b/cc/metrics/frame_sequence_tracker_collection.h
index 29bf960..0dded01 100644
--- a/cc/metrics/frame_sequence_tracker_collection.h
+++ b/cc/metrics/frame_sequence_tracker_collection.h
@@ -14,7 +14,6 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "cc/cc_export.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/metrics/frame_info.h"
 #include "cc/metrics/frame_sequence_metrics.h"
 #include "cc/metrics/frame_sorter.h"
@@ -37,8 +36,7 @@
 // submitted frames.
 class CC_EXPORT FrameSequenceTrackerCollection : public FrameSorterObserver {
  public:
-  FrameSequenceTrackerCollection(bool is_single_threaded,
-                                 DroppedFrameCounter* dropped_frame_counter);
+  explicit FrameSequenceTrackerCollection(bool is_single_threaded);
   ~FrameSequenceTrackerCollection() override;
 
   FrameSequenceTrackerCollection(const FrameSequenceTrackerCollection&) =
@@ -157,7 +155,6 @@
   NotifyCustomerTrackerResutlsCallback custom_tracker_results_added_callback_;
 
   std::vector<std::unique_ptr<FrameSequenceTracker>> removal_trackers_;
-  const raw_ptr<DroppedFrameCounter> dropped_frame_counter_ = nullptr;
   ActiveTrackers active_trackers_;
   FrameInfo::SmoothEffectDrivingThread scrolling_thread_ =
       FrameInfo::SmoothEffectDrivingThread::kUnknown;
diff --git a/cc/metrics/frame_sequence_tracker_unittest.cc b/cc/metrics/frame_sequence_tracker_unittest.cc
index bc3fa3c..9f942166 100644
--- a/cc/metrics/frame_sequence_tracker_unittest.cc
+++ b/cc/metrics/frame_sequence_tracker_unittest.cc
@@ -42,30 +42,31 @@
 
 }  // namespace
 
-// Mock DroppedFrameCounter class in order to test the number of times that
+// Mock FrameSorter class in order to test the number of times that
 // frames get backfilled. This is necessary since `WillBeginImplFrame` creates
-// `CompositorFrameReporter`s for backfilled frames which submit to the DFC
+// `CompositorFrameReporter`s for backfilled frames which submit to FrameSorter
 // without a good interim spot to analyze the frame info contents.
-class DroppedFrameCounterMock : public DroppedFrameCounter {
+class FrameSorterMock : public FrameSorter {
  public:
-  MOCK_METHOD2(OnEndFrame, void(const viz::BeginFrameArgs&, const FrameInfo&));
+  MOCK_METHOD2(AddFrameResult,
+               void(const viz::BeginFrameArgs&, const FrameInfo&));
 };
 
-class FrameSequenceTrackerTest : public testing::Test, FrameSorterObserver {
+class FrameSequenceTrackerTest : public testing::Test,
+                                 public FrameSorterObserver {
  public:
   const uint32_t kImplDamage = 0x1;
   const uint32_t kMainDamage = 0x2;
 
   FrameSequenceTrackerTest()
-      : dfc_mock_(DroppedFrameCounterMock()),
-        collection_(/*is_single_threaded=*/false, &dfc_mock_),
+      : sorter_(FrameSorterMock()),
+        collection_(/*is_single_threaded=*/false),
         compositor_frame_reporting_controller_(
             std::make_unique<CompositorFrameReportingController>(
                 /*should_report_histograms=*/true,
                 /*should_report_ukm=*/false,
                 /*layer_tree_host_id=*/1)) {
     compositor_frame_reporting_controller_->SetFrameSorter(&sorter_);
-    compositor_frame_reporting_controller_->SetDroppedFrameCounter(&dfc_mock_);
     sorter_.AddObserver(this);
     tracker_ = collection_.StartScrollSequence(
         FrameSequenceTrackerType::kTouchScroll,
@@ -327,9 +328,8 @@
   }
 
  protected:
-  DroppedFrameCounterMock dfc_mock_;
+  FrameSorterMock sorter_;
   FrameSequenceTrackerCollection collection_;
-  FrameSorter sorter_;
   // Since CFRC destructor cleans up the FrameSorter's
   // registered observers (in this case, DFC and FSTC)
   // it needs to be declared last so that it will be
@@ -838,6 +838,12 @@
 
 TEST_F(FrameSequenceTrackerTest, CustomTrackerOutOfOrderFramesMissingV3Data) {
   CustomTrackerResults results;
+
+  // Override the FrameSorter mock
+  FrameSorter frame_sorter;
+  compositor_frame_reporting_controller_->SetFrameSorter(&frame_sorter);
+  frame_sorter.AddObserver(this);
+
   collection_.set_custom_tracker_results_added_callback(
       base::BindLambdaForTesting([&](const CustomTrackerResults& reported) {
         for (const auto& pair : reported) {
@@ -855,11 +861,11 @@
   // Dispatch 2 frames: frame 0 and frame 1.
   auto frame0_args = CreateBeginFrameArgs(source, ++sequence);
   DispatchCompleteFrame(frame0_args, kImplDamage | kMainDamage);
-  sorter_.AddNewFrame(frame0_args);
+  frame_sorter.AddNewFrame(frame0_args);
 
   auto frame1_args = CreateBeginFrameArgs(source, ++sequence);
   DispatchCompleteFrame(frame1_args, kImplDamage | kMainDamage);
-  sorter_.AddNewFrame(frame1_args);
+  frame_sorter.AddNewFrame(frame1_args);
 
   // Frame 1 gets its result before frame 0.
   FrameInfo frame_info;
@@ -867,20 +873,20 @@
   frame_info.smooth_thread = FrameInfo::SmoothThread::kSmoothMain;
   frame_info.scroll_thread = FrameInfo::SmoothEffectDrivingThread::kMain;
   frame_info.sequence_number = frame1_args.frame_id.sequence_number;
-  sorter_.AddFrameResult(frame1_args, frame_info);
+  frame_sorter.AddFrameResult(frame1_args, frame_info);
 
   // Stop the tracker.
   collection_.StopCustomSequence(1);
 
   // Frame 0 gets its result after tracker is stopped. FrameSorter flushes all
   // frames and metrics for both frames should be recorded for v3.
-  sorter_.AddFrameResult(frame0_args, frame_info);
+  frame_sorter.AddFrameResult(frame0_args, frame_info);
 
   // Frame 2 is dispatched after the tracker is stopped and should be ignored.
   auto frame2_args = CreateBeginFrameArgs(source, ++sequence);
   DispatchCompleteFrame(frame2_args, kImplDamage | kMainDamage);
-  sorter_.AddNewFrame(frame2_args);
-  sorter_.AddFrameResult(frame2_args, frame_info);
+  frame_sorter.AddNewFrame(frame2_args);
+  frame_sorter.AddFrameResult(frame2_args, frame_info);
 
   // The upcoming call to ClearAll will destroy tracker_.
   tracker_ = nullptr;
@@ -899,11 +905,10 @@
   uint64_t sequence = 0;
   const uint64_t kNumFramesSkipped = 5;
 
-  dfc_mock_.OnFirstContentfulPaintReceived();
   sorter_.OnFirstContentfulPaintReceived();
   // Expect that kNumFramesSkipped are backfilled with the appropriate smooth
   // thread set.
-  EXPECT_CALL(dfc_mock_, OnEndFrame(testing::_, testing::_))
+  EXPECT_CALL(sorter_, AddFrameResult(testing::_, testing::_))
       .Times(kNumFramesSkipped)
       .WillRepeatedly([=](const viz::BeginFrameArgs& args,
                           const FrameInfo& frame_info) {
@@ -925,7 +930,7 @@
                            base::TimeTicks::Now() /*+ base::Seconds(5)*/);
   compositor_frame_reporting_controller_->WillBeginImplFrame(frame5_args);
   // Clear the expectation before simulating finishing the frame.
-  testing::Mock::VerifyAndClearExpectations(&dfc_mock_);
+  testing::Mock::VerifyAndClearExpectations(&sorter_);
   compositor_frame_reporting_controller_->WillBeginMainFrame(frame5_args);
   compositor_frame_reporting_controller_->NotifyReadyToCommit(nullptr);
   compositor_frame_reporting_controller_->WillCommit();
diff --git a/cc/metrics/frame_sorter.h b/cc/metrics/frame_sorter.h
index 418d035b..2917d36 100644
--- a/cc/metrics/frame_sorter.h
+++ b/cc/metrics/frame_sorter.h
@@ -70,8 +70,8 @@
 
   // The results can be added in any order. However, the frame must have been
   // added by an earlier call to |AddNewFrame()|.
-  void AddFrameResult(const viz::BeginFrameArgs& args,
-                      const FrameInfo& frame_info);
+  virtual void AddFrameResult(const viz::BeginFrameArgs& args,
+                              const FrameInfo& frame_info);
 
   // Check if a frame has been previously reported as dropped.
   bool IsAlreadyReportedDropped(const viz::BeginFrameId& id) const;
diff --git a/cc/scheduler/scheduler_unittest.cc b/cc/scheduler/scheduler_unittest.cc
index 54e64b9b..d7fdd05 100644
--- a/cc/scheduler/scheduler_unittest.cc
+++ b/cc/scheduler/scheduler_unittest.cc
@@ -31,7 +31,6 @@
 #include "base/trace_event/trace_event.h"
 #include "cc/base/features.h"
 #include "cc/metrics/begin_main_frame_metrics.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/metrics/event_metrics.h"
 #include "cc/metrics/frame_sequence_tracker_collection.h"
 #include "cc/test/fake_compositor_frame_reporting_controller.h"
@@ -377,7 +376,7 @@
   SchedulerTest()
       : task_runner_(base::MakeRefCounted<SchedulerTestTaskRunner>()),
         fake_external_begin_frame_source_(nullptr),
-        tracker_collection_(false, &dropped_counter) {}
+        tracker_collection_(false) {}
 
   ~SchedulerTest() override { client_->set_scheduler(nullptr); }
 
@@ -417,7 +416,6 @@
     reporting_controller->SetFrameSorter(&frame_sorter);
     reporting_controller->SetFrameSequenceTrackerCollection(
         &tracker_collection_);
-    reporting_controller->SetDroppedFrameCounter(&dropped_counter);
 
     scheduler_ = std::make_unique<TestScheduler>(
         task_runner_->GetMockTickClock(), client_.get(), scheduler_settings_, 0,
@@ -619,7 +617,6 @@
   std::unique_ptr<FakeSchedulerClient> client_;
   std::unique_ptr<TestScheduler> scheduler_;
   raw_ptr<FakeCompositorTimingHistory> fake_compositor_timing_history_;
-  DroppedFrameCounter dropped_counter;
   FrameSequenceTrackerCollection tracker_collection_;
   FrameSorter frame_sorter;
   // Since CFRC destructor cleans up the FrameSorter's
diff --git a/cc/test/fake_proxy.cc b/cc/test/fake_proxy.cc
index 60833d2..269feac 100644
--- a/cc/test/fake_proxy.cc
+++ b/cc/test/fake_proxy.cc
@@ -49,7 +49,7 @@
   return false;
 }
 
-double FakeProxy::GetPercentDroppedFrames() const {
+double FakeProxy::GetAverageThroughput() const {
   return 0.0;
 }
 
diff --git a/cc/test/fake_proxy.h b/cc/test/fake_proxy.h
index 7e66eea..fe09661 100644
--- a/cc/test/fake_proxy.h
+++ b/cc/test/fake_proxy.h
@@ -73,7 +73,7 @@
   void CompositeImmediatelyForTest(base::TimeTicks frame_begin_time,
                                    bool raster,
                                    base::OnceClosure callback) override {}
-  double GetPercentDroppedFrames() const override;
+  double GetAverageThroughput() const override;
   void SetPauseRendering(bool pause_rendering) override {}
   void SetInputResponsePending() override {}
   bool IsRenderingPaused() const override;
diff --git a/cc/test/fake_tile_manager_client.cc b/cc/test/fake_tile_manager_client.cc
index 2ffdc99..324763f 100644
--- a/cc/test/fake_tile_manager_client.cc
+++ b/cc/test/fake_tile_manager_client.cc
@@ -19,7 +19,7 @@
 }
 
 std::unique_ptr<EvictionTilePriorityQueue>
-FakeTileManagerClient::BuildEvictionQueue(TreePriority tree_priority) {
+FakeTileManagerClient::BuildEvictionQueue() {
   return nullptr;
 }
 
diff --git a/cc/test/fake_tile_manager_client.h b/cc/test/fake_tile_manager_client.h
index dabd2da..49ce9a8 100644
--- a/cc/test/fake_tile_manager_client.h
+++ b/cc/test/fake_tile_manager_client.h
@@ -25,8 +25,7 @@
   std::unique_ptr<RasterTilePriorityQueue> BuildRasterQueue(
       TreePriority tree_priority,
       RasterTilePriorityQueue::Type type) override;
-  std::unique_ptr<EvictionTilePriorityQueue> BuildEvictionQueue(
-      TreePriority tree_priority) override;
+  std::unique_ptr<EvictionTilePriorityQueue> BuildEvictionQueue() override;
   std::unique_ptr<TilesWithResourceIterator> CreateTilesWithResourceIterator()
       override;
   void SetIsLikelyToRequireADraw(bool is_likely_to_require_a_draw) override {}
diff --git a/cc/test/layer_tree_test.cc b/cc/test/layer_tree_test.cc
index 15de1aa..9b7772d 100644
--- a/cc/test/layer_tree_test.cc
+++ b/cc/test/layer_tree_test.cc
@@ -224,12 +224,10 @@
         this, reason, scroll_and_viewport_changes_synced);
   }
 
-  void ReadyToCommit(const viz::BeginFrameArgs& commit_args,
-                     bool scroll_and_viewport_changes_synced,
+  void ReadyToCommit(bool scroll_and_viewport_changes_synced,
                      const BeginMainFrameMetrics* begin_main_frame_metrics,
                      bool commit_timeout) override {
-    LayerTreeHostImpl::ReadyToCommit(commit_args,
-                                     scroll_and_viewport_changes_synced,
+    LayerTreeHostImpl::ReadyToCommit(scroll_and_viewport_changes_synced,
                                      begin_main_frame_metrics, commit_timeout);
     test_hooks_->ReadyToCommitOnThread(this);
   }
diff --git a/cc/test/scheduler_test_common.h b/cc/test/scheduler_test_common.h
index 0164dd8..477160d 100644
--- a/cc/test/scheduler_test_common.h
+++ b/cc/test/scheduler_test_common.h
@@ -14,7 +14,6 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "cc/metrics/compositor_timing_history.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/scheduler/scheduler.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
diff --git a/cc/tiles/raster_tile_priority_queue_all.cc b/cc/tiles/raster_tile_priority_queue_all.cc
index c4103d3..9f18145f 100644
--- a/cc/tiles/raster_tile_priority_queue_all.cc
+++ b/cc/tiles/raster_tile_priority_queue_all.cc
@@ -49,7 +49,6 @@
 
 void CreateTilingSetRasterQueues(
     const std::vector<raw_ptr<PictureLayerImpl, VectorExperimental>>& layers,
-    TreePriority tree_priority,
     std::vector<std::unique_ptr<TilingSetRasterQueueAll>>* queues) {
   DCHECK(queues->empty());
 
@@ -62,11 +61,9 @@
     if (cc_slimming_enabled && tiling_set->all_tiles_done()) {
       continue;
     }
-    bool prioritize_low_res = tree_priority == SMOOTHNESS_TAKES_PRIORITY;
     std::unique_ptr<TilingSetRasterQueueAll> tiling_set_queue =
         TilingSetRasterQueueAll::Create(
-            tiling_set, prioritize_low_res,
-            layer->contributes_to_drawn_render_surface());
+            tiling_set, layer->contributes_to_drawn_render_surface());
     // Queues will only contain non empty tiling sets.
     if (tiling_set_queue && !tiling_set_queue->IsEmpty()) {
       queues->push_back(std::move(tiling_set_queue));
@@ -88,8 +85,8 @@
     TreePriority tree_priority) {
   tree_priority_ = tree_priority;
 
-  CreateTilingSetRasterQueues(active_layers, tree_priority_, &active_queues_);
-  CreateTilingSetRasterQueues(pending_layers, tree_priority_, &pending_queues_);
+  CreateTilingSetRasterQueues(active_layers, &active_queues_);
+  CreateTilingSetRasterQueues(pending_layers, &pending_queues_);
 }
 
 bool RasterTilePriorityQueueAll::IsEmpty() const {
diff --git a/cc/tiles/tile_manager.cc b/cc/tiles/tile_manager.cc
index 1468a56..d29b6c4 100644
--- a/cc/tiles/tile_manager.cc
+++ b/cc/tiles/tile_manager.cc
@@ -502,7 +502,7 @@
   has_pending_tile_trimming_task_ = false;
 
   std::unique_ptr<EvictionTilePriorityQueue> eviction_priority_queue =
-      client_->BuildEvictionQueue(global_state_.tree_priority);
+      client_->BuildEvictionQueue();
   bool has_eligible_used_tiles = false;
   for (; !eviction_priority_queue->IsEmpty(); eviction_priority_queue->Pop()) {
     const auto& prioritized_tile = eviction_priority_queue->Top();
@@ -811,8 +811,7 @@
     MemoryUsage* usage) {
   while (usage->Exceeds(limit)) {
     if (!eviction_priority_queue) {
-      eviction_priority_queue =
-          client_->BuildEvictionQueue(global_state_.tree_priority);
+      eviction_priority_queue = client_->BuildEvictionQueue();
     }
     if (eviction_priority_queue->IsEmpty())
       break;
@@ -833,8 +832,7 @@
     MemoryUsage* usage) {
   while (usage->Exceeds(limit)) {
     if (!eviction_priority_queue) {
-      eviction_priority_queue =
-          client_->BuildEvictionQueue(global_state_.tree_priority);
+      eviction_priority_queue = client_->BuildEvictionQueue();
     }
     if (eviction_priority_queue->IsEmpty())
       break;
@@ -2209,7 +2207,7 @@
                   global_state_.num_resources_limit);
 
   std::unique_ptr<EvictionTilePriorityQueue> eviction_priority_queue(
-      client_->BuildEvictionQueue(global_state_.tree_priority));
+      client_->BuildEvictionQueue());
   std::set<Tile*> tiles_to_evict;
   while (!eviction_priority_queue->IsEmpty()) {
     const PrioritizedTile& tile = eviction_priority_queue->Top();
diff --git a/cc/tiles/tile_manager_client.h b/cc/tiles/tile_manager_client.h
index f7945557..48619ce 100644
--- a/cc/tiles/tile_manager_client.h
+++ b/cc/tiles/tile_manager_client.h
@@ -54,8 +54,7 @@
   // Given an empty eviction tile priority queue, this will build a priority
   // queue that will return tiles in the order in which they should be evicted.
   // Note if the queue was previously built, Reset must be called on it.
-  virtual std::unique_ptr<EvictionTilePriorityQueue> BuildEvictionQueue(
-      TreePriority tree_priority) = 0;
+  virtual std::unique_ptr<EvictionTilePriorityQueue> BuildEvictionQueue() = 0;
 
   // Returns an iterator over all the tiles that have a resource.
   virtual std::unique_ptr<TilesWithResourceIterator>
diff --git a/cc/tiles/tile_manager_perftest.cc b/cc/tiles/tile_manager_perftest.cc
index c961c6bc..36713e52 100644
--- a/cc/tiles/tile_manager_perftest.cc
+++ b/cc/tiles/tile_manager_perftest.cc
@@ -140,13 +140,6 @@
 
   void RunEvictionQueueConstructTest(const std::string& test_name,
                                      int layer_count) {
-    auto priorities = std::to_array<TreePriority>({
-        SAME_PRIORITY_FOR_BOTH_TREES,
-        SMOOTHNESS_TAKES_PRIORITY,
-        NEW_CONTENT_TAKES_PRIORITY,
-    });
-    int priority_count = 0;
-
     std::vector<FakePictureLayerImpl*> layers = CreateLayers(layer_count, 10);
     for (auto* layer : layers) {
       layer->UpdateTiles();
@@ -159,8 +152,7 @@
     timer_.Reset();
     do {
       std::unique_ptr<EvictionTilePriorityQueue> queue(
-          host_impl()->BuildEvictionQueue(priorities[priority_count]));
-      priority_count = (priority_count + 1) % std::size(priorities);
+          host_impl()->BuildEvictionQueue());
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
@@ -172,13 +164,6 @@
   void RunEvictionQueueConstructAndIterateTest(const std::string& test_name,
                                                int layer_count,
                                                int tile_count) {
-    auto priorities = std::to_array<TreePriority>({
-        SAME_PRIORITY_FOR_BOTH_TREES,
-        SMOOTHNESS_TAKES_PRIORITY,
-        NEW_CONTENT_TAKES_PRIORITY,
-    });
-    int priority_count = 0;
-
     std::vector<FakePictureLayerImpl*> layers =
         CreateLayers(layer_count, tile_count);
     for (auto* layer : layers) {
@@ -193,13 +178,12 @@
     do {
       int count = tile_count;
       std::unique_ptr<EvictionTilePriorityQueue> queue(
-          host_impl()->BuildEvictionQueue(priorities[priority_count]));
+          host_impl()->BuildEvictionQueue());
       while (count--) {
         ASSERT_FALSE(queue->IsEmpty());
         ASSERT_TRUE(queue->Top().tile());
         queue->Pop();
       }
-      priority_count = (priority_count + 1) % std::size(priorities);
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
diff --git a/cc/tiles/tile_manager_unittest.cc b/cc/tiles/tile_manager_unittest.cc
index c46677ea..6cd7045b 100644
--- a/cc/tiles/tile_manager_unittest.cc
+++ b/cc/tiles/tile_manager_unittest.cc
@@ -741,7 +741,7 @@
   ASSERT_TRUE(pending_layer()->HighResTiling());
 
   std::unique_ptr<EvictionTilePriorityQueue> empty_queue(
-      host_impl()->BuildEvictionQueue(SAME_PRIORITY_FOR_BOTH_TREES));
+      host_impl()->BuildEvictionQueue());
   EXPECT_TRUE(empty_queue->IsEmpty());
   std::set<Tile*> all_tiles;
   size_t tile_count = 0;
@@ -763,7 +763,7 @@
       std::vector<Tile*>(all_tiles.begin(), all_tiles.end()));
 
   std::unique_ptr<EvictionTilePriorityQueue> queue(
-      host_impl()->BuildEvictionQueue(SMOOTHNESS_TAKES_PRIORITY));
+      host_impl()->BuildEvictionQueue());
   EXPECT_FALSE(queue->IsEmpty());
 
   // Sanity check, all tiles should be visible.
@@ -814,7 +814,7 @@
   smoothness_tiles.clear();
   tile_count = 0;
   // Here we expect to get increasing combined priority_bin.
-  queue = host_impl()->BuildEvictionQueue(SMOOTHNESS_TAKES_PRIORITY);
+  queue = host_impl()->BuildEvictionQueue();
   int distance_increasing = 0;
   int distance_decreasing = 0;
   while (!queue->IsEmpty()) {
@@ -857,7 +857,7 @@
   std::set<Tile*> new_content_tiles;
   last_tile = PrioritizedTile();
   // Again, we expect to get increasing combined priority_bin.
-  queue = host_impl()->BuildEvictionQueue(NEW_CONTENT_TAKES_PRIORITY);
+  queue = host_impl()->BuildEvictionQueue();
   distance_decreasing = 0;
   distance_increasing = 0;
   while (!queue->IsEmpty()) {
@@ -1013,11 +1013,10 @@
       std::vector<Tile*>(all_tiles.begin(), all_tiles.end()));
 
   // Verify occlusion is considered by EvictionTilePriorityQueue.
-  TreePriority tree_priority = NEW_CONTENT_TAKES_PRIORITY;
   size_t occluded_count = 0u;
   PrioritizedTile last_tile;
   std::unique_ptr<EvictionTilePriorityQueue> queue(
-      host_impl()->BuildEvictionQueue(tree_priority));
+      host_impl()->BuildEvictionQueue());
   while (!queue->IsEmpty()) {
     PrioritizedTile prioritized_tile = queue->Top();
     if (!last_tile.tile())
@@ -1118,11 +1117,10 @@
   // Verify that eviction queue returns tiles also from layers without valid
   // tile priorities and that the tile priority bin of those tiles is (at most)
   // EVENTUALLY.
-  TreePriority tree_priority = NEW_CONTENT_TAKES_PRIORITY;
   std::set<Tile*> new_content_tiles;
   size_t tile_count = 0;
   std::unique_ptr<EvictionTilePriorityQueue> queue(
-      host_impl()->BuildEvictionQueue(tree_priority));
+      host_impl()->BuildEvictionQueue());
   while (!queue->IsEmpty()) {
     PrioritizedTile prioritized_tile = queue->Top();
     Tile* tile = prioritized_tile.tile();
@@ -1220,7 +1218,7 @@
   }
 
   std::unique_ptr<EvictionTilePriorityQueue> queue(
-      host_impl()->BuildEvictionQueue(SAME_PRIORITY_FOR_BOTH_TREES));
+      host_impl()->BuildEvictionQueue());
   EXPECT_FALSE(queue->IsEmpty());
 
   tile_count = 0;
@@ -1276,7 +1274,7 @@
   //    marked as ready to draw.
   for (int i = 0; i < 3; ++i) {
     std::unique_ptr<TilingSetRasterQueueAll> queue =
-        TilingSetRasterQueueAll::Create(tiling_set.get(), false, false);
+        TilingSetRasterQueueAll::Create(tiling_set.get(), false);
     EXPECT_TRUE(queue);
 
     // There are 3 bins in TilePriority.
@@ -1389,7 +1387,7 @@
   int eventually_bin_order_correct_count = 0;
   int eventually_bin_order_incorrect_count = 0;
   std::unique_ptr<TilingSetRasterQueueAll> queue =
-      TilingSetRasterQueueAll::Create(tiling_set.get(), false, false);
+      TilingSetRasterQueueAll::Create(tiling_set.get(), false);
   EXPECT_TRUE(queue);
   for (; !queue->IsEmpty(); queue->Pop()) {
     if (!last_tile.tile())
@@ -1551,7 +1549,7 @@
         intersecting_rect,      // Soon rect.
         intersecting_rect);     // Eventually rect.
     std::unique_ptr<TilingSetRasterQueueAll> queue =
-        TilingSetRasterQueueAll::Create(tiling_set.get(), false, false);
+        TilingSetRasterQueueAll::Create(tiling_set.get(), false);
     if (features::IsCCSlimmingEnabled()) {
       EXPECT_FALSE(queue);
     } else {
@@ -1572,7 +1570,7 @@
         intersecting_rect,      // Soon rect.
         intersecting_rect);     // Eventually rect.
     std::unique_ptr<TilingSetRasterQueueAll> queue =
-        TilingSetRasterQueueAll::Create(tiling_set.get(), false, false);
+        TilingSetRasterQueueAll::Create(tiling_set.get(), false);
     EXPECT_TRUE(queue);
     EXPECT_FALSE(queue->IsEmpty());
   }
@@ -1584,7 +1582,7 @@
         intersecting_rect,      // Soon rect.
         intersecting_rect);     // Eventually rect.
     std::unique_ptr<TilingSetRasterQueueAll> queue =
-        TilingSetRasterQueueAll::Create(tiling_set.get(), false, false);
+        TilingSetRasterQueueAll::Create(tiling_set.get(), false);
     EXPECT_TRUE(queue);
     EXPECT_FALSE(queue->IsEmpty());
   }
@@ -1595,7 +1593,7 @@
         non_intersecting_rect,  // Soon rect.
         intersecting_rect);     // Eventually rect.
     std::unique_ptr<TilingSetRasterQueueAll> queue =
-        TilingSetRasterQueueAll::Create(tiling_set.get(), false, false);
+        TilingSetRasterQueueAll::Create(tiling_set.get(), false);
     EXPECT_TRUE(queue);
     EXPECT_FALSE(queue->IsEmpty());
   }
@@ -4036,8 +4034,7 @@
   host_impl()->active_tree()->SetDeviceViewportRect(viewport);
   MakeFrame(global_tile_state);
 
-  auto eviction_queue = host_impl()->BuildEvictionQueue(
-      host_impl()->global_tile_state().tree_priority);
+  auto eviction_queue = host_impl()->BuildEvictionQueue();
   bool has_eventually_tiles = false;
   size_t tiles_count_before = 0;
   for (; !eviction_queue->IsEmpty(); eviction_queue->Pop()) {
@@ -4054,8 +4051,7 @@
   task_runner()->FastForwardBy(TileManager::GetTrimPrepaintTilesDelay());
 
   // Since the policy is still ALLOW_ANYTHING, no changes.
-  eviction_queue = host_impl()->BuildEvictionQueue(
-      host_impl()->global_tile_state().tree_priority);
+  eviction_queue = host_impl()->BuildEvictionQueue();
   size_t tiles_count_after = 0;
   for (; !eviction_queue->IsEmpty(); eviction_queue->Pop()) {
     const auto& tile = eviction_queue->Top();
@@ -4080,8 +4076,7 @@
   task_runner()->FastForwardBy(TileManager::GetTrimPrepaintTilesDelay());
 
   tiles_count_after = 0;
-  eviction_queue = host_impl()->BuildEvictionQueue(
-      host_impl()->global_tile_state().tree_priority);
+  eviction_queue = host_impl()->BuildEvictionQueue();
   for (; !eviction_queue->IsEmpty(); eviction_queue->Pop()) {
     const auto& tile = eviction_queue->Top();
     if (tile.priority().priority_bin == TilePriority::EVENTUALLY) {
@@ -4099,8 +4094,7 @@
 
   // All the EVENTUALLY tiles are gone.
   tiles_count_after = 0;
-  eviction_queue = host_impl()->BuildEvictionQueue(
-      host_impl()->global_tile_state().tree_priority);
+  eviction_queue = host_impl()->BuildEvictionQueue();
   for (; !eviction_queue->IsEmpty(); eviction_queue->Pop()) {
     const auto& tile = eviction_queue->Top();
     EXPECT_NE(tile.priority().priority_bin, TilePriority::EVENTUALLY);
@@ -4124,8 +4118,7 @@
   host_impl()->active_tree()->SetDeviceViewportRect(viewport);
   MakeFrame(global_tile_state);
 
-  auto eviction_queue = host_impl()->BuildEvictionQueue(
-      host_impl()->global_tile_state().tree_priority);
+  auto eviction_queue = host_impl()->BuildEvictionQueue();
   bool has_eventually_tiles = false;
   size_t tiles_count_before = 0;
   for (; !eviction_queue->IsEmpty(); eviction_queue->Pop()) {
@@ -4149,8 +4142,7 @@
 
   size_t tiles_count_after = 0;
   Tile* first_eventually_tile = nullptr;
-  eviction_queue = host_impl()->BuildEvictionQueue(
-      host_impl()->global_tile_state().tree_priority);
+  eviction_queue = host_impl()->BuildEvictionQueue();
   for (; !eviction_queue->IsEmpty(); eviction_queue->Pop()) {
     const auto& tile = eviction_queue->Top();
     if (tile.priority().priority_bin == TilePriority::EVENTUALLY) {
@@ -4175,8 +4167,7 @@
   // The tile is there, it's the only remaining EVENTUALLY one.
   tiles_count_after = 0;
   bool has_found_tile = false;
-  eviction_queue = host_impl()->BuildEvictionQueue(
-      host_impl()->global_tile_state().tree_priority);
+  eviction_queue = host_impl()->BuildEvictionQueue();
   for (; !eviction_queue->IsEmpty(); eviction_queue->Pop()) {
     const auto& tile = eviction_queue->Top();
     EXPECT_TRUE(tile.priority().priority_bin != TilePriority::EVENTUALLY ||
@@ -4193,8 +4184,7 @@
   task_runner()->FastForwardBy(TileManager::GetTrimPrepaintTilesDelay());
 
   // The tile is not there anymore.
-  eviction_queue = host_impl()->BuildEvictionQueue(
-      host_impl()->global_tile_state().tree_priority);
+  eviction_queue = host_impl()->BuildEvictionQueue();
   for (; !eviction_queue->IsEmpty(); eviction_queue->Pop()) {
     const auto& tile = eviction_queue->Top();
     EXPECT_NE(tile.tile(), first_eventually_tile);
diff --git a/cc/tiles/tiling_set_raster_queue_all.cc b/cc/tiles/tiling_set_raster_queue_all.cc
index 1602cae..3dc92543 100644
--- a/cc/tiles/tiling_set_raster_queue_all.cc
+++ b/cc/tiles/tiling_set_raster_queue_all.cc
@@ -25,7 +25,6 @@
 // static
 std::unique_ptr<TilingSetRasterQueueAll> TilingSetRasterQueueAll::Create(
     PictureLayerTilingSet* tiling_set,
-    bool prioritize_low_res,
     bool is_drawing_layer) {
   DCHECK(tiling_set);
 
diff --git a/cc/tiles/tiling_set_raster_queue_all.h b/cc/tiles/tiling_set_raster_queue_all.h
index aac0ef0..9c6c943 100644
--- a/cc/tiles/tiling_set_raster_queue_all.h
+++ b/cc/tiles/tiling_set_raster_queue_all.h
@@ -28,7 +28,6 @@
  public:
   static std::unique_ptr<TilingSetRasterQueueAll> Create(
       PictureLayerTilingSet* tiling_set,
-      bool prioritize_low_res,
       bool is_drawing_layer);
 
   TilingSetRasterQueueAll(const TilingSetRasterQueueAll&) = delete;
diff --git a/cc/trees/layer_tree_host.cc b/cc/trees/layer_tree_host.cc
index d02c680..eec4a03 100644
--- a/cc/trees/layer_tree_host.cc
+++ b/cc/trees/layer_tree_host.cc
@@ -2041,9 +2041,9 @@
   return result;
 }
 
-double LayerTreeHost::GetPercentDroppedFrames() const {
+double LayerTreeHost::GetAverageThroughput() const {
   DCHECK(IsMainThread());
-  return proxy_->GetPercentDroppedFrames();
+  return proxy_->GetAverageThroughput();
 }
 
 void LayerTreeHost::DropActiveScrollDeltaNextCommit(ElementId scroll_element) {
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 6c04fb4..91ebd9b 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -941,8 +941,8 @@
   std::vector<ViewTransitionRequest::ViewTransitionCaptureCallback>
   TakeViewTransitionCallbacksForTesting();
 
-  // Returns a percentage of dropped frames of the last second.
-  double GetPercentDroppedFrames() const;
+  // Returns a percentage of dropped frames as measured by the FrameSorter.
+  double GetAverageThroughput() const;
 
   // TODO(szager): Remove these once threaded compositing is enabled for all
   // web_tests.
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 66acd867..7255cd42 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -26,7 +26,6 @@
 #include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
-#include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ptr_exclusion.h"
@@ -466,8 +465,7 @@
                   .single_thread_proxy_scheduler,
               /*should_report_ukm=*/!settings.single_thread_proxy_scheduler,
               id)),
-      frame_trackers_(settings.single_thread_proxy_scheduler,
-                      &dropped_frame_counter_),
+      frame_trackers_(settings.single_thread_proxy_scheduler),
       lcd_text_metrics_reporter_(LCDTextMetricsReporter::CreateIfNeeded(this)),
       has_input_resetter_(
           GetTaskRunner(),
@@ -513,8 +511,6 @@
 
   SetDebugState(settings.initial_debug_state);
   compositor_frame_reporting_controller_->SetFrameSorter(&frame_sorter_);
-  compositor_frame_reporting_controller_->SetDroppedFrameCounter(
-      &dropped_frame_counter_);
   compositor_frame_reporting_controller_->SetFrameSequenceTrackerCollection(
       &frame_trackers_);
 
@@ -523,7 +519,6 @@
     compositor_frame_reporting_controller_->set_event_latency_tracker(this);
 
 #if BUILDFLAG(IS_CHROMEOS)
-    dropped_frame_counter_.EnableReportForUI();
     frame_sorter_.EnableReportForUI();
     frame_trackers_.StartSequence(
         FrameSequenceTrackerType::kCompositorAnimation);
@@ -582,7 +577,6 @@
   // CFRC needs to unregister the frame trackers from the frame_sorter observer
   // set before being cleaned up.
   compositor_frame_reporting_controller_->ClearFrameSequenceTrackerCollection();
-  compositor_frame_reporting_controller_->ClearDroppedFrameCounter();
 }
 
 InputHandler& LayerTreeHostImpl::GetInputHandler() {
@@ -630,17 +624,14 @@
 }
 
 void LayerTreeHostImpl::ReadyToCommit(
-    const viz::BeginFrameArgs& commit_args,
     bool scroll_and_viewport_changes_synced,
     const BeginMainFrameMetrics* begin_main_frame_metrics,
     bool commit_timeout) {
-  if (!is_measuring_smoothness_ &&
-      ((begin_main_frame_metrics &&
+  if (((begin_main_frame_metrics &&
         begin_main_frame_metrics->should_measure_smoothness) ||
-       commit_timeout)) {
-    is_measuring_smoothness_ = true;
+       commit_timeout) &&
+      !frame_sorter_.first_contentful_paint_received()) {
     frame_sorter_.OnFirstContentfulPaintReceived();
-    dropped_frame_counter()->OnFirstContentfulPaintReceived();
   }
 
   // Notify the browser controls manager that we have processed any
@@ -1969,7 +1960,7 @@
 }
 
 std::unique_ptr<EvictionTilePriorityQueue>
-LayerTreeHostImpl::BuildEvictionQueue(TreePriority tree_priority) {
+LayerTreeHostImpl::BuildEvictionQueue() {
   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
                "LayerTreeHostImpl::BuildEvictionQueue");
 
@@ -3734,7 +3725,6 @@
   client_->DidLoseLayerTreeFrameSinkOnImplThread();
   lag_tracking_manager_.Clear();
   frame_sorter_.Reset(/*reset_fcp=*/false);
-  dropped_frame_counter_.ResetPendingFrames(base::TimeTicks::Now());
 }
 
 bool LayerTreeHostImpl::OnlyExpandTopControlsAtPageTop() const {
@@ -4036,9 +4026,7 @@
   }
 
   if (!visible_) {
-    auto now = base::TimeTicks::Now();
     frame_sorter_.Reset(/*reset_fcp=*/false);
-    dropped_frame_counter_.ResetPendingFrames(now);
 
     // When page is invisible, throw away corresponding EventsMetrics since
     // these metrics will be incorrect due to duration of page being invisible.
@@ -5970,14 +5958,10 @@
   // The source id has already been associated to the URL.
   compositor_frame_reporting_controller_->SetSourceId(source_id);
   frame_sorter_.Reset(/*reset_fcp=*/true);
-  dropped_frame_counter_.Reset();
-  is_measuring_smoothness_ = false;
 }
 
 void LayerTreeHostImpl::SetUkmSmoothnessDestination(
     base::WritableSharedMemoryMapping ukm_smoothness_data) {
-  dropped_frame_counter_.SetUkmSmoothnessDestination(
-      ukm_smoothness_data.GetMemoryAs<UkmSmoothnessDataShared>());
   ukm_smoothness_mapping_ = std::move(ukm_smoothness_data);
 }
 
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index e06bbfc1..086c5e4f 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -38,7 +38,6 @@
 #include "cc/input/scrollbar_animation_controller.h"
 #include "cc/layers/layer_collections.h"
 #include "cc/metrics/average_lag_tracking_manager.h"
-#include "cc/metrics/dropped_frame_counter.h"
 #include "cc/metrics/event_latency_tracker.h"
 #include "cc/metrics/event_metrics.h"
 #include "cc/metrics/events_metrics_manager.h"
@@ -281,7 +280,6 @@
       bool next_bmf,
       bool scroll_and_viewport_changes_synced);
   virtual void ReadyToCommit(
-      const viz::BeginFrameArgs& commit_args,
       bool scroll_and_viewport_changes_synced,
       const BeginMainFrameMetrics* begin_main_frame_metrics,
       bool commit_timeout);
@@ -516,8 +514,7 @@
   std::unique_ptr<RasterTilePriorityQueue> BuildRasterQueue(
       TreePriority tree_priority,
       RasterTilePriorityQueue::Type type) override;
-  std::unique_ptr<EvictionTilePriorityQueue> BuildEvictionQueue(
-      TreePriority tree_priority) override;
+  std::unique_ptr<EvictionTilePriorityQueue> BuildEvictionQueue() override;
   void SetIsLikelyToRequireADraw(bool is_likely_to_require_a_draw) override;
   std::unique_ptr<TilesWithResourceIterator> CreateTilesWithResourceIterator()
       override;
@@ -693,9 +690,6 @@
   std::unique_ptr<CompositorCommitData> ProcessCompositorDeltas(
       const MutatorHost* main_thread_mutator_host);
 
-  DroppedFrameCounter* dropped_frame_counter() {
-    return &dropped_frame_counter_;
-  }
   FrameSorter* frame_sorter() { return &frame_sorter_; }
   MemoryHistory* memory_history() { return memory_history_.get(); }
   DebugRectHistory* debug_rect_history() { return debug_rect_history_.get(); }
@@ -874,9 +868,6 @@
 
   Viewport& viewport() const { return *viewport_.get(); }
 
-  DroppedFrameCounter* dropped_frame_counter_for_testing() {
-    return &dropped_frame_counter_;
-  }
   FrameSorter* frame_sorter_for_testing() { return &frame_sorter_; }
 
   // Returns true if the client is currently compositing synchronously.
@@ -1158,10 +1149,6 @@
   base::WritableSharedMemoryMapping ukm_smoothness_mapping_;
   base::WritableSharedMemoryMapping ukm_dropped_frames_mapping_;
 
-  // `dropped_frame_counter_` holds a pointer `to ukm_smoothness_mapping_` so
-  // it must be declared last and deleted first;
-  DroppedFrameCounter dropped_frame_counter_;
-
   std::unique_ptr<MemoryHistory> memory_history_;
   std::unique_ptr<DebugRectHistory> debug_rect_history_;
 
@@ -1276,8 +1263,8 @@
 
   PresentationTimeCallbackBuffer presentation_time_callbacks_;
 
-  // `compositor_frame_reporting_controller_` has a dependency on
-  // `dropped_frame_counter_` so it must be declared last and deleted first.
+  // `compositor_frame_reporting_controller_` is an observer of
+  // `frame_sorter_` so it must be declared last and deleted first.
   FrameSorter frame_sorter_;
   std::unique_ptr<CompositorFrameReportingController>
       compositor_frame_reporting_controller_;
@@ -1321,10 +1308,6 @@
   bool has_non_fling_input_since_last_frame_ = false;
   bool has_observed_first_scroll_delay_ = false;
 
-  // True if we are measuring smoothness in DroppedFrameCounter.
-  // Currently true when first contentful paint is done.
-  bool is_measuring_smoothness_ = false;
-
   // Cache for the results of calls to gfx::ColorSpace::Contains() on sRGB. This
   // computation is deterministic for a given color space, can be called
   // multiple times per frame, and incurs a non-trivial cost.
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 7e45112..2f4e0d1 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -14446,8 +14446,6 @@
 
 // Test that TotalFrameCounter resets itself under certain conditions
 TEST_P(LayerTreeHostImplTest, FrameCounterReset) {
-  DroppedFrameCounter* dropped_frame_counter =
-      host_impl_->dropped_frame_counter_for_testing();
   FrameSorter* frame_sorter = host_impl_->frame_sorter_for_testing();
   EXPECT_EQ(frame_sorter->total_frames(), 0u);
   FrameInfo frame_info;
@@ -14467,23 +14465,20 @@
   frame_sorter->AddFrameResult(
       args, CreateFakeFrameInfo(FrameInfo::FrameFinalState::kDropped));
   // FCP not received, so the total_smoothness_dropped_ won't increase.
-  EXPECT_EQ(dropped_frame_counter->total_smoothness_dropped(), 0u);
+  EXPECT_EQ(frame_sorter->total_dropped(), 0u);
 
   BeginMainFrameMetrics begin_frame_metrics;
   begin_frame_metrics.should_measure_smoothness = true;
-  host_impl_->ReadyToCommit(args, /*scroll_and_viewport_changes_synced=*/true,
+  host_impl_->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true,
                             &begin_frame_metrics, /*commit_timeout=*/false);
-  dropped_frame_counter->SetTimeFirstContentfulPaintReceivedForTesting(
-      args.frame_time);
   frame_sorter->AddNewFrame(args);
   // Delegates to DFC::AddSortedFrame, which calls DFC::OnEndFrame.
   frame_sorter->AddFrameResult(
       args, CreateFakeFrameInfo(FrameInfo::FrameFinalState::kDropped));
-  EXPECT_EQ(dropped_frame_counter->total_smoothness_dropped(), 1u);
   frame_sorter->AddFrameInfoToBuffer(frame_info);
   host_impl_->SetActiveURL(GURL(), 1u);
   EXPECT_EQ(frame_sorter->total_frames(), 0u);
-  EXPECT_EQ(dropped_frame_counter->total_dropped(), 0u);
+  EXPECT_EQ(frame_sorter->total_dropped(), 0u);
 }
 
 // Test that TotalFrameCounter does not reset itself under certain conditions
@@ -14499,7 +14494,7 @@
       deadline, interval, viz::BeginFrameArgs::NORMAL);
   BeginMainFrameMetrics begin_frame_metrics;
   begin_frame_metrics.should_measure_smoothness = true;
-  host_impl_->ReadyToCommit(arg1, /*scroll_and_viewport_changes_synced=*/true,
+  host_impl_->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true,
                             &begin_frame_metrics, /*commit_timeout=*/false);
   EXPECT_EQ(frame_sorter->total_frames(), 0u);
   FrameInfo frame_info;
@@ -14514,7 +14509,7 @@
       deadline, interval, viz::BeginFrameArgs::NORMAL);
   // Consecutive BeginFrameMetrics with the same |should_measure_smoothness|
   // flag should not reset the counter.
-  host_impl_->ReadyToCommit(arg2, /*scroll_and_viewport_changes_synced=*/true,
+  host_impl_->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true,
                             &begin_frame_metrics, /*commit_timeout=*/false);
   EXPECT_EQ(frame_sorter->total_frames(), 1u);
 }
diff --git a/cc/trees/layer_tree_host_unittest.cc b/cc/trees/layer_tree_host_unittest.cc
index 131f9e7..d40e7e89 100644
--- a/cc/trees/layer_tree_host_unittest.cc
+++ b/cc/trees/layer_tree_host_unittest.cc
@@ -9708,85 +9708,6 @@
 // TODO(crbug.com/40756887): Disabled because test is flaky on Linux and CrOS.
 // MULTI_THREAD_TEST_F(LayerTreeHostTestIgnoreEventsMetricsForNoUpdate);
 
-class LayerTreeHostUkmSmoothnessMetric : public LayerTreeTest {
- public:
-  LayerTreeHostUkmSmoothnessMetric() = default;
-  ~LayerTreeHostUkmSmoothnessMetric() override = default;
-
-  void InitializeSettings(LayerTreeSettings* settings) override {
-    settings->commit_to_active_tree = false;
-  }
-
-  void SetupTree() override {
-    LayerTreeTest::SetupTree();
-    shmem_region_ = layer_tree_host()->CreateSharedMemoryForSmoothnessUkm();
-    shmem_region_dropped_frames_ =
-        layer_tree_host()->CreateSharedMemoryForDroppedFramesUkm();
-  }
-
-  void BeginTest() override {
-    // Start with requesting main-frames.
-    PostSetNeedsCommitToMainThread();
-  }
-
-  void AfterTest() override {
-    ASSERT_TRUE(shmem_region_.IsValid());
-    auto mapping = shmem_region_.Map();
-    auto* smoothness = mapping.GetMemoryAs<UkmSmoothnessDataShared>();
-    ASSERT_TRUE(smoothness);
-    // It is not always possible to guarantee an exact number of dropped frames.
-    // So validate that there are non-zero dropped frames.
-    EXPECT_GT(smoothness->data.avg_smoothness, 0);
-
-    ASSERT_TRUE(shmem_region_dropped_frames_.IsValid());
-    auto mapping_dropped_frames = shmem_region_dropped_frames_.Map();
-    auto* smoothness_dropped_frames =
-        mapping.GetMemoryAs<UkmDroppedFramesDataShared>();
-    ASSERT_TRUE(smoothness_dropped_frames);
-    // It is not always possible to guarantee an exact number of dropped frames.
-    // So validate that there are non-zero dropped frames.
-    EXPECT_GT(smoothness_dropped_frames->data.percent_dropped_frames, 0);
-  }
-
-  void WillBeginImplFrameOnThread(LayerTreeHostImpl* host_impl,
-                                  const viz::BeginFrameArgs& args,
-                                  bool has_damage) override {
-    last_args_ = args;
-    if (!fcp_sent_) {
-      host_impl->dropped_frame_counter()->OnFirstContentfulPaintReceived();
-      fcp_sent_ = true;
-    }
-    host_impl->frame_sorter_for_testing()->AddNewFrame(last_args_);
-  }
-
-  void DidFinishImplFrameOnThread(LayerTreeHostImpl* host_impl) override {
-    if (TestEnded())
-      return;
-
-    if (frames_counter_ == 0) {
-      EndTest();
-      return;
-    }
-
-    // Mark every frame as a dropped frame affecting smoothness.
-    // Delegates to DFC::AddSortedFrame, which calls DFC::OnEndFrame.
-    host_impl->frame_sorter_for_testing()->AddFrameResult(
-        last_args_, CreateFakeImplDroppedFrameInfo());
-    host_impl->SetNeedsRedraw();
-    --frames_counter_;
-  }
-
- private:
-  const uint32_t kTotalFramesForTest = 5;
-  uint32_t frames_counter_ = kTotalFramesForTest;
-  bool fcp_sent_ = false;
-  viz::BeginFrameArgs last_args_;
-  base::ReadOnlySharedMemoryRegion shmem_region_;
-  base::ReadOnlySharedMemoryRegion shmem_region_dropped_frames_;
-};
-
-MULTI_THREAD_TEST_F(LayerTreeHostUkmSmoothnessMetric);
-
 class LayerTreeHostUkmSmoothnessMemoryOwnership : public LayerTreeTest {
  public:
   LayerTreeHostUkmSmoothnessMemoryOwnership() = default;
@@ -9809,7 +9730,6 @@
                                   bool has_damage) override {
     last_args_ = args;
     if (!fcp_sent_) {
-      host_impl->dropped_frame_counter()->OnFirstContentfulPaintReceived();
       fcp_sent_ = true;
     }
     host_impl->SetNeedsCommit();
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index c5861ef1..eeda72c 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -1981,10 +1981,6 @@
   return host_impl_->image_animation_controller();
 }
 
-DroppedFrameCounter* LayerTreeImpl::dropped_frame_counter() const {
-  return host_impl_->dropped_frame_counter();
-}
-
 FrameSorter* LayerTreeImpl::frame_sorter() const {
   return host_impl_->frame_sorter();
 }
diff --git a/cc/trees/layer_tree_impl.h b/cc/trees/layer_tree_impl.h
index f39a762b..9010b93a 100644
--- a/cc/trees/layer_tree_impl.h
+++ b/cc/trees/layer_tree_impl.h
@@ -51,7 +51,6 @@
 enum class ActivelyScrollingType;
 class DebugRectHistory;
 class ViewTransitionRequest;
-class DroppedFrameCounter;
 class GlobalStateThatImpactsTilePriority;
 class HeadsUpDisplayLayerImpl;
 class ImageDecodeCache;
@@ -133,7 +132,6 @@
   TileManager* tile_manager() const;
   ImageDecodeCache* image_decode_cache() const;
   ImageAnimationController* image_animation_controller() const;
-  DroppedFrameCounter* dropped_frame_counter() const;
   FrameSorter* frame_sorter() const;
   MemoryHistory* memory_history() const;
   DebugRectHistory* debug_rect_history() const;
diff --git a/cc/trees/proxy.h b/cc/trees/proxy.h
index d537058..e9a4d91 100644
--- a/cc/trees/proxy.h
+++ b/cc/trees/proxy.h
@@ -132,9 +132,9 @@
                                            bool raster,
                                            base::OnceClosure callback) = 0;
 
-  // Returns a percentage of dropped frames of the last second.
+  // Returns the average throughput as measured by the FrameSorter.
   // Only implemenented for single threaded proxy.
-  virtual double GetPercentDroppedFrames() const = 0;
+  virtual double GetAverageThroughput() const = 0;
 
   // Returns true if we have requested to have rendering paused.
   virtual bool IsRenderingPaused() const = 0;
diff --git a/cc/trees/proxy_impl.cc b/cc/trees/proxy_impl.cc
index 25156e0..84017d90 100644
--- a/cc/trees/proxy_impl.cc
+++ b/cc/trees/proxy_impl.cc
@@ -44,6 +44,7 @@
 #include "cc/trees/swap_promise.h"
 #include "cc/trees/task_runner_provider.h"
 #include "cc/trees/trace_utils.h"
+#include "components/viz/common/frame_sinks/begin_frame_args.h"
 #include "components/viz/common/frame_sinks/delay_based_time_source.h"
 #include "components/viz/common/frame_timing_details.h"
 #include "components/viz/common/gpu/raster_context_provider.h"
@@ -353,7 +354,6 @@
     std::unique_ptr<CommitState> commit_state,
     const ThreadUnsafeCommitState* unsafe_state,
     base::TimeTicks main_thread_start_time,
-    const viz::BeginFrameArgs& commit_args,
     bool scroll_and_viewport_changes_synced,
     CommitTimestamps* commit_timestamps,
     bool commit_timeout) {
@@ -395,7 +395,7 @@
   scheduler_->NotifyBeginMainFrameStarted(main_thread_start_time);
 
   auto& begin_main_frame_metrics = commit_state->begin_main_frame_metrics;
-  host_impl_->ReadyToCommit(commit_args, scroll_and_viewport_changes_synced,
+  host_impl_->ReadyToCommit(scroll_and_viewport_changes_synced,
                             begin_main_frame_metrics.get(), commit_timeout);
 
   int source_frame_number = commit_state->source_frame_number;
diff --git a/cc/trees/proxy_impl.h b/cc/trees/proxy_impl.h
index 7aa5610a..131df3c 100644
--- a/cc/trees/proxy_impl.h
+++ b/cc/trees/proxy_impl.h
@@ -94,7 +94,6 @@
                                  std::unique_ptr<CommitState> commit_state,
                                  const ThreadUnsafeCommitState* unsafe_state,
                                  base::TimeTicks main_thread_start_time,
-                                 const viz::BeginFrameArgs& commit_args,
                                  bool scroll_and_viewport_changes_synced,
                                  CommitTimestamps* commit_timestamps,
                                  bool commit_timeout = false);
diff --git a/cc/trees/proxy_main.cc b/cc/trees/proxy_main.cc
index 2a984b3a..a0d057e 100644
--- a/cc/trees/proxy_main.cc
+++ b/cc/trees/proxy_main.cc
@@ -469,13 +469,13 @@
       main_thread_blocked.emplace(task_runner_provider_);
 
     ImplThreadTaskRunner()->PostTask(
-        FROM_HERE,
-        base::BindOnce(
-            &ProxyImpl::NotifyReadyToCommitOnImpl,
-            base::Unretained(proxy_impl_.get()), completion_event,
-            std::move(commit_state), &unsafe_state, begin_main_frame_start_time,
-            frame_args, scroll_and_viewport_changes_synced,
-            blocking ? &commit_timestamps : nullptr, commit_timeout));
+        FROM_HERE, base::BindOnce(&ProxyImpl::NotifyReadyToCommitOnImpl,
+                                  base::Unretained(proxy_impl_.get()),
+                                  completion_event, std::move(commit_state),
+                                  &unsafe_state, begin_main_frame_start_time,
+                                  scroll_and_viewport_changes_synced,
+                                  (blocking ? &commit_timestamps : nullptr),
+                                  commit_timeout));
     if (blocking)
       layer_tree_host_->WaitForProtectedSequenceCompletion();
   }
@@ -960,7 +960,7 @@
   SetNeedsCommit();
 }
 
-double ProxyMain::GetPercentDroppedFrames() const {
+double ProxyMain::GetAverageThroughput() const {
   NOTIMPLEMENTED();
   return 0.0;
 }
diff --git a/cc/trees/proxy_main.h b/cc/trees/proxy_main.h
index ed13d3e..1fc6401f 100644
--- a/cc/trees/proxy_main.h
+++ b/cc/trees/proxy_main.h
@@ -141,7 +141,7 @@
   void CompositeImmediatelyForTest(base::TimeTicks frame_begin_time,
                                    bool raster,
                                    base::OnceClosure callback) override;
-  double GetPercentDroppedFrames() const override;
+  double GetAverageThroughput() const override;
   bool IsRenderingPaused() const override;
   void NotifyNewLocalSurfaceIdExpectedWhilePaused() override;
 
diff --git a/cc/trees/single_thread_proxy.cc b/cc/trees/single_thread_proxy.cc
index 92bfce75..ba264aa1 100644
--- a/cc/trees/single_thread_proxy.cc
+++ b/cc/trees/single_thread_proxy.cc
@@ -821,7 +821,7 @@
     layer_tree_host_->RecordStartOfFrameMetrics();
     DoBeginMainFrame(begin_frame_args);
     commit_requested_ = false;
-    DoPainting(begin_frame_args);
+    DoPainting();
     layer_tree_host_->RecordEndOfFrameMetrics(frame_begin_time,
                                               /* trackers */ 0u);
     DoCommit(begin_frame_args);
@@ -1008,10 +1008,9 @@
   host_impl_->SetRenderFrameObserver(std::move(observer));
 }
 
-double SingleThreadProxy::GetPercentDroppedFrames() const {
+double SingleThreadProxy::GetAverageThroughput() const {
   DebugScopedSetImplThread impl(task_runner_provider_);
-  return host_impl_->dropped_frame_counter()
-      ->sliding_window_current_percent_dropped();
+  return host_impl_->frame_sorter()->GetAverageThroughput();
 }
 
 void SingleThreadProxy::UpdateBrowserControlsState(
@@ -1154,7 +1153,7 @@
     return;
   }
 
-  DoPainting(begin_frame_args);
+  DoPainting();
   layer_tree_host_->RecordEndOfFrameMetrics(frame_start_time,
                                             /* trackers */ 0u);
 }
@@ -1193,14 +1192,13 @@
   did_apply_compositor_deltas_ = false;
 }
 
-void SingleThreadProxy::DoPainting(const viz::BeginFrameArgs& commit_args) {
+void SingleThreadProxy::DoPainting() {
   layer_tree_host_->UpdateLayers();
   update_layers_requested_ = false;
 
   std::unique_ptr<BeginMainFrameMetrics> begin_main_frame_metrics =
       layer_tree_host_->TakeBeginMainFrameMetrics();
-  host_impl_->ReadyToCommit(commit_args,
-                            /*scroll_and_viewport_changes_synced=*/true,
+  host_impl_->ReadyToCommit(/*scroll_and_viewport_changes_synced=*/true,
                             begin_main_frame_metrics.get(),
                             /*commit_timeout=*/false);
 
diff --git a/cc/trees/single_thread_proxy.h b/cc/trees/single_thread_proxy.h
index 7aaa669..601c08f 100644
--- a/cc/trees/single_thread_proxy.h
+++ b/cc/trees/single_thread_proxy.h
@@ -94,7 +94,7 @@
   void CompositeImmediatelyForTest(base::TimeTicks frame_begin_time,
                                    bool raster,
                                    base::OnceClosure callback) override;
-  double GetPercentDroppedFrames() const override;
+  double GetAverageThroughput() const override;
 
   void UpdateBrowserControlsState(
       BrowserControlsState constraints,
@@ -202,7 +202,7 @@
   void BeginMainFrame(const viz::BeginFrameArgs& begin_frame_args);
   void BeginMainFrameAbortedOnImplThread(CommitEarlyOutReason reason);
   void DoBeginMainFrame(const viz::BeginFrameArgs& begin_frame_args);
-  void DoPainting(const viz::BeginFrameArgs& commit_args);
+  void DoPainting();
   void DoCommit(const viz::BeginFrameArgs& commit_args);
   void DoPostCommit();
   DrawResult DoComposite(LayerTreeHostImpl::FrameData* frame);
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 5329e1f7..86007c8 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -26,7 +26,6 @@
 import("//extensions/buildflags/buildflags.gni")
 import("//media/media_options.gni")
 import("//ppapi/buildflags/buildflags.gni")
-import("//services/shape_detection/features.gni")
 import("//third_party/angle/gni/angle.gni")
 import("//third_party/blink/public/public_features.gni")
 import("//third_party/widevine/cdm/widevine.gni")
@@ -1724,9 +1723,6 @@
       if (build_with_internal_optimization_guide) {
         deps += [ ":optimization_guide_symbols" ]
       }
-      if (build_with_internal_shape_detection) {
-        deps += [ ":shape_detection_symbols" ]
-      }
       if (!use_static_angle) {
         deps += [
           ":angle_egl_symbols",
@@ -1825,21 +1821,6 @@
         deps = [ "//components/optimization_guide/internal:optimization_guide_internal" ]
       }
     }
-    if (build_with_internal_shape_detection) {
-      extract_symbols("shape_detection_symbols") {
-        binary = "$root_out_dir/libshape_detection_internal.so"
-        if (current_cpu == "x86") {
-          # GYP used "ia32" so keep that naming for back-compat.
-          symbol_file = "$root_out_dir/shape_detection_internal.breakpad.ia32"
-        } else {
-          symbol_file =
-              "$root_out_dir/shape_detection_internal.breakpad.$current_cpu"
-        }
-
-        deps =
-            [ "//services/shape_detection/internal:shape_detection_internal" ]
-      }
-    }
   }
 
   # Copies some scripts and resources that are used for desktop integration.
diff --git a/chrome/VERSION b/chrome/VERSION
index 105f431..4cb20bf5 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=139
 MINOR=0
-BUILD=7250
+BUILD=7251
 PATCH=0
diff --git a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
index d6a9c28..02fa447 100644
--- a/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
+++ b/chrome/android/features/keyboard_accessory/javatests/src/org/chromium/chrome/browser/keyboard_accessory/sheet_tabs/AddressAccessoryIntegrationTest.java
@@ -22,7 +22,6 @@
 import static org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper.selectTabWithDescription;
 import static org.chromium.chrome.browser.keyboard_accessory.ManualFillingTestHelper.whenDisplayed;
 
-import android.os.Build;
 import android.widget.TextView;
 
 import androidx.test.filters.MediumTest;
@@ -35,7 +34,6 @@
 
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Features;
 import org.chromium.chrome.browser.ChromeWindow;
@@ -130,12 +128,7 @@
 
     @Test
     @MediumTest
-    @DisabledTest(message = "https://crbug.com/418234086")
-    @DisableIf.Build(
-            sdk_is_less_than = Build.VERSION_CODES.TIRAMISU,
-            sdk_is_greater_than = Build.VERSION_CODES.P,
-            supported_abis_includes = "x86_64",
-            message = "crbug.com/40190628")
+    @DisabledTest(message = "https://crbug.com/418234086,https://crbug.com/40190628")
     public void testFillsSuggestionOnClick() throws TimeoutException {
         loadTestPage(FakeKeyboard::new);
         mHelper.clickNodeAndShowKeyboard("NAME_FIRST", 1);
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherSearchRenderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherSearchRenderTest.java
index a244878..fda6bb9 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherSearchRenderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherSearchRenderTest.java
@@ -8,8 +8,6 @@
 
 import static org.chromium.ui.base.DeviceFormFactor.PHONE;
 
-import android.os.Build;
-
 import androidx.test.filters.MediumTest;
 
 import org.junit.After;
@@ -25,7 +23,6 @@
 import org.chromium.base.test.params.ParameterAnnotations;
 import org.chromium.base.test.params.ParameterizedRunner;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Features;
 import org.chromium.base.test.util.Restriction;
@@ -64,10 +61,6 @@
     ChromeFeatureList.GRID_TAB_SWITCHER_UPDATE,
     ChromeFeatureList.ANDROID_THEME_MODULE
 })
-// Disable in Pie because search box does not get focus automatically.
-@DisableIf.Build(
-        sdk_is_greater_than = Build.VERSION_CODES.O_MR1,
-        sdk_is_less_than = Build.VERSION_CODES.Q)
 public class TabSwitcherSearchRenderTest {
     private static final int SERVER_PORT = 13245;
 
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherSearchTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherSearchTest.java
index c443ca48..06799d90 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherSearchTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherSearchTest.java
@@ -12,8 +12,6 @@
 import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking;
 import static org.chromium.ui.base.DeviceFormFactor.PHONE;
 
-import android.os.Build;
-
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -29,7 +27,6 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.RequiresRestart;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -59,10 +56,6 @@
 @RunWith(ChromeJUnit4ClassRunner.class)
 @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
 @Batch(Batch.PER_CLASS)
-// Disable in Pie because search box does not get focus automatically.
-@DisableIf.Build(
-        sdk_is_greater_than = Build.VERSION_CODES.O_MR1,
-        sdk_is_less_than = Build.VERSION_CODES.Q)
 public class TabSwitcherSearchTest {
     private static final int SERVER_PORT = 13245;
     private static final String URL_PREFIX = "127.0.0.1:" + SERVER_PORT;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialog.java
index a6b52cdb..82eb851e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialog.java
@@ -24,8 +24,10 @@
 import org.chromium.chrome.browser.tab.TabLoadIfNeededCaller;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.media.capture.ScreenCapture;
+import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modaldialog.ModalDialogManagerHolder;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
 import org.chromium.ui.modaldialog.ModalDialogProperties.ButtonType;
 import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
@@ -39,6 +41,8 @@
 
 /** Dialog for selecting a media source for media capture. */
 public class MediaCapturePickerDialog implements AllTabObserver.Observer {
+    // This web contents is the one that is receiving the shared content.
+    private final WebContents mWebContents;
     private final ModalDialogManager mModalDialogManager;
     private final String mAppName;
     private final View mDialogView;
@@ -114,32 +118,39 @@
         int DEFAULT = 0;
     }
 
+    private static @Nullable Context maybeGetContext(WebContents webContents) {
+        final WindowAndroid window = webContents.getTopLevelNativeWindow();
+        if (window == null) return null;
+        return window.getContext().get();
+    }
+
     /**
      * Shows the media capture picker dialog.
      *
-     * @param modalDialogManager Manager for managing the modal dialog.
+     * @param webContents The {@link WebContents} to show the dialog on behalf of.
      * @param appName Name of the app that wants to share content.
      * @param requestAudio True if audio sharing is also requested.
      * @param delegate Invoked with a WebContents if a tab is selected, or {@code null} if the
      *     dialog is dismissed.
      */
     public static void showDialog(
-            Context context,
-            ModalDialogManager modalDialogManager,
-            String appName,
-            boolean requestAudio,
-            Delegate delegate) {
-        new MediaCapturePickerDialog(context, modalDialogManager, appName, requestAudio, delegate)
-                .show();
+            WebContents webContents, String appName, boolean requestAudio, Delegate delegate) {
+        final Context context = maybeGetContext(webContents);
+        if (context == null) {
+            delegate.onCancel();
+            return;
+        }
+        new MediaCapturePickerDialog(context, webContents, appName, requestAudio, delegate).show();
     }
 
     private MediaCapturePickerDialog(
             Context context,
-            ModalDialogManager modalDialogManager,
+            WebContents webContents,
             String appName,
             boolean requestAudio,
             Delegate delegate) {
-        mModalDialogManager = modalDialogManager;
+        mWebContents = webContents;
+        mModalDialogManager = ((ModalDialogManagerHolder) context).getModalDialogManager();
         mAppName = appName;
         mDelegate = delegate;
 
@@ -198,7 +209,7 @@
         fragment.startAndroidCapturePrompt(
                 (action, result) -> {
                     if (action != CaptureAction.CAPTURE_CANCELLED) {
-                        ScreenCapture.onPick(result);
+                        ScreenCapture.onPick(mWebContents, result);
                     }
 
                     switch (action) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialogBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialogBridge.java
index a78499b..2d2cdfe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialogBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCapturePickerDialogBridge.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.media;
 
-import android.app.Activity;
-
 import androidx.annotation.NonNull;
 
 import org.jni_zero.CalledByNative;
@@ -13,8 +11,6 @@
 import org.jni_zero.NativeMethods;
 
 import org.chromium.content_public.browser.WebContents;
-import org.chromium.ui.base.WindowAndroid;
-import org.chromium.ui.modaldialog.ModalDialogManagerHolder;
 
 /** Glue for the media capture picker dialog UI code and communication with the native backend. */
 public class MediaCapturePickerDialogBridge implements MediaCapturePickerDialog.Delegate {
@@ -42,22 +38,17 @@
     /**
      * Shows the media capture picker dialog.
      *
-     * @param windowAndroid Window to show the dialog on.
+     * @param webContents The {@link WebContents} to show the dialog on behalf of.
      * @param appName Name of the app that wants to share content.
      * @param requestAudio True if audio sharing is also requested.
      */
     @CalledByNative
     public void showDialog(
-            WindowAndroid windowAndroid,
+            WebContents webContents,
             @JniType("std::u16string") String appName,
             boolean requestAudio) {
-        Activity activity = windowAndroid.getActivity().get();
         MediaCapturePickerDialog.showDialog(
-                activity,
-                ((ModalDialogManagerHolder) activity).getModalDialogManager(),
-                appName,
-                requestAudio,
-                this);
+                webContents, appName, requestAudio, /* delegate= */ this);
     }
 
     @CalledByNative
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBridge.java
index a2db93e..1f558c4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBridge.java
@@ -88,7 +88,7 @@
             @JniType("std::vector") List<LoyaltyCard> affiliatedLoyaltyCards,
             @JniType("std::vector") List<LoyaltyCard> allLoyaltyCards,
             boolean firstTimeUsage) {
-        mComponent.showLoyaltyCards(allLoyaltyCards, affiliatedLoyaltyCards, firstTimeUsage);
+        mComponent.showLoyaltyCards(affiliatedLoyaltyCards, allLoyaltyCards, firstTimeUsage);
     }
 
     @CalledByNative
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchCriticalTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchCriticalTest.java
index 8620769..8808d59 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchCriticalTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchCriticalTest.java
@@ -6,8 +6,6 @@
 
 import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
 
-import android.os.Build.VERSION_CODES;
-
 import androidx.test.filters.SmallTest;
 
 import org.hamcrest.MatcherAssert;
@@ -24,7 +22,6 @@
 
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Features.EnableFeatures;
@@ -140,7 +137,7 @@
     @SmallTest
     @Feature({"ContextualSearch"})
     // Previously disabled: crbug.com/765403
-    @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.P, message = "crbug.com/377363763")
+    @DisabledTest(message = "crbug.com/377363763")
     public void testSearchTermResolutionError() throws Exception {
         simulateSlowResolveSearch("states");
         assertSearchTermRequested();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
index 38351b5..7e463c88 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java
@@ -624,11 +624,7 @@
                 expectedCaptionStart,
                 barControl.getCaptionText().subSequence(0, expectedCaptionStart.length()));
         // TODO(donnd): figure out why we get ~0.65 on Oreo rather than 1. https://crbug.com/818515.
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
-            Assert.assertEquals(1.f, imageControl.getCustomImageVisibilityPercentage(), 0);
-        } else {
-            Assert.assertTrue(0.5f < imageControl.getCustomImageVisibilityPercentage());
-        }
+        Assert.assertTrue(0.5f < imageControl.getCustomImageVisibilityPercentage());
 
         CompositorAnimationHandler.setTestingMode(false);
     }
@@ -684,7 +680,7 @@
     @SmallTest
     @Feature({"ContextualSearch"})
     // TODO(donnd): reenable - recent fixes as of 3/31/2023
-    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.O, message = "crbug.com/1075895")
+    @DisabledTest(message = "crbug.com/1075895")
     // Previously disabled: https://crbug.com/1127796
     public void testQuickActionUrl() throws Exception {
         final String testUrl = mTestServer.getURL("/chrome/test/data/android/google.html");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
index 84bc0fa..3232e94 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
@@ -9,7 +9,6 @@
 import android.app.DownloadManager;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build.VERSION_CODES;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.MediumTest;
@@ -29,7 +28,7 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
+import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.browser.download.DownloadManagerBridge.DownloadQueryResult;
@@ -312,10 +311,9 @@
      */
     @Test
     @MediumTest
-    @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.P) // https://crbug.com/338971643
+    @DisabledTest(message = "https://crbug.com/338971643")
     @Feature({"Download"})
     public void testQueryDownloadResult() {
-        Context context = getTestContext();
         DownloadManager manager =
                 (DownloadManager) getTestContext().getSystemService(Context.DOWNLOAD_SERVICE);
         long downloadId1 =
@@ -346,7 +344,7 @@
      */
     @Test
     @MediumTest
-    @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.P) // https://crbug.com/338971643
+    @DisabledTest(message = "https://crbug.com/338971643")
     @Feature({"Download"})
     public void testClearPendingOMADownloads() {
         Context context = getTestContext();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ManifestHWATest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ManifestHWATest.java
index 499c6d85..aa19b9f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ManifestHWATest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ManifestHWATest.java
@@ -25,7 +25,6 @@
 @RunWith(BaseJUnit4ClassRunner.class)
 @Batch(Batch.UNIT_TESTS)
 @DisableIf.Build(
-        sdk_is_greater_than = Build.VERSION_CODES.P,
         sdk_is_less_than = Build.VERSION_CODES.TIRAMISU,
         supported_abis_includes = "x86_64",
         message = "vr tests do not apply to emulator")
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
index dcc630d..4592b99 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
@@ -6,7 +6,6 @@
 
 import android.app.Activity;
 import android.net.Uri;
-import android.os.Build;
 import android.os.Environment;
 
 import androidx.test.core.app.ApplicationProvider;
@@ -28,7 +27,6 @@
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.CriteriaHelper;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.offlinepages.OfflinePageBridge.OfflinePageModelObserver;
@@ -293,10 +291,6 @@
     @Test
     @MediumTest
     @CommandLineFlags.Add({"enable-features=OfflinePagesSharing"})
-    @DisableIf.Build(
-            message = "https://crbug.com/1001506",
-            sdk_is_greater_than = Build.VERSION_CODES.N,
-            sdk_is_less_than = Build.VERSION_CODES.P)
     public void testShareTemporaryOfflinePage() throws Exception {
         loadOfflinePage(SUGGESTED_ARTICLES_ID);
         final Semaphore semaphore = new Semaphore(0);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
index fefaf26..6dae1b46 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/suggestions/SwitchToTabTest.java
@@ -10,7 +10,6 @@
 import static org.chromium.chrome.browser.multiwindow.MultiWindowTestHelper.waitForSecondChromeTabbedActivity;
 
 import android.app.Activity;
-import android.os.Build;
 import android.text.TextUtils;
 import android.view.ViewGroup;
 import android.widget.ImageView;
@@ -32,7 +31,6 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.CriteriaNotSatisfiedException;
-import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
@@ -240,7 +238,7 @@
 
     @Test
     @MediumTest
-    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.P, message = "crbug.com/1195129")
+    @DisabledTest(message = "crbug.com/1195129")
     public void testSwitchToTabSuggestion() throws InterruptedException {
         mTestServer =
                 EmbeddedTestServer.createAndStartHTTPSServer(
@@ -314,7 +312,7 @@
 
     @Test
     @MediumTest
-    @DisableIf.Build(sdk_is_greater_than = Build.VERSION_CODES.P, message = "crbug.com/1195129")
+    @DisabledTest(message = "crbug.com/1195129")
     public void testNoSwitchToIncognitoTabFromNormalModel() throws InterruptedException {
         mTestServer =
                 EmbeddedTestServer.createAndStartHTTPSServer(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/usage_stats/TabSuspensionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/usage_stats/TabSuspensionTest.java
index df319aa..3c87db0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/usage_stats/TabSuspensionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/usage_stats/TabSuspensionTest.java
@@ -12,7 +12,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioManager;
-import android.os.Build;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.MediumTest;
@@ -32,7 +31,6 @@
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
@@ -72,7 +70,6 @@
     "ignore-certificate-errors",
     MediaSwitches.AUTOPLAY_NO_GESTURE_REQUIRED_POLICY
 })
-@MinAndroidSdkLevel(Build.VERSION_CODES.Q)
 public class TabSuspensionTest {
     private static final String STARTING_FQDN = "example.com";
     private static final String DIFFERENT_FQDN = "www.google.com";
@@ -215,9 +212,7 @@
 
     @Test
     @MediumTest
-    @DisableIf.Build(
-            sdk_is_greater_than = Build.VERSION_CODES.P,
-            message = "https://crbug.com/1036556")
+    @DisabledTest(message = "https://crbug.com/1036556")
     public void testMediaSuspension() throws TimeoutException {
         mActivityTestRule.loadUrl(
                 mTestServer.getURLWithHostName(STARTING_FQDN, MEDIA_FILE_TEST_PATH));
diff --git a/chrome/browser/actor/BUILD.gn b/chrome/browser/actor/BUILD.gn
index ebca02f..16d0928 100644
--- a/chrome/browser/actor/BUILD.gn
+++ b/chrome/browser/actor/BUILD.gn
@@ -22,6 +22,7 @@
     "execution_engine.h",
     "tools/observation_delay_controller.h",
     "tools/tool_controller.h",
+    "tools/tool_request.h",
   ]
   public_deps = [
     ":types",
@@ -54,22 +55,45 @@
     "execution_engine.cc",
     "site_policy.cc",
     "site_policy.h",
+    "tools/click_tool_request.cc",
+    "tools/click_tool_request.h",
+    "tools/drag_and_release_tool_request.cc",
+    "tools/drag_and_release_tool_request.h",
     "tools/history_tool.cc",
     "tools/history_tool.h",
+    "tools/history_tool_request.cc",
+    "tools/history_tool_request.h",
+    "tools/move_mouse_tool_request.cc",
+    "tools/move_mouse_tool_request.h",
     "tools/navigate_tool.cc",
     "tools/navigate_tool.h",
+    "tools/navigate_tool_request.cc",
+    "tools/navigate_tool_request.h",
     "tools/observation_delay_controller.cc",
     "tools/page_tool.cc",
     "tools/page_tool.h",
+    "tools/page_tool_request.cc",
+    "tools/page_tool_request.h",
+    "tools/scroll_tool_request.cc",
+    "tools/scroll_tool_request.h",
+    "tools/select_tool_request.cc",
+    "tools/select_tool_request.h",
     "tools/tab_management_tool.cc",
     "tools/tab_management_tool.h",
+    "tools/tab_management_tool_request.cc",
+    "tools/tab_management_tool_request.h",
     "tools/tool.cc",
     "tools/tool.h",
     "tools/tool_callbacks.cc",
     "tools/tool_callbacks.h",
     "tools/tool_controller.cc",
+    "tools/tool_request.cc",
+    "tools/type_tool_request.cc",
+    "tools/type_tool_request.h",
     "tools/wait_tool.cc",
     "tools/wait_tool.h",
+    "tools/wait_tool_request.cc",
+    "tools/wait_tool_request.h",
   ]
 
   public_deps = [ "//chrome/browser:browser_public_dependencies" ]
diff --git a/chrome/browser/actor/browser_action_util.cc b/chrome/browser/actor/browser_action_util.cc
index fec656e4..01430dc4 100644
--- a/chrome/browser/actor/browser_action_util.cc
+++ b/chrome/browser/actor/browser_action_util.cc
@@ -4,200 +4,479 @@
 
 #include "chrome/browser/actor/browser_action_util.h"
 
+#include <optional>
+
+#include "chrome/browser/actor/tools/click_tool_request.h"
+#include "chrome/browser/actor/tools/drag_and_release_tool_request.h"
+#include "chrome/browser/actor/tools/history_tool_request.h"
+#include "chrome/browser/actor/tools/move_mouse_tool_request.h"
+#include "chrome/browser/actor/tools/navigate_tool_request.h"
+#include "chrome/browser/actor/tools/scroll_tool_request.h"
+#include "chrome/browser/actor/tools/select_tool_request.h"
+#include "chrome/browser/actor/tools/tab_management_tool_request.h"
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "chrome/browser/actor/tools/type_tool_request.h"
+#include "chrome/browser/actor/tools/wait_tool_request.h"
 #include "chrome/common/actor/actor_logging.h"
 #include "components/optimization_guide/content/browser/page_content_proto_provider.h"
 #include "components/optimization_guide/proto/features/actions_data.pb.h"
-#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
-#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/render_widget_host.h"
 #include "content/public/browser/web_contents.h"
-#include "ui/gfx/geometry/point_f.h"
+#include "ui/base/window_open_disposition.h"
 
 namespace actor {
 
-using ::content::RenderFrameHost;
-using ::content::RenderWidgetHost;
-using ::content::WebContents;
+// Alias the namespace to make the long enums a bit more readable in
+// implementations.
+namespace apc = ::optimization_guide::proto;
+
+using apc::Action;
+using apc::ActionTarget;
+using apc::ActivateTabAction;
+using apc::ClickAction;
+using apc::CloseTabAction;
+using apc::CreateTabAction;
+using apc::DragAndReleaseAction;
+using apc::HistoryBackAction;
+using apc::HistoryForwardAction;
+using apc::MoveMouseAction;
+using apc::NavigateAction;
+using apc::ScrollAction;
+using apc::SelectAction;
+using apc::TypeAction;
+using apc::WaitAction;
 using ::optimization_guide::DocumentIdentifierUserData;
-using ::optimization_guide::proto::Action;
-using ::optimization_guide::proto::ActionTarget;
-using ::optimization_guide::proto::DocumentIdentifier;
+using ::tabs::TabHandle;
+using ::tabs::TabInterface;
 
 namespace {
 
-bool IsTargetingTab(const Action& action) {
-  switch (action.action_case()) {
-    case Action::ActionCase::kClick:
-    case Action::ActionCase::kType:
-    case Action::ActionCase::kScroll:
-    case Action::ActionCase::kMoveMouse:
-    case Action::ActionCase::kDragAndRelease:
-    case Action::ActionCase::kSelect:
-    // These actions target neither tabs nor frames.
-    case Action::ActionCase::kCreateTab:
-    case Action::ActionCase::kCloseTab:
-    case Action::ActionCase::kActivateTab:
-    case Action::ActionCase::kCreateWindow:
-    case Action::ActionCase::kCloseWindow:
-    case Action::ActionCase::kActivateWindow:
-    case Action::ActionCase::kYieldToUser:
-      return false;
-    case Action::ActionCase::kNavigate:
-    case Action::ActionCase::kBack:
-    case Action::ActionCase::kForward:
-    case Action::ActionCase::kWait:
-      return true;
-    case Action::ActionCase::ACTION_NOT_SET:
-      NOTREACHED();
+struct PageScopedParams {
+  std::string document_identifier;
+  TabHandle tab_handle;
+};
+
+template <class T>
+TabHandle GetTabHandle(const T& action, TabInterface* deprecated_fallback_tab) {
+  tabs::TabHandle tab_handle;
+  if (action.has_tab_id()) {
+    tab_handle = TabHandle(action.tab_id());
+  } else if (deprecated_fallback_tab) {
+    tab_handle = deprecated_fallback_tab->GetHandle();
   }
+  return tab_handle;
 }
 
-// Finds the local root of a given RenderFrameHost. The local root is the
-// highest ancestor in the frame tree that shares the same RenderWidgetHost.
-RenderFrameHost* GetLocalRoot(RenderFrameHost* rfh) {
-  RenderFrameHost* local_root = rfh;
-  while (local_root && local_root->GetParent()) {
-    if (local_root->GetRenderWidgetHost() !=
-        local_root->GetParent()->GetRenderWidgetHost()) {
-      break;
-    }
-    local_root = local_root->GetParent();
+template <class T>
+PageScopedParams GetPageScopedParams(const T& action,
+                                     TabInterface* deprecated_fallback_tab) {
+  std::string document_identifier;
+  if (action.has_target()) {
+    document_identifier =
+        action.target().document_identifier().serialized_token();
   }
-  return local_root;
+
+  tabs::TabHandle tab_handle;
+  if (action.has_tab_id()) {
+    tab_handle = TabHandle(action.tab_id());
+  } else if (deprecated_fallback_tab) {
+    tab_handle = deprecated_fallback_tab->GetHandle();
+  }
+  return {document_identifier, tab_handle};
+}
+
+// DragAndRelease is unusual in that it has no target, it has a from_target and
+// to_target. Specialize and use the from_target as the document-scoping target.
+template <>
+PageScopedParams GetPageScopedParams<DragAndReleaseAction>(
+    const DragAndReleaseAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  std::string document_identifier;
+  if (action.has_from_target()) {
+    document_identifier =
+        action.from_target().document_identifier().serialized_token();
+  }
+
+  tabs::TabHandle tab_handle;
+  if (action.has_tab_id()) {
+    tab_handle = TabHandle(action.tab_id());
+  } else if (deprecated_fallback_tab) {
+    tab_handle = deprecated_fallback_tab->GetHandle();
+  }
+  return {document_identifier, tab_handle};
+}
+
+PageToolRequest::Target ToPageToolTarget(
+    const optimization_guide::proto::ActionTarget& target) {
+  if (target.has_coordinate()) {
+    return PageToolRequest::Target(
+        gfx::Point(target.coordinate().x(), target.coordinate().y()));
+  } else {
+    return PageToolRequest::Target(target.content_node_id());
+  }
+}
+std::unique_ptr<ToolRequest> CreateClickRequest(
+    const ClickAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  using ClickCount = ClickToolRequest::ClickCount;
+  using ClickType = ClickToolRequest::ClickType;
+
+  PageScopedParams page = GetPageScopedParams(action, deprecated_fallback_tab);
+
+  if (!action.has_target() || !action.has_click_count() ||
+      !action.has_click_type() || page.tab_handle == TabHandle::Null()) {
+    return nullptr;
+  }
+
+  ClickCount count;
+  switch (action.click_count()) {
+    case apc::ClickAction_ClickCount_SINGLE:
+      count = ClickCount::kSingle;
+      break;
+    case apc::ClickAction_ClickCount_DOUBLE:
+      count = ClickCount::kDouble;
+      break;
+    case apc::ClickAction_ClickCount_UNKNOWN_CLICK_COUNT:
+    case apc::
+        ClickAction_ClickCount_ClickAction_ClickCount_INT_MIN_SENTINEL_DO_NOT_USE_:
+    case apc::
+        ClickAction_ClickCount_ClickAction_ClickCount_INT_MAX_SENTINEL_DO_NOT_USE_:
+      // TODO(crbug.com/412700289): Revert once this is set.
+      count = ClickCount::kSingle;
+      break;
+  }
+
+  ClickType type;
+  switch (action.click_type()) {
+    case apc::ClickAction_ClickType_LEFT:
+      type = ClickType::kLeft;
+      break;
+    case apc::ClickAction_ClickType_RIGHT:
+      type = ClickType::kRight;
+      break;
+    case apc::
+        ClickAction_ClickType_ClickAction_ClickType_INT_MIN_SENTINEL_DO_NOT_USE_:
+    case apc::
+        ClickAction_ClickType_ClickAction_ClickType_INT_MAX_SENTINEL_DO_NOT_USE_:
+    case apc::ClickAction_ClickType_UNKNOWN_CLICK_TYPE:
+      // TODO(crbug.com/412700289): Revert once this is set.
+      type = ClickType::kLeft;
+      break;
+  }
+
+  return std::make_unique<ClickToolRequest>(
+      page.tab_handle, page.document_identifier,
+      ToPageToolTarget(action.target()), type, count);
+}
+
+std::unique_ptr<ToolRequest> CreateTypeRequest(
+    const TypeAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  using TypeMode = TypeToolRequest::Mode;
+
+  PageScopedParams page = GetPageScopedParams(action, deprecated_fallback_tab);
+
+  if (!action.has_target() || !action.has_text() || !action.has_mode() ||
+      !action.has_follow_by_enter() || page.tab_handle == TabHandle::Null()) {
+    return nullptr;
+  }
+
+  TypeMode mode;
+  switch (action.mode()) {
+    case apc::TypeAction_TypeMode_DELETE_EXISTING:
+      mode = TypeMode::kReplace;
+      break;
+    case apc::TypeAction_TypeMode_PREPEND:
+      mode = TypeMode::kPrepend;
+      break;
+    case apc::TypeAction_TypeMode_APPEND:
+      mode = TypeMode::kAppend;
+      break;
+    case apc::TypeAction_TypeMode_UNKNOWN_TYPE_MODE:
+    case apc::
+        TypeAction_TypeMode_TypeAction_TypeMode_INT_MIN_SENTINEL_DO_NOT_USE_:
+    case apc::
+        TypeAction_TypeMode_TypeAction_TypeMode_INT_MAX_SENTINEL_DO_NOT_USE_:
+      // TODO(crbug.com/412700289): Revert once this is set.
+      mode = TypeMode::kReplace;
+      break;
+  }
+  return std::make_unique<TypeToolRequest>(
+      page.tab_handle, page.document_identifier,
+      ToPageToolTarget(action.target()), action.text(),
+      action.follow_by_enter(), mode);
+}
+
+std::unique_ptr<ToolRequest> CreateScrollRequest(
+    const ScrollAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  using Direction = ScrollToolRequest::Direction;
+
+  PageScopedParams page = GetPageScopedParams(action, deprecated_fallback_tab);
+
+  if (!action.has_direction() || !action.has_distance() ||
+      page.tab_handle == TabHandle::Null()) {
+    return nullptr;
+  }
+
+  PageToolRequest::Target page_target;
+
+  if (action.has_target()) {
+    page_target = ToPageToolTarget(action.target());
+  } else {
+    // Scroll action may omit a target which means "target the viewport".
+    // TODO(bokan): This can be removed once the scroll action provides the main
+    // document's id in these cases.
+    page_target = std::nullopt;
+    if (page.document_identifier.empty()) {
+      TabInterface* tab = page.tab_handle.Get();
+      if (!tab) {
+        return nullptr;
+      }
+      page.document_identifier =
+          DocumentIdentifierUserData::GetOrCreateForCurrentDocument(
+              tab->GetContents()->GetPrimaryMainFrame())
+              ->serialized_token();
+    }
+  }
+
+  Direction direction;
+  switch (action.direction()) {
+    case apc::ScrollAction_ScrollDirection_LEFT:
+      direction = Direction::kLeft;
+      break;
+    case apc::ScrollAction_ScrollDirection_RIGHT:
+      direction = Direction::kRight;
+      break;
+    case apc::ScrollAction_ScrollDirection_UP:
+      direction = Direction::kUp;
+      break;
+    case apc::ScrollAction_ScrollDirection_DOWN:
+      direction = Direction::kDown;
+      break;
+    case apc::ScrollAction_ScrollDirection_UNKNOWN_SCROLL_DIRECTION:
+    case apc::
+        ScrollAction_ScrollDirection_ScrollAction_ScrollDirection_INT_MIN_SENTINEL_DO_NOT_USE_:
+    case apc::
+        ScrollAction_ScrollDirection_ScrollAction_ScrollDirection_INT_MAX_SENTINEL_DO_NOT_USE_:
+      // TODO(crbug.com/412700289): Revert once this is set.
+      direction = Direction::kDown;
+      break;
+  }
+
+  return std::make_unique<ScrollToolRequest>(
+      page.tab_handle, page.document_identifier, page_target, direction,
+      action.distance());
+}
+
+std::unique_ptr<ToolRequest> CreateMoveMouseRequest(
+    const MoveMouseAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  PageScopedParams page = GetPageScopedParams(action, deprecated_fallback_tab);
+  if (!action.has_target() || page.tab_handle == TabHandle::Null()) {
+    return nullptr;
+  }
+
+  return std::make_unique<MoveMouseToolRequest>(
+      page.tab_handle, page.document_identifier,
+      ToPageToolTarget(action.target()));
+}
+
+std::unique_ptr<ToolRequest> CreateDragAndReleaseRequest(
+    const DragAndReleaseAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  PageScopedParams page = GetPageScopedParams(action, deprecated_fallback_tab);
+
+  if (!action.has_from_target() || !action.has_to_target() ||
+      page.tab_handle == TabHandle::Null()) {
+    return nullptr;
+  }
+
+  return std::make_unique<DragAndReleaseToolRequest>(
+      page.tab_handle, page.document_identifier,
+      ToPageToolTarget(action.from_target()),
+      ToPageToolTarget(action.to_target()));
+}
+
+std::unique_ptr<ToolRequest> CreateSelectRequest(
+    const SelectAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  PageScopedParams page = GetPageScopedParams(action, deprecated_fallback_tab);
+  if (!action.has_value() || !action.has_target() ||
+      page.tab_handle == TabHandle::Null()) {
+    return nullptr;
+  }
+
+  return std::make_unique<SelectToolRequest>(
+      page.tab_handle, page.document_identifier,
+      ToPageToolTarget(action.target()), action.value());
+}
+
+std::unique_ptr<ToolRequest> CreateNavigateRequest(
+    const NavigateAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  TabHandle tab_handle = GetTabHandle(action, deprecated_fallback_tab);
+  if (!action.has_url() || tab_handle == TabHandle::Null()) {
+    return nullptr;
+  }
+
+  return std::make_unique<NavigateToolRequest>(tab_handle, GURL(action.url()));
+}
+
+std::unique_ptr<ToolRequest> CreateCreateTabRequest(
+    const CreateTabAction& action) {
+  if (!action.has_window_id()) {
+    return nullptr;
+  }
+
+  int32_t window_id = action.window_id();
+
+  // TODO(bokan): Is the foreground bit always set? If not, should this return
+  // an error or default to what? For now we default to foreground.
+  WindowOpenDisposition disposition =
+      !action.has_foreground() || action.foreground()
+          ? WindowOpenDisposition::NEW_FOREGROUND_TAB
+          : WindowOpenDisposition::NEW_BACKGROUND_TAB;
+
+  return std::make_unique<CreateTabToolRequest>(window_id, disposition);
+}
+
+std::unique_ptr<ToolRequest> CreateActivateTabRequest(
+    const ActivateTabAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  tabs::TabHandle tab_handle;
+  if (action.has_tab_id()) {
+    tab_handle = tabs::TabHandle(action.tab_id());
+  } else if (deprecated_fallback_tab) {
+    tab_handle = deprecated_fallback_tab->GetHandle();
+  } else {
+    return nullptr;
+  }
+
+  return std::make_unique<ActivateTabToolRequest>(tab_handle);
+}
+
+std::unique_ptr<ToolRequest> CreateCloseTabRequest(
+    const CloseTabAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  tabs::TabHandle tab_handle;
+  if (action.has_tab_id()) {
+    tab_handle = tabs::TabHandle(action.tab_id());
+  } else if (deprecated_fallback_tab) {
+    tab_handle = deprecated_fallback_tab->GetHandle();
+  } else {
+    return nullptr;
+  }
+
+  return std::make_unique<CloseTabToolRequest>(tab_handle);
+}
+
+std::unique_ptr<ToolRequest> CreateBackRequest(
+    const HistoryBackAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  tabs::TabHandle tab_handle;
+  if (action.has_tab_id()) {
+    tab_handle = tabs::TabHandle(action.tab_id());
+  } else if (deprecated_fallback_tab) {
+    tab_handle = deprecated_fallback_tab->GetHandle();
+  } else {
+    return nullptr;
+  }
+
+  return std::make_unique<HistoryToolRequest>(
+      tab_handle, HistoryToolRequest::Direction::kBack);
+}
+
+std::unique_ptr<ToolRequest> CreateForwardRequest(
+    const HistoryForwardAction& action,
+    TabInterface* deprecated_fallback_tab) {
+  tabs::TabHandle tab_handle;
+  if (action.has_tab_id()) {
+    tab_handle = tabs::TabHandle(action.tab_id());
+  } else if (deprecated_fallback_tab) {
+    tab_handle = deprecated_fallback_tab->GetHandle();
+  } else {
+    return nullptr;
+  }
+
+  return std::make_unique<HistoryToolRequest>(
+      tab_handle, HistoryToolRequest::Direction::kForward);
+}
+
+std::unique_ptr<ToolRequest> CreateWaitRequest(const WaitAction& action) {
+  constexpr base::TimeDelta kWaitTime = base::Seconds(3);
+  return std::make_unique<WaitToolRequest>(kWaitTime);
 }
 
 }  // namespace
 
-const ActionTarget* ExtractTarget(const Action& action) {
+std::unique_ptr<ToolRequest> CreateToolRequest(
+    const optimization_guide::proto::Action& action,
+    TabInterface* deprecated_fallback_tab) {
   switch (action.action_case()) {
-    case Action::ActionCase::kClick:
-      if (!action.click().has_target()) {
-        return nullptr;
-      }
-      return &action.click().target();
-    case Action::ActionCase::kType:
-      if (!action.type().has_target()) {
-        return nullptr;
-      }
-      return &action.type().target();
-    case Action::ActionCase::kScroll:
-      if (!action.scroll().has_target()) {
-        return nullptr;
-      }
-      return &action.scroll().target();
-    case Action::ActionCase::kMoveMouse:
-      if (!action.move_mouse().has_target()) {
-        return nullptr;
-      }
-      return &action.move_mouse().target();
-    case Action::ActionCase::kDragAndRelease:
-      if (!action.drag_and_release().has_from_target()) {
-        return nullptr;
-      }
-      return &action.drag_and_release().from_target();
-    case Action::ActionCase::kSelect:
-      if (!action.select().has_target()) {
-        return nullptr;
-      }
-      return &action.select().target();
-    case Action::ActionCase::kNavigate:
-    case Action::ActionCase::kBack:
-    case Action::ActionCase::kForward:
-    case Action::ActionCase::kWait:
-    case Action::ActionCase::kCreateTab:
-    case Action::ActionCase::kCloseTab:
-    case Action::ActionCase::kActivateTab:
-    case Action::ActionCase::kCreateWindow:
-    case Action::ActionCase::kCloseWindow:
-    case Action::ActionCase::kActivateWindow:
-    case Action::ActionCase::kYieldToUser:
-    case Action::ActionCase::ACTION_NOT_SET:
-      return nullptr;
-  }
-}
-
-RenderFrameHost* GetRenderFrameForDocumentIdentifier(
-    content::WebContents& web_contents,
-    std::string_view target_document_token) {
-  RenderFrameHost* render_frame = nullptr;
-  web_contents.ForEachRenderFrameHostWithAction([&target_document_token,
-                                                 &render_frame](
-                                                    RenderFrameHost* rfh) {
-    // Skip inactive frame and its children.
-    if (!rfh->IsActive()) {
-      return RenderFrameHost::FrameIterationAction::kSkipChildren;
+    case optimization_guide::proto::Action::kClick: {
+      const ClickAction& click_action = action.click();
+      return CreateClickRequest(click_action, deprecated_fallback_tab);
     }
-    auto* user_data = DocumentIdentifierUserData::GetForCurrentDocument(rfh);
-    if (user_data && user_data->serialized_token() == target_document_token) {
-      render_frame = rfh;
-      return RenderFrameHost::FrameIterationAction::kStop;
+    case optimization_guide::proto::Action::kType: {
+      const TypeAction& type_action = action.type();
+      return CreateTypeRequest(type_action, deprecated_fallback_tab);
     }
-    return RenderFrameHost::FrameIterationAction::kContinue;
-  });
-  return render_frame;
-}
-
-RenderFrameHost* GetRootFrameForWidget(content::WebContents& web_contents,
-                                       RenderWidgetHost* rwh) {
-  RenderFrameHost* root_frame = nullptr;
-  web_contents.ForEachRenderFrameHostWithAction([rwh, &root_frame](
-                                                    RenderFrameHost* rfh) {
-    if (!rfh->IsActive()) {
-      return RenderFrameHost::FrameIterationAction::kSkipChildren;
+    case optimization_guide::proto::Action::kScroll: {
+      const ScrollAction& scroll_action = action.scroll();
+      return CreateScrollRequest(scroll_action, deprecated_fallback_tab);
     }
-    // A frame is a local root if it has no parent or if its parent belongs
-    // to a different widget. We are looking for the local root frame
-    // associated with the target widget.
-    if (rfh->GetRenderWidgetHost() == rwh &&
-        (!rfh->GetParent() || rfh->GetParent()->GetRenderWidgetHost() != rwh)) {
-      root_frame = rfh;
-      return RenderFrameHost::FrameIterationAction::kStop;
+    case optimization_guide::proto::Action::kMoveMouse: {
+      const MoveMouseAction& move_mouse_action = action.move_mouse();
+      return CreateMoveMouseRequest(move_mouse_action, deprecated_fallback_tab);
     }
-    return RenderFrameHost::FrameIterationAction::kContinue;
-  });
-  return root_frame;
-}
-
-RenderFrameHost* FindTargetLocalRootFrame(WebContents& web_contents,
-                                          const Action& action) {
-  // If the action targets the tab as a whole, the target is the primary main
-  // frame.
-  if (IsTargetingTab(action)) {
-    return web_contents.GetPrimaryMainFrame();
+    case optimization_guide::proto::Action::kDragAndRelease: {
+      const DragAndReleaseAction& drag_action = action.drag_and_release();
+      return CreateDragAndReleaseRequest(drag_action, deprecated_fallback_tab);
+    }
+    case optimization_guide::proto::Action::kSelect: {
+      const SelectAction& select_action = action.select();
+      return CreateSelectRequest(select_action, deprecated_fallback_tab);
+    }
+    case optimization_guide::proto::Action::kNavigate: {
+      const NavigateAction& navigate_action = action.navigate();
+      return CreateNavigateRequest(navigate_action, deprecated_fallback_tab);
+    }
+    case optimization_guide::proto::Action::kBack: {
+      const HistoryBackAction& back_action = action.back();
+      return CreateBackRequest(back_action, deprecated_fallback_tab);
+    }
+    case optimization_guide::proto::Action::kForward: {
+      const HistoryForwardAction& forward_action = action.forward();
+      return CreateForwardRequest(forward_action, deprecated_fallback_tab);
+    }
+    case optimization_guide::proto::Action::kWait: {
+      const WaitAction& wait_action = action.wait();
+      return CreateWaitRequest(wait_action);
+    }
+    case optimization_guide::proto::Action::kCreateTab: {
+      const CreateTabAction& create_tab_action = action.create_tab();
+      return CreateCreateTabRequest(create_tab_action);
+    }
+    case optimization_guide::proto::Action::kCloseTab: {
+      const CloseTabAction& close_tab_action = action.close_tab();
+      return CreateCloseTabRequest(close_tab_action, deprecated_fallback_tab);
+    }
+    case optimization_guide::proto::Action::kActivateTab: {
+      const ActivateTabAction& activate_tab_action = action.activate_tab();
+      return CreateActivateTabRequest(activate_tab_action,
+                                      deprecated_fallback_tab);
+    }
+    case optimization_guide::proto::Action::kCreateWindow:
+    case optimization_guide::proto::Action::kCloseWindow:
+    case optimization_guide::proto::Action::kActivateWindow:
+    case optimization_guide::proto::Action::kYieldToUser:
+      NOTIMPLEMENTED();
+      break;
+    case optimization_guide::proto::Action::ACTION_NOT_SET:
+      ACTOR_LOG() << "Action Type Not Set!";
+      break;
   }
 
-  const ActionTarget* target = ExtractTarget(action);
-  if (!target) {
-    // A scroll action may not have a target, which indicates scrolling the main
-    // frame.
-    if (action.action_case() == Action::kScroll) {
-      return web_contents.GetPrimaryMainFrame();
-    }
-    ACTOR_LOG() << "Page-level BrowserAction did not specify an ActionTarget.";
-    return nullptr;
-  }
-
-  if (target->has_content_node_id()) {
-    const std::string& serialized_token =
-        target->document_identifier().serialized_token();
-
-    RenderFrameHost* target_frame =
-        GetRenderFrameForDocumentIdentifier(web_contents, serialized_token);
-    // After finding the target frame, walk up to its local root.
-    return GetLocalRoot(target_frame);
-  }
-
-  if (target->has_coordinate()) {
-    const gfx::PointF target_point =
-        gfx::PointF(target->coordinate().x(), target->coordinate().y());
-    RenderWidgetHost* target_rwh = web_contents.FindWidgetAtPoint(target_point);
-    if (!target_rwh) {
-      return nullptr;
-    }
-    return GetRootFrameForWidget(web_contents, target_rwh);
-  }
-
-  ACTOR_LOG() << "Page-level BrowserAction ActionTarget is invalid.";
   return nullptr;
 }
 
diff --git a/chrome/browser/actor/browser_action_util.h b/chrome/browser/actor/browser_action_util.h
index ada981d..6f9eee7 100644
--- a/chrome/browser/actor/browser_action_util.h
+++ b/chrome/browser/actor/browser_action_util.h
@@ -5,50 +5,35 @@
 #ifndef CHROME_BROWSER_ACTOR_BROWSER_ACTION_UTIL_H_
 #define CHROME_BROWSER_ACTOR_BROWSER_ACTION_UTIL_H_
 
-#include <string_view>
+#include <memory>
 
-namespace content {
-class RenderFrameHost;
-class RenderWidgetHost;
-class WebContents;
-}  // namespace content
+// Conversion function for turning optimization_guide::proto::* types into
+// ToolRequests usable by the actor framework.
+// TODO(bokan): Rename to actor_proto_conversion.h|cc
 
 namespace optimization_guide::proto {
 class Action;
-class ActionTarget;
-class DocumentIdentifier;
 }  // namespace optimization_guide::proto
 
+namespace tabs {
+class TabInterface;
+}
+
 namespace actor {
+class ToolRequest;
 
-// Helper to pick out the ActionTarget from the specific type of action in the
-// Action proto. Returns nullptr actions which don't contain this
-// field (tab-targeting actions).
-const optimization_guide::proto::ActionTarget* ExtractTarget(
-    const optimization_guide::proto::Action& action);
-
-// Iterates through the frame tree to find the active RenderFrameHost associated
-// with a given DocumentIdentifier.
-content::RenderFrameHost* GetRenderFrameForDocumentIdentifier(
-    content::WebContents& web_contents,
-    const std::string_view target_document_token);
-
-// Iterates through the frame tree to find the local root RenderFrameHost
-// associated with a given RenderWidgetHost. A local root is a frame that is
-// the highest in its frame subtree to be associated with the widget.
-content::RenderFrameHost* GetRootFrameForWidget(
-    content::WebContents& web_contents,
-    content::RenderWidgetHost* rwh);
-
-// Finds local root frame in the given WebContents that's requested by the
-// given action. For a tab-targeting action, this returns the current primary
-// main frame. For frame-targeting actions, this returns a nullptr if the
-// frame is no longer active or has a new document since the action was
-// generated. Otherwise it returns the local root RenderFrameHost that contains
-// the target node or coordinate.
-content::RenderFrameHost* FindTargetLocalRootFrame(
-    content::WebContents& web_contents,
-    const optimization_guide::proto::Action& action);
+// Build a ToolRequest from the provided optimization_guide Action proto. If the
+// action proto doesn't provide a tab_id, and the fallback_tab parameter is
+// provided (non-null), the fallback_tab will be used as the acting tab.
+// However, this parameter will eventually be phased out and clients will be
+// expected to always provide a tab id on each Action. Returns nullptr if the
+// action is invalid.
+// TODO(https://crbug.com/411462297): The client should eventually always
+// provide a tab id for actions where one is needed. Remove this parameter when
+// that's done.
+std::unique_ptr<ToolRequest> CreateToolRequest(
+    const optimization_guide::proto::Action& action,
+    tabs::TabInterface* deprecated_fallback_tab);
 
 }  // namespace actor
 
diff --git a/chrome/browser/actor/execution_engine.cc b/chrome/browser/actor/execution_engine.cc
index deadb22..8a28152 100644
--- a/chrome/browser/actor/execution_engine.cc
+++ b/chrome/browser/actor/execution_engine.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/actor/browser_action_util.h"
 #include "chrome/browser/actor/site_policy.h"
 #include "chrome/browser/actor/tools/tool_controller.h"
+#include "chrome/browser/actor/tools/tool_request.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
@@ -27,9 +28,7 @@
 #include "chrome/common/actor/action_result.h"
 #include "chrome/common/chrome_features.h"
 #include "components/optimization_guide/content/browser/page_content_proto_provider.h"
-#include "components/optimization_guide/content/browser/page_content_proto_util.h"
 #include "components/optimization_guide/proto/features/actions_data.pb.h"
-#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
 #include "components/tabs/public/tab_interface.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
@@ -83,36 +82,6 @@
   }
 }
 
-// Whether the action requires a frame.
-bool ActionRequiresFrame(const Action& action) {
-  switch (action.action_case()) {
-    case Action::kClick:
-    case Action::kType:
-    case Action::kScroll:
-    case Action::kMoveMouse:
-    case Action::kDragAndRelease:
-    case Action::kSelect:
-      return true;
-    // TODO(crbug.com/411462297): These requests do not require frames. For now
-    // we return `true` to preserve existing behavior.
-    case Action::kBack:
-    case Action::kForward:
-    case Action::kNavigate:
-    case Action::kWait:
-      return true;
-
-    case Action::kCreateTab:
-    case Action::kCloseTab:
-    case Action::kActivateTab:
-    case Action::kCreateWindow:
-    case Action::kCloseWindow:
-    case Action::kActivateWindow:
-    case Action::kYieldToUser:
-    case Action::ACTION_NOT_SET:
-      return false;
-  }
-}
-
 tabs::TabHandle GetTabHandleFromAction(
     const optimization_guide::proto::Action& action) {
   switch (action.action_case()) {
@@ -183,51 +152,6 @@
       FROM_HERE, base::BindOnce(std::move(callback), std::move(result)));
 }
 
-// Perform validation based on APC and document identifier for coordinate based
-// target to compare the candidate frame with the target frame identified in
-// last observation.
-bool ValidateTargetFrameCandidate(
-    const ActionTarget* target,
-    RenderFrameHost* candidate_frame,
-    WebContents& web_contents,
-    AnnotatedPageContent* last_observed_page_content) {
-  std::string target_document_token;
-  // The document identifier in the action itself takes highest precedence.
-  if (target->has_document_identifier()) {
-    target_document_token = target->document_identifier().serialized_token();
-  } else if (last_observed_page_content) {
-    // Otherwise, fall back to APC hit testing.
-    // TODO(crbug.com/426021822): FindNodeAtPoint does not handle corner cases
-    // like clip paths. Need more checks to ensure we don't drop actions
-    // unnecessarily.
-    std::optional<optimization_guide::TargetNodeInfo> target_node_info =
-        optimization_guide::FindNodeAtPoint(*last_observed_page_content,
-                                            target->coordinate());
-    if (target_node_info) {
-      target_document_token =
-          target_node_info->document_identifier.serialized_token();
-    } else {
-      return false;
-    }
-  } else {
-    // An error if document identifier isn't set on target and no cached APC
-    // available
-    return false;
-  }
-
-  RenderFrameHost* apc_target_frame =
-      GetRenderFrameForDocumentIdentifier(web_contents, target_document_token);
-
-  // Only return the candidate if its RenderWidgetHost matches the target
-  // and it's also a local root frame(i.e. has no parent or parent has
-  // a different RenderWidgetHost)
-  if (apc_target_frame && apc_target_frame->GetRenderWidgetHost() ==
-                              candidate_frame->GetRenderWidgetHost()) {
-    return true;
-  }
-  return false;
-}
-
 }  // namespace
 
 ExecutionEngine::ExecutionEngine(Profile* profile)
@@ -258,6 +182,7 @@
 
 void ExecutionEngine::SetOwner(ActorTask* task) {
   task_ = task;
+  tool_controller_ = std::make_unique<ToolController>(task_->id(), *journal_);
 }
 
 // static
@@ -433,50 +358,23 @@
 
 void ExecutionEngine::ExecuteNextAction() {
   CHECK(actions_v1_ || actions_v2_);
+  CHECK(tool_controller_);
 
   const Action& action = GetNextAction();
   ++action_index_;
 
-  tabs::TabInterface* tab = GetTab(action);
-
-  if (ActionRequiresTab(action) && !tab) {
+  // TODO(bokan): ExecutionEngine shouldn't know about the Action proto, it
+  // should operate in terms of ToolRequest.
+  std::unique_ptr<ToolRequest> tool_request = CreateToolRequest(action, tab_);
+  if (!tool_request) {
     journal_->Log(GURL::EmptyGURL(), task_->id(), "Act Failed",
-                  "The tab is no longer present");
-    CompleteActions(MakeResult(mojom::ActionResultCode::kTabWentAway,
-                               "The tab is no longer present."));
+                  "Failed to convert ActionInformation proto to ToolRequest");
+    CompleteActions(MakeResult(mojom::ActionResultCode::kArgumentsInvalid));
     return;
   }
-  RenderFrameHost* target_frame_candidate = nullptr;
-  if (ActionRequiresFrame(action)) {
-    target_frame_candidate =
-        FindTargetLocalRootFrame(*tab->GetContents(), action);
 
-    if (!target_frame_candidate) {
-      journal_->Log(LastCommittedURLOfCurrentTask(), task_->id(), "Act Failed",
-                    "The target frame is no longer present in the tab.");
-      CompleteActions(
-          MakeResult(mojom::ActionResultCode::kFrameWentAway,
-                     "The target frame is no longer present in the tab."));
-      return;
-    }
-
-    const ActionTarget* target = ExtractTarget(action);
-
-    // Perform validation for coordinate based target only.
-    if (target && target->has_coordinate() &&
-        !ValidateTargetFrameCandidate(target, target_frame_candidate,
-                                      *tab->GetContents(),
-                                      last_observed_page_content_.get())) {
-      journal_->Log(LastCommittedURLOfCurrentTask(), task_->id(), "Act Failed",
-                    "The target frame has changed.");
-      CompleteActions(MakeResult(
-          mojom::ActionResultCode::kFrameLocationChangedSinceObservation,
-          "The target frame has changed."));
-      return;
-    }
-  }
-  tool_controller_.Invoke(
-      action, *journal_, task_->id(), tab, target_frame_candidate,
+  tool_controller_->Invoke(
+      std::move(tool_request), last_observed_page_content_.get(),
       base::BindOnce(&ExecutionEngine::FinishOneAction, GetWeakPtr()));
 }
 
diff --git a/chrome/browser/actor/execution_engine.h b/chrome/browser/actor/execution_engine.h
index 6cebd1a..3f11edc 100644
--- a/chrome/browser/actor/execution_engine.h
+++ b/chrome/browser/actor/execution_engine.h
@@ -170,7 +170,9 @@
   // Owns `this`.
   raw_ptr<ActorTask> task_;
 
-  ToolController tool_controller_;
+  // Created when task_ is set. Handles execution details for an individual tool
+  // request.
+  std::unique_ptr<ToolController> tool_controller_;
 
   // A sequence of actions that the model has requested. When it is finished
   // being processed it is reset.
diff --git a/chrome/browser/actor/tools/click_tool_request.cc b/chrome/browser/actor/tools/click_tool_request.cc
new file mode 100644
index 0000000..15a6bcd
--- /dev/null
+++ b/chrome/browser/actor/tools/click_tool_request.cc
@@ -0,0 +1,58 @@
+// Copyright 2025 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/actor/tools/click_tool_request.h"
+
+#include "chrome/common/actor.mojom.h"
+
+namespace actor {
+
+using ::tabs::TabHandle;
+
+ClickToolRequest::ClickToolRequest(TabHandle tab_handle,
+                                   std::string_view document_identifier,
+                                   const Target& target,
+                                   ClickType type,
+                                   ClickCount count)
+    : PageToolRequest(tab_handle, document_identifier, target),
+      click_type_(type),
+      click_count_(count) {}
+
+ClickToolRequest::~ClickToolRequest() = default;
+
+std::string ClickToolRequest::JournalEvent() const {
+  return "Click";
+}
+
+mojom::ToolActionPtr ClickToolRequest::ToMojoToolAction() const {
+  auto click = mojom::ClickAction::New();
+
+  click->target = PageToolRequest::ToMojoToolTarget(GetTarget());
+
+  switch (click_type_) {
+    case ClickType::kLeft:
+      click->type = actor::mojom::ClickAction::Type::kLeft;
+      break;
+    case ClickType::kRight:
+      click->type = actor::mojom::ClickAction::Type::kRight;
+      break;
+  }
+
+  switch (click_count_) {
+    case ClickCount::kSingle:
+      click->count = actor::mojom::ClickAction::Count::kSingle;
+      break;
+    case ClickCount::kDouble:
+      click->count = actor::mojom::ClickAction::Count::kDouble;
+      break;
+  }
+
+  return mojom::ToolAction::NewClick(std::move(click));
+}
+
+std::unique_ptr<PageToolRequest> ClickToolRequest::Clone() const {
+  return std::make_unique<ClickToolRequest>(*this);
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/click_tool_request.h b/chrome/browser/actor/tools/click_tool_request.h
new file mode 100644
index 0000000..cff83f3
--- /dev/null
+++ b/chrome/browser/actor/tools/click_tool_request.h
@@ -0,0 +1,42 @@
+// Copyright 2025 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_ACTOR_TOOLS_CLICK_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_CLICK_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/browser/actor/tools/page_tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+
+namespace actor {
+
+class ClickToolRequest : public PageToolRequest {
+ public:
+  enum class ClickType { kLeft, kRight };
+  enum class ClickCount { kSingle, kDouble };
+
+  ClickToolRequest(tabs::TabHandle tab_handle,
+                   std::string_view document_identifier,
+                   const Target& target,
+                   ClickType type,
+                   ClickCount count);
+  ~ClickToolRequest() override;
+
+  // ToolRequest
+  std::string JournalEvent() const override;
+
+  // PageToolRequest
+  mojom::ToolActionPtr ToMojoToolAction() const override;
+  std::unique_ptr<PageToolRequest> Clone() const override;
+
+ private:
+  ClickType click_type_;
+  ClickCount click_count_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_CLICK_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/drag_and_release_tool_request.cc b/chrome/browser/actor/tools/drag_and_release_tool_request.cc
new file mode 100644
index 0000000..4ce8671
--- /dev/null
+++ b/chrome/browser/actor/tools/drag_and_release_tool_request.cc
@@ -0,0 +1,41 @@
+// Copyright 2025 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/actor/tools/drag_and_release_tool_request.h"
+
+#include "chrome/common/actor.mojom.h"
+
+namespace actor {
+
+using ::tabs::TabHandle;
+
+DragAndReleaseToolRequest::DragAndReleaseToolRequest(
+    TabHandle tab_handle,
+    std::string_view document_identifier,
+    const Target& from_target,
+    const Target& to_target)
+    : PageToolRequest(tab_handle, document_identifier, from_target),
+      from_target_(from_target),
+      to_target_(to_target) {}
+
+DragAndReleaseToolRequest::~DragAndReleaseToolRequest() = default;
+
+std::string DragAndReleaseToolRequest::JournalEvent() const {
+  return "DragAndRelease";
+}
+
+mojom::ToolActionPtr DragAndReleaseToolRequest::ToMojoToolAction() const {
+  auto drag = mojom::DragAndReleaseAction::New();
+
+  drag->from_target = PageToolRequest::ToMojoToolTarget(from_target_);
+  drag->to_target = PageToolRequest::ToMojoToolTarget(to_target_);
+
+  return mojom::ToolAction::NewDragAndRelease(std::move(drag));
+}
+
+std::unique_ptr<PageToolRequest> DragAndReleaseToolRequest::Clone() const {
+  return std::make_unique<DragAndReleaseToolRequest>(*this);
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/drag_and_release_tool_request.h b/chrome/browser/actor/tools/drag_and_release_tool_request.h
new file mode 100644
index 0000000..31d331c
--- /dev/null
+++ b/chrome/browser/actor/tools/drag_and_release_tool_request.h
@@ -0,0 +1,41 @@
+// Copyright 2025 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_ACTOR_TOOLS_DRAG_AND_RELEASE_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_DRAG_AND_RELEASE_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/browser/actor/tools/page_tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+
+namespace actor {
+
+// Simulates a mouse press, move, release sequence. As this is a PageTool, the
+// sequence can only span a local subtree (i.e. cannot drag and drop between
+// OOPIFs or RenderWidgetHosts).
+class DragAndReleaseToolRequest : public PageToolRequest {
+ public:
+  DragAndReleaseToolRequest(tabs::TabHandle tab_handle,
+                            std::string_view document_identifier,
+                            const Target& from_target,
+                            const Target& to_target);
+  ~DragAndReleaseToolRequest() override;
+
+  // ToolRequest
+  std::string JournalEvent() const override;
+
+  // PageToolRequest
+  mojom::ToolActionPtr ToMojoToolAction() const override;
+  std::unique_ptr<PageToolRequest> Clone() const override;
+
+ private:
+  Target from_target_;
+  Target to_target_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_DRAG_AND_RELEASE_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/history_tool.cc b/chrome/browser/actor/tools/history_tool.cc
index 2c0b9ef..e5098739 100644
--- a/chrome/browser/actor/tools/history_tool.cc
+++ b/chrome/browser/actor/tools/history_tool.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/actor/tools/history_tool.h"
 
 #include "base/time/time.h"
+#include "chrome/browser/actor/tools/observation_delay_controller.h"
 #include "chrome/browser/actor/tools/tool_callbacks.h"
 #include "chrome/common/actor.mojom.h"
 #include "chrome/common/actor/action_result.h"
@@ -29,8 +30,11 @@
 using ::content::NavigationController;
 using ::content::NavigationHandle;
 using ::content::WebContents;
+using ::tabs::TabHandle;
+using ::tabs::TabInterface;
 
-HistoryTool::HistoryTool(WebContents& web_contents, Direction direction)
+HistoryTool::HistoryTool(WebContents& web_contents,
+                         HistoryToolRequest::Direction direction)
     : WebContentsObserver(&web_contents), direction_(direction) {}
 
 HistoryTool::~HistoryTool() = default;
@@ -39,9 +43,11 @@
   NavigationController& controller = web_contents()->GetController();
   mojom::ActionResultPtr result;
 
-  if (direction_ == kBack && !controller.CanGoBack()) {
+  if (direction_ == HistoryToolRequest::Direction::kBack &&
+      !controller.CanGoBack()) {
     result = MakeResult(mojom::ActionResultCode::kHistoryNoBackEntries);
-  } else if (direction_ == kForward && !controller.CanGoForward()) {
+  } else if (direction_ == HistoryToolRequest::Direction::kForward &&
+             !controller.CanGoForward()) {
     result = MakeResult(mojom::ActionResultCode::kHistoryNoForwardEntries);
   } else {
     result = MakeOkResult();
@@ -69,10 +75,10 @@
   // is manually dismissed by the user but we may want to provide automatic
   // resolution here.
 
-  if (direction_ == kBack) {
+  if (direction_ == HistoryToolRequest::Direction::kBack) {
     pending_navigations_ = web_contents()->GetController().GoBack();
   } else {
-    CHECK_EQ(direction_, kForward);
+    CHECK_EQ(direction_, HistoryToolRequest::Direction::kForward);
     pending_navigations_ = web_contents()->GetController().GoForward();
   }
 
@@ -93,7 +99,14 @@
 }
 
 std::string HistoryTool::JournalEvent() const {
-  return direction_ == kBack ? "Back" : "Forward";
+  return direction_ == HistoryToolRequest::Direction::kBack ? "Back"
+                                                            : "Forward";
+}
+
+std::unique_ptr<ObservationDelayController> HistoryTool::GetObservationDelayer()
+    const {
+  return std::make_unique<ObservationDelayController>(
+      *web_contents()->GetPrimaryMainFrame());
 }
 
 void HistoryTool::DidStartNavigation(NavigationHandle* navigation_handle) {
diff --git a/chrome/browser/actor/tools/history_tool.h b/chrome/browser/actor/tools/history_tool.h
index 1a03fc5..6262ee5 100644
--- a/chrome/browser/actor/tools/history_tool.h
+++ b/chrome/browser/actor/tools/history_tool.h
@@ -5,12 +5,16 @@
 #ifndef CHROME_BROWSER_ACTOR_TOOLS_HISTORY_TOOL_H_
 #define CHROME_BROWSER_ACTOR_TOOLS_HISTORY_TOOL_H_
 
+#include <memory>
 #include <optional>
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
+#include "chrome/browser/actor/tools/history_tool_request.h"
 #include "chrome/browser/actor/tools/tool.h"
+#include "chrome/browser/actor/tools/tool_request.h"
 #include "chrome/common/actor.mojom-forward.h"
+#include "components/tabs/public/tab_interface.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
@@ -25,12 +29,8 @@
 // Performs a history navigation in a WebContents.
 class HistoryTool : public Tool, content::WebContentsObserver {
  public:
-  enum Direction {
-    kBack,
-    kForward,
-  };
-
-  HistoryTool(content::WebContents& web_contents, Direction direction);
+  HistoryTool(content::WebContents& web_contents,
+              HistoryToolRequest::Direction direction);
   ~HistoryTool() override;
 
   // actor::Tool
@@ -38,6 +38,8 @@
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
   std::string JournalEvent() const override;
+  std::unique_ptr<ObservationDelayController> GetObservationDelayer()
+      const override;
 
   // content::WebContentsObserver
   void DidStartNavigation(
@@ -53,7 +55,7 @@
   bool IsInvokeInProgress() const;
 
   // Whether the navigation is backwards or forwards in session history.
-  Direction direction_;
+  HistoryToolRequest::Direction direction_;
 
   // This class tracks all navigation handles created as a result of the history
   // traversal in `pending_navigations_`. However, these navigations may or may
diff --git a/chrome/browser/actor/tools/history_tool_request.cc b/chrome/browser/actor/tools/history_tool_request.cc
new file mode 100644
index 0000000..ded0730
--- /dev/null
+++ b/chrome/browser/actor/tools/history_tool_request.cc
@@ -0,0 +1,38 @@
+// Copyright 2025 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/actor/tools/history_tool_request.h"
+
+#include "chrome/browser/actor/tools/history_tool.h"
+#include "chrome/common/actor.mojom.h"
+#include "chrome/common/actor/action_result.h"
+
+namespace actor {
+
+using ::tabs::TabHandle;
+using ::tabs::TabInterface;
+
+HistoryToolRequest::HistoryToolRequest(tabs::TabHandle tab, Direction direction)
+    : TabToolRequest(tab), direction_(direction) {}
+HistoryToolRequest::~HistoryToolRequest() = default;
+
+ToolRequest::CreateToolResult HistoryToolRequest::CreateTool(
+    AggregatedJournal& journal) const {
+  TabInterface* tab = GetTabHandle().Get();
+
+  if (!tab) {
+    return {/*tool=*/nullptr, MakeResult(mojom::ActionResultCode::kTabWentAway,
+                                         "The tab is no longer present.")};
+  }
+
+  CHECK(tab->GetContents());
+  return {std::make_unique<HistoryTool>(*tab->GetContents(), direction_),
+          MakeOkResult()};
+}
+
+std::string HistoryToolRequest::JournalEvent() const {
+  return "History";
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/history_tool_request.h b/chrome/browser/actor/tools/history_tool_request.h
new file mode 100644
index 0000000..16f9c17
--- /dev/null
+++ b/chrome/browser/actor/tools/history_tool_request.h
@@ -0,0 +1,38 @@
+// Copyright 2025 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_ACTOR_TOOLS_HISTORY_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_HISTORY_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+#include "url/gurl.h"
+
+namespace actor {
+
+// Invokes a history back or forward traversal in a specified tab.
+class HistoryToolRequest : public TabToolRequest {
+ public:
+  enum class Direction {
+    kBack,
+    kForward,
+  };
+
+  HistoryToolRequest(tabs::TabHandle handle, Direction direction);
+  ~HistoryToolRequest() override;
+
+  // ToolRequest
+  CreateToolResult CreateTool(AggregatedJournal& journal) const override;
+  std::string JournalEvent() const override;
+
+  // Whether the navigation is backwards or forwards in session history.
+  Direction direction_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_HISTORY_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/move_mouse_tool_request.cc b/chrome/browser/actor/tools/move_mouse_tool_request.cc
new file mode 100644
index 0000000..41a1a3a
--- /dev/null
+++ b/chrome/browser/actor/tools/move_mouse_tool_request.cc
@@ -0,0 +1,36 @@
+// Copyright 2025 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/actor/tools/move_mouse_tool_request.h"
+
+#include "chrome/common/actor.mojom.h"
+
+namespace actor {
+
+using ::tabs::TabHandle;
+
+MoveMouseToolRequest::MoveMouseToolRequest(TabHandle tab_handle,
+                                           std::string_view document_identifier,
+                                           const Target& target)
+    : PageToolRequest(tab_handle, document_identifier, target) {}
+
+MoveMouseToolRequest::~MoveMouseToolRequest() = default;
+
+std::string MoveMouseToolRequest::JournalEvent() const {
+  return "MoveMouse";
+}
+
+mojom::ToolActionPtr MoveMouseToolRequest::ToMojoToolAction() const {
+  auto move_mouse = mojom::MouseMoveAction::New();
+
+  move_mouse->target = PageToolRequest::ToMojoToolTarget(GetTarget());
+
+  return mojom::ToolAction::NewMouseMove(std::move(move_mouse));
+}
+
+std::unique_ptr<PageToolRequest> MoveMouseToolRequest::Clone() const {
+  return std::make_unique<MoveMouseToolRequest>(*this);
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/move_mouse_tool_request.h b/chrome/browser/actor/tools/move_mouse_tool_request.h
new file mode 100644
index 0000000..fbd1a2d
--- /dev/null
+++ b/chrome/browser/actor/tools/move_mouse_tool_request.h
@@ -0,0 +1,34 @@
+// Copyright 2025 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_ACTOR_TOOLS_MOVE_MOUSE_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_MOVE_MOUSE_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/browser/actor/tools/page_tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+
+namespace actor {
+
+// Injects a mouse move event at the given target.
+class MoveMouseToolRequest : public PageToolRequest {
+ public:
+  MoveMouseToolRequest(tabs::TabHandle tab_handle,
+                       std::string_view document_identifier,
+                       const Target& target);
+  ~MoveMouseToolRequest() override;
+
+  // ToolRequest
+  std::string JournalEvent() const override;
+
+  // PageToolRequest
+  mojom::ToolActionPtr ToMojoToolAction() const override;
+  std::unique_ptr<PageToolRequest> Clone() const override;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_MOVE_MOUSE_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/navigate_tool.cc b/chrome/browser/actor/tools/navigate_tool.cc
index a0b0f963..07edfea 100644
--- a/chrome/browser/actor/tools/navigate_tool.cc
+++ b/chrome/browser/actor/tools/navigate_tool.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/actor/tools/navigate_tool.h"
 
+#include "chrome/browser/actor/tools/observation_delay_controller.h"
 #include "chrome/browser/actor/tools/tool_callbacks.h"
 #include "chrome/common/actor.mojom.h"
 #include "chrome/common/actor/action_result.h"
@@ -62,6 +63,12 @@
   return "Navigate";
 }
 
+std::unique_ptr<ObservationDelayController>
+NavigateTool::GetObservationDelayer() const {
+  return std::make_unique<ObservationDelayController>(
+      *web_contents()->GetPrimaryMainFrame());
+}
+
 void NavigateTool::DidFinishNavigation(NavigationHandle* navigation_handle) {
   // TODO(crbug.com/411748801): We should probably handle the case where the
   // page navigates before it's done loading. Common with client-side redirects.
diff --git a/chrome/browser/actor/tools/navigate_tool.h b/chrome/browser/actor/tools/navigate_tool.h
index 38c3cba..8a5f771 100644
--- a/chrome/browser/actor/tools/navigate_tool.h
+++ b/chrome/browser/actor/tools/navigate_tool.h
@@ -30,6 +30,8 @@
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
   std::string JournalEvent() const override;
+  std::unique_ptr<ObservationDelayController> GetObservationDelayer()
+      const override;
 
   // content::WebContentsObserver
   void DidFinishNavigation(
diff --git a/chrome/browser/actor/tools/navigate_tool_request.cc b/chrome/browser/actor/tools/navigate_tool_request.cc
new file mode 100644
index 0000000..518db2b
--- /dev/null
+++ b/chrome/browser/actor/tools/navigate_tool_request.cc
@@ -0,0 +1,37 @@
+// Copyright 2025 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/actor/tools/navigate_tool_request.h"
+
+#include "chrome/browser/actor/tools/navigate_tool.h"
+#include "chrome/common/actor.mojom.h"
+#include "chrome/common/actor/action_result.h"
+
+namespace actor {
+
+using ::tabs::TabHandle;
+using ::tabs::TabInterface;
+
+NavigateToolRequest::NavigateToolRequest(TabHandle tab_handle, GURL url)
+    : TabToolRequest(tab_handle), url_(url) {}
+
+NavigateToolRequest::~NavigateToolRequest() = default;
+
+ToolRequest::CreateToolResult NavigateToolRequest::CreateTool(
+    AggregatedJournal& journal) const {
+  TabInterface* tab = GetTabHandle().Get();
+  if (!tab) {
+    return {/*tool=*/nullptr, MakeResult(mojom::ActionResultCode::kTabWentAway,
+                                         "The tab is no longer present.")};
+  }
+
+  return {std::make_unique<NavigateTool>(*tab->GetContents(), url_),
+          MakeOkResult()};
+}
+
+std::string NavigateToolRequest::JournalEvent() const {
+  return "Navigate";
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/navigate_tool_request.h b/chrome/browser/actor/tools/navigate_tool_request.h
new file mode 100644
index 0000000..642c737
--- /dev/null
+++ b/chrome/browser/actor/tools/navigate_tool_request.h
@@ -0,0 +1,33 @@
+// Copyright 2025 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_ACTOR_TOOLS_NAVIGATE_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_NAVIGATE_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+#include "url/gurl.h"
+
+namespace actor {
+
+// Navigates a specified tab to a specified URL.
+class NavigateToolRequest : public TabToolRequest {
+ public:
+  NavigateToolRequest(tabs::TabHandle tab_handle, GURL url);
+  ~NavigateToolRequest() override;
+
+  // ToolRequest
+  CreateToolResult CreateTool(AggregatedJournal& journal) const override;
+  std::string JournalEvent() const override;
+
+ private:
+  GURL url_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_NAVIGATE_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/page_tool.cc b/chrome/browser/actor/tools/page_tool.cc
index 84c6b9ea..bf703e56 100644
--- a/chrome/browser/actor/tools/page_tool.cc
+++ b/chrome/browser/actor/tools/page_tool.cc
@@ -8,9 +8,16 @@
 #include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/actor/aggregated_journal.h"
 #include "chrome/browser/actor/execution_engine.h"
+#include "chrome/browser/actor/tools/observation_delay_controller.h"
+#include "chrome/browser/actor/tools/page_tool_request.h"
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
 #include "chrome/common/actor/action_result.h"
 #include "chrome/common/chrome_render_frame.mojom.h"
-#include "components/optimization_guide/proto/features/actions_data.pb.h"
+#include "components/optimization_guide/content/browser/page_content_proto_provider.h"
+#include "components/optimization_guide/content/browser/page_content_proto_util.h"
+#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
+#include "components/tabs/public/tab_interface.h"
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
@@ -20,173 +27,159 @@
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "ui/gfx/geometry/point.h"
 
-namespace {
+namespace actor {
 
 using ::content::GlobalRenderFrameHostId;
 using ::content::RenderFrameHost;
+using ::content::RenderWidgetHost;
 using ::content::WebContents;
 using ::content::WebContentsObserver;
-using ::optimization_guide::proto::Action;
-using ::optimization_guide::proto::ActionTarget;
-using ::optimization_guide::proto::ClickAction_ClickCount;
-using ::optimization_guide::proto::ClickAction_ClickType;
-using ::optimization_guide::proto::ScrollAction_ScrollDirection;
-using ::optimization_guide::proto::TypeAction_TypeMode;
+using ::optimization_guide::DocumentIdentifierUserData;
+using optimization_guide::proto::ActionTarget;
+using optimization_guide::proto::AnnotatedPageContent;
+using ::tabs::TabHandle;
+using ::tabs::TabInterface;
 
-void SetMojoTarget(const ActionTarget& target,
-                   actor::mojom::ToolTargetPtr& out_mojo_target) {
-  if (target.has_coordinate()) {
-    out_mojo_target = actor::mojom::ToolTarget::NewCoordinate(
-        gfx::Point(target.coordinate().x(), target.coordinate().y()));
-  } else {
-    // A ContentNodeId of 0 indicates the viewport. The mojo message indicates
-    // viewport by omitting a target.
-    if (target.content_node_id() > 0) {
-      out_mojo_target =
-          actor::mojom::ToolTarget::NewDomNodeId(target.content_node_id());
+namespace {
+
+// Finds the local root of a given RenderFrameHost. The local root is the
+// highest ancestor in the frame tree that shares the same RenderWidgetHost.
+RenderFrameHost* GetLocalRoot(RenderFrameHost* rfh) {
+  RenderFrameHost* local_root = rfh;
+  while (local_root && local_root->GetParent()) {
+    if (local_root->GetRenderWidgetHost() !=
+        local_root->GetParent()->GetRenderWidgetHost()) {
+      break;
     }
+    local_root = local_root->GetParent();
   }
+  return local_root;
 }
 
-// Set mojom for click action based on proto. Returns false if the proto does
-// not contain correct/sufficient information, true otherwise.
-bool SetClickToolArgs(actor::mojom::ClickActionPtr& click,
-                      const Action& action) {
-  SetMojoTarget(action.click().target(), click->target);
-
-  switch (action.click().click_type()) {
-    case ClickAction_ClickType::ClickAction_ClickType_LEFT:
-      click->type = actor::mojom::ClickAction::Type::kLeft;
-      break;
-    case ClickAction_ClickType::ClickAction_ClickType_RIGHT:
-      click->type = actor::mojom::ClickAction::Type::kRight;
-      break;
-    case ClickAction_ClickType::ClickAction_ClickType_UNKNOWN_CLICK_TYPE:
-    case ClickAction_ClickType::
-        ClickAction_ClickType_ClickAction_ClickType_INT_MAX_SENTINEL_DO_NOT_USE_:
-    case ClickAction_ClickType::
-        ClickAction_ClickType_ClickAction_ClickType_INT_MIN_SENTINEL_DO_NOT_USE_:
-      // TODO(issuetracker.google.com/412700289): Revert once this is set.
-      click->type = actor::mojom::ClickAction::Type::kLeft;
-      break;
-      // return false;
-  }
-
-  switch (action.click().click_count()) {
-    case ClickAction_ClickCount::ClickAction_ClickCount_SINGLE:
-      click->count = actor::mojom::ClickAction::Count::kSingle;
-      break;
-    case ClickAction_ClickCount::ClickAction_ClickCount_DOUBLE:
-      click->count = actor::mojom::ClickAction::Count::kDouble;
-      break;
-    case ClickAction_ClickCount::ClickAction_ClickCount_UNKNOWN_CLICK_COUNT:
-    case ClickAction_ClickCount::
-        ClickAction_ClickCount_ClickAction_ClickCount_INT_MIN_SENTINEL_DO_NOT_USE_:
-    case ClickAction_ClickCount::
-        ClickAction_ClickCount_ClickAction_ClickCount_INT_MAX_SENTINEL_DO_NOT_USE_:
-      // TODO(issuetracker.google.com/412700289): Revert once this is set.
-      click->count = actor::mojom::ClickAction::Count::kSingle;
-      break;
-      // return false;
-  }
-  return true;
+RenderFrameHost* GetRenderFrameForDocumentIdentifier(
+    content::WebContents& web_contents,
+    std::string_view target_document_token) {
+  RenderFrameHost* render_frame = nullptr;
+  web_contents.ForEachRenderFrameHostWithAction([&target_document_token,
+                                                 &render_frame](
+                                                    RenderFrameHost* rfh) {
+    // Skip inactive frame and its children.
+    if (!rfh->IsActive()) {
+      return RenderFrameHost::FrameIterationAction::kSkipChildren;
+    }
+    auto* user_data = DocumentIdentifierUserData::GetForCurrentDocument(rfh);
+    if (user_data && user_data->serialized_token() == target_document_token) {
+      render_frame = rfh;
+      return RenderFrameHost::FrameIterationAction::kStop;
+    }
+    return RenderFrameHost::FrameIterationAction::kContinue;
+  });
+  return render_frame;
 }
 
-// Set mojom for mouse move action based on proto.
-void SetMouseMoveToolArgs(actor::mojom::MouseMoveActionPtr& move,
-                          const Action& action) {
-  SetMojoTarget(action.move_mouse().target(), move->target);
+RenderFrameHost* GetRootFrameForWidget(content::WebContents& web_contents,
+                                       RenderWidgetHost* rwh) {
+  RenderFrameHost* root_frame = nullptr;
+  web_contents.ForEachRenderFrameHostWithAction([rwh, &root_frame](
+                                                    RenderFrameHost* rfh) {
+    if (!rfh->IsActive()) {
+      return RenderFrameHost::FrameIterationAction::kSkipChildren;
+    }
+    // A frame is a local root if it has no parent or if its parent belongs
+    // to a different widget. We are looking for the local root frame
+    // associated with the target widget.
+    if (rfh->GetRenderWidgetHost() == rwh &&
+        (!rfh->GetParent() || rfh->GetParent()->GetRenderWidgetHost() != rwh)) {
+      root_frame = rfh;
+      return RenderFrameHost::FrameIterationAction::kStop;
+    }
+    return RenderFrameHost::FrameIterationAction::kContinue;
+  });
+  return root_frame;
 }
 
-// Set mojom for type action based on proto.
-// Returns false if the proto does not contain correct/sufficient information,
-// true otherwise.
-bool SetTypeToolArgs(actor::mojom::TypeActionPtr& type_action,
-                     const Action& action) {
-  SetMojoTarget(action.type().target(), type_action->target);
-
-  type_action->text = action.type().text();
-  type_action->follow_by_enter = action.type().follow_by_enter();
-
-  // Map proto enum to mojom enum
-  switch (action.type().mode()) {
-    case TypeAction_TypeMode::TypeAction_TypeMode_DELETE_EXISTING:
-      type_action->mode = actor::mojom::TypeAction::Mode::kDeleteExisting;
-      break;
-    case TypeAction_TypeMode::TypeAction_TypeMode_PREPEND:
-      type_action->mode = actor::mojom::TypeAction::Mode::kPrepend;
-      break;
-    case TypeAction_TypeMode::TypeAction_TypeMode_APPEND:
-      type_action->mode = actor::mojom::TypeAction::Mode::kAppend;
-      break;
-    case TypeAction_TypeMode::TypeAction_TypeMode_UNKNOWN_TYPE_MODE:
-    case TypeAction_TypeMode::
-        TypeAction_TypeMode_TypeAction_TypeMode_INT_MIN_SENTINEL_DO_NOT_USE_:
-    case TypeAction_TypeMode::
-        TypeAction_TypeMode_TypeAction_TypeMode_INT_MAX_SENTINEL_DO_NOT_USE_:
-      // TODO(issuetracker.google.com/412700289): Revert once this is set.
-      type_action->mode = actor::mojom::TypeAction::Mode::kDeleteExisting;
-      break;
-      //      DLOG(ERROR) << "TypeAction proto type mode not supported"
-      //                  << action.type().mode();
-      //      return false;
+RenderFrameHost* FindTargetLocalRootFrame(
+    TabHandle tab_handle,
+    std::optional<std::string> document_identifier,
+    PageToolRequest::Target target) {
+  TabInterface* tab = tab_handle.Get();
+  if (!tab) {
+    return nullptr;
   }
 
-  return true;
-}
+  WebContents& contents = *tab->GetContents();
 
-bool SetScrollToolArgs(actor::mojom::ScrollActionPtr& scroll,
-                       const Action& action) {
-  if (action.scroll().has_target()) {
-    SetMojoTarget(action.scroll().target(), scroll->target);
+  if (std::holds_alternative<PageToolRequest::CoordinateTarget>(target)) {
+    auto coordinate = std::get<PageToolRequest::CoordinateTarget>(target);
+
+    RenderWidgetHost* target_rwh =
+        contents.FindWidgetAtPoint(gfx::PointF(coordinate));
+    if (!target_rwh) {
+      return nullptr;
+    }
+    return GetRootFrameForWidget(contents, target_rwh);
   }
-  switch (action.scroll().direction()) {
-    case ScrollAction_ScrollDirection::ScrollAction_ScrollDirection_LEFT:
-      scroll->direction = actor::mojom::ScrollAction::ScrollDirection::kLeft;
-      break;
-    case ScrollAction_ScrollDirection::ScrollAction_ScrollDirection_RIGHT:
-      scroll->direction = actor::mojom::ScrollAction::ScrollDirection::kRight;
-      break;
-    case ScrollAction_ScrollDirection::ScrollAction_ScrollDirection_UP:
-      scroll->direction = actor::mojom::ScrollAction::ScrollDirection::kUp;
-      break;
-    case ScrollAction_ScrollDirection::ScrollAction_ScrollDirection_DOWN:
-      scroll->direction = actor::mojom::ScrollAction::ScrollDirection::kDown;
-      break;
-    case ScrollAction_ScrollDirection::
-        ScrollAction_ScrollDirection_UNKNOWN_SCROLL_DIRECTION:
-    case ScrollAction_ScrollDirection::
-        ScrollAction_ScrollDirection_ScrollAction_ScrollDirection_INT_MIN_SENTINEL_DO_NOT_USE_:
-    case ScrollAction_ScrollDirection::
-        ScrollAction_ScrollDirection_ScrollAction_ScrollDirection_INT_MAX_SENTINEL_DO_NOT_USE_:
-      // TODO(issuetracker.google.com/412700289): Revert once this is set.
-      scroll->direction = actor::mojom::ScrollAction::ScrollDirection::kDown;
-      break;
-      // return false;
+
+  CHECK(std::holds_alternative<PageToolRequest::NodeTarget>(target));
+
+  CHECK(document_identifier.has_value());
+  const std::string& serialized_token = document_identifier.value();
+
+  RenderFrameHost* target_frame = GetRenderFrameForDocumentIdentifier(
+      *tab->GetContents(), serialized_token);
+
+  // After finding the target frame, walk up to its local root.
+  return GetLocalRoot(target_frame);
+}
+
+// Perform validation based on APC and document identifier for coordinate based
+// target to compare the candidate frame with the target frame identified in
+// last observation.
+bool ValidateTargetFrameCandidate(
+    const PageToolRequest::Target& target,
+    RenderFrameHost* candidate_frame,
+    WebContents& web_contents,
+    const AnnotatedPageContent* last_observed_page_content) {
+  // Frame validation is performed only when targeting using coordinates.
+  CHECK(std::holds_alternative<PageToolRequest::CoordinateTarget>(target));
+
+  if (!last_observed_page_content) {
+    // TODO(bokan): We can't perform a TOCTOU check If there's no last
+    // observation. Consider what to do in this case.
+    return true;
   }
-  scroll->distance = action.scroll().distance();
-  return true;
-}
 
-void SetSelectToolArgs(actor::mojom::SelectActionPtr& select,
-                       const Action& action) {
-  SetMojoTarget(action.select().target(), select->target);
-  select->value = action.select().value();
-}
+  // TODO(bokan): This helper should take a gfx::Point.
+  auto coordinate = std::get<PageToolRequest::CoordinateTarget>(target);
+  optimization_guide::proto::Coordinate apc_coordinate;
+  apc_coordinate.set_x(coordinate.x());
+  apc_coordinate.set_y(coordinate.y());
 
-void SetDragAndReleaseToolArgs(
-    actor::mojom::DragAndReleaseActionPtr& drag_and_release,
-    Action action) {
-  SetMojoTarget(action.drag_and_release().from_target(),
-                drag_and_release->from_target);
-  SetMojoTarget(action.drag_and_release().to_target(),
-                drag_and_release->to_target);
+  // TODO(crbug.com/426021822): FindNodeAtPoint does not handle corner cases
+  // like clip paths. Need more checks to ensure we don't drop actions
+  // unnecessarily.
+  std::optional<optimization_guide::TargetNodeInfo> target_node_info =
+      optimization_guide::FindNodeAtPoint(*last_observed_page_content,
+                                          apc_coordinate);
+  if (!target_node_info) {
+    return false;
+  }
+
+  RenderFrameHost* apc_target_frame = GetRenderFrameForDocumentIdentifier(
+      web_contents, target_node_info->document_identifier.serialized_token());
+
+  // Only return the candidate if its RenderWidgetHost matches the target
+  // and it's also a local root frame(i.e. has no parent or parent has
+  // a different RenderWidgetHost)
+  if (apc_target_frame && apc_target_frame->GetRenderWidgetHost() ==
+                              candidate_frame->GetRenderWidgetHost()) {
+    return true;
+  }
+  return false;
 }
 
 }  // namespace
 
-namespace actor {
-
 // Observer to track if the a given RenderFrameHost is changed.
 class RenderFrameChangeObserver : public WebContentsObserver {
  public:
@@ -212,11 +205,17 @@
   base::OnceClosure callback_;
 };
 
-PageTool::PageTool(AggregatedJournal& journal,
-                   RenderFrameHost& frame,
-                   const Action& action)
-    : render_frame_host_(frame.GetWeakDocumentPtr()), action_(action) {
-  journal.EnsureJournalBound(frame);
+PageTool::PageTool(const PageToolRequest& request, AggregatedJournal& journal)
+    : request_(request.Clone()) {
+  // TODO(crbug.com/411462297): We shouldn't assume we have a frame until TOCTOU
+  // checks are done - a frame should only be resolved at that time. Same for
+  // tab.
+  RenderFrameHost* frame = FindTargetLocalRootFrame(
+      request.GetTabHandle(), request.DocumentIdentifier(),
+      request.GetTarget());
+  if (frame) {
+    journal.EnsureJournalBound(*frame);
+  }
 }
 
 PageTool::~PageTool() = default;
@@ -227,77 +226,46 @@
       FROM_HERE, base::BindOnce(std::move(callback), MakeOkResult()));
 }
 
+mojom::ActionResultPtr PageTool::TimeOfUseValidation(
+    const AnnotatedPageContent* last_observation) const {
+  RenderFrameHost* frame = FindTargetLocalRootFrame(
+      request_->GetTabHandle(), request_->DocumentIdentifier(),
+      request_->GetTarget());
+  if (!frame) {
+    return MakeResult(mojom::ActionResultCode::kFrameWentAway);
+  }
+
+  TabInterface* tab = request_->GetTabHandle().Get();
+  // If the frame still exists the tab must as well.
+  CHECK(tab);
+
+  // Perform validation for coordinate based target only.
+  if (std::holds_alternative<PageToolRequest::CoordinateTarget>(
+          request_->GetTarget())) {
+    if (!ValidateTargetFrameCandidate(request_->GetTarget(), frame,
+                                      *tab->GetContents(), last_observation)) {
+      return MakeResult(
+          mojom::ActionResultCode::kFrameLocationChangedSinceObservation);
+    }
+  }
+
+  return MakeOkResult();
+}
+
 void PageTool::Invoke(InvokeCallback callback) {
   invoke_callback_ = std::move(callback);
-  RenderFrameHost* frame = render_frame_host_.AsRenderFrameHostIfValid();
-  if (!frame) {
-    PostFinishInvoke(mojom::ActionResultCode::kFrameWentAway);
-    return;
-  }
+
+  // Frame was validated in TimeOfUseValidation.
+  RenderFrameHost* frame = FindTargetLocalRootFrame(
+      request_->GetTabHandle(), request_->DocumentIdentifier(),
+      request_->GetTarget());
+  CHECK(frame);
 
   auto request = actor::mojom::ToolInvocation::New();
+  request->action = request_->ToMojoToolAction();
 
-  switch (action_.action_case()) {
-    case Action::ActionCase::kClick: {
-      auto click = mojom::ClickAction::New();
-      if (!SetClickToolArgs(click, action_)) {
-        PostFinishInvoke(mojom::ActionResultCode::kArgumentsInvalid);
-        return;
-      }
-      request->action = mojom::ToolAction::NewClick(std::move(click));
-      break;
-    }
-    case Action::ActionCase::kType: {
-      auto type = mojom::TypeAction::New();
-      if (!SetTypeToolArgs(type, action_)) {
-        PostFinishInvoke(mojom::ActionResultCode::kArgumentsInvalid);
-        return;
-      }
-      request->action = mojom::ToolAction::NewType(std::move(type));
-      break;
-    }
-    case Action::ActionCase::kScroll: {
-      auto scroll = mojom::ScrollAction::New();
-      if (!SetScrollToolArgs(scroll, action_)) {
-        PostFinishInvoke(mojom::ActionResultCode::kArgumentsInvalid);
-        return;
-      }
-      request->action = mojom::ToolAction::NewScroll(std::move(scroll));
-      break;
-    }
-    case Action::ActionCase::kMoveMouse: {
-      auto mouse_move = mojom::MouseMoveAction::New();
-      SetMouseMoveToolArgs(mouse_move, action_);
-      request->action = mojom::ToolAction::NewMouseMove(std::move(mouse_move));
-      break;
-    }
-    case Action::ActionCase::kDragAndRelease: {
-      auto drag_and_release = mojom::DragAndReleaseAction::New();
-      SetDragAndReleaseToolArgs(drag_and_release, action_);
-      request->action =
-          mojom::ToolAction::NewDragAndRelease(std::move(drag_and_release));
-      break;
-    }
-    case Action::ActionCase::kSelect: {
-      auto select = mojom::SelectAction::New();
-      SetSelectToolArgs(select, action_);
-      request->action = mojom::ToolAction::NewSelect(std::move(select));
-      break;
-    }
-    case Action::ActionCase::kNavigate:
-    case Action::ActionCase::kBack:
-    case Action::ActionCase::kForward:
-    case Action::ActionCase::kWait:
-    case Action::kCreateTab:
-    case Action::kCloseTab:
-    case Action::kActivateTab:
-    case Action::kCreateWindow:
-    case Action::kCloseWindow:
-    case Action::kActivateWindow:
-    case Action::kYieldToUser:
-    case Action::ActionCase::ACTION_NOT_SET:
-      NOTREACHED();
-  }
+  // ToolRequest params are checked for validity at creation.
+  CHECK(request->action);
 
   frame->GetRemoteAssociatedInterfaces()->GetInterface(&chrome_render_frame_);
 
@@ -339,40 +307,23 @@
   return absl::StrFormat("PageTool:%s", JournalEvent().c_str());
 }
 
+GURL PageTool::JournalURL() const {
+  return request_->GetURLForJournal();
+}
+
 std::string PageTool::JournalEvent() const {
-  switch (action_.action_case()) {
-    case Action::ActionCase::kClick: {
-      return "Click";
-    }
-    case Action::ActionCase::kType: {
-      return "Type";
-    }
-    case Action::ActionCase::kScroll: {
-      return "Scroll";
-    }
-    case Action::ActionCase::kMoveMouse: {
-      return "MoveMouse";
-    }
-    case Action::ActionCase::kDragAndRelease: {
-      return "DragAndRelease";
-    }
-    case Action::ActionCase::kSelect: {
-      return "Select";
-    }
-    case Action::ActionCase::kNavigate:
-    case Action::ActionCase::kBack:
-    case Action::ActionCase::kForward:
-    case Action::ActionCase::kWait:
-    case Action::kCreateTab:
-    case Action::kCloseTab:
-    case Action::kActivateTab:
-    case Action::kCreateWindow:
-    case Action::kCloseWindow:
-    case Action::kActivateWindow:
-    case Action::kYieldToUser:
-    case Action::ActionCase::ACTION_NOT_SET:
-      NOTREACHED();
-  }
+  return request_->JournalEvent();
+}
+
+std::unique_ptr<ObservationDelayController> PageTool::GetObservationDelayer()
+    const {
+  RenderFrameHost* frame = FindTargetLocalRootFrame(
+      request_->GetTabHandle(), request_->DocumentIdentifier(),
+      request_->GetTarget());
+  // It's the caller's responsibility to ensure a frame is still live if calling
+  // this method.
+  CHECK(frame);
+  return std::make_unique<ObservationDelayController>(*frame);
 }
 
 void PageTool::FinishInvoke(mojom::ActionResultPtr result) {
diff --git a/chrome/browser/actor/tools/page_tool.h b/chrome/browser/actor/tools/page_tool.h
index 490b3099..6032223 100644
--- a/chrome/browser/actor/tools/page_tool.h
+++ b/chrome/browser/actor/tools/page_tool.h
@@ -9,19 +9,17 @@
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/actor/tools/tool.h"
+#include "chrome/browser/actor/tools/tool_request.h"
 #include "chrome/common/actor.mojom-forward.h"
 #include "chrome/common/chrome_render_frame.mojom.h"
 #include "components/optimization_guide/proto/features/actions_data.pb.h"
 #include "content/public/browser/weak_document_ptr.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 
-namespace content {
-class RenderFrameHost;
-}
-
 namespace actor {
 
 class AggregatedJournal;
+class PageToolRequest;
 class RenderFrameChangeObserver;
 
 // A page tool is any tool implemented in the renderer by ToolExecutor. This
@@ -29,16 +27,20 @@
 // of the request to the renderer.
 class PageTool : public Tool {
  public:
-  PageTool(AggregatedJournal& journal,
-           content::RenderFrameHost& frame,
-           const optimization_guide::proto::Action& action);
+  PageTool(const PageToolRequest& params, AggregatedJournal& journal);
   ~PageTool() override;
 
   // actor::Tool
   void Validate(ValidateCallback callback) override;
+  mojom::ActionResultPtr TimeOfUseValidation(
+      const optimization_guide::proto::AnnotatedPageContent* last_observation)
+      const override;
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
+  GURL JournalURL() const override;
   std::string JournalEvent() const override;
+  std::unique_ptr<ObservationDelayController> GetObservationDelayer()
+      const override;
 
  private:
   void FinishInvoke(mojom::ActionResultPtr result);
@@ -46,9 +48,8 @@
   void PostFinishInvoke(mojom::ActionResultCode result_code);
 
   InvokeCallback invoke_callback_;
-  content::WeakDocumentPtr render_frame_host_;
+  std::unique_ptr<PageToolRequest> request_;
   std::unique_ptr<RenderFrameChangeObserver> frame_change_observer_;
-  optimization_guide::proto::Action action_;
   mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> chrome_render_frame_;
 
   base::WeakPtrFactory<PageTool> weak_ptr_factory_{this};
diff --git a/chrome/browser/actor/tools/page_tool_request.cc b/chrome/browser/actor/tools/page_tool_request.cc
new file mode 100644
index 0000000..00cf5fd
--- /dev/null
+++ b/chrome/browser/actor/tools/page_tool_request.cc
@@ -0,0 +1,64 @@
+// Copyright 2025 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/actor/tools/page_tool_request.h"
+
+#include "chrome/browser/actor/tools/page_tool.h"
+#include "chrome/common/actor.mojom.h"
+#include "chrome/common/actor/action_result.h"
+#include "chrome/common/actor/actor_constants.h"
+#include "components/optimization_guide/content/browser/page_content_proto_provider.h"
+#include "components/tabs/public/tab_interface.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+
+namespace actor {
+
+using content::RenderFrameHost;
+using content::WebContents;
+using optimization_guide::DocumentIdentifierUserData;
+using tabs::TabHandle;
+
+// static
+mojom::ToolTargetPtr PageToolRequest::ToMojoToolTarget(const Target& target) {
+  if (std::holds_alternative<CoordinateTarget>(target)) {
+    return actor::mojom::ToolTarget::NewCoordinate(
+        std::get<CoordinateTarget>(target));
+  }
+
+  NodeTarget node_id = std::get<NodeTarget>(target);
+  return actor::mojom::ToolTarget::NewDomNodeId(
+      node_id.value_or(kRootElementDomNodeId));
+}
+
+PageToolRequest::PageToolRequest(TabHandle tab_handle,
+                                 std::string_view document_identifier,
+                                 const Target& target)
+    : TabToolRequest(tab_handle),
+      document_identifier_(document_identifier),
+      target_(target) {}
+
+PageToolRequest::~PageToolRequest() = default;
+
+PageToolRequest::PageToolRequest(const PageToolRequest& other) = default;
+
+ToolRequest::CreateToolResult PageToolRequest::CreateTool(
+    AggregatedJournal& journal) const {
+  if (!GetTabHandle().Get()) {
+    return {/*tool=*/nullptr, MakeResult(mojom::ActionResultCode::kTabWentAway,
+                                         "The tab is no longer present.")};
+  }
+
+  return {std::make_unique<PageTool>(*this, journal), MakeOkResult()};
+}
+
+const std::optional<std::string>& PageToolRequest::DocumentIdentifier() const {
+  return document_identifier_;
+}
+
+const PageToolRequest::Target& PageToolRequest::GetTarget() const {
+  return target_;
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/page_tool_request.h b/chrome/browser/actor/tools/page_tool_request.h
new file mode 100644
index 0000000..56c048c
--- /dev/null
+++ b/chrome/browser/actor/tools/page_tool_request.h
@@ -0,0 +1,73 @@
+// Copyright 2025 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_ACTOR_TOOLS_PAGE_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_PAGE_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "components/tabs/public/tab_interface.h"
+#include "content/public/browser/weak_document_ptr.h"
+#include "url/gurl.h"
+
+namespace actor {
+
+class AggregatedJournal;
+
+// Tool requests targeting a specific, existing document should inherit from
+// this subclass. Being page-scoped implies also being tab-scoped since a page
+// exists inside a tab.
+class PageToolRequest : public TabToolRequest {
+ public:
+  // Page tool requests must specify a target within the document. This must be
+  // one of:
+  //   * A coordinate, relative to the local root origin
+  //   * A specific node, specified by DOMNodeId. If not set, targets the root
+  //     element / viewport.
+  using NodeTarget = std::optional<int>;
+  using CoordinateTarget = gfx::Point;
+  using Target = std::variant<NodeTarget, CoordinateTarget>;
+
+  // A document identifier is optional if a CoordinateTarget. It is required
+  // when using a NodeTarget.
+  // TODO(crbug.com/411462297): Put document identifier into the Target type.
+  PageToolRequest(tabs::TabHandle tab_handle,
+                  std::string_view document_identifier,
+                  const Target& target);
+  ~PageToolRequest() override;
+  PageToolRequest(const PageToolRequest& other);
+
+  // Converts this request into the ToolAction mojo message which can be
+  // executed in the renderer.
+  virtual mojom::ToolActionPtr ToMojoToolAction() const = 0;
+
+  virtual std::unique_ptr<PageToolRequest> Clone() const = 0;
+
+  // ToolRequest
+  CreateToolResult CreateTool(AggregatedJournal& journal) const override;
+
+  // The provided document identifier in which this request should act. nullopt
+  // if using coordinates in which case the target document must be hit tested
+  // from coordinates.
+  const std::optional<std::string>& DocumentIdentifier() const;
+
+  // Returns what in the page the tool should act upon.
+  const Target& GetTarget() const;
+
+ protected:
+  // Helper usable by child classes when implementing ToMojoToolAction.
+  // Constructs an actor::mojom::ToolTarget from a PageToolRequest::Target.
+  static mojom::ToolTargetPtr ToMojoToolTarget(const Target& target);
+
+ private:
+  std::optional<std::string> document_identifier_;
+  Target target_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_PAGE_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/scroll_tool_request.cc b/chrome/browser/actor/tools/scroll_tool_request.cc
new file mode 100644
index 0000000..4ce3de6c
--- /dev/null
+++ b/chrome/browser/actor/tools/scroll_tool_request.cc
@@ -0,0 +1,56 @@
+// Copyright 2025 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/actor/tools/scroll_tool_request.h"
+
+#include "chrome/common/actor.mojom.h"
+
+namespace actor {
+
+using ::tabs::TabHandle;
+
+ScrollToolRequest::ScrollToolRequest(TabHandle tab_handle,
+                                     std::string_view document_identifier,
+                                     const Target& target,
+                                     Direction direction,
+                                     float distance)
+    : PageToolRequest(tab_handle, document_identifier, target),
+      direction_(direction),
+      distance_(distance) {}
+
+ScrollToolRequest::~ScrollToolRequest() = default;
+
+std::string ScrollToolRequest::JournalEvent() const {
+  return "Scroll";
+}
+
+mojom::ToolActionPtr ScrollToolRequest::ToMojoToolAction() const {
+  auto scroll = mojom::ScrollAction::New();
+
+  scroll->target = PageToolRequest::ToMojoToolTarget(GetTarget());
+  switch (direction_) {
+    case Direction::kLeft:
+      scroll->direction = mojom::ScrollAction::ScrollDirection::kLeft;
+      break;
+    case Direction::kRight:
+      scroll->direction = mojom::ScrollAction::ScrollDirection::kRight;
+      break;
+    case Direction::kUp:
+      scroll->direction = mojom::ScrollAction::ScrollDirection::kUp;
+      break;
+    case Direction::kDown:
+      scroll->direction = mojom::ScrollAction::ScrollDirection::kDown;
+      break;
+  }
+
+  scroll->distance = distance_;
+
+  return mojom::ToolAction::NewScroll(std::move(scroll));
+}
+
+std::unique_ptr<PageToolRequest> ScrollToolRequest::Clone() const {
+  return std::make_unique<ScrollToolRequest>(*this);
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/scroll_tool_request.h b/chrome/browser/actor/tools/scroll_tool_request.h
new file mode 100644
index 0000000..a9bc006b
--- /dev/null
+++ b/chrome/browser/actor/tools/scroll_tool_request.h
@@ -0,0 +1,45 @@
+// Copyright 2025 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_ACTOR_TOOLS_SCROLL_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_SCROLL_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/browser/actor/tools/page_tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+
+namespace actor {
+
+// Scrolls an element or viewport in the page a given distance.
+class ScrollToolRequest : public PageToolRequest {
+ public:
+  enum class Direction { kLeft, kRight, kUp, kDown };
+
+  // Programmatically scrolls the scroller specified by target a given distance.
+  // If Target is a nullopt ContentNodeId, the root viewport is scrolled.
+  // Distance is specified in physical pixels.
+  ScrollToolRequest(tabs::TabHandle tab_handle,
+                    std::string_view document_identifier,
+                    const Target& target,
+                    Direction direction,
+                    float distance);
+  ~ScrollToolRequest() override;
+
+  // ToolRequest
+  std::string JournalEvent() const override;
+
+  // PageToolRequest
+  mojom::ToolActionPtr ToMojoToolAction() const override;
+  std::unique_ptr<PageToolRequest> Clone() const override;
+
+ private:
+  Direction direction_;
+  float distance_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_SCROLL_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/select_tool_request.cc b/chrome/browser/actor/tools/select_tool_request.cc
new file mode 100644
index 0000000..5848584b
--- /dev/null
+++ b/chrome/browser/actor/tools/select_tool_request.cc
@@ -0,0 +1,38 @@
+// Copyright 2025 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/actor/tools/select_tool_request.h"
+
+#include "chrome/common/actor.mojom.h"
+
+namespace actor {
+
+using ::tabs::TabHandle;
+
+SelectToolRequest::SelectToolRequest(TabHandle tab_handle,
+                                     std::string_view document_identifier,
+                                     const Target& target,
+                                     std::string_view value)
+    : PageToolRequest(tab_handle, document_identifier, target), value_(value) {}
+
+SelectToolRequest::~SelectToolRequest() = default;
+
+std::string SelectToolRequest::JournalEvent() const {
+  return "Select";
+}
+
+mojom::ToolActionPtr SelectToolRequest::ToMojoToolAction() const {
+  auto select = mojom::SelectAction::New();
+
+  select->target = PageToolRequest::ToMojoToolTarget(GetTarget());
+  select->value = value_;
+
+  return mojom::ToolAction::NewSelect(std::move(select));
+}
+
+std::unique_ptr<PageToolRequest> SelectToolRequest::Clone() const {
+  return std::make_unique<SelectToolRequest>(*this);
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/select_tool_request.h b/chrome/browser/actor/tools/select_tool_request.h
new file mode 100644
index 0000000..28f1401
--- /dev/null
+++ b/chrome/browser/actor/tools/select_tool_request.h
@@ -0,0 +1,41 @@
+// Copyright 2025 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_ACTOR_TOOLS_SELECT_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_SELECT_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+#include "chrome/browser/actor/tools/page_tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+
+namespace actor {
+
+// Chooses an option in a <select> box on the page based on the value attribute
+// of the <option> children.
+class SelectToolRequest : public PageToolRequest {
+ public:
+  SelectToolRequest(tabs::TabHandle tab_handle,
+                    std::string_view document_identifier,
+                    const Target& target,
+                    std::string_view value);
+  ~SelectToolRequest() override;
+
+  // ToolRequest
+  std::string JournalEvent() const override;
+
+  // PageToolRequest
+  mojom::ToolActionPtr ToMojoToolAction() const override;
+  std::unique_ptr<PageToolRequest> Clone() const override;
+
+ private:
+  // The <option> whose value attribute matches this parameter will be selected.
+  std::string value_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_SELECT_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/tab_management_tool.cc b/chrome/browser/actor/tools/tab_management_tool.cc
index c0ae0ef9..eb88a19 100644
--- a/chrome/browser/actor/tools/tab_management_tool.cc
+++ b/chrome/browser/actor/tools/tab_management_tool.cc
@@ -4,12 +4,14 @@
 
 #include "chrome/browser/actor/tools/tab_management_tool.h"
 
+#include "chrome/browser/actor/tools/observation_delay_controller.h"
 #include "chrome/browser/actor/tools/tool_callbacks.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/common/actor.mojom.h"
 #include "chrome/common/actor/action_result.h"
 #include "components/sessions/core/session_id.h"
+#include "components/tabs/public/tab_interface.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/abseil-cpp/absl/strings/str_format.h"
 #include "ui/base/window_open_disposition.h"
@@ -18,10 +20,16 @@
 
 namespace actor {
 
-TabManagementTool::TabManagementTool(
-    int32_t window_id,
-    const optimization_guide::proto::CreateTabAction& action)
-    : window_id_(window_id), action_(action) {}
+using ::tabs::TabHandle;
+
+TabManagementTool::TabManagementTool(int32_t window_id,
+                                     WindowOpenDisposition create_disposition)
+    : action_(Action::kCreate),
+      create_disposition_(create_disposition),
+      window_id_(window_id) {}
+
+TabManagementTool::TabManagementTool(Action action, TabHandle tab_handle)
+    : action_(action), target_tab_(tab_handle) {}
 
 TabManagementTool::~TabManagementTool() = default;
 
@@ -30,25 +38,35 @@
 }
 
 void TabManagementTool::Invoke(InvokeCallback callback) {
-  BrowserWindowInterface* browser_window_interface =
-      BrowserWindowInterface::FromSessionID(
-          SessionID::FromSerializedValue(window_id_));
-  if (!browser_window_interface) {
-    PostResponseTask(std::move(callback),
-                     MakeResult(mojom::ActionResultCode::kWindowWentAway));
-    return;
+  // TODO(crbug.com/411462297): Only the create action is hooked up and
+  // implemented.
+  switch (action_) {
+    case kCreate: {
+      CHECK(window_id_.has_value());
+      CHECK(create_disposition_.has_value());
+      BrowserWindowInterface* browser_window_interface =
+          BrowserWindowInterface::FromSessionID(
+              SessionID::FromSerializedValue(window_id_.value()));
+      if (!browser_window_interface) {
+        PostResponseTask(std::move(callback),
+                         MakeResult(mojom::ActionResultCode::kWindowWentAway));
+        return;
+      }
+
+      // Open a blank tab.
+      browser_window_interface->OpenGURL(GURL(url::kAboutBlankURL),
+                                         create_disposition_.value());
+      break;
+    }
+    case kActivate:
+    case kClose:
+      CHECK(target_tab_.has_value());
+      NOTIMPLEMENTED() << "ActivateTab and CloseTab not yet implemented";
+      PostResponseTask(std::move(callback),
+                       MakeResult(mojom::ActionResultCode::kError));
+      return;
   }
 
-  // TODO(bokan): Is the foreground bit always set? If not, should this return
-  // an error or default to what? For now we default to foreground.
-  WindowOpenDisposition disposition =
-      (!action_.has_foreground() || action_.foreground())
-          ? WindowOpenDisposition::NEW_FOREGROUND_TAB
-          : WindowOpenDisposition::NEW_BACKGROUND_TAB;
-
-  // Open a blank tab.
-  browser_window_interface->OpenGURL(GURL(url::kAboutBlankURL), disposition);
-
   PostResponseTask(std::move(callback), MakeOkResult());
 }
 
@@ -57,13 +75,19 @@
 }
 
 std::string TabManagementTool::JournalEvent() const {
-  return "CreateTab";
+  switch (action_) {
+    case kCreate:
+      return "CreateTab";
+    case kActivate:
+      return "ActivateTab";
+    case kClose:
+      return "CloseTab";
+  }
 }
 
-bool TabManagementTool::RequiresFrame() const {
-  // This is to avoid the kFrameWentAway check in
-  // ToolController::ValidationComplete.
-  return false;
+std::unique_ptr<ObservationDelayController>
+TabManagementTool::GetObservationDelayer() const {
+  return nullptr;
 }
 
 }  // namespace actor
diff --git a/chrome/browser/actor/tools/tab_management_tool.h b/chrome/browser/actor/tools/tab_management_tool.h
index a0c0bd5..0fd1725 100644
--- a/chrome/browser/actor/tools/tab_management_tool.h
+++ b/chrome/browser/actor/tools/tab_management_tool.h
@@ -7,17 +7,26 @@
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/actor/tools/tool.h"
-#include "components/optimization_guide/proto/features/actions_data.pb.h"
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "ui/base/window_open_disposition.h"
 
 namespace actor {
 
+class ObservationDelayController;
+
 // A tool to manage the tabs in a browser window, e.g. create, close,
 // activate, etc.
 // TODO(crbug.com/411462297): Implement actions other than create.
 class TabManagementTool : public Tool {
  public:
-  TabManagementTool(int32_t window_id,
-                    const optimization_guide::proto::CreateTabAction& action);
+  enum Action { kCreate, kActivate, kClose };
+
+  // Public for std::make_unique, use For* static methods above.
+  // Create constructor
+  TabManagementTool(int32_t window, WindowOpenDisposition create_disposition);
+  // Activate|Close constructor.
+  TabManagementTool(Action action, tabs::TabHandle target_tab);
+
   ~TabManagementTool() override;
 
   // actor::Tool:
@@ -25,11 +34,20 @@
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
   std::string JournalEvent() const override;
-  bool RequiresFrame() const override;
+  std::unique_ptr<ObservationDelayController> GetObservationDelayer()
+      const override;
 
  private:
-  int32_t window_id_;
-  const optimization_guide::proto::CreateTabAction action_;
+  Action action_;
+
+  // Used for activate or close action.
+  std::optional<tabs::TabHandle> target_tab_;
+
+  // If creating a tab, whether to create in the foreground.
+  std::optional<WindowOpenDisposition> create_disposition_;
+
+  // If creating a tab, the window in which to create the tab.
+  std::optional<int32_t> window_id_;
 
   base::WeakPtrFactory<TabManagementTool> weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/actor/tools/tab_management_tool_request.cc b/chrome/browser/actor/tools/tab_management_tool_request.cc
new file mode 100644
index 0000000..7174618a
--- /dev/null
+++ b/chrome/browser/actor/tools/tab_management_tool_request.cc
@@ -0,0 +1,74 @@
+// Copyright 2025 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/actor/tools/tab_management_tool_request.h"
+
+#include <memory>
+
+#include "chrome/browser/actor/tools/tab_management_tool.h"
+#include "chrome/common/actor/action_result.h"
+
+namespace actor {
+
+using tabs::TabInterface;
+
+CreateTabToolRequest::CreateTabToolRequest(int32_t window_id,
+                                           WindowOpenDisposition disposition)
+    : window_id_(window_id), disposition_(disposition) {}
+
+CreateTabToolRequest::~CreateTabToolRequest() = default;
+
+ToolRequest::CreateToolResult CreateTabToolRequest::CreateTool(
+    AggregatedJournal& journal) const {
+  return {std::make_unique<TabManagementTool>(window_id_, disposition_),
+          MakeOkResult()};
+}
+
+std::string CreateTabToolRequest::JournalEvent() const {
+  return "CreateTab";
+}
+
+ActivateTabToolRequest::ActivateTabToolRequest(tabs::TabHandle tab_handle)
+    : TabToolRequest(tab_handle) {}
+
+ActivateTabToolRequest::~ActivateTabToolRequest() = default;
+
+ToolRequest::CreateToolResult ActivateTabToolRequest::CreateTool(
+    AggregatedJournal& journal) const {
+  TabInterface* tab = GetTabHandle().Get();
+  if (!tab) {
+    return {/*tool=*/nullptr, MakeResult(mojom::ActionResultCode::kTabWentAway,
+                                         "The tab is no longer present.")};
+  }
+  return {std::make_unique<TabManagementTool>(TabManagementTool::kActivate,
+                                              GetTabHandle()),
+          MakeOkResult()};
+}
+
+std::string ActivateTabToolRequest::JournalEvent() const {
+  return "ActivateTab";
+}
+
+CloseTabToolRequest::CloseTabToolRequest(tabs::TabHandle tab_handle)
+    : TabToolRequest(tab_handle) {}
+
+CloseTabToolRequest::~CloseTabToolRequest() = default;
+
+ToolRequest::CreateToolResult CloseTabToolRequest::CreateTool(
+    AggregatedJournal& journal) const {
+  TabInterface* tab = GetTabHandle().Get();
+  if (!tab) {
+    return {/*tool=*/nullptr, MakeResult(mojom::ActionResultCode::kTabWentAway,
+                                         "The tab is no longer present.")};
+  }
+  return {std::make_unique<TabManagementTool>(TabManagementTool::kClose,
+                                              GetTabHandle()),
+          MakeOkResult()};
+}
+
+std::string CloseTabToolRequest::JournalEvent() const {
+  return "CloseTab";
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/tab_management_tool_request.h b/chrome/browser/actor/tools/tab_management_tool_request.h
new file mode 100644
index 0000000..355970c
--- /dev/null
+++ b/chrome/browser/actor/tools/tab_management_tool_request.h
@@ -0,0 +1,51 @@
+// Copyright 2025 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_ACTOR_TOOLS_TAB_MANAGEMENT_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_TAB_MANAGEMENT_TOOL_REQUEST_H_
+
+#include <string>
+
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "ui/base/window_open_disposition.h"
+
+namespace actor {
+
+// Creates a new blank tab in the specified window.
+class CreateTabToolRequest : public ToolRequest {
+ public:
+  enum class Disposition { kForeground, kBackground };
+
+  CreateTabToolRequest(int32_t window_id, WindowOpenDisposition disposition);
+  ~CreateTabToolRequest() override;
+
+  CreateToolResult CreateTool(AggregatedJournal& journal) const override;
+  std::string JournalEvent() const override;
+
+ private:
+  int32_t window_id_;
+  WindowOpenDisposition disposition_;
+};
+
+// Brings the specified tab to the foreground.
+class ActivateTabToolRequest : public TabToolRequest {
+ public:
+  explicit ActivateTabToolRequest(tabs::TabHandle tab);
+  ~ActivateTabToolRequest() override;
+  CreateToolResult CreateTool(AggregatedJournal& journal) const override;
+  std::string JournalEvent() const override;
+};
+
+// Closes the specified tab to the foreground.
+class CloseTabToolRequest : public TabToolRequest {
+ public:
+  explicit CloseTabToolRequest(tabs::TabHandle tab);
+  ~CloseTabToolRequest() override;
+  CreateToolResult CreateTool(AggregatedJournal& journal) const override;
+  std::string JournalEvent() const override;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_TAB_MANAGEMENT_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/tool.cc b/chrome/browser/actor/tools/tool.cc
index 75c24c7..ea9dcf9 100644
--- a/chrome/browser/actor/tools/tool.cc
+++ b/chrome/browser/actor/tools/tool.cc
@@ -4,19 +4,19 @@
 
 #include "chrome/browser/actor/tools/tool.h"
 
-#include <memory>
-
-#include "chrome/browser/actor/tools/observation_delay_controller.h"
+#include "chrome/common/actor/action_result.h"
 
 namespace actor {
 
-std::unique_ptr<ObservationDelayController> Tool::GetObservationDelayer(
-    content::RenderFrameHost& target_frame) const {
-  return std::make_unique<ObservationDelayController>(target_frame);
+mojom::ActionResultPtr Tool::TimeOfUseValidation(
+    const optimization_guide::proto::AnnotatedPageContent* last_observation)
+    const {
+  // TODO(crbug.com/411462297): This should be made pure-virtual.
+  return MakeOkResult();
 }
 
-bool Tool::RequiresFrame() const {
-  return true;
+GURL Tool::JournalURL() const {
+  return GURL::EmptyGURL();
 }
 
 }  // namespace actor
diff --git a/chrome/browser/actor/tools/tool.h b/chrome/browser/actor/tools/tool.h
index 57fab05..b0a5107 100644
--- a/chrome/browser/actor/tools/tool.h
+++ b/chrome/browser/actor/tools/tool.h
@@ -9,11 +9,13 @@
 #include <string>
 
 #include "base/functional/callback_forward.h"
-#include "chrome/common/actor.mojom-forward.h"
+#include "chrome/browser/actor/aggregated_journal.h"
+#include "chrome/common/actor.mojom.h"
+#include "url/gurl.h"
 
-namespace content {
-class RenderFrameHost;
-}  // namespace content
+namespace optimization_guide::proto {
+class AnnotatedPageContent;
+}  // namespace optimization_guide::proto
 
 namespace actor {
 
@@ -37,6 +39,16 @@
   // the tool will be destroyed.
   virtual void Validate(ValidateCallback callback) = 0;
 
+  // Perform any synchronous time-of-use checks just before invoking the tool.
+  // These are typically TOCTOU (time-of-check/time-of-use) validations that the
+  // live state of the page/browser still matches what the client can see as of
+  // the last observation snapshot. This is a synchronous check so there are no
+  // further opportunities for changes to the live browser state before invoking
+  // the tool.
+  virtual mojom::ActionResultPtr TimeOfUseValidation(
+      const optimization_guide::proto::AnnotatedPageContent* last_observation)
+      const;
+
   // Perform the action of the tool. The given callback must be invoked when the
   // tool has finished its actions.
   virtual void Invoke(InvokeCallback callback) = 0;
@@ -45,22 +57,19 @@
   // debugging purposes.
   virtual std::string DebugString() const = 0;
 
+  // Provides the URL to be recorded in journal entries for this tool. This can
+  // be an empty URL for tool not associated with a tab/frame or if the
+  // tab/frame is no longer available.
+  virtual GURL JournalURL() const;
+
   // Provides a journal event name.
   virtual std::string JournalEvent() const = 0;
 
   // Returns an optional delay object that can be used to delay completion of
-  // the tool until some external conditions are met. By default, this returns
-  // an object that watches for loading navigations and waits until load is
-  // completed and a new frame presented.
-  virtual std::unique_ptr<ObservationDelayController> GetObservationDelayer(
-      content::RenderFrameHost& target_frame) const;
-
-  // Whether or not the tool requires a frame to operate on. Note, this also
-  // includes "tab-scoped" tools which are considered to operate on the "main
-  // frame" in the tab.
-  // TODO(crbug.com/411462297): Temporary until we have a better mechanism for
-  // non-frame-scoped tools.
-  virtual bool RequiresFrame() const;
+  // the tool until some external conditions are met, typically waiting on a
+  // loading navigation to settle.
+  virtual std::unique_ptr<ObservationDelayController> GetObservationDelayer()
+      const = 0;
 };
 
 }  // namespace actor
diff --git a/chrome/browser/actor/tools/tool_controller.cc b/chrome/browser/actor/tools/tool_controller.cc
index 534dd51c..bfa2618b 100644
--- a/chrome/browser/actor/tools/tool_controller.cc
+++ b/chrome/browser/actor/tools/tool_controller.cc
@@ -5,142 +5,67 @@
 #include "chrome/browser/actor/tools/tool_controller.h"
 
 #include <memory>
+#include <string>
 
 #include "base/feature_list.h"
 #include "base/functional/callback.h"
 #include "base/memory/safe_ref.h"
-#include "base/notimplemented.h"
 #include "chrome/browser/actor/aggregated_journal.h"
-#include "chrome/browser/actor/execution_engine.h"
-#include "chrome/browser/actor/tools/history_tool.h"
-#include "chrome/browser/actor/tools/navigate_tool.h"
-#include "chrome/browser/actor/tools/page_tool.h"
-#include "chrome/browser/actor/tools/tab_management_tool.h"
 #include "chrome/browser/actor/tools/tool.h"
 #include "chrome/browser/actor/tools/tool_callbacks.h"
-#include "chrome/browser/actor/tools/wait_tool.h"
-#include "chrome/common/actor.mojom.h"
+#include "chrome/browser/actor/tools/tool_request.h"
 #include "chrome/common/actor/action_result.h"
 #include "chrome/common/chrome_features.h"
-#include "components/optimization_guide/proto/features/actions_data.pb.h"
-#include "content/public/browser/weak_document_ptr.h"
-#include "content/public/browser/web_contents.h"
 #include "url/gurl.h"
 
-using content::RenderFrameHost;
-using content::WebContents;
-using optimization_guide::proto::Action;
-using tabs::TabInterface;
-
 namespace actor {
 
-namespace {
-const GURL& GetURL(TabInterface* tab, RenderFrameHost* target_frame) {
-  if (target_frame) {
-    return target_frame->GetLastCommittedURL();
-  } else if (tab) {
-    return tab->GetContents()->GetLastCommittedURL();
-  }
-  return GURL::EmptyGURL();
-}
-
-}  // namespace
+using ::optimization_guide::proto::AnnotatedPageContent;
 
 ToolController::ActiveState::ActiveState(
     std::unique_ptr<Tool> tool,
     ResultCallback completion_callback,
-    content::WeakDocumentPtr weak_document_ptr,
-    std::unique_ptr<AggregatedJournal::PendingAsyncEntry> journal_entry)
+    std::unique_ptr<AggregatedJournal::PendingAsyncEntry> journal_entry,
+    const optimization_guide::proto::AnnotatedPageContent* last_observation)
     : tool(std::move(tool)),
       completion_callback(std::move(completion_callback)),
-      weak_document_ptr(weak_document_ptr),
-      journal_entry(std::move(journal_entry)) {
+      journal_entry(std::move(journal_entry)),
+      last_observation(last_observation) {
   CHECK(this->tool);
   CHECK(!this->completion_callback.is_null());
 }
 ToolController::ActiveState::~ActiveState() = default;
 
-ToolController::ToolController() {
+ToolController::ToolController(TaskId task_id, AggregatedJournal& journal)
+    : task_id_(task_id), journal_(journal.GetSafeRef()) {
   CHECK(base::FeatureList::IsEnabled(features::kGlicActor));
 }
 
 ToolController::~ToolController() = default;
 
-std::unique_ptr<Tool> ToolController::CreateTool(AggregatedJournal& journal,
-                                                 TaskId task_id,
-                                                 TabInterface* tab,
-                                                 RenderFrameHost* frame,
-                                                 const Action& action) {
-  switch (action.action_case()) {
-    case Action::kClick:
-    case Action::kType:
-    case Action::kScroll:
-    case Action::kMoveMouse:
-    case Action::kDragAndRelease:
-    case Action::kSelect: {
-      CHECK(frame);
-      // PageTools are all implemented in the renderer so share the PageTool
-      // implementation to shuttle them there.
-      return std::make_unique<PageTool>(journal, *frame, action);
-    }
-    case Action::kNavigate: {
-      GURL url(action.navigate().url());
-      return std::make_unique<NavigateTool>(*tab->GetContents(), url);
-    }
-    case Action::kBack: {
-      return std::make_unique<HistoryTool>(*tab->GetContents(),
-                                           HistoryTool::kBack);
-    }
-    case Action::kForward: {
-      return std::make_unique<HistoryTool>(*tab->GetContents(),
-                                           HistoryTool::kForward);
-    }
-    case Action::kWait: {
-      return std::make_unique<WaitTool>();
-    }
-    case Action::kCreateTab: {
-      // Extract the window ID from the action.
-      int32_t window_id = action.create_tab().window_id();
-      return std::make_unique<TabManagementTool>(window_id,
-                                                 action.create_tab());
-    }
-    case Action::kCloseTab:
-    case Action::kActivateTab:
-    case Action::kCreateWindow:
-    case Action::kCloseWindow:
-    case Action::kActivateWindow:
-    case Action::kYieldToUser:
-    case Action::ACTION_NOT_SET:
-      NOTREACHED();
-  }
-}
-
-void ToolController::Invoke(const Action& action,
-                            AggregatedJournal& journal,
-                            TaskId task_id,
-                            tabs::TabInterface* tab,
-                            content::RenderFrameHost* target_frame,
+void ToolController::Invoke(std::unique_ptr<ToolRequest> request,
+                            const AnnotatedPageContent* last_observation,
                             ResultCallback result_callback) {
-  std::unique_ptr<Tool> created_tool =
-      CreateTool(journal, task_id, tab, target_frame, action);
+  CHECK(request);
+  ToolRequest::CreateToolResult create_result = request->CreateTool(*journal_);
 
-  if (!created_tool) {
-    // Tool not found.
+  if (!IsOk(*create_result.result)) {
+    CHECK(!create_result.tool);
+    journal_->Log(request->GetURLForJournal(), task_id_,
+                  "ToolController Invoke Failed",
+                  create_result.result->message);
     PostResponseTask(std::move(result_callback),
-                     MakeResult(mojom::ActionResultCode::kToolUnknown));
+                     std::move(create_result.result));
     return;
   }
 
-  auto journal_event = journal.CreatePendingAsyncEntry(
-      GetURL(tab, target_frame), task_id, created_tool->JournalEvent(),
-      created_tool->DebugString());
+  std::unique_ptr<Tool>& tool = create_result.tool;
+  CHECK(tool);
 
-  content::WeakDocumentPtr document_ptr;
-  if (target_frame) {
-    document_ptr = target_frame->GetWeakDocumentPtr();
-  }
-  active_state_.emplace(std::move(created_tool), std::move(result_callback),
-                        document_ptr, std::move(journal_event));
+  auto journal_event = journal_->CreatePendingAsyncEntry(
+      tool->JournalURL(), task_id_, tool->JournalEvent(), tool->DebugString());
+  active_state_.emplace(std::move(tool), std::move(result_callback),
+                        std::move(journal_event), last_observation);
 
   active_state_->tool->Validate(base::BindOnce(
       &ToolController::ValidationComplete, weak_ptr_factory_.GetWeakPtr()));
@@ -154,18 +79,17 @@
     return;
   }
 
+  mojom::ActionResultPtr toctou_result =
+      active_state_->tool->TimeOfUseValidation(active_state_->last_observation);
+  if (!IsOk(*toctou_result)) {
+    CompleteToolRequest(std::move(toctou_result));
+    return;
+  }
+
   // TODO(crbug.com/389739308): Ensure the acting tab remains valid (i.e. alive
   // and focused), return error otherwise.
-  if (active_state_->tool->RequiresFrame()) {
-    RenderFrameHost* target_frame =
-        active_state_->weak_document_ptr.AsRenderFrameHostIfValid();
-    if (!target_frame) {
-      CompleteToolRequest(MakeResult(mojom::ActionResultCode::kFrameWentAway));
-      return;
-    }
-    observation_delayer_ =
-        active_state_->tool->GetObservationDelayer(*target_frame);
-  }
+
+  observation_delayer_ = active_state_->tool->GetObservationDelayer();
 
   active_state_->tool->Invoke(base::BindOnce(
       &ToolController::DidFinishToolInvoke, weak_ptr_factory_.GetWeakPtr()));
diff --git a/chrome/browser/actor/tools/tool_controller.h b/chrome/browser/actor/tools/tool_controller.h
index 514ce97..5b0811dc 100644
--- a/chrome/browser/actor/tools/tool_controller.h
+++ b/chrome/browser/actor/tools/tool_controller.h
@@ -8,29 +8,21 @@
 #include <memory>
 
 #include "base/functional/callback.h"
+#include "base/memory/raw_ref.h"
+#include "base/memory/safe_ref.h"
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/actor/aggregated_journal.h"
 #include "chrome/browser/actor/task_id.h"
 #include "chrome/browser/actor/tools/observation_delay_controller.h"
 #include "chrome/common/actor.mojom-forward.h"
+#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
 #include "content/public/browser/weak_document_ptr.h"
 
-namespace tabs {
-class TabInterface;
-}  // namespace tabs
-
-namespace content {
-class RenderFrameHost;
-}  // namespace content
-
-namespace optimization_guide::proto {
-class Action;
-}  // namespace optimization_guide::proto
-
 namespace actor {
 
 class AggregatedJournal;
 class Tool;
+class ToolRequest;
 
 // Entry point into actor tool usage. ToolController is a profile-scoped,
 // ExecutionEngine-owned object. This class routes a tool use request to the
@@ -39,18 +31,16 @@
 class ToolController {
  public:
   using ResultCallback = base::OnceCallback<void(mojom::ActionResultPtr)>;
-  ToolController();
+  ToolController(TaskId task_id, AggregatedJournal& journal);
   ~ToolController();
   ToolController(const ToolController&) = delete;
   ToolController& operator=(const ToolController&) = delete;
 
-  // Invokes a tool action. Both `tab` and `target_frame` can be null.
-  void Invoke(const optimization_guide::proto::Action& action,
-              AggregatedJournal& journal,
-              TaskId task_id,
-              tabs::TabInterface* tab,
-              content::RenderFrameHost* target_frame,
-              ResultCallback result_callback);
+  // Invokes a tool action.
+  void Invoke(
+      std::unique_ptr<ToolRequest> request,
+      const optimization_guide::proto::AnnotatedPageContent* last_observation,
+      ResultCallback result_callback);
 
  private:
   // Called when the tool itself finishes its invocation.
@@ -60,13 +50,6 @@
   // the initiator. Must only be called when a tool invocation is in-progress.
   void CompleteToolRequest(mojom::ActionResultPtr result);
 
-  std::unique_ptr<Tool> CreateTool(
-      AggregatedJournal& journal,
-      TaskId task_id,
-      tabs::TabInterface* tab,
-      content::RenderFrameHost* frame,
-      const optimization_guide::proto::Action& action);
-
   void ValidationComplete(mojom::ActionResultPtr result);
 
   // This state is non-null whenever a tool invocation is in progress.
@@ -74,8 +57,9 @@
     ActiveState(
         std::unique_ptr<Tool> tool,
         ResultCallback completion_callback,
-        content::WeakDocumentPtr weak_document_ptr,
-        std::unique_ptr<AggregatedJournal::PendingAsyncEntry> journal_entry);
+        std::unique_ptr<AggregatedJournal::PendingAsyncEntry> journal_entry,
+        const optimization_guide::proto::AnnotatedPageContent*
+            last_observation);
     ~ActiveState();
     ActiveState(const ActiveState&) = delete;
     ActiveState& operator=(const ActiveState&) = delete;
@@ -84,8 +68,9 @@
     // active_state_ is set.
     std::unique_ptr<Tool> tool;
     ResultCallback completion_callback;
-    content::WeakDocumentPtr weak_document_ptr;
     std::unique_ptr<AggregatedJournal::PendingAsyncEntry> journal_entry;
+    raw_ptr<const optimization_guide::proto::AnnotatedPageContent>
+        last_observation;
   };
   std::optional<ActiveState> active_state_;
 
@@ -93,6 +78,10 @@
   // completion_callback until the page is ready for observation.
   std::unique_ptr<ObservationDelayController> observation_delayer_;
 
+  TaskId task_id_;
+
+  base::SafeRef<AggregatedJournal> journal_;
+
   base::WeakPtrFactory<ToolController> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/actor/tools/tool_request.cc b/chrome/browser/actor/tools/tool_request.cc
new file mode 100644
index 0000000..431e9f3c
--- /dev/null
+++ b/chrome/browser/actor/tools/tool_request.cc
@@ -0,0 +1,49 @@
+// Copyright 2025 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/actor/tools/tool_request.h"
+
+#include "chrome/browser/actor/tools/tool.h"
+#include "components/tabs/public/tab_interface.h"
+#include "content/public/browser/web_contents.h"
+
+namespace actor {
+
+using ::content::WebContents;
+using ::tabs::TabHandle;
+using ::tabs::TabInterface;
+
+ToolRequest::CreateToolResult::CreateToolResult(std::unique_ptr<Tool> tool,
+                                                mojom::ActionResultPtr result)
+    : tool(std::move(tool)), result(std::move(result)) {}
+ToolRequest::CreateToolResult::~CreateToolResult() = default;
+
+ToolRequest::ToolRequest() = default;
+ToolRequest::~ToolRequest() = default;
+
+GURL ToolRequest::GetURLForJournal() const {
+  return GURL::EmptyGURL();
+}
+
+TabToolRequest::TabToolRequest(const tabs::TabInterface::Handle tab_handle)
+    : tab_handle_(tab_handle) {
+  // The given handle need not be valid - the handle is validated at time of
+  // dereferencing when instantiating a tool. However, it must be a non-null
+  // value.
+  CHECK_NE(tab_handle.raw_value(), TabHandle::Null().raw_value());
+}
+TabToolRequest::~TabToolRequest() = default;
+
+GURL TabToolRequest::GetURLForJournal() const {
+  if (TabInterface* tab = tab_handle_.Get()) {
+    return tab->GetContents()->GetLastCommittedURL();
+  }
+  return ToolRequest::GetURLForJournal();
+}
+
+tabs::TabInterface::Handle TabToolRequest::GetTabHandle() const {
+  return tab_handle_;
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/tool_request.h b/chrome/browser/actor/tools/tool_request.h
new file mode 100644
index 0000000..37f3f592
--- /dev/null
+++ b/chrome/browser/actor/tools/tool_request.h
@@ -0,0 +1,73 @@
+// Copyright 2025 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_ACTOR_TOOLS_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string_view>
+#include <variant>
+
+#include "base/types/expected.h"
+#include "chrome/browser/actor/task_id.h"
+#include "chrome/common/actor.mojom.h"
+#include "components/tabs/public/tab_interface.h"
+#include "ui/gfx/geometry/point.h"
+#include "url/gurl.h"
+
+namespace actor {
+
+class AggregatedJournal;
+class Tool;
+
+// Base class for all tool requests. For tools scoped to a tab (e.g. History
+// traversal, Navigate) derive from TabToolRequest. For tools operating in a web
+// contents, implemented in the renderer, derive from PageToolRequest. Tools not
+// scoped to either can derive directly from this class.
+class ToolRequest {
+ public:
+  ToolRequest();
+  virtual ~ToolRequest();
+
+  // Returns the URL to record in the journal when recording entries for this
+  // request. This may be empty for requests that aren't tied to a frame/tab or
+  // if the scoped object no longer exists.
+  virtual GURL GetURLForJournal() const;
+
+  // Returns the name to use for the journal when recording entries for this
+  // request.
+  virtual std::string JournalEvent() const = 0;
+
+  struct CreateToolResult {
+    CreateToolResult(std::unique_ptr<Tool> tool, mojom::ActionResultPtr result);
+    ~CreateToolResult();
+    std::unique_ptr<Tool> tool;
+    mojom::ActionResultPtr result;
+  };
+
+  // Instantiates the tool requested by this object.
+  virtual CreateToolResult CreateTool(AggregatedJournal& journal) const = 0;
+};
+
+// Tool requests targeting a specific, existing tab should inherit from this
+// subclass.
+class TabToolRequest : public ToolRequest {
+ public:
+  explicit TabToolRequest(const tabs::TabInterface::Handle tab_handle);
+  ~TabToolRequest() override;
+
+  // ToolRequest
+  GURL GetURLForJournal() const override;
+
+  // Returns a handle to the tab being targeted by this request. This handle
+  // should never be null but it may be for a tab that is no longer available.
+  tabs::TabInterface::Handle GetTabHandle() const;
+
+ private:
+  tabs::TabInterface::Handle tab_handle_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/type_tool_request.cc b/chrome/browser/actor/tools/type_tool_request.cc
new file mode 100644
index 0000000..d82e47954
--- /dev/null
+++ b/chrome/browser/actor/tools/type_tool_request.cc
@@ -0,0 +1,57 @@
+// Copyright 2025 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/actor/tools/type_tool_request.h"
+
+#include "chrome/common/actor.mojom.h"
+
+namespace actor {
+
+using ::tabs::TabHandle;
+
+TypeToolRequest::TypeToolRequest(TabHandle tab_handle,
+                                 std::string_view document_identifier,
+                                 const Target& target,
+                                 std::string_view text,
+                                 bool follow_by_enter,
+                                 Mode mode)
+    : PageToolRequest(tab_handle, document_identifier, target),
+      text(text),
+      follow_by_enter(follow_by_enter),
+      mode(mode) {}
+
+TypeToolRequest::~TypeToolRequest() = default;
+
+std::string TypeToolRequest::JournalEvent() const {
+  return "Type";
+}
+
+mojom::ToolActionPtr TypeToolRequest::ToMojoToolAction() const {
+  auto type = mojom::TypeAction::New();
+
+  type->target = PageToolRequest::ToMojoToolTarget(GetTarget());
+
+  type->text = text;
+  type->follow_by_enter = follow_by_enter;
+
+  switch (mode) {
+    case Mode::kReplace:
+      type->mode = mojom::TypeAction::Mode::kDeleteExisting;
+      break;
+    case Mode::kPrepend:
+      type->mode = mojom::TypeAction::Mode::kPrepend;
+      break;
+    case Mode::kAppend:
+      type->mode = mojom::TypeAction::Mode::kAppend;
+      break;
+  }
+
+  return mojom::ToolAction::NewType(std::move(type));
+}
+
+std::unique_ptr<PageToolRequest> TypeToolRequest::Clone() const {
+  return std::make_unique<TypeToolRequest>(*this);
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/type_tool_request.h b/chrome/browser/actor/tools/type_tool_request.h
new file mode 100644
index 0000000..d47aa8b
--- /dev/null
+++ b/chrome/browser/actor/tools/type_tool_request.h
@@ -0,0 +1,56 @@
+// Copyright 2025 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_ACTOR_TOOLS_TYPE_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_TYPE_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "chrome/browser/actor/tools/page_tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+
+namespace actor {
+
+class TypeToolRequest : public PageToolRequest {
+ public:
+  enum class Mode {
+    // Replace all existing text in the editing context.
+    kReplace,
+
+    // Insert text before any existing text in the editing context.
+    kPrepend,
+
+    // Insert text after any existing text in the editing context.
+    kAppend
+  };
+
+  TypeToolRequest(tabs::TabHandle tab_handle,
+                  std::string_view document_identifier,
+                  const Target& target,
+                  std::string_view text,
+                  bool follow_by_enter,
+                  Mode mode);
+  ~TypeToolRequest() override;
+
+  // ToolRequest
+  std::string JournalEvent() const override;
+
+  // PageToolRequest
+  mojom::ToolActionPtr ToMojoToolAction() const override;
+  std::unique_ptr<PageToolRequest> Clone() const override;
+
+  // Text to type.
+  std::string text;
+
+  // Whether to inject an enter/return key after typing.
+  bool follow_by_enter;
+
+  // Behavior with respect to existing text.
+  Mode mode;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_TYPE_TOOL_REQUEST_H_
diff --git a/chrome/browser/actor/tools/wait_tool.cc b/chrome/browser/actor/tools/wait_tool.cc
index 07dd27f..4b3f481 100644
--- a/chrome/browser/actor/tools/wait_tool.cc
+++ b/chrome/browser/actor/tools/wait_tool.cc
@@ -14,15 +14,10 @@
 
 namespace actor {
 
-namespace {
-
-constexpr base::TimeDelta kWaitTime = base::Seconds(3);
-
-}  // namespace
-
 bool WaitTool::no_delay_for_testing_ = false;
 
-WaitTool::WaitTool() = default;
+WaitTool::WaitTool(base::TimeDelta wait_duration)
+    : wait_duration_(wait_duration) {}
 
 WaitTool::~WaitTool() = default;
 
@@ -35,7 +30,7 @@
       FROM_HERE,
       base::BindOnce(&WaitTool::OnDelayFinished, weak_ptr_factory_.GetWeakPtr(),
                      std::move(callback)),
-      no_delay_for_testing_ ? base::TimeDelta() : kWaitTime);
+      no_delay_for_testing_ ? base::TimeDelta() : wait_duration_);
 }
 
 std::string WaitTool::DebugString() const {
@@ -46,8 +41,8 @@
   return "Wait";
 }
 
-std::unique_ptr<ObservationDelayController> WaitTool::GetObservationDelayer(
-    content::RenderFrameHost&) const {
+std::unique_ptr<ObservationDelayController> WaitTool::GetObservationDelayer()
+    const {
   // Wait tool shouldn't delay observation aside from its own built-in delay.
   return nullptr;
 }
diff --git a/chrome/browser/actor/tools/wait_tool.h b/chrome/browser/actor/tools/wait_tool.h
index 4e86116e..33b30fc 100644
--- a/chrome/browser/actor/tools/wait_tool.h
+++ b/chrome/browser/actor/tools/wait_tool.h
@@ -7,13 +7,15 @@
 
 #include "base/memory/weak_ptr.h"
 #include "chrome/browser/actor/tools/tool.h"
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "chrome/browser/actor/tools/wait_tool_request.h"
 
 namespace actor {
 
 // Waits for a page to settle before continuing with other tools.
 class WaitTool : public Tool {
  public:
-  WaitTool();
+  explicit WaitTool(base::TimeDelta wait_duration);
   ~WaitTool() override;
 
   // actor::Tool
@@ -21,16 +23,20 @@
   void Invoke(InvokeCallback callback) override;
   std::string DebugString() const override;
   std::string JournalEvent() const override;
-  std::unique_ptr<ObservationDelayController> GetObservationDelayer(
-      content::RenderFrameHost& target_frame) const override;
+  std::unique_ptr<ObservationDelayController> GetObservationDelayer()
+      const override;
 
   static void SetNoDelayForTesting();
 
  private:
   void OnDelayFinished(InvokeCallback callback);
 
+  // TODO(bokan): This could be removed in place of tests setting the wait
+  // duration explicitly.
   static bool no_delay_for_testing_;
 
+  base::TimeDelta wait_duration_;
+
   base::WeakPtrFactory<WaitTool> weak_ptr_factory_{this};
 };
 
diff --git a/chrome/browser/actor/tools/wait_tool_request.cc b/chrome/browser/actor/tools/wait_tool_request.cc
new file mode 100644
index 0000000..da6ceb3
--- /dev/null
+++ b/chrome/browser/actor/tools/wait_tool_request.cc
@@ -0,0 +1,26 @@
+// Copyright 2025 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/actor/tools/wait_tool_request.h"
+
+#include "chrome/browser/actor/tools/wait_tool.h"
+#include "chrome/common/actor/action_result.h"
+
+namespace actor {
+
+WaitToolRequest::WaitToolRequest(base::TimeDelta wait_duration)
+    : wait_duration_(wait_duration) {}
+
+WaitToolRequest::~WaitToolRequest() = default;
+
+ToolRequest::CreateToolResult WaitToolRequest::CreateTool(
+    AggregatedJournal& journal) const {
+  return {std::make_unique<WaitTool>(wait_duration_), MakeOkResult()};
+}
+
+std::string WaitToolRequest::JournalEvent() const {
+  return "Wait";
+}
+
+}  // namespace actor
diff --git a/chrome/browser/actor/tools/wait_tool_request.h b/chrome/browser/actor/tools/wait_tool_request.h
new file mode 100644
index 0000000..ea24cbc6d
--- /dev/null
+++ b/chrome/browser/actor/tools/wait_tool_request.h
@@ -0,0 +1,32 @@
+// Copyright 2025 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_ACTOR_TOOLS_WAIT_TOOL_REQUEST_H_
+#define CHROME_BROWSER_ACTOR_TOOLS_WAIT_TOOL_REQUEST_H_
+
+#include <memory>
+#include <string>
+
+#include "base/time/time.h"
+#include "chrome/browser/actor/tools/tool_request.h"
+#include "chrome/common/actor.mojom-forward.h"
+
+namespace actor {
+
+class WaitToolRequest : public ToolRequest {
+ public:
+  explicit WaitToolRequest(base::TimeDelta wait_duration);
+  ~WaitToolRequest() override;
+
+  // ToolRequest
+  CreateToolResult CreateTool(AggregatedJournal& journal) const override;
+  std::string JournalEvent() const override;
+
+ private:
+  base::TimeDelta wait_duration_;
+};
+
+}  // namespace actor
+
+#endif  // CHROME_BROWSER_ACTOR_TOOLS_WAIT_TOOL_REQUEST_H_
diff --git a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
index ca7351d..a9afb5c 100644
--- a/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/ash/extensions/autotest_private/autotest_private_api.cc
@@ -1008,7 +1008,7 @@
     DCHECK_EQ(windows.size(), 1u);
     auto* root_window = windows[0].get();
     throughput_.push_back(
-        100 - root_window->GetHost()->compositor()->GetPercentDroppedFrames());
+        root_window->GetHost()->compositor()->GetAverageThroughput());
   }
 
   aura::WindowTracker root_window_tracker_;
@@ -6077,7 +6077,7 @@
 
   auto* root_window = ash::Shell::GetRootWindowForDisplayId(display_id);
   const uint32_t smoothness =
-      100 - root_window->GetHost()->compositor()->GetPercentDroppedFrames();
+      root_window->GetHost()->compositor()->GetAverageThroughput();
   return RespondNow(
       ArgumentList(api::autotest_private::GetDisplaySmoothness::Results::Create(
           smoothness)));
diff --git a/chrome/browser/autofill/android/android_autofill_availability_status.h b/chrome/browser/autofill/android/android_autofill_availability_status.h
index 482a98a..b6b76efd 100644
--- a/chrome/browser/autofill/android/android_autofill_availability_status.h
+++ b/chrome/browser/autofill/android/android_autofill_availability_status.h
@@ -22,7 +22,7 @@
   kNotAllowedByPolicy = 1,
 
   // The Android version doesn't provide a compatible Autofill framework.
-  kAndroidVersionTooOld = 2,
+  // Deprecated: kAndroidVersionTooOld = 2,
 
   // The Autofill Manager is not available or even provided by the OEM.
   kAndroidAutofillManagerNotAvailable = 3,
diff --git a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java
index e814e48..16606d5 100644
--- a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java
+++ b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/AutofillClientProviderUtils.java
@@ -9,7 +9,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.SharedPreferences.Editor;
-import android.os.Build;
 import android.view.autofill.AutofillManager;
 
 import org.jni_zero.CalledByNative;
@@ -96,9 +95,6 @@
         if (!prefs.getBoolean(Pref.AUTOFILL_THIRD_PARTY_PASSWORD_MANAGERS_ALLOWED)) {
             return AndroidAutofillAvailabilityStatus.NOT_ALLOWED_BY_POLICY;
         }
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
-            return AndroidAutofillAvailabilityStatus.ANDROID_VERSION_TOO_OLD;
-        }
         AutofillManager manager =
                 ContextUtils.getApplicationContext().getSystemService(AutofillManager.class);
         if (manager == null) {
diff --git a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsMediator.java b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsMediator.java
index ae43532..2bcc4de 100644
--- a/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsMediator.java
+++ b/chrome/browser/autofill/android/java/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsMediator.java
@@ -138,7 +138,6 @@
             case AndroidAutofillAvailabilityStatus.SETTING_TURNED_OFF: // Pref may be changed!
             case AndroidAutofillAvailabilityStatus.AVAILABLE:
                 return false;
-            case AndroidAutofillAvailabilityStatus.ANDROID_VERSION_TOO_OLD:
             case AndroidAutofillAvailabilityStatus.ANDROID_AUTOFILL_MANAGER_NOT_AVAILABLE:
             case AndroidAutofillAvailabilityStatus.ANDROID_AUTOFILL_NOT_SUPPORTED:
             case AndroidAutofillAvailabilityStatus.UNKNOWN_ANDROID_AUTOFILL_SERVICE:
diff --git a/chrome/browser/autofill/android/junit/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsTest.java b/chrome/browser/autofill/android/junit/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsTest.java
index 593d272..b817d905 100644
--- a/chrome/browser/autofill/android/junit/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsTest.java
+++ b/chrome/browser/autofill/android/junit/src/org/chromium/chrome/browser/autofill/options/AutofillOptionsTest.java
@@ -17,8 +17,8 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.ANDROID_AUTOFILL_MANAGER_NOT_AVAILABLE;
 import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.ANDROID_AUTOFILL_SERVICE_IS_GOOGLE;
-import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.ANDROID_VERSION_TOO_OLD;
 import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.AVAILABLE;
 import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.NOT_ALLOWED_BY_POLICY;
 import static org.chromium.chrome.browser.autofill.AndroidAutofillAvailabilityStatus.UNKNOWN_ANDROID_AUTOFILL_SERVICE;
@@ -217,7 +217,7 @@
     @Test
     @SmallTest
     public void overrideForAwgDoesntAllowOtherChecksToBeSkipped() {
-        setAutofillAvailabilityToUseForTesting(ANDROID_VERSION_TOO_OLD);
+        setAutofillAvailabilityToUseForTesting(ANDROID_AUTOFILL_MANAGER_NOT_AVAILABLE);
         addFeatureOverrideToSkipChecks(ONLY_SKIP_AWG_CHECK_PARAM_VALUE);
         doReturn(false).when(mPrefs).getBoolean(Pref.AUTOFILL_USING_VIRTUAL_VIEW_STRUCTURE);
 
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowMediator.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowMediator.java
index b220911..e446e9f 100644
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowMediator.java
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkSaveFlowMediator.java
@@ -8,7 +8,6 @@
 
 import android.content.Context;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.text.SpannableString;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
@@ -346,9 +345,7 @@
         setPriceTrackingToggleVisualsOnly(true);
 
         // Make sure the notification channel is initialized when the user tracks the product.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            mPriceDropNotificationManager.createNotificationChannel();
-        }
+        mPriceDropNotificationManager.createNotificationChannel();
     }
 
     @Override
diff --git a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkUtils.java b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkUtils.java
index ed811d0..368db4b 100644
--- a/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkUtils.java
+++ b/chrome/browser/bookmarks/android/java/src/org/chromium/chrome/browser/bookmarks/PowerBookmarkUtils.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.bookmarks;
 
 import android.content.res.Resources;
-import android.os.Build;
 
 import com.google.common.primitives.UnsignedLongs;
 
@@ -195,9 +194,7 @@
         // Make sure the notification channel is initialized when the user tracks a product.
         // TODO(crbug.com/40245507): Add a SubscriptionsObserver in the PriceDropNotificationManager
         // and initialize the channel there.
-        if (enabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            priceDropNotificationManager.createNotificationChannel();
-        }
+        priceDropNotificationManager.createNotificationChannel();
         PriceTrackingUtils.setPriceTrackingStateForBookmark(
                 profile, bookmarkId.getId(), enabled, wrapperCallback);
     }
diff --git a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
index 6e5384c..8f9813d 100644
--- a/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
+++ b/chrome/browser/chromeos/extensions/file_system_provider/file_system_provider_api.cc
@@ -327,7 +327,7 @@
   EXTENSION_FUNCTION_VALIDATE(params);
 
   int64_t request_id = params->request_id;
-  bool result = ForwardMountResult(request_id, mutable_args());
+  bool result = ForwardMountResult(request_id, GetMutableArgs());
   if (!result)
     Respond(Error(kInterfaceUnavailable));
   return RespondLater();
@@ -365,7 +365,7 @@
   EXTENSION_FUNCTION_VALIDATE(params);
 
   bool result = ForwardOperationResult(
-      params, mutable_args(),
+      params, GetMutableArgs(),
       crosapi::mojom::FSPOperationResponse::kUnmountSuccess);
   if (!result)
     Respond(Error(kInterfaceUnavailable));
@@ -379,7 +379,7 @@
   EXTENSION_FUNCTION_VALIDATE(params);
 
   bool result = ForwardOperationResult(
-      params, mutable_args(),
+      params, GetMutableArgs(),
       crosapi::mojom::FSPOperationResponse::kGetEntryMetadataSuccess);
   if (!result)
     return RespondNow(Error(kInterfaceUnavailable));
@@ -392,7 +392,7 @@
   std::optional<Params> params(Params::Create(args()));
   EXTENSION_FUNCTION_VALIDATE(params);
   bool result = ForwardOperationResult(
-      params, mutable_args(),
+      params, GetMutableArgs(),
       crosapi::mojom::FSPOperationResponse::kGetActionsSuccess);
   if (!result)
     return RespondNow(Error(kInterfaceUnavailable));
@@ -406,7 +406,7 @@
   std::optional<Params> params(Params::Create(args()));
   EXTENSION_FUNCTION_VALIDATE(params);
   bool result = ForwardOperationResult(
-      params, mutable_args(),
+      params, GetMutableArgs(),
       crosapi::mojom::FSPOperationResponse::kReadDirectorySuccess);
   if (!result)
     return RespondNow(Error(kInterfaceUnavailable));
@@ -422,7 +422,7 @@
   std::optional<Params> params(Params::Create(args()));
   EXTENSION_FUNCTION_VALIDATE(params);
   bool result = ForwardOperationResult(
-      params, mutable_args(),
+      params, GetMutableArgs(),
       crosapi::mojom::FSPOperationResponse::kReadFileSuccess);
   if (!result)
     return RespondNow(Error(kInterfaceUnavailable));
@@ -438,7 +438,7 @@
   std::optional<Params> params(Params::Create(args()));
   EXTENSION_FUNCTION_VALIDATE(params);
   bool result = ForwardOpenFileFinishedSuccessullyResult(std::move(params),
-                                                         mutable_args());
+                                                         GetMutableArgs());
   if (!result) {
     return RespondNow(Error(kInterfaceUnavailable));
   }
@@ -468,7 +468,7 @@
       ->crosapi_ash()
       ->file_system_provider_service_ash()
       ->OpenFileFinishedSuccessfullyWithProfile(
-          std::move(file_system_id), request_id, std::move(mutable_args()),
+          std::move(file_system_id), request_id, std::move(GetMutableArgs()),
           std::move(callback), profile);
   return true;
 }
@@ -480,7 +480,7 @@
   EXTENSION_FUNCTION_VALIDATE(params);
 
   bool result = ForwardOperationResult(
-      params, mutable_args(),
+      params, GetMutableArgs(),
       crosapi::mojom::FSPOperationResponse::kGenericSuccess);
   if (!result)
     return RespondNow(Error(kInterfaceUnavailable));
@@ -499,7 +499,7 @@
   }
 
   bool result = ForwardOperationResult(
-      params, mutable_args(),
+      params, GetMutableArgs(),
       crosapi::mojom::FSPOperationResponse::kGenericFailure);
   if (!result)
     return RespondNow(Error(kInterfaceUnavailable));
diff --git a/chrome/browser/device_reauth/android/device_authenticator_android.cc b/chrome/browser/device_reauth/android/device_authenticator_android.cc
index fcb0310e..36091d70 100644
--- a/chrome/browser/device_reauth/android/device_authenticator_android.cc
+++ b/chrome/browser/device_reauth/android/device_authenticator_android.cc
@@ -146,7 +146,6 @@
     case device_reauth::BiometricsAvailability::kHwUnavailable:
     case device_reauth::BiometricsAvailability::kNotEnrolled:
     case device_reauth::BiometricsAvailability::kSecurityUpdateRequired:
-    case device_reauth::BiometricsAvailability::kAndroidVersionNotSupported:
     case device_reauth::BiometricsAvailability::kOtherError:
       break;
   }
diff --git a/chrome/browser/device_reauth/android/device_authenticator_bridge.h b/chrome/browser/device_reauth/android/device_authenticator_bridge.h
index 2a5a396..07f3a6a6 100644
--- a/chrome/browser/device_reauth/android/device_authenticator_bridge.h
+++ b/chrome/browser/device_reauth/android/device_authenticator_bridge.h
@@ -38,7 +38,7 @@
   kHwUnavailable = 4,
   kNotEnrolled = 5,
   kSecurityUpdateRequired = 6,
-  kAndroidVersionNotSupported = 7,
+  // Deprecated: kAndroidVersionNotSupported = 7,
   kRequired = 8,
   kRequiredButHasError = 9,
 
diff --git a/chrome/browser/device_reauth/android/java/src/org/chromium/chrome/browser/device_reauth/DeviceAuthenticatorControllerImpl.java b/chrome/browser/device_reauth/android/java/src/org/chromium/chrome/browser/device_reauth/DeviceAuthenticatorControllerImpl.java
index ab4d4e9..eccef5f2 100644
--- a/chrome/browser/device_reauth/android/java/src/org/chromium/chrome/browser/device_reauth/DeviceAuthenticatorControllerImpl.java
+++ b/chrome/browser/device_reauth/android/java/src/org/chromium/chrome/browser/device_reauth/DeviceAuthenticatorControllerImpl.java
@@ -15,11 +15,8 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
-import android.os.Build;
 import android.os.CancellationSignal;
 
-import androidx.annotation.RequiresApi;
-
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
 import org.chromium.build.annotations.NullMarked;
@@ -31,29 +28,23 @@
 class DeviceAuthenticatorControllerImpl implements DeviceAuthenticatorController {
     private final Context mContext;
     private final Delegate mDelegate;
-    private @Nullable BiometricPrompt mBiometricPrompt;
+    private final @Nullable BiometricPrompt mBiometricPrompt;
     protected @Nullable CancellationSignal mCancellationSignal;
 
     public DeviceAuthenticatorControllerImpl(Context context, Delegate delegate) {
         mContext = context;
         mDelegate = delegate;
-        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
-            BiometricPrompt.Builder promptBuilder =
-                    new BiometricPrompt.Builder(mContext)
-                            .setTitle(
-                                    mContext.getString(
-                                            R.string.password_filling_reauth_prompt_title));
-            promptBuilder.setDeviceCredentialAllowed(true);
-            promptBuilder.setConfirmationRequired(false);
-            mBiometricPrompt = promptBuilder.build();
-        }
+        BiometricPrompt.Builder promptBuilder =
+                new BiometricPrompt.Builder(mContext)
+                        .setTitle(
+                                mContext.getString(R.string.password_filling_reauth_prompt_title));
+        promptBuilder.setDeviceCredentialAllowed(true);
+        promptBuilder.setConfirmationRequired(false);
+        mBiometricPrompt = promptBuilder.build();
     }
 
     @Override
     public @BiometricsAvailability int canAuthenticateWithBiometric() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
-            return BiometricsAvailability.ANDROID_VERSION_NOT_SUPPORTED;
-        }
         BiometricManager biometricManager = mContext.getSystemService(BiometricManager.class);
         if (biometricManager == null) return BiometricsAvailability.OTHER_ERROR;
 
@@ -82,15 +73,10 @@
 
     @Override
     public boolean canAuthenticateWithBiometricOrScreenLock() {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
-            return false;
-        }
-
         @BiometricsAvailability int availability = canAuthenticateWithBiometric();
         return (availability == BiometricsAvailability.AVAILABLE) || hasScreenLockSetUp();
     }
 
-    @RequiresApi(Build.VERSION_CODES.P)
     @Override
     public void authenticate() {
         if (mBiometricPrompt == null) {
diff --git a/chrome/browser/download/android/download_manager_bridge.cc b/chrome/browser/download/android/download_manager_bridge.cc
index fcf6904..7bdccd4 100644
--- a/chrome/browser/download/android/download_manager_bridge.cc
+++ b/chrome/browser/download/android/download_manager_bridge.cc
@@ -39,23 +39,12 @@
     download::DownloadItem* download,
     AddCompletedDownloadCallback callback) {
   JNIEnv* env = base::android::AttachCurrentThread();
-  std::string file_name = download->GetFileNameToReportUser().value();
-  std::string mime_type = download->GetMimeType();
-  std::string file_path = download->GetTargetFilePath().value();
-  int64_t file_size = download->GetReceivedBytes();
-  ScopedJavaLocalRef<jobject> joriginal_url =
-      url::GURLAndroid::FromNativeGURL(env, download->GetOriginalUrl());
-  ScopedJavaLocalRef<jobject> jreferer =
-      url::GURLAndroid::FromNativeGURL(env, download->GetReferrerUrl());
-  std::string download_guid = download->GetGuid();
 
   // Make copy on the heap so we can pass the pointer through JNI.
   intptr_t callback_id = reinterpret_cast<intptr_t>(
       new AddCompletedDownloadCallback(std::move(callback)));
 
-  Java_DownloadManagerBridge_addCompletedDownload(
-      env, file_name, file_name, mime_type, file_path, file_size, joriginal_url,
-      jreferer, download_guid, callback_id);
+  Java_DownloadManagerBridge_addCompletedDownload(env, callback_id);
 }
 
 void DownloadManagerBridge::RemoveCompletedDownload(
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
index 7ad36d1..b59b4126 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadManagerBridge.java
@@ -9,9 +9,6 @@
 import android.content.SharedPreferences;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Build;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
 import android.os.Environment;
 import android.text.TextUtils;
 
@@ -20,7 +17,6 @@
 import org.jni_zero.NativeMethods;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ContentUriUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
@@ -30,7 +26,6 @@
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.components.browser_ui.util.DownloadUtils;
-import org.chromium.url.GURL;
 
 import java.io.File;
 import java.util.concurrent.RejectedExecutionException;
@@ -85,41 +80,6 @@
     }
 
     /**
-     * Adds a download to the Android DownloadManager.
-     * @see android.app.DownloadManager#addCompletedDownload(String, String, boolean, String,
-     * String, long, boolean)
-     */
-    public static long addCompletedDownload(
-            String fileName,
-            String description,
-            String mimeType,
-            String filePath,
-            long fileSizeBytes,
-            GURL originalUrl,
-            GURL referer,
-            String downloadGuid) {
-        assert !ThreadUtils.runningOnUiThread();
-        assert VERSION.SDK_INT < VERSION_CODES.Q
-                : "addCompletedDownload is deprecated in Q, may cause crash.";
-        long downloadId = getDownloadIdForDownloadGuid(downloadGuid);
-        if (downloadId != DownloadUtils.INVALID_SYSTEM_DOWNLOAD_ID) return downloadId;
-
-        downloadId =
-                DownloadUtils.addCompletedDownload(
-                        fileName,
-                        description,
-                        mimeType,
-                        filePath,
-                        fileSizeBytes,
-                        originalUrl,
-                        referer);
-        if (downloadId != DownloadUtils.INVALID_SYSTEM_DOWNLOAD_ID) {
-            addDownloadIdMapping(downloadId, downloadGuid);
-        }
-        return downloadId;
-    }
-
-    /**
      * Removes a download from Android DownloadManager.
      *
      * @param downloadGuid The GUID of the download.
@@ -242,25 +202,11 @@
     }
 
     /**
-     * Inserts a new download ID mapping into the SharedPreferences
-     *
-     * @param downloadId system download ID from Android DownloadManager.
-     * @param downloadGuid Download GUID.
-     */
-    private static void addDownloadIdMapping(long downloadId, String downloadGuid) {
-        synchronized (sLock) {
-            SharedPreferences sharedPrefs = getSharedPreferences();
-            SharedPreferences.Editor editor = sharedPrefs.edit();
-            editor.putLong(downloadGuid, downloadId);
-            editor.apply();
-        }
-    }
-
-    /**
      * Removes a download Id mapping from the SharedPreferences given the download GUID.
+     *
      * @param downloadGuid Download GUID.
      * @return the Android DownloadManager's download ID that is removed, or
-     *         INVALID_SYSTEM_DOWNLOAD_ID if it is not found.
+     *     INVALID_SYSTEM_DOWNLOAD_ID if it is not found.
      */
     private static long removeDownloadIdMapping(String downloadGuid) {
         long downloadId = DownloadUtils.INVALID_SYSTEM_DOWNLOAD_ID;
@@ -296,38 +242,12 @@
      * to the android's DownloadManager if the download is not a content URI.
      */
     @CalledByNative
-    private static void addCompletedDownload(
-            @JniType("std::string") String fileName,
-            @JniType("std::string") String description,
-            @JniType("std::string") String originalMimeType,
-            @JniType("std::string") String filePath,
-            long fileSizeBytes,
-            GURL originalUrl,
-            GURL referrer,
-            @JniType("std::string") String downloadGuid,
-            long callbackId) {
-        final String mimeType =
-                MimeUtils.remapGenericMimeType(originalMimeType, originalUrl.getSpec(), fileName);
+    private static void addCompletedDownload(long callbackId) {
         AsyncTask<Long> task =
                 new AsyncTask<>() {
                     @Override
                     protected Long doInBackground() {
-                        long downloadId = DownloadConstants.INVALID_DOWNLOAD_ID;
-                        // On Android Q-, add the completed download to Android download manager.
-                        if (!ContentUriUtils.isContentUri(filePath)
-                                && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
-                            downloadId =
-                                    addCompletedDownload(
-                                            fileName,
-                                            description,
-                                            mimeType,
-                                            filePath,
-                                            fileSizeBytes,
-                                            originalUrl,
-                                            referrer,
-                                            downloadGuid);
-                        }
-                        return downloadId;
+                        return DownloadConstants.INVALID_DOWNLOAD_ID;
                     }
 
                     @Override
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_api.cc b/chrome/browser/extensions/api/content_settings/content_settings_api.cc
index 3b1daaa..249c3fe8 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_api.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_api.cc
@@ -80,7 +80,8 @@
 ExtensionFunction::ResponseAction
 ContentSettingsContentSettingClearFunction::Run() {
   ContentSettingsType content_type;
-  EXTENSION_FUNCTION_VALIDATE(RemoveContentType(mutable_args(), &content_type));
+  EXTENSION_FUNCTION_VALIDATE(
+      RemoveContentType(GetMutableArgs(), &content_type));
 
   std::optional<Clear::Params> params = Clear::Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
@@ -117,7 +118,8 @@
 ExtensionFunction::ResponseAction
 ContentSettingsContentSettingGetFunction::Run() {
   ContentSettingsType content_type;
-  EXTENSION_FUNCTION_VALIDATE(RemoveContentType(mutable_args(), &content_type));
+  EXTENSION_FUNCTION_VALIDATE(
+      RemoveContentType(GetMutableArgs(), &content_type));
 
   std::optional<Get::Params> params = Get::Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
@@ -189,7 +191,8 @@
 ExtensionFunction::ResponseAction
 ContentSettingsContentSettingSetFunction::Run() {
   ContentSettingsType content_type;
-  EXTENSION_FUNCTION_VALIDATE(RemoveContentType(mutable_args(), &content_type));
+  EXTENSION_FUNCTION_VALIDATE(
+      RemoveContentType(GetMutableArgs(), &content_type));
 
   std::optional<Set::Params> params = Set::Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
diff --git a/chrome/browser/extensions/api/desktop_capture/desktop_capture_api.cc b/chrome/browser/extensions/api/desktop_capture/desktop_capture_api.cc
index 828882c..ae52c23c 100644
--- a/chrome/browser/extensions/api/desktop_capture/desktop_capture_api.cc
+++ b/chrome/browser/extensions/api/desktop_capture/desktop_capture_api.cc
@@ -46,7 +46,7 @@
   DesktopCaptureRequestsRegistry::GetInstance()->AddRequest(source_process_id(),
                                                             request_id_, this);
 
-  mutable_args().erase(args().begin());
+  GetMutableArgs().erase(args().begin());
 
   std::optional<api::desktop_capture::ChooseDesktopMedia::Params> params =
       api::desktop_capture::ChooseDesktopMedia::Params::Create(args());
diff --git a/chrome/browser/extensions/api/extension_action/extension_action_api.cc b/chrome/browser/extensions/api/extension_action/extension_action_api.cc
index 998e868b..d802758 100644
--- a/chrome/browser/extensions/api/extension_action/extension_action_api.cc
+++ b/chrome/browser/extensions/api/extension_action/extension_action_api.cc
@@ -130,7 +130,7 @@
     return true;
   }
 
-  base::Value& first_arg = mutable_args()[0];
+  const base::Value& first_arg = args()[0];
 
   switch (first_arg.type()) {
     case base::Value::Type::INTEGER:
@@ -141,7 +141,7 @@
       // Found the details argument.
       details_ = &first_arg.GetDict();
       // Still need to check for the tabId within details.
-      if (base::Value* tab_id_value = details_->Find("tabId")) {
+      if (const base::Value* tab_id_value = details_->Find("tabId")) {
         switch (tab_id_value->type()) {
           case base::Value::Type::NONE:
             // OK; tabId is optional, leave it default.
@@ -212,7 +212,7 @@
 
   // setIcon can take a variant argument: either a dictionary of canvas
   // ImageData, or an icon index.
-  base::Value::Dict* canvas_set = details_->FindDict("imageData");
+  const base::Value::Dict* canvas_set = details_->FindDict("imageData");
   if (canvas_set) {
     gfx::ImageSkia icon;
 
@@ -257,7 +257,7 @@
 ExtensionFunction::ResponseAction
 ExtensionActionSetPopupFunction::RunExtensionAction() {
   EXTENSION_FUNCTION_VALIDATE(details_);
-  std::string* popup_string = details_->FindString("popup");
+  const std::string* popup_string = details_->FindString("popup");
   EXTENSION_FUNCTION_VALIDATE(popup_string);
   GURL popup_url;
 
@@ -280,7 +280,7 @@
 ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
   EXTENSION_FUNCTION_VALIDATE(details_);
 
-  std::string* badge_text = details_->FindString("text");
+  const std::string* badge_text = details_->FindString("text");
   if (badge_text) {
     extension_action_->SetBadgeText(tab_id_, *badge_text);
   } else {
@@ -294,7 +294,7 @@
 ExtensionFunction::ResponseAction
 ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
   EXTENSION_FUNCTION_VALIDATE(details_);
-  base::Value* color_value = details_->Find("color");
+  const base::Value* color_value = details_->Find("color");
   EXTENSION_FUNCTION_VALIDATE(color_value);
   SkColor color = 0;
   if (!ParseColor(*color_value, color)) {
@@ -308,7 +308,7 @@
 ExtensionFunction::ResponseAction
 ActionSetBadgeTextColorFunction::RunExtensionAction() {
   EXTENSION_FUNCTION_VALIDATE(details_);
-  base::Value* color_value = details_->Find("color");
+  const base::Value* color_value = details_->Find("color");
   EXTENSION_FUNCTION_VALIDATE(color_value);
   SkColor color = 0;
   if (!ParseColor(*color_value, color)) {
diff --git a/chrome/browser/extensions/api/extension_action/extension_action_api.h b/chrome/browser/extensions/api/extension_action/extension_action_api.h
index eb7f3fad..de62a845 100644
--- a/chrome/browser/extensions/api/extension_action/extension_action_api.h
+++ b/chrome/browser/extensions/api/extension_action/extension_action_api.h
@@ -50,7 +50,7 @@
 
   // All the extension action APIs take a single argument called details that
   // is a dictionary.
-  raw_ptr<base::Value::Dict> details_;
+  raw_ptr<const base::Value::Dict> details_;
 
   // The tab id the extension action function should apply to, if any, or
   // kDefaultTabId if none was specified.
diff --git a/chrome/browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc b/chrome/browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc
index aadb317..5d075d1 100644
--- a/chrome/browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc
+++ b/chrome/browser/extensions/api/webrtc_desktop_capture_private/webrtc_desktop_capture_private_api.cc
@@ -41,7 +41,7 @@
   DesktopCaptureRequestsRegistry::GetInstance()->AddRequest(source_process_id(),
                                                             request_id_, this);
 
-  mutable_args().erase(args().begin());
+  GetMutableArgs().erase(args().begin());
 
   std::optional<Params> params = Params::Create(args());
   EXTENSION_FUNCTION_VALIDATE(params);
diff --git a/chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/settings/LanguageSettingsTest.java b/chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/settings/LanguageSettingsTest.java
index 32161d2..394406f 100644
--- a/chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/settings/LanguageSettingsTest.java
+++ b/chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/settings/LanguageSettingsTest.java
@@ -121,7 +121,6 @@
     @Test
     @SmallTest
     @DisableIf.Build(
-            sdk_is_greater_than = Build.VERSION_CODES.P,
             sdk_is_less_than = Build.VERSION_CODES.S,
             message = "Flaky in Q and R, crbug.com/40190787")
     public void testToggleOfferToTranslate() {
diff --git a/chrome/browser/media/android/media_capture_picker_dialog_bridge.cc b/chrome/browser/media/android/media_capture_picker_dialog_bridge.cc
index d28edc8..1219125 100644
--- a/chrome/browser/media/android/media_capture_picker_dialog_bridge.cc
+++ b/chrome/browser/media/android/media_capture_picker_dialog_bridge.cc
@@ -10,7 +10,6 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
-#include "ui/android/window_android.h"
 
 // Must come after all headers that specialize FromJniType() / ToJniType().
 #include "chrome/android/chrome_jni_headers/MediaCapturePickerDialogBridge_jni.h"
@@ -39,16 +38,8 @@
   CHECK(callback_.is_null());
   callback_ = std::move(callback);
   JNIEnv* env = base::android::AttachCurrentThread();
-  ui::WindowAndroid* window_android = web_contents->GetTopLevelNativeWindow();
-  if (!window_android) {
-    content::GetUIThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(std::move(callback_), content::DesktopMediaID()));
-    return;
-  }
-
   Java_MediaCapturePickerDialogBridge_showDialog(
-      env, java_object_, window_android->GetJavaObject(), app_name,
+      env, java_object_, web_contents->GetJavaWebContents(), app_name,
       request_audio);
 }
 
diff --git a/chrome/browser/page_load_metrics/integration_tests/smoothness_metric_browsertest.cc b/chrome/browser/page_load_metrics/integration_tests/smoothness_metric_browsertest.cc
index 0eb9f73..f1b92ba 100644
--- a/chrome/browser/page_load_metrics/integration_tests/smoothness_metric_browsertest.cc
+++ b/chrome/browser/page_load_metrics/integration_tests/smoothness_metric_browsertest.cc
@@ -77,14 +77,8 @@
   web_contents()->ClosePage();
   ui_test_utils::WaitForBrowserToClose(browser());
 
-  int64_t fsm_pdf_value, avg_value;
-  // Ensure that the smoothness UKM is reported.
-  ASSERT_TRUE(ExtractUKMSmoothnessMetric(
-      ukm_recorder(),
-      Graphics_Smoothness_NormalizedPercentDroppedFrames::kEntryName,
-      Graphics_Smoothness_NormalizedPercentDroppedFrames::kAverageName,
-      &avg_value));
-  // FrameSequenceMetric export should also show a value.
+  int64_t fsm_pdf_value;
+  // FrameSequenceMetric export should show a value.
   ASSERT_TRUE(ExtractUKMSmoothnessMetric(
       ukm_recorder(), Graphics_Smoothness_FrameSequence::kEntryName,
       Graphics_Smoothness_FrameSequence::kPercentDroppedFramesName,
@@ -93,5 +87,4 @@
   // Some of the frames should be dropped. It is not possible to measure the
   // exact number of dropped frames, so validate that it is non-zero.
   EXPECT_NE(fsm_pdf_value, 0);
-  EXPECT_NE(avg_value, 0);
 }
diff --git a/chrome/browser/printing/printer_manager_dialog_linux.cc b/chrome/browser/printing/printer_manager_dialog_linux.cc
index 47454d2..7e4ead49 100644
--- a/chrome/browser/printing/printer_manager_dialog_linux.cc
+++ b/chrome/browser/printing/printer_manager_dialog_linux.cc
@@ -83,6 +83,7 @@
     case base::nix::DESKTOP_ENVIRONMENT_PANTHEON:
     case base::nix::DESKTOP_ENVIRONMENT_UNITY:
     case base::nix::DESKTOP_ENVIRONMENT_XFCE:
+    case base::nix::DESKTOP_ENVIRONMENT_COSMIC:
       opened = OpenPrinterConfigDialog(kSystemConfigPrinterCommand);
       break;
     case base::nix::DESKTOP_ENVIRONMENT_DEEPIN:
diff --git a/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteController.java b/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteController.java
index b91ace5..6e4d484c 100644
--- a/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteController.java
+++ b/chrome/browser/quick_delete/android/java/src/org/chromium/chrome/browser/quick_delete/QuickDeleteController.java
@@ -7,7 +7,6 @@
 import static org.chromium.build.NullUtil.assumeNonNull;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.view.LayoutInflater;
@@ -233,12 +232,7 @@
     private void triggerHapticFeedback() {
         Vibrator v = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         final long duration = 50;
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            v.vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE));
-        } else {
-            // Deprecated in API 26.
-            v.vibrate(duration);
-        }
+        v.vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE));
     }
 
     /** A method to show the quick delete snack-bar. */
diff --git a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/TapToSeekSelectionManager.java b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/TapToSeekSelectionManager.java
index 8a35f5e..70367eb 100644
--- a/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/TapToSeekSelectionManager.java
+++ b/chrome/browser/readaloud/android/java/src/org/chromium/chrome/browser/readaloud/TapToSeekSelectionManager.java
@@ -5,7 +5,6 @@
 
 import static org.chromium.build.NullUtil.assumeNonNull;
 
-import android.os.Build;
 import android.view.textclassifier.TextClassifier;
 
 import androidx.annotation.VisibleForTesting;
@@ -29,11 +28,6 @@
  */
 @NullMarked
 public class TapToSeekSelectionManager implements SelectionClient.SurroundingTextCallback {
-    // Whether Smart Select is allowed to be enabled in Chrome. Set to true for Android O+. This
-    // check is also mirrored in {@link SelectionClientManager} when making the Smart Selection
-    // Client
-    private static final boolean IS_SMART_SELECTION_ENABLED_IN_CHROME =
-            Build.VERSION.SDK_INT > Build.VERSION_CODES.O;
     // Tab that Tap to Seek is hooked into. Can be null if not hooked into any tab.
     private @Nullable Tab mObservingTab;
     private final ReadAloudController mReadAloudController;
@@ -51,9 +45,7 @@
             ReadAloudController readAloudController,
             ObservableSupplier<@Nullable Tab> activePlaybackTab) {
         mReadAloudController = readAloudController;
-        if (IS_SMART_SELECTION_ENABLED_IN_CHROME) {
-            activePlaybackTab.addObserver(this::onActivePlaybackTabUpdated);
-        }
+        activePlaybackTab.addObserver(this::onActivePlaybackTabUpdated);
     }
 
     @Override
@@ -88,7 +80,7 @@
     }
 
     private void addHooks(@Nullable WebContents webContents) {
-        if (IS_SMART_SELECTION_ENABLED_IN_CHROME && webContents != null) {
+        if (webContents != null) {
             mSelectionClient =
                     new TapToSeekSelectionClient(
                             assumeNonNull(
diff --git a/chrome/browser/resources/web_app_internals/web_app_internals.ts b/chrome/browser/resources/web_app_internals/web_app_internals.ts
index 7021609..a75260e5 100644
--- a/chrome/browser/resources/web_app_internals/web_app_internals.ts
+++ b/chrome/browser/resources/web_app_internals/web_app_internals.ts
@@ -235,6 +235,13 @@
   const installButton = getRequiredElement<HTMLButtonElement>(
       'iwa-update-manifest-dialog-install');
 
+  const closeButton =
+      getRequiredElement<HTMLButtonElement>('iwa-update-manifest-dialog-close');
+
+  closeButton.addEventListener('click', () => {
+    iwaDevUpdateManifestDialog.close();
+  }, {once: true});
+
   const installEventListener = async () => {
     installButton.removeEventListener('click', installEventListener);
 
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/BUILD.gn b/chrome/browser/touch_to_fill/autofill/android/internal/BUILD.gn
index ba9436c..d708974 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/BUILD.gn
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/BUILD.gn
@@ -46,6 +46,7 @@
 
 android_resources("java_resources") {
   sources = [
+    "java/res/layout/touch_to_fill_all_loyalty_cards_item.xml",
     "java/res/layout/touch_to_fill_credit_card_sheet_item.xml",
     "java/res/layout/touch_to_fill_iban_sheet_item.xml",
     "java/res/layout/touch_to_fill_loyalty_card_sheet_item.xml",
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/res/layout/touch_to_fill_all_loyalty_cards_item.xml b/chrome/browser/touch_to_fill/autofill/android/internal/java/res/layout/touch_to_fill_all_loyalty_cards_item.xml
new file mode 100644
index 0000000..0498b3f
--- /dev/null
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/res/layout/touch_to_fill_all_loyalty_cards_item.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2025 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:descendantFocusability="blocksDescendants"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginBottom="2dp"
+    android:layout_marginHorizontal="8dp"
+    android:background="@color/baseline_neutral_90"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:padding="16dp">
+
+    <TextView
+        android:id="@+id/all_loyalty_cards_item_title"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start"
+        android:layout_weight="1"
+        android:maxLines="1"
+        android:text="@string/autofill_bottom_sheet_all_your_loyalty_cards"
+        android:textAppearance="@style/TextAppearance.TextLarge.Primary"
+        android:ellipsize="middle" />
+
+    <ImageView
+        android:id="@+id/loyalty_card_icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end"
+        android:contentDescription="@string/autofill_bottom_sheet_expand_all_loyalty_cards_description"
+        app:tint="@color/default_icon_color_secondary_tint_list"
+        app:srcCompat="@drawable/ic_expand_more_horizontal_black_24dp" />
+
+</LinearLayout>
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
index 29348f3..e713c3f 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodControllerRobolectricTest.java
@@ -49,6 +49,7 @@
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.IBAN_NICKNAME;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.IBAN_VALUE;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.ON_IBAN_CLICK_ACTION;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.ALL_LOYALTY_CARDS;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.CREDIT_CARD;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.FILL_BUTTON;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.FOOTER;
@@ -914,6 +915,8 @@
                 is(LOYALTY_CARD_1.getLoyaltyCardNumber()));
         assertThat(loyaltyCardModel.get(MERCHANT_NAME), is(LOYALTY_CARD_1.getMerchantName()));
 
+        assertThat(getModelsOfType(itemList, ALL_LOYALTY_CARDS).size(), is(0));
+
         assertThat(getModelsOfType(itemList, FILL_BUTTON).size(), is(1));
         assertThat(getModelsOfType(itemList, WALLET_SETTINGS_BUTTON).size(), is(1));
         PropertyModel walletSettingButtonModel =
@@ -949,7 +952,9 @@
                 HistogramWatcher.newSingleRecordWatcher(
                         TOUCH_TO_FILL_NUMBER_OF_LOYALTY_CARDS_SHOWN, 1);
         mCoordinator.showLoyaltyCards(
-                List.of(LOYALTY_CARD_1), List.of(LOYALTY_CARD_1), /* firstTimeUsage= */ false);
+                List.of(LOYALTY_CARD_1),
+                List.of(LOYALTY_CARD_1, LOYALTY_CARD_2),
+                /* firstTimeUsage= */ false);
         histogramWatcher.assertExpected();
 
         ModelList itemList = mTouchToFillPaymentMethodModel.get(SHEET_ITEMS);
@@ -967,6 +972,7 @@
                 is(LOYALTY_CARD_1.getLoyaltyCardNumber()));
         assertThat(loyaltyCardModel.get(MERCHANT_NAME), is(LOYALTY_CARD_1.getMerchantName()));
 
+        assertThat(getModelsOfType(itemList, ALL_LOYALTY_CARDS).size(), is(1));
         assertThat(getModelsOfType(itemList, FILL_BUTTON).size(), is(1));
         assertThat(getModelsOfType(itemList, WALLET_SETTINGS_BUTTON).size(), is(0));
         assertThat(getModelsOfType(itemList, FOOTER).size(), is(1));
@@ -1004,6 +1010,7 @@
                 is(LOYALTY_CARD_2.getLoyaltyCardNumber()));
         assertThat(loyaltyCardModel2.get(MERCHANT_NAME), is(LOYALTY_CARD_2.getMerchantName()));
 
+        assertThat(getModelsOfType(itemList, ALL_LOYALTY_CARDS).size(), is(1));
         assertThat(getModelsOfType(itemList, FILL_BUTTON).size(), is(0));
         assertThat(getModelsOfType(itemList, WALLET_SETTINGS_BUTTON).size(), is(0));
         assertThat(getModelsOfType(itemList, FOOTER).size(), is(1));
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodCoordinator.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodCoordinator.java
index 9e3b91e..7f9d7bc9 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodCoordinator.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodCoordinator.java
@@ -7,6 +7,7 @@
 import static org.chromium.chrome.browser.autofill.AutofillUiUtils.getCardIcon;
 import static org.chromium.chrome.browser.autofill.AutofillUiUtils.getValuableIcon;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.DISMISS_HANDLER;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.ALL_LOYALTY_CARDS;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.CREDIT_CARD;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.FILL_BUTTON;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.FOOTER;
@@ -132,6 +133,10 @@
                 TouchToFillPaymentMethodViewBinder::createLoyaltyCardItemView,
                 TouchToFillPaymentMethodViewBinder::bindLoyaltyCardItemView);
         adapter.registerType(
+                ALL_LOYALTY_CARDS,
+                TouchToFillPaymentMethodViewBinder::createAllLoyaltyCardsItemView,
+                TouchToFillPaymentMethodViewBinder::bindAllLoyaltyCardsItemView);
+        adapter.registerType(
                 HEADER,
                 TouchToFillPaymentMethodViewBinder::createHeaderItemView,
                 TouchToFillPaymentMethodViewBinder::bindHeaderView);
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
index 6f038ac..7af1b923 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodMediator.java
@@ -27,6 +27,7 @@
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.IBAN_VALUE;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.NON_TRANSFORMING_IBAN_KEYS;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.ON_IBAN_CLICK_ACTION;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.ALL_LOYALTY_CARDS;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.CREDIT_CARD;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.FILL_BUTTON;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.FOOTER;
@@ -60,6 +61,7 @@
 import org.chromium.chrome.browser.touch_to_fill.common.FillableItemCollectionInfo;
 import org.chromium.chrome.browser.touch_to_fill.common.TouchToFillResourceProvider;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodComponent.Delegate;
+import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.AllLoyaltyCardsItemProperties;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ButtonProperties;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.FooterProperties;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.HeaderProperties;
@@ -189,7 +191,7 @@
     private PropertyModel mModel;
     private List<AutofillSuggestion> mSuggestions;
     private List<Iban> mIbans;
-    private List<LoyaltyCard> mLoyaltyCards;
+    private List<LoyaltyCard> mAffiliatedLoyaltyCards;
     private BottomSheetFocusHelper mBottomSheetFocusHelper;
 
     private InputProtector mInputProtector = new InputProtector();
@@ -212,7 +214,7 @@
         assert suggestions != null;
         mSuggestions = suggestions;
         mIbans = null;
-        mLoyaltyCards = null;
+        mAffiliatedLoyaltyCards = null;
 
         ModelList sheetItems = mModel.get(SHEET_ITEMS);
         sheetItems.clear();
@@ -262,7 +264,7 @@
         assert ibans != null;
         mIbans = ibans;
         mSuggestions = null;
-        mLoyaltyCards = null;
+        mAffiliatedLoyaltyCards = null;
 
         ModelList sheetItems = mModel.get(SHEET_ITEMS);
         sheetItems.clear();
@@ -301,7 +303,7 @@
         mInputProtector.markShowTime();
 
         assert allLoyaltyCards != null && affiliatedLoyaltyCards != null;
-        mLoyaltyCards = allLoyaltyCards;
+        mAffiliatedLoyaltyCards = affiliatedLoyaltyCards;
         mSuggestions = null;
         mIbans = null;
         // TODO: crbug.com/420957826 - Display affiliated loyalty cards.
@@ -309,12 +311,17 @@
         ModelList sheetItems = mModel.get(SHEET_ITEMS);
         sheetItems.clear();
 
-        for (LoyaltyCard loyaltyCard : mLoyaltyCards) {
+        for (LoyaltyCard loyaltyCard : mAffiliatedLoyaltyCards) {
             final PropertyModel model = createLoyaltyCardModel(loyaltyCard, valuableImageFunction);
             sheetItems.add(new ListItem(LOYALTY_CARD, model));
         }
 
-        if (mLoyaltyCards.size() == 1) {
+        if (!firstTimeUsage) {
+            assert !allLoyaltyCards.isEmpty();
+            sheetItems.add(new ListItem(ALL_LOYALTY_CARDS, createAllLoyaltyCardsItemModel()));
+        }
+
+        if (mAffiliatedLoyaltyCards.size() == 1) {
             // Use the LOYALTY_CARD model as the property model for the fill button too.
             assert sheetItems.get(0).type == LOYALTY_CARD;
             sheetItems.add(
@@ -322,7 +329,9 @@
                             FILL_BUTTON,
                             createFillButtonModel(
                                     R.string.autofill_loyalty_card_autofill_button,
-                                    () -> this.onSelectedLoyaltyCard(mLoyaltyCards.get(0)))));
+                                    () ->
+                                            this.onSelectedLoyaltyCard(
+                                                    mAffiliatedLoyaltyCards.get(0)))));
         }
 
         if (firstTimeUsage) {
@@ -336,7 +345,7 @@
         mModel.set(VISIBLE, true);
 
         RecordHistogram.recordCount100Histogram(
-                TOUCH_TO_FILL_NUMBER_OF_LOYALTY_CARDS_SHOWN, mLoyaltyCards.size());
+                TOUCH_TO_FILL_NUMBER_OF_LOYALTY_CARDS_SHOWN, mAffiliatedLoyaltyCards.size());
     }
 
     void hideSheet() {
@@ -364,7 +373,7 @@
                         TouchToFillIbanOutcome.DISMISS,
                         TouchToFillIbanOutcome.MAX_VALUE);
             } else {
-                assert mLoyaltyCards != null;
+                assert mAffiliatedLoyaltyCards != null;
                 recordTouchToFillLoyaltyCardOutcomeHistogram(TouchToFillLoyaltyCardOutcome.DISMISS);
             }
         }
@@ -387,13 +396,13 @@
     }
 
     public void showGoogleWalletSettings() {
-        assert mLoyaltyCards != null;
+        assert mAffiliatedLoyaltyCards != null;
         recordTouchToFillLoyaltyCardOutcomeHistogram(TouchToFillLoyaltyCardOutcome.WALLET_SETTINGS);
         mDelegate.showGoogleWalletSettings();
     }
 
     public void showManageLoyaltyCards() {
-        assert mLoyaltyCards != null;
+        assert mAffiliatedLoyaltyCards != null;
         mDelegate.openPassesManagementUi();
         recordTouchToFillLoyaltyCardOutcomeHistogram(
                 TouchToFillLoyaltyCardOutcome.MANAGE_LOYALTY_CARDS);
@@ -414,7 +423,7 @@
                 TOUCH_TO_FILL_CREDIT_CARD_INDEX_SELECTED, mSuggestions.indexOf(suggestion));
     }
 
-    public void onSelectedIban(Iban iban) {
+    private void onSelectedIban(Iban iban) {
         if (!mInputProtector.shouldInputBeProcessed()) return;
         if (iban.getRecordType() == IbanRecordType.LOCAL_IBAN) {
             mDelegate.localIbanSuggestionSelected(iban.getGuid());
@@ -426,12 +435,17 @@
                 TOUCH_TO_FILL_IBAN_INDEX_SELECTED, mIbans.indexOf(iban));
     }
 
-    public void onSelectedLoyaltyCard(LoyaltyCard loyaltyCard) {
+    private void onSelectedLoyaltyCard(LoyaltyCard loyaltyCard) {
         if (!mInputProtector.shouldInputBeProcessed()) return;
         mDelegate.loyaltyCardSuggestionSelected(loyaltyCard.getLoyaltyCardNumber());
         recordTouchToFillLoyaltyCardOutcomeHistogram(TouchToFillLoyaltyCardOutcome.LOYALTY_CARD);
         RecordHistogram.recordCount100Histogram(
-                TOUCH_TO_FILL_LOYALTY_CARD_INDEX_SELECTED, mLoyaltyCards.indexOf(loyaltyCard));
+                TOUCH_TO_FILL_LOYALTY_CARD_INDEX_SELECTED,
+                mAffiliatedLoyaltyCards.indexOf(loyaltyCard));
+    }
+
+    private void showAllLoyaltyCards() {
+        // TODO: crbug.com/420957826 - Implement all loyalty cards screen.
     }
 
     private PropertyModel createCardSuggestionModel(
@@ -496,6 +510,12 @@
         return loyaltyCardModelBuilder.build();
     }
 
+    private PropertyModel createAllLoyaltyCardsItemModel() {
+        return new PropertyModel.Builder(AllLoyaltyCardsItemProperties.ALL_KEYS)
+                .with(AllLoyaltyCardsItemProperties.ON_CLICK_ACTION, this::showAllLoyaltyCards)
+                .build();
+    }
+
     private PropertyModel createFillButtonModel(@StringRes int titleId, Runnable onClickAction) {
         return new PropertyModel.Builder(ButtonProperties.ALL_KEYS)
                 .with(TEXT_ID, titleId)
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodProperties.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodProperties.java
index 6f3b900..c69a7e3 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodProperties.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodProperties.java
@@ -43,18 +43,21 @@
         // A section containing the loyalty card data.
         int LOYALTY_CARD = 3;
 
+        // An item which displays all user's loyalty cards upon click.
+        int ALL_LOYALTY_CARDS = 4;
+
         // A "Continue" button, which is shown when there is only one payment
         // method available.
-        int FILL_BUTTON = 4;
+        int FILL_BUTTON = 5;
 
         // A button that redirects the user to the Wallet settings in Chrome.
-        int WALLET_SETTINGS_BUTTON = 5;
+        int WALLET_SETTINGS_BUTTON = 6;
 
         // A footer section containing additional actions.
-        int FOOTER = 6;
+        int FOOTER = 7;
 
         // A section with a terms label is present when card benefits are available.
-        int TERMS_LABEL = 7;
+        int TERMS_LABEL = 8;
     }
 
     /** Metadata associated with a card's image. */
@@ -151,6 +154,16 @@
         private LoyaltyCardProperties() {}
     }
 
+    /** Properties for the "All your loyalty cards" item in the TouchToFill sheet for payments. */
+    static class AllLoyaltyCardsItemProperties {
+        static final PropertyModel.ReadableObjectPropertyKey<Runnable> ON_CLICK_ACTION =
+                new PropertyModel.ReadableObjectPropertyKey<>("all_loyalty_cards_on_click_action");
+
+        static final PropertyKey[] ALL_KEYS = {ON_CLICK_ACTION};
+
+        private AllLoyaltyCardsItemProperties() {}
+    }
+
     /**
      * Properties defined here reflect the visible state of the terms message in the TouchToFill
      * sheet for payments.
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodView.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodView.java
index cc97044..21b698e 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodView.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodView.java
@@ -50,6 +50,7 @@
                 case ItemType.CREDIT_CARD:
                 case ItemType.IBAN:
                 case ItemType.LOYALTY_CARD:
+                case ItemType.ALL_LOYALTY_CARDS:
                     return false;
             }
             assert false : "Undefined whether to skip setting background for item of type: " + type;
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBinder.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBinder.java
index 0c7249b0..ba35740 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBinder.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewBinder.java
@@ -48,6 +48,7 @@
 
 import org.chromium.chrome.browser.autofill.AutofillUiUtils;
 import org.chromium.chrome.browser.touch_to_fill.common.FillableItemCollectionInfo;
+import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.AllLoyaltyCardsItemProperties;
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
@@ -256,6 +257,24 @@
         }
     }
 
+    static View createAllLoyaltyCardsItemView(ViewGroup parent) {
+        View view =
+                LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.touch_to_fill_all_loyalty_cards_item, parent, false);
+        AutofillUiUtils.setFilterTouchForSecurity(view);
+        return view;
+    }
+
+    static void bindAllLoyaltyCardsItemView(
+            PropertyModel model, View view, PropertyKey propertyKey) {
+        if (propertyKey == AllLoyaltyCardsItemProperties.ON_CLICK_ACTION) {
+            view.setOnClickListener(
+                    unusedView -> model.get(AllLoyaltyCardsItemProperties.ON_CLICK_ACTION).run());
+        } else {
+            assert false : "Unhandled update to property: " + propertyKey;
+        }
+    }
+
     /**
      * Factory used to create a new header inside the ListView inside the {@link
      * TouchToFillPaymentMethodView}.
diff --git a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
index 6050d8a7..09e6df2 100644
--- a/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
+++ b/chrome/browser/touch_to_fill/autofill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/payments/TouchToFillPaymentMethodViewTest.java
@@ -44,6 +44,7 @@
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.IBAN_VALUE;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.NON_TRANSFORMING_IBAN_KEYS;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.IbanProperties.ON_IBAN_CLICK_ACTION;
+import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.ALL_LOYALTY_CARDS;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.CREDIT_CARD;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.FILL_BUTTON;
 import static org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ItemType.HEADER;
@@ -65,6 +66,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.IdRes;
 import androidx.annotation.StringRes;
 import androidx.recyclerview.widget.RecyclerView;
 import androidx.test.filters.MediumTest;
@@ -95,6 +97,7 @@
 import org.chromium.chrome.browser.flags.ChromeSwitches;
 import org.chromium.chrome.browser.touch_to_fill.common.FillableItemCollectionInfo;
 import org.chromium.chrome.browser.touch_to_fill.common.TouchToFillResourceProvider;
+import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.AllLoyaltyCardsItemProperties;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.ButtonProperties;
 import org.chromium.chrome.browser.touch_to_fill.payments.TouchToFillPaymentMethodProperties.HeaderProperties;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@@ -368,10 +371,7 @@
                         .findViewById(R.id.touch_to_fill_sheet_title);
         assertThat(
                 title.getText().toString(),
-                is(
-                        mActivityTestRule
-                                .getActivity()
-                                .getString(R.string.autofill_loyalty_card_bottom_sheet_title)));
+                is(getString(R.string.autofill_loyalty_card_bottom_sheet_title)));
     }
 
     @Test
@@ -595,12 +595,7 @@
                 });
         BottomSheetTestSupport.waitForOpen(mBottomSheetController);
 
-        onView(
-                        withText(
-                                mActivityTestRule
-                                        .getActivity()
-                                        .getString(
-                                                R.string.autofill_payment_method_continue_button)))
+        onView(withText(getString(R.string.autofill_payment_method_continue_button)))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
         waitForEvent(actionCallback).run();
     }
@@ -636,19 +631,9 @@
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
         onView(withText(NICKNAMED_VISA_SUGGESTION.getLabel()))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED));
-        onView(
-                        withText(
-                                mActivityTestRule
-                                        .getActivity()
-                                        .getString(
-                                                R.string.autofill_payment_method_continue_button)))
+        onView(withText(getString(R.string.autofill_payment_method_continue_button)))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
-        onView(
-                        withText(
-                                mActivityTestRule
-                                        .getActivity()
-                                        .getString(
-                                                R.string.autofill_payment_method_continue_button)))
+        onView(withText(getString(R.string.autofill_payment_method_continue_button)))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED));
     }
 
@@ -756,10 +741,7 @@
         assertContentDescriptionEquals(secondLineLabel, /* position= */ 1, /* total= */ 1);
         TextView benefitsTermsLabel = getCreditCardBenefitsTermsLabel();
         String expectedBenefitsTermsLabel =
-                mActivityTestRule
-                        .getActivity()
-                        .getString(
-                                R.string.autofill_payment_method_bottom_sheet_benefits_terms_label);
+                getString(R.string.autofill_payment_method_bottom_sheet_benefits_terms_label);
         assertThat(benefitsTermsLabel.getText(), is(expectedBenefitsTermsLabel));
     }
 
@@ -822,10 +804,7 @@
         assertContentDescriptionEquals(secondLineLabel, /* position= */ 1, /* total= */ 1);
         TextView benefitsTermsLabel = getCreditCardBenefitsTermsLabel();
         String expectedBenefitsTermsLabel =
-                mActivityTestRule
-                        .getActivity()
-                        .getString(
-                                R.string.autofill_payment_method_bottom_sheet_benefits_terms_label);
+                getString(R.string.autofill_payment_method_bottom_sheet_benefits_terms_label);
         assertThat(benefitsTermsLabel.getText(), is(expectedBenefitsTermsLabel));
     }
 
@@ -929,12 +908,7 @@
                 });
         BottomSheetTestSupport.waitForOpen(mBottomSheetController);
 
-        onView(
-                        withText(
-                                mActivityTestRule
-                                        .getActivity()
-                                        .getString(
-                                                R.string.autofill_payment_method_continue_button)))
+        onView(withText(getString(R.string.autofill_payment_method_continue_button)))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
         waitForEvent(actionCallback).run();
     }
@@ -969,19 +943,9 @@
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
         onView(withText(LOCAL_IBAN.getLabel()))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED));
-        onView(
-                        withText(
-                                mActivityTestRule
-                                        .getActivity()
-                                        .getString(
-                                                R.string.autofill_payment_method_continue_button)))
+        onView(withText(getString(R.string.autofill_payment_method_continue_button)))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
-        onView(
-                        withText(
-                                mActivityTestRule
-                                        .getActivity()
-                                        .getString(
-                                                R.string.autofill_payment_method_continue_button)))
+        onView(withText(getString(R.string.autofill_payment_method_continue_button)))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED));
     }
 
@@ -1083,11 +1047,36 @@
                 });
         BottomSheetTestSupport.waitForOpen(mBottomSheetController);
 
-        onView(
-                        withText(
-                                mActivityTestRule
-                                        .getActivity()
-                                        .getString(R.string.autofill_loyalty_card_autofill_button)))
+        onView(withText(getString(R.string.autofill_loyalty_card_autofill_button)))
+                .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
+        waitForEvent(actionCallback).run();
+    }
+
+    @Test
+    @MediumTest
+    public void testAllLoyaltyCardsItem() {
+        Runnable actionCallback = mock(Runnable.class);
+        runOnUiThreadBlocking(
+                () -> {
+                    mTouchToFillPaymentMethodModel
+                            .get(SHEET_ITEMS)
+                            .add(
+                                    new ListItem(
+                                            ALL_LOYALTY_CARDS,
+                                            createAllLoyaltyCardsItemModel(actionCallback)));
+                    mTouchToFillPaymentMethodModel.set(VISIBLE, true);
+                });
+        BottomSheetTestSupport.waitForOpen(mBottomSheetController);
+
+        TextView allLoyaltyCardsItemTitle =
+                mTouchToFillPaymentMethodView
+                        .getContentView()
+                        .findViewById(R.id.all_loyalty_cards_item_title);
+        assertThat(
+                allLoyaltyCardsItemTitle.getText().toString(),
+                is(getString(R.string.autofill_bottom_sheet_all_your_loyalty_cards)));
+
+        onView(withText(getString(R.string.autofill_bottom_sheet_all_your_loyalty_cards)))
                 .perform(createClickActionWithFlags(MotionEvent.FLAG_WINDOW_IS_OBSCURED));
         waitForEvent(actionCallback).run();
     }
@@ -1122,6 +1111,10 @@
         return mBottomSheetController.getSheetState();
     }
 
+    private String getString(@IdRes int id) {
+        return mActivityTestRule.getActivity().getString(id);
+    }
+
     private static PropertyModel createHeaderModel() {
         return new PropertyModel.Builder(HeaderProperties.ALL_KEYS)
                 .with(IMAGE_DRAWABLE_ID, R.drawable.ic_globe_24dp)
@@ -1167,12 +1160,17 @@
 
     private static PropertyModel createLoyaltyCardModel(
             LoyaltyCard loyaltyCard, Runnable runnable) {
-        PropertyModel.Builder loyaltyCardModelBuilder =
-                new PropertyModel.Builder(NON_TRANSFORMING_LOYALTY_CARD_KEYS)
-                        .with(LOYALTY_CARD_NUMBER, loyaltyCard.getLoyaltyCardNumber())
-                        .with(MERCHANT_NAME, loyaltyCard.getMerchantName())
-                        .with(ON_LOYALTY_CARD_CLICK_ACTION, runnable);
-        return loyaltyCardModelBuilder.build();
+        return new PropertyModel.Builder(NON_TRANSFORMING_LOYALTY_CARD_KEYS)
+                .with(LOYALTY_CARD_NUMBER, loyaltyCard.getLoyaltyCardNumber())
+                .with(MERCHANT_NAME, loyaltyCard.getMerchantName())
+                .with(ON_LOYALTY_CARD_CLICK_ACTION, runnable)
+                .build();
+    }
+
+    private static PropertyModel createAllLoyaltyCardsItemModel(Runnable runnable) {
+        return new PropertyModel.Builder(AllLoyaltyCardsItemProperties.ALL_KEYS)
+                .with(AllLoyaltyCardsItemProperties.ON_CLICK_ACTION, runnable)
+                .build();
     }
 
     private static PropertyModel createFillButtonModel(Runnable actionCallback) {
diff --git a/chrome/browser/translate/translate_manager_render_view_host_unittest.cc b/chrome/browser/translate/translate_manager_render_view_host_unittest.cc
index 40894456..b4a951a 100644
--- a/chrome/browser/translate/translate_manager_render_view_host_unittest.cc
+++ b/chrome/browser/translate/translate_manager_render_view_host_unittest.cc
@@ -245,7 +245,8 @@
     // expiration delay to a large value by default (in case it was zeroed in a
     // previous test).
     TranslateService::InitializeForTesting(
-        network::mojom::ConnectionType::CONNECTION_WIFI);
+        network::mojom::ConnectionType::CONNECTION_WIFI,
+        /*wait_for_eula=*/false);
     translate::TranslateDownloadManager* download_manager =
         translate::TranslateDownloadManager::GetInstance();
     download_manager->ClearTranslateScriptForTesting();
diff --git a/chrome/browser/translate/translate_service.cc b/chrome/browser/translate/translate_service.cc
index 368d763..0f1d12b 100644
--- a/chrome/browser/translate/translate_service.cc
+++ b/chrome/browser/translate/translate_service.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/translate/translate_service.h"
 
+#include "base/check_is_test.h"
 #include "base/command_line.h"
 #include "base/functional/bind.h"
 #include "base/metrics/field_trial.h"
@@ -34,6 +35,10 @@
 namespace {
 // The singleton instance of TranslateService.
 TranslateService* g_translate_service = nullptr;
+
+// Controls whether EULA is checked for resource requests.
+// May be false only in test.
+bool g_wait_for_eula = true;
 }  // namespace
 
 TranslateService::TranslateService()
@@ -41,7 +46,11 @@
           g_browser_process->local_state(),
           switches::kDisableBackgroundNetworking,
           base::BindOnce(&content::GetNetworkConnectionTracker)) {
-  resource_request_allowed_notifier_.Init(this, true /* leaky */);
+  if (!g_wait_for_eula) {
+    CHECK_IS_TEST();
+  }
+  resource_request_allowed_notifier_.Init(this, true /* leaky */,
+                                          g_wait_for_eula);
 }
 
 TranslateService::~TranslateService() = default;
@@ -75,8 +84,10 @@
 }
 
 // static
-void TranslateService::InitializeForTesting(
-    network::mojom::ConnectionType type) {
+void TranslateService::InitializeForTesting(network::mojom::ConnectionType type,
+                                            bool wait_for_eula) {
+  g_wait_for_eula = wait_for_eula;
+
   translate::TranslateDownloadManager::GetInstance()->ResetForTesting();
   TranslateService::Initialize();
   translate::TranslateManager::SetIgnoreMissingKeyForTesting(true);
@@ -89,6 +100,7 @@
 // static
 void TranslateService::ShutdownForTesting() {
   TranslateService::Shutdown();
+  g_wait_for_eula = true;
 }
 
 void TranslateService::OnResourceRequestsAllowed() {
diff --git a/chrome/browser/translate/translate_service.h b/chrome/browser/translate/translate_service.h
index bde60e5..fe837f1 100644
--- a/chrome/browser/translate/translate_service.h
+++ b/chrome/browser/translate/translate_service.h
@@ -27,7 +27,10 @@
   // Initializes the TranslateService in a way that it can be initialized
   // multiple times in a unit test suite (once for each test). Should be paired
   // with ShutdownForTesting at the end of the test.
-  static void InitializeForTesting(network::mojom::ConnectionType type);
+  // If `wait_for_eula` is false, EULA acceptance state is ignored for resource
+  // requests.
+  static void InitializeForTesting(network::mojom::ConnectionType type,
+                                   bool wait_for_eula = true);
 
   // Shuts down the TranslateService at the end of a test in a way that the next
   // test can initialize and use the service.
diff --git a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java
index c535659..685d176 100644
--- a/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java
+++ b/chrome/browser/ui/android/default_browser_promo/java/src/org/chromium/chrome/browser/ui/default_browser_promo/DefaultBrowserPromoUtilsTest.java
@@ -16,7 +16,6 @@
 import android.app.role.RoleManager;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
-import android.os.Build;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -88,10 +87,8 @@
         MessagesFactory.attachMessageDispatcher(mWindowAndroid, mMockMessageDispatcher);
         SearchEngineChoiceService.setInstanceForTests(mMockSearchEngineChoiceService);
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            mShadowRoleManager = shadowOf(mActivity.getSystemService(RoleManager.class));
-            mShadowRoleManager.addAvailableRole(RoleManager.ROLE_BROWSER);
-        }
+        mShadowRoleManager = shadowOf(mActivity.getSystemService(RoleManager.class));
+        mShadowRoleManager.addAvailableRole(RoleManager.ROLE_BROWSER);
 
         mUtils = new DefaultBrowserPromoUtils(mCounter, mProvider);
         setDepsMockWithDefaultValues();
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java
index 0666d1ee..a7992d1 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/NightModeUtils.java
@@ -9,7 +9,6 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.os.Build;
 import android.view.ContextThemeWrapper;
 
 import androidx.annotation.StyleRes;
@@ -58,16 +57,7 @@
 
         // Rebase the theme against the new configuration, so the attributes get resolved to the
         // correct colors based on the night mode setting. See https://crbug.com/1280540.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            activity.getTheme().rebase();
-        } else {
-            // Theme#rebase() is only available on APIs 29+ and the support library of the method
-            // isn't guaranteed to succeed on older versions. So, we manually re-apply all the
-            // cached styles.
-            for (Integer themeResId : themeResIds) {
-                activity.getTheme().applyStyle(themeResId, true);
-            }
-        }
+        activity.getTheme().rebase();
     }
 
     /**
@@ -122,7 +112,7 @@
     public static @ThemeType int getThemeSetting() {
         int userSetting = ChromeSharedPreferences.getInstance().readInt(UI_THEME_SETTING, -1);
         if (userSetting == -1) {
-            return isNightModeDefaultToLight() ? ThemeType.LIGHT : ThemeType.SYSTEM_DEFAULT;
+            return ThemeType.SYSTEM_DEFAULT;
         } else {
             return userSetting;
         }
@@ -151,11 +141,4 @@
         sNightModeSupportedForTest = nightModeSupported;
         ResettersForTesting.register(() -> sNightModeSupportedForTest = null);
     }
-
-    /**
-     * @return Whether or not to default to the light theme.
-     */
-    public static boolean isNightModeDefaultToLight() {
-        return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q;
-    }
 }
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/RadioButtonGroupThemePreference.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/RadioButtonGroupThemePreference.java
index 8744c389..28e7fb4 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/RadioButtonGroupThemePreference.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/RadioButtonGroupThemePreference.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.night_mode.settings;
 
 import android.content.Context;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.CheckBox;
@@ -96,11 +95,9 @@
         mButtons.set(
                 ThemeType.SYSTEM_DEFAULT,
                 (RadioButtonWithDescription) holder.findViewById(R.id.system_default));
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            mButtons.get(ThemeType.SYSTEM_DEFAULT)
-                    .setDescriptionText(
-                            getContext().getString(R.string.themes_system_default_summary_api_29));
-        }
+        mButtons.get(ThemeType.SYSTEM_DEFAULT)
+                .setDescriptionText(
+                        getContext().getString(R.string.themes_system_default_summary_api_29));
         mButtons.set(ThemeType.LIGHT, (RadioButtonWithDescription) holder.findViewById(R.id.light));
         mButtons.set(ThemeType.DARK, (RadioButtonWithDescription) holder.findViewById(R.id.dark));
 
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/ThemeSettingsFragment.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/ThemeSettingsFragment.java
index b38ea15..963049c 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/ThemeSettingsFragment.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/ThemeSettingsFragment.java
@@ -6,7 +6,6 @@
 
 import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.UI_THEME_SETTING;
 
-import android.os.Build;
 import android.os.Bundle;
 
 import org.chromium.base.shared_preferences.SharedPreferencesManager;
@@ -24,7 +23,6 @@
 import org.chromium.chrome.browser.settings.ChromeBaseSettingsFragment;
 import org.chromium.components.browser_ui.settings.CustomDividerFragment;
 import org.chromium.components.browser_ui.settings.SettingsUtils;
-import org.chromium.ui.UiUtils;
 
 /** Fragment to manage the theme user settings. */
 @NullMarked
@@ -89,20 +87,6 @@
     }
 
     @Override
-    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        // On O_MR1, the flag View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR in this fragment is not
-        // updated to the attribute android:windowLightNavigationBar set in preference theme, so
-        // we set the flag explicitly to workaround the issue. See https://crbug.com/942551.
-        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O_MR1) {
-            UiUtils.setNavigationBarIconColor(
-                    getActivity().getWindow().getDecorView(),
-                    getResources().getBoolean(R.bool.window_light_navigation_bar));
-        }
-    }
-
-    @Override
     public boolean hasDivider() {
         return false;
     }
diff --git a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/ThemeSettingsFragmentTest.java b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/ThemeSettingsFragmentTest.java
index 9651670db..8d321dcb 100644
--- a/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/ThemeSettingsFragmentTest.java
+++ b/chrome/browser/ui/android/night_mode/java/src/org/chromium/chrome/browser/night_mode/settings/ThemeSettingsFragmentTest.java
@@ -11,7 +11,6 @@
 import static org.chromium.chrome.browser.flags.ChromeFeatureList.DARKEN_WEBSITES_CHECKBOX_IN_THEMES_SETTING;
 import static org.chromium.chrome.browser.preferences.ChromePreferenceKeys.UI_THEME_SETTING;
 
-import android.os.Build;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -100,13 +99,7 @@
         launchThemeSettings(ThemeSettingsEntry.SETTINGS);
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
-                    int expectedDefaultTheme = ThemeType.LIGHT;
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                        Assert.assertFalse(
-                                "Q should not default to light.",
-                                NightModeUtils.isNightModeDefaultToLight());
-                        expectedDefaultTheme = ThemeType.SYSTEM_DEFAULT;
-                    }
+                    int expectedDefaultTheme = ThemeType.SYSTEM_DEFAULT;
 
                     Assert.assertEquals(
                             "Incorrect default theme setting.",
@@ -151,13 +144,7 @@
         launchThemeSettings(ThemeSettingsEntry.SETTINGS);
         ThreadUtils.runOnUiThreadBlocking(
                 () -> {
-                    int expectedDefaultTheme = ThemeType.LIGHT;
-                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-                        Assert.assertFalse(
-                                "Q should not default to light.",
-                                NightModeUtils.isNightModeDefaultToLight());
-                        expectedDefaultTheme = ThemeType.SYSTEM_DEFAULT;
-                    }
+                    int expectedDefaultTheme = ThemeType.SYSTEM_DEFAULT;
 
                     LinearLayout checkboxContainer = mPreference.getCheckboxContainerForTesting();
                     RadioButtonWithDescriptionLayout group = mPreference.getGroupForTesting();
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
index 548d89e..dbde738 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -445,17 +445,15 @@
         // See crbug.com/410642190
         super.onTextChanged(text, start, lengthBefore, lengthAfter);
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            // Due to crbug.com/1103555, Autofill had to be disabled on the UrlBar to work around
-            // an issue on Android Q+. With Autofill disabled, the Autofill compat mode no longer
-            // learns of changes to the UrlBar, which prevents it from cancelling the session if
-            // the domain changes. We restore this behavior by mimicking the relevant part of
-            // TextView.notifyListeningManagersAfterTextChanged().
-            // https://cs.android.com/android/platform/superproject/+/5d123b67756dffcfdebdb936ab2de2b29c799321:frameworks/base/core/java/android/widget/TextView.java;l=10618;drc=master;bpv=0
-            final AutofillManager afm = getContext().getSystemService(AutofillManager.class);
-            if (afm != null) {
-                afm.notifyValueChanged(this);
-            }
+        // Due to crbug.com/1103555, Autofill had to be disabled on the UrlBar to work around
+        // an issue on Android Q+. With Autofill disabled, the Autofill compat mode no longer
+        // learns of changes to the UrlBar, which prevents it from cancelling the session if
+        // the domain changes. We restore this behavior by mimicking the relevant part of
+        // TextView.notifyListeningManagersAfterTextChanged().
+        // https://cs.android.com/android/platform/superproject/+/5d123b67756dffcfdebdb936ab2de2b29c799321:frameworks/base/core/java/android/widget/TextView.java;l=10618;drc=master;bpv=0
+        final AutofillManager afm = getContext().getSystemService(AutofillManager.class);
+        if (afm != null) {
+            afm.notifyValueChanged(this);
         }
 
         limitDisplayableLength();
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java
index 1a36102..c5339940 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.omnibox;
 
 import android.content.Context;
-import android.os.Build;
 import android.util.AttributeSet;
 import android.view.ViewStructure;
 
@@ -38,10 +37,6 @@
         // https://crbug.com/1103555: Prevent augmented autofill service from taking over the
         // session by disabling both standard and augmented autofill on versions of Android
         // where both are supported.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
-            return AUTOFILL_TYPE_NONE;
-        } else {
-            return super.getAutofillType();
-        }
+        return AUTOFILL_TYPE_NONE;
     }
 }
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java
index b48d670f..5eb4f64 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/optional_button/OptionalButtonView.java
@@ -10,8 +10,6 @@
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Build.VERSION;
 import android.os.Handler;
 import android.transition.ChangeBounds;
 import android.transition.Fade;
@@ -325,8 +323,7 @@
         // Set hover state tooltip text for optional toolbar buttons(e.g. share, voice search, new
         // tab and profile).
         if (buttonSpec.getHoverTooltipTextId() != ButtonSpec.INVALID_TOOLTIP_TEXT_ID
-                && mButton != null
-                && VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                && mButton != null) {
             TooltipCompat.setTooltipText(
                     mButton, getContext().getString(buttonSpec.getHoverTooltipTextId()));
         } else {
diff --git a/chrome/browser/ui/autofill/autofill_client_provider.cc b/chrome/browser/ui/autofill/autofill_client_provider.cc
index 8e6581e..5e6e3a2b 100644
--- a/chrome/browser/ui/autofill/autofill_client_provider.cc
+++ b/chrome/browser/ui/autofill/autofill_client_provider.cc
@@ -92,7 +92,6 @@
         availability = AndroidAutofillAvailabilityStatus::kAvailable;
       }
       ABSL_FALLTHROUGH_INTENDED;  // No skip-awg-check but skip-all may apply.
-    case AndroidAutofillAvailabilityStatus::kAndroidVersionTooOld:
     case AndroidAutofillAvailabilityStatus::kAndroidAutofillManagerNotAvailable:
     case AndroidAutofillAvailabilityStatus::kAndroidAutofillNotSupported:
     case AndroidAutofillAvailabilityStatus::kUnknownAndroidAutofillService:
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 b36eb03..8c8ca11 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
@@ -297,6 +297,7 @@
 #endif
   browser_frame_ = nullptr;
   browser_view_ = nullptr;
+  DesktopWindowTreeHostLinux::ClientDestroyedWidget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc
index 766aa916..8b9a1aa 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_win.cc
@@ -546,6 +546,7 @@
   browser_window_property_manager_.reset();
   browser_frame_ = nullptr;
   browser_view_ = nullptr;
+  DesktopWindowTreeHostWin::ClientDestroyedWidget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc b/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc
index 4b208868..ce504264 100644
--- a/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc
+++ b/chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc
@@ -183,6 +183,7 @@
     case base::nix::DESKTOP_ENVIRONMENT_UNITY:
     case base::nix::DESKTOP_ENVIRONMENT_XFCE:
     case base::nix::DESKTOP_ENVIRONMENT_LXQT:
+    case base::nix::DESKTOP_ENVIRONMENT_COSMIC:
       return false;
   }
   NOTREACHED();
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
index edac099..5e15a858 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
@@ -8,6 +8,7 @@
 #include <optional>
 
 #include "base/callback_list.h"
+#include "base/check_is_test.h"
 #include "base/command_line.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
@@ -124,6 +125,7 @@
 
 bool IsBrowserForSystemWebApp(const Browser* browser) {
 #if BUILDFLAG(IS_CHROMEOS)
+  CHECK(browser);
   const auto* const app_controller = browser->app_controller();
   if (app_controller && app_controller->system_app()) {
     return true;
@@ -210,7 +212,10 @@
 
     // Register for memory usage enabled pref change events. Exclude
     // tracking them for system web apps (e.g. ChromeOS terminal app).
-    if (!IsBrowserForSystemWebApp(tab_strip_->GetBrowser())) {
+    Browser* browser = tab_strip_->GetBrowser();
+    if (!browser) {
+      CHECK_IS_TEST();
+    } else if (!IsBrowserForSystemWebApp(browser)) {
       OnHovercardMemoryUsageEnabledChanged();
       pref_change_registrar_.Add(
           prefs::kHoverCardMemoryUsageEnabled,
diff --git a/chrome/browser/ui/web_applications/navigation_capturing_process.cc b/chrome/browser/ui/web_applications/navigation_capturing_process.cc
index 5c22c94..0d41110 100644
--- a/chrome/browser/ui/web_applications/navigation_capturing_process.cc
+++ b/chrome/browser/ui/web_applications/navigation_capturing_process.cc
@@ -656,12 +656,6 @@
     return CancelInitialNavigation(
         NavigationCapturingInitialResult::kNavigationCanceled);
   }
-  // App popups and picture-in-picture are handled in the switch statement in
-  // `GetBrowserAndTabForDisposition()`.
-  if (disposition_ == WindowOpenDisposition::NEW_POPUP ||
-      disposition_ == WindowOpenDisposition::NEW_PICTURE_IN_PICTURE) {
-    return CapturingDisabled();
-  }
 
   const webapps::AppId& iwa_id = *first_navigation_app_id_;
 
@@ -669,19 +663,34 @@
   bool iwa_browser =
       params.browser &&
       web_app::AppBrowserController::IsForWebApp(params.browser, iwa_id);
-  if (iwa_browser) {
-    if (disposition_ == WindowOpenDisposition::CURRENT_TAB) {
-      return CapturingDisabled();
-    }
 
-    // If the browser window does not yet have any tabs, and we are
-    // attempting to add the first tab to it, allow for it to be reused.
-    bool navigating_new_tab =
-        disposition_ == WindowOpenDisposition::NEW_FOREGROUND_TAB ||
-        disposition_ == WindowOpenDisposition::NEW_BACKGROUND_TAB;
-    if (navigating_new_tab && params.browser->tab_strip_model()->empty()) {
-      return CapturingDisabled();
+  bool capturing_disabled = [&]() {
+    switch (disposition_) {
+      case WindowOpenDisposition::NEW_POPUP:
+      case WindowOpenDisposition::NEW_PICTURE_IN_PICTURE:
+        // App popups and picture-in-picture are handled in the switch statement
+        // in `GetBrowserAndTabForDisposition()`.
+        return true;
+      case WindowOpenDisposition::NEW_FOREGROUND_TAB:
+      case WindowOpenDisposition::NEW_BACKGROUND_TAB:
+        // If the browser window does not yet have any tabs, and we are
+        // attempting to add the first tab to it, allow for it to be reused.
+        return iwa_browser && params.browser->tab_strip_model()->empty();
+      case WindowOpenDisposition::CURRENT_TAB:
+        return iwa_browser;
+      case WindowOpenDisposition::NEW_WINDOW:
+      case WindowOpenDisposition::UNKNOWN:
+      case WindowOpenDisposition::SINGLETON_TAB:
+      case WindowOpenDisposition::SAVE_TO_DISK:
+      case WindowOpenDisposition::OFF_THE_RECORD:
+      case WindowOpenDisposition::IGNORE_ACTION:
+      case WindowOpenDisposition::SWITCH_TO_TAB:
+        return false;
     }
+  }();
+
+  if (capturing_disabled) {
+    return CapturingDisabled();
   }
 
   Browser* host_window = CreateWebAppWindowFromNavigationParams(
diff --git a/chrome/browser/ui/web_applications/web_app_browser_controller.cc b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
index d3e21d9..7e923d2e 100644
--- a/chrome/browser/ui/web_applications/web_app_browser_controller.cc
+++ b/chrome/browser/ui/web_applications/web_app_browser_controller.cc
@@ -584,6 +584,10 @@
     return app_name;
   }
 
+  if (provider_->ui_manager().GetNumWindowsForApp(app_id()) == 1) {
+    return app_name;
+  }
+
   return base::StrCat({app_name, u" - ", raw_title});
 }
 
diff --git a/chrome/browser/ui/web_applications/web_app_browsertest.cc b/chrome/browser/ui/web_applications/web_app_browsertest.cc
index 68e68408..aad1b0c 100644
--- a/chrome/browser/ui/web_applications/web_app_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_browsertest.cc
@@ -2158,7 +2158,9 @@
   EXPECT_EQ(app_title, app_browser->GetWindowTitleForCurrentTab(false));
   NavigateViaLinkClickToURLAndWait(
       app_browser, https_server()->GetURL("app.site.test", "/simple.html"));
-  EXPECT_EQ(u"A Web App - OK", app_browser->GetWindowTitleForCurrentTab(false));
+  // The page title is "OK" but the app title should be used instead
+  // for a single window instance of an app.
+  EXPECT_EQ(app_title, app_browser->GetWindowTitleForCurrentTab(false));
 }
 
 // Ensure that web app windows display the app title instead of the page
@@ -2178,9 +2180,9 @@
       app_browser->tab_strip_model()->GetActiveWebContents();
   EXPECT_TRUE(content::WaitForLoadStop(web_contents));
 
-  // When we are within scope, show the page title.
-  EXPECT_EQ(u"A Web App - Google",
-            app_browser->GetWindowTitleForCurrentTab(false));
+  // When we are within scope, show the app title for a single instance of an
+  // app.
+  EXPECT_EQ(app_title, app_browser->GetWindowTitleForCurrentTab(false));
   NavigateViaLinkClickToURLAndWait(
       app_browser, https_server()->GetURL("app.site.test", "/simple.html"));
 
@@ -2210,6 +2212,44 @@
   EXPECT_EQ(app_title, app_browser->GetWindowTitleForCurrentTab(false));
 }
 
+// Ensure that when a single PWA window is open, only the app name is shown
+// in the title.
+// When a second window is opened, it includes the page title as a suffix.
+// Navigating in the first window updates its title to include the page
+// title as a suffix.
+IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, MultipleAppWindowTitleTest) {
+  BrowserWaiter browser_waiter(nullptr);
+  const GURL app_url =
+      https_server()->GetURL("/banners/manifest_test_page.html");
+  NavigateViaLinkClickToURLAndWait(browser(), app_url);
+
+  content::WebContents* web_contents =
+      browser()->tab_strip_model()->GetActiveWebContents();
+  const webapps::AppId app_id = test::InstallForWebContents(
+      browser()->profile(), web_contents,
+      webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON);
+
+  Browser* app_browser = browser_waiter.AwaitAdded(FROM_HERE);
+  EXPECT_EQ(u"Manifest test app", app_browser->GetWindowTitleForCurrentTab(
+                                      /*include_app_name=*/false));
+
+  Browser* const second_browser = LaunchWebAppBrowserAndWait(app_id);
+  content::WebContents* const second_web_contents =
+      second_browser->tab_strip_model()->GetActiveWebContents();
+  EXPECT_TRUE(content::WaitForLoadStop(second_web_contents));
+  ASSERT_TRUE(AppBrowserController::IsForWebApp(second_browser, app_id));
+
+  EXPECT_EQ(
+      u"Manifest test app - Web app banner test page",
+      second_browser->GetWindowTitleForCurrentTab(/*include_app_name=*/false));
+
+  // The first browser window should update title after navigation.
+  NavigateViaLinkClickToURLAndWait(app_browser, app_url);
+  EXPECT_EQ(
+      u"Manifest test app - Web app banner test page",
+      app_browser->GetWindowTitleForCurrentTab(/*include_app_name=*/false));
+}
+
 // WebApps should have origin text.
 IN_PROC_BROWSER_TEST_F(WebAppBrowserTest, OriginTextRemoved) {
   const GURL app_url = GetInstallableAppURL();
diff --git a/chrome/browser/ui/webui/settings/settings_utils_linux.cc b/chrome/browser/ui/webui/settings/settings_utils_linux.cc
index a416e01..e8ec30c 100644
--- a/chrome/browser/ui/webui/settings/settings_utils_linux.cc
+++ b/chrome/browser/ui/webui/settings/settings_utils_linux.cc
@@ -49,6 +49,8 @@
 const char* const kDeepinProxyConfigCommand[] = {"dde-control-center", "-m",
                                                  "network"};
 
+const char* const kCosmicProxyConfigCommand[] = {"cosmic-settings", "network"};
+
 // The URL for Linux proxy configuration help when not running under a
 // supported desktop environment.
 constexpr char kLinuxProxyConfigUrl[] = "chrome://linux-proxy-config";
@@ -149,6 +151,10 @@
       launched = StartProxyConfigUtil(kKDE6ProxyConfigCommand);
       break;
 
+    case base::nix::DESKTOP_ENVIRONMENT_COSMIC:
+      launched = StartProxyConfigUtil(kCosmicProxyConfigCommand);
+      break;
+
     case base::nix::DESKTOP_ENVIRONMENT_XFCE:
     case base::nix::DESKTOP_ENVIRONMENT_LXQT:
     case base::nix::DESKTOP_ENVIRONMENT_OTHER:
diff --git a/chrome/browser/ui/webui/whats_new/whats_new_storage_service_impl_unittest.cc b/chrome/browser/ui/webui/whats_new/whats_new_storage_service_impl_unittest.cc
index 722befa6..c30c720 100644
--- a/chrome/browser/ui/webui/whats_new/whats_new_storage_service_impl_unittest.cc
+++ b/chrome/browser/ui/webui/whats_new/whats_new_storage_service_impl_unittest.cc
@@ -4,9 +4,11 @@
 
 #include "chrome/browser/ui/webui/whats_new/whats_new_storage_service_impl.h"
 
+#include "chrome/browser/global_features.h"
 #include "chrome/common/chrome_version.h"
 #include "chrome/test/base/scoped_testing_local_state.h"
 #include "chrome/test/base/testing_browser_process.h"
+#include "components/user_education/webui/whats_new_registry.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 class WhatsNewStorageServiceTest : public testing::Test {
@@ -17,17 +19,25 @@
 
   void SetUp() override {
     testing::Test::SetUp();
-    storage_service_ =
-        std::make_unique<whats_new::WhatsNewStorageServiceImpl>();
+
+    // WhatsNewStorageServiceImpl is created and initialized in
+    // GlobalFeatures::CreateWhatsNewRegistry() in the same way as the
+    // production.
+    storage_service_ = TestingBrowserProcess::GetGlobal()
+                           ->GetFeatures()
+                           ->whats_new_registry()
+                           ->GetMutableStorageServiceForTesting();
+    // Resets it here to satisfy the precondition.
+    storage_service_->Reset();
   }
 
   void TearDown() override {
-    storage_service_.reset();
+    storage_service_ = nullptr;
     testing::Test::TearDown();
   }
 
  protected:
-  std::unique_ptr<whats_new::WhatsNewStorageService> storage_service_;
+  raw_ptr<whats_new::WhatsNewStorageService> storage_service_;
   ScopedTestingLocalState local_state_;
 };
 
diff --git a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
index 030de51..5137b73f 100644
--- a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
+++ b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
@@ -4675,7 +4675,7 @@
   // Check update takes effect on live web app window.
   app_window_update.Run();
   AppBrowserController* app_controller = app_browser->app_controller();
-  EXPECT_EQ(app_controller->GetTitle(), u"New name - Web app banner test page");
+  EXPECT_EQ(app_controller->GetTitle(), u"New name");
   EXPECT_EQ(app_controller->GetThemeColor(), SK_ColorGREEN);
 
   // Force the app icon to load again and check that it's the new one.
diff --git a/chrome/build/android-arm32.pgo.txt b/chrome/build/android-arm32.pgo.txt
index 0cf1957..30874fa 100644
--- a/chrome/build/android-arm32.pgo.txt
+++ b/chrome/build/android-arm32.pgo.txt
@@ -1 +1 @@
-chrome-android32-main-1750269586-2541c10ab89657ed3256b53b3de94658682995c1-dd570630dd79aeff1c3fb36a63d96f1ffd6181cb.profdata
+chrome-android32-main-1750398892-0c6509bb8a78b91ba7face94e542ca7ad6910a9b-f77793b2afb14e49cb5be2c29b1269741be81695.profdata
diff --git a/chrome/build/android-arm64.pgo.txt b/chrome/build/android-arm64.pgo.txt
index c2e2bfc5..e12e17a 100644
--- a/chrome/build/android-arm64.pgo.txt
+++ b/chrome/build/android-arm64.pgo.txt
@@ -1 +1 @@
-chrome-android64-main-1750357489-177384a0ab01db513be8fc3baa999ad2f53aacf4-f06918e3c7a545f7b681d02a7590c3973be54cc9.profdata
+chrome-android64-main-1750405413-29933facb59d6f55a29bb1b07fe237032aa14329-558ed507cbc466d97a9139a5f77581d4e5f55d0d.profdata
diff --git a/chrome/build/android-desktop-x64.pgo.txt b/chrome/build/android-desktop-x64.pgo.txt
index ea85cb2..47255ec 100644
--- a/chrome/build/android-desktop-x64.pgo.txt
+++ b/chrome/build/android-desktop-x64.pgo.txt
@@ -1 +1 @@
-chrome-android-desktop-x64-main-1750318566-189f587f0bb5c58b601f136f0560aac9109aeda6-8af14c9bdc88f1a0975c23e63c0834d75b06c770.profdata
+chrome-android-desktop-x64-main-1750399281-0d6a4fab094e091b031248ed5e363c178368e522-8d1dfcf686439ae4b28eaf59cfed8925b167369d.profdata
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 630a6112..eb9fcf6 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1750334220-384f3305931f50ac93bab045eb3391c976ede5c9-12dc10e4b46d562de2a591ed14c26ad65506775b.profdata
+chrome-linux-main-1750398892-570f6f17b0026a29e46be76ac316669e7d58d453-f77793b2afb14e49cb5be2c29b1269741be81695.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index a439f7d..7450f3a 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1750363175-8b4ba7735d137dc6ea78ef20fa63c4f54e6b04d5-913bcb67a2810d40c6e41dbdfd265980c84344af.profdata
+chrome-mac-arm-main-1750405413-9c19d675cd73804cd1a53e96f05cd2a4d28d47d9-558ed507cbc466d97a9139a5f77581d4e5f55d0d.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index f446300..27d5b6d 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1750334220-5e66a37f9ea1a1bbeee19c78bb57ec8685768d6f-12dc10e4b46d562de2a591ed14c26ad65506775b.profdata
+chrome-mac-main-1750398892-2796e3beec873d3e688f7837ba67be39cb5a0926-f77793b2afb14e49cb5be2c29b1269741be81695.profdata
diff --git a/chrome/build/win-arm64.pgo.txt b/chrome/build/win-arm64.pgo.txt
index ea397f8..8669eb0 100644
--- a/chrome/build/win-arm64.pgo.txt
+++ b/chrome/build/win-arm64.pgo.txt
@@ -1 +1 @@
-chrome-win-arm64-main-1750334220-7f06cb7efbfdb140ecf56ae1cab346e18943aff6-12dc10e4b46d562de2a591ed14c26ad65506775b.profdata
+chrome-win-arm64-main-1750377569-cd7e02dc5a39229b108c336f8aa3f4706412566c-819d9c41f166fd60a26d8034d3cfb31468b15635.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index aadc8c9d..5cb85f6 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1750323273-a3ecc05df4b51f2bae00cbd691a02256f9a4fc30-da89cfc19ad7b608b6f9741a598b97dbb3dfc2f5.profdata
+chrome-win32-main-1750366781-864436dd8343f85f2c935d83e08087703e381749-19d14ae4a957ef0f158e3b37cc7f3c76e839ce5d.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index b15fd5e2..fbccebc2 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1750312787-7a08c10602284f11ad063d6a444da3dc21d6c582-b8253859060147acedd34cb5515d4d9c96d56aac.profdata
+chrome-win64-main-1750366781-3028e378b10524f70cd39d9457a3bfb780e0f099-19d14ae4a957ef0f158e3b37cc7f3c76e839ce5d.profdata
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 97829cfe..03c5ca7 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -361,6 +361,7 @@
     sources += [
       "actor/action_result.cc",
       "actor/action_result.h",
+      "actor/actor_constants.h",
       "actor/actor_logging.h",
       "read_anything/read_anything_util.cc",
       "read_anything/read_anything_util.h",
diff --git a/chrome/common/actor.mojom b/chrome/common/actor.mojom
index cceaa513..81dbf0a 100644
--- a/chrome/common/actor.mojom
+++ b/chrome/common/actor.mojom
@@ -88,7 +88,7 @@
   // This target is the element to scroll. A null target implies scrolling the
   // page's viewport. If the scroll action returns failure, it means the target
   // isn't scrollable.
-  ToolTarget? target;
+  ToolTarget target;
   ScrollDirection direction;
   // Scroll distance in physical pixels, and it should always be positive.
   float distance;
diff --git a/chrome/common/actor/actor_constants.h b/chrome/common/actor/actor_constants.h
new file mode 100644
index 0000000..28893d1
--- /dev/null
+++ b/chrome/common/actor/actor_constants.h
@@ -0,0 +1,16 @@
+// Copyright 2025 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_COMMON_ACTOR_ACTOR_CONSTANTS_H_
+#define CHROME_COMMON_ACTOR_ACTOR_CONSTANTS_H_
+
+namespace actor {
+
+// 0 is not a valid DOMNodeId so it is used to indicate targeting the
+// root/viewport.
+inline constexpr int kRootElementDomNodeId = 0;
+
+}  // namespace actor
+
+#endif  // CHROME_COMMON_ACTOR_ACTOR_CONSTANTS_H_
diff --git a/chrome/renderer/actor/scroll_tool.cc b/chrome/renderer/actor/scroll_tool.cc
index 90e4f135..b0c831e6 100644
--- a/chrome/renderer/actor/scroll_tool.cc
+++ b/chrome/renderer/actor/scroll_tool.cc
@@ -10,6 +10,7 @@
 #include "base/strings/to_string.h"
 #include "base/time/time.h"
 #include "chrome/common/actor/action_result.h"
+#include "chrome/common/actor/actor_constants.h"
 #include "chrome/common/actor/actor_logging.h"
 #include "chrome/renderer/actor/tool_utils.h"
 #include "content/public/renderer/render_frame.h"
@@ -87,25 +88,22 @@
         mojom::ActionResultCode::kArgumentsInvalid, "Negative Distance"));
   }
 
+  if (action_->target->is_coordinate()) {
+    NOTIMPLEMENTED() << "Coordinate-based target not yet supported.";
+    return base::unexpected(MakeErrorResult());
+  }
 
   WebElement scrolling_element;
-  if (!action_->target) {
+  int32_t dom_node_id = action_->target->get_dom_node_id();
+  if (dom_node_id == kRootElementDomNodeId) {
     scrolling_element = web_frame->GetDocument().ScrollingElement();
-
     if (scrolling_element.IsNull()) {
       return base::unexpected(
           MakeResult(mojom::ActionResultCode::kScrollNoScrollingElement));
     }
   } else {
-    if (action_->target->is_coordinate()) {
-      NOTIMPLEMENTED() << "Coordinate-based target not yet supported.";
-      return base::unexpected(MakeErrorResult());
-    }
-
-    int32_t dom_node_id = action_->target->get_dom_node_id();
     scrolling_element =
         GetNodeFromId(frame_.get(), dom_node_id).DynamicTo<WebElement>();
-
     if (scrolling_element.IsNull()) {
       return base::unexpected(
           MakeResult(mojom::ActionResultCode::kInvalidDomNodeId));
diff --git a/chrome/test/base/scoped_testing_local_state.cc b/chrome/test/base/scoped_testing_local_state.cc
index 9731da0..71154a4 100644
--- a/chrome/test/base/scoped_testing_local_state.cc
+++ b/chrome/test/base/scoped_testing_local_state.cc
@@ -4,20 +4,14 @@
 
 #include "chrome/test/base/scoped_testing_local_state.h"
 
-#include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/test/base/testing_browser_process.h"
-#include "testing/gtest/include/gtest/gtest.h"
 
 ScopedTestingLocalState::ScopedTestingLocalState(
     TestingBrowserProcess* browser_process)
-    : browser_process_(browser_process) {
-  CHECK(browser_process_);
-  RegisterLocalState(local_state_.registry());
-  EXPECT_FALSE(browser_process_->local_state());
-  browser_process_->SetLocalState(&local_state_);
-}
+    : browser_process_(browser_process) {}
 
-ScopedTestingLocalState::~ScopedTestingLocalState() {
-  EXPECT_EQ(&local_state_, browser_process_->local_state());
-  browser_process_->SetLocalState(nullptr);
+ScopedTestingLocalState::~ScopedTestingLocalState() = default;
+
+TestingPrefServiceSimple* ScopedTestingLocalState::Get() {
+  return browser_process_->GetTestingLocalState();
 }
diff --git a/chrome/test/base/scoped_testing_local_state.h b/chrome/test/base/scoped_testing_local_state.h
index 9764ffc..12f5c34 100644
--- a/chrome/test/base/scoped_testing_local_state.h
+++ b/chrome/test/base/scoped_testing_local_state.h
@@ -10,6 +10,11 @@
 
 class TestingBrowserProcess;
 
+// DEPRECATED: This class no longer have any effect. TestingBrowserProcess has
+// its own testing local state, so unit tests can just use it without setting up
+// one.
+// TODO(crbug.com/422039036): Remove this class and existing usage.
+//
 // Helper class to temporarily set up a |local_state| in the global
 // TestingBrowserProcess (for most unit tests it's NULL).
 class ScopedTestingLocalState {
@@ -19,13 +24,10 @@
   ScopedTestingLocalState& operator=(const ScopedTestingLocalState&) = delete;
   ~ScopedTestingLocalState();
 
-  TestingPrefServiceSimple* Get() {
-    return &local_state_;
-  }
+  TestingPrefServiceSimple* Get();
 
  private:
   raw_ptr<TestingBrowserProcess> browser_process_;
-  TestingPrefServiceSimple local_state_;
 };
 
 #endif  // CHROME_TEST_BASE_SCOPED_TESTING_LOCAL_STATE_H_
diff --git a/chrome/test/base/testing_browser_process.cc b/chrome/test/base/testing_browser_process.cc
index 9f895493..693321f 100644
--- a/chrome/test/base/testing_browser_process.cc
+++ b/chrome/test/base/testing_browser_process.cc
@@ -26,6 +26,7 @@
 #include "chrome/browser/notifications/system_notification_helper.h"
 #include "chrome/browser/permissions/chrome_permissions_client.h"
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
+#include "chrome/browser/prefs/browser_prefs.h"
 #include "chrome/browser/printing/print_job_manager.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/resource_coordinator/resource_coordinator_parts.h"
@@ -42,6 +43,7 @@
 #include "components/permissions/permissions_client.h"
 #include "components/policy/core/browser/browser_policy_connector.h"
 #include "components/prefs/pref_service.h"
+#include "components/prefs/testing_pref_service.h"
 #include "components/subresource_filter/content/shared/browser/ruleset_service.h"
 #include "content/public/browser/network_service_instance.h"
 #include "extensions/buildflags/buildflags.h"
@@ -144,8 +146,11 @@
 }
 
 TestingBrowserProcess::TestingBrowserProcess()
-    : platform_part_(std::make_unique<TestingBrowserProcessPlatformPart>()),
+    : testing_local_state_(std::make_unique<TestingPrefServiceSimple>()),
+      platform_part_(std::make_unique<TestingBrowserProcessPlatformPart>()),
       os_crypt_async_(os_crypt_async::GetTestOSCryptAsyncForTesting()) {
+  RegisterLocalState(testing_local_state_->registry());
+
   // Observe TaskEnvironment to get a chance to teardown components before
   // ThreadPool is destroyed.
   // In production, BrowserProcess is destroyed while ThreadPool is still
@@ -159,14 +164,19 @@
   // Tear down components for tests that do not have TaskEnvironment.
   MaybeStartTearDown();
 
-  EXPECT_FALSE(local_state_);
 #if BUILDFLAG(ENABLE_EXTENSIONS)
   extensions::ExtensionsBrowserClient::Set(nullptr);
   extensions::AppWindowClient::Set(nullptr);
 #endif
 
-  if (test_network_connection_tracker_)
+  if (test_network_connection_tracker_) {
     content::SetNetworkConnectionTrackerForTesting(nullptr);
+  }
+
+  // Destroy objects in the same way as BrowserProcessImpl does.
+  serial_policy_allowed_ports_.reset();
+  testing_local_state_.reset();
+  browser_policy_connector_.reset();
 
   // Destructors for some objects owned by TestingBrowserProcess will use
   // g_browser_process if it is not null, so it must be null before proceeding.
@@ -278,7 +288,7 @@
   // NotificationUIManager can contain references to elements in the current
   // ProfileManager. So when we change the ProfileManager (typically during test
   // shutdown) make sure to reset any objects that might maintain references to
-  // it. See SetLocalState() for a description of a similar situation.
+  // it.
   notification_ui_manager_.reset();
 #endif
   profile_manager_ = std::move(profile_manager);
@@ -290,7 +300,7 @@
 }
 
 PrefService* TestingBrowserProcess::local_state() {
-  return local_state_;
+  return testing_local_state_.get();
 }
 
 signin::ActivePrimaryAccountsMetricsRecorder*
@@ -309,9 +319,6 @@
 policy::ChromeBrowserPolicyConnector*
 TestingBrowserProcess::browser_policy_connector() {
   if (!browser_policy_connector_) {
-    EXPECT_FALSE(created_browser_policy_connector_);
-    created_browser_policy_connector_ = true;
-
 #if BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC)
     // Make sure that the machine policy directory does not exist so that
     // machine-wide policies do not affect tests.
@@ -511,13 +518,11 @@
 network_time::NetworkTimeTracker*
 TestingBrowserProcess::network_time_tracker() {
   if (!network_time_tracker_) {
-    if (!local_state_)
-      return nullptr;
-
+    CHECK(local_state());
     network_time_tracker_ = std::make_unique<network_time::NetworkTimeTracker>(
         std::unique_ptr<base::Clock>(new base::DefaultClock()),
         std::unique_ptr<base::TickClock>(new base::DefaultTickClock()),
-        local_state_, nullptr, std::nullopt);
+        local_state(), nullptr, std::nullopt);
   }
   return network_time_tracker_.get();
 }
@@ -618,34 +623,16 @@
   system_notification_helper_ = std::move(system_notification_helper);
 }
 
-void TestingBrowserProcess::SetLocalState(PrefService* local_state) {
-  if (!local_state) {
-    // The local_state_ PrefService is owned outside of TestingBrowserProcess,
-    // but some of the members of TestingBrowserProcess hold references to it
-    // (for example, via PrefNotifier members). But given our test
-    // infrastructure which tears down individual tests before freeing the
-    // TestingBrowserProcess, there's not a good way to make local_state outlive
-    // these dependencies. As a workaround, whenever local_state_ is cleared
-    // (assumedly as part of exiting the test and freeing TestingBrowserProcess)
-    // any components owned by TestingBrowserProcess that depend on local_state
-    // are also freed.
-    network_time_tracker_.reset();
-#if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS)
-    notification_ui_manager_.reset();
-#endif
-    serial_policy_allowed_ports_.reset();
-    ShutdownBrowserPolicyConnector();
-    created_browser_policy_connector_ = false;
-  }
-  local_state_ = local_state;
-}
-
 void TestingBrowserProcess::MaybeStartTearDown() {
   if (is_torn_down_) {
     return;
   }
   is_torn_down_ = true;
 
+  network_time_tracker_.reset();
+#if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS)
+  notification_ui_manager_.reset();
+#endif
   ShutdownBrowserPolicyConnector();
 }
 
@@ -664,7 +651,6 @@
 #endif
     browser_policy_connector_->Shutdown();
   }
-  browser_policy_connector_.reset();
 }
 
 TestingBrowserProcessPlatformPart*
@@ -719,6 +705,10 @@
 }
 #endif
 
+TestingPrefServiceSimple* TestingBrowserProcess::GetTestingLocalState() {
+  return testing_local_state_.get();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 TestingBrowserProcessInitializer::TestingBrowserProcessInitializer() {
diff --git a/chrome/test/base/testing_browser_process.h b/chrome/test/base/testing_browser_process.h
index 07e8e1e..8985217 100644
--- a/chrome/test/base/testing_browser_process.h
+++ b/chrome/test/base/testing_browser_process.h
@@ -36,6 +36,7 @@
 class NotificationPlatformBridge;
 class NotificationUIManager;
 class PrefService;
+class TestingPrefServiceSimple;
 class SystemNotificationHelper;
 
 namespace extensions {
@@ -177,9 +178,6 @@
   // TaskEnvironment::DestructionObserver:
   void WillDestroyCurrentTaskEnvironment() override;
 
-  // Set the local state for tests. Consumer is responsible for cleaning it up
-  // afterwards (using ScopedTestingLocalState, for example).
-  void SetLocalState(PrefService* local_state);
   void SetMetricsService(metrics::MetricsService* metrics_service);
   void SetProfileManager(std::unique_ptr<ProfileManager> profile_manager);
   void SetSafeBrowsingService(safe_browsing::SafeBrowsingService* sb_service);
@@ -210,6 +208,9 @@
       std::unique_ptr<UsbSystemTrayIcon> usb_system_tray_icon);
 #endif
 
+  // Same as local_state() but provides TestingPrefServiceSimple interface.
+  TestingPrefServiceSimple* GetTestingLocalState();
+
  private:
   // See CreateInstance() and DestoryInstance() above.
   TestingBrowserProcess();
@@ -230,13 +231,14 @@
 
   std::unique_ptr<policy::ChromeBrowserPolicyConnector>
       browser_policy_connector_;
-  bool created_browser_policy_connector_ = false;
   std::unique_ptr<network::TestNetworkQualityTracker>
       test_network_quality_tracker_;
   raw_ptr<metrics::MetricsService> metrics_service_ = nullptr;
   raw_ptr<variations::VariationsService> variations_service_ = nullptr;
   std::unique_ptr<ProfileManager> profile_manager_;
 
+  std::unique_ptr<TestingPrefServiceSimple> testing_local_state_;
+
 #if BUILDFLAG(ENABLE_CHROME_NOTIFICATIONS)
   std::unique_ptr<NotificationUIManager> notification_ui_manager_;
 #endif
@@ -269,7 +271,6 @@
   std::unique_ptr<network_time::NetworkTimeTracker> network_time_tracker_;
 
   // The following objects are not owned by TestingBrowserProcess:
-  raw_ptr<PrefService> local_state_ = nullptr;
   scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
 
   std::unique_ptr<TestingBrowserProcessPlatformPart> platform_part_;
@@ -311,7 +312,6 @@
 //  ...stuff...
 //  private:
 //   TestingBrowserProcessInitializer initializer_;
-//   LocalState local_state_;  // Needs a BrowserProcess to initialize.
 // };
 class TestingBrowserProcessInitializer {
  public:
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index b23c736..ed26639 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-16323.0.0-1069691
\ No newline at end of file
+16324.0.0-1069713
\ No newline at end of file
diff --git a/chromeos/ash/services/recording/recording_service.cc b/chromeos/ash/services/recording/recording_service.cc
index d897246..6f825ef9 100644
--- a/chromeos/ash/services/recording/recording_service.cc
+++ b/chromeos/ash/services/recording/recording_service.cc
@@ -389,7 +389,8 @@
           info->visible_rect);
   scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
       info->pixel_format, info->coded_size, visible_rect, visible_rect.size(),
-      mapping, info->timestamp);
+      reinterpret_cast<const uint8_t*>(mapping.memory()), mapping.size(),
+      info->timestamp);
   if (!frame) {
     DLOG(ERROR) << "Failed to create a VideoFrame.";
     return;
diff --git a/clank b/clank
index ceeb89e..87625a3 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit ceeb89e0f8224ca96ca05598cf0cfc1a7b652963
+Subproject commit 87625a3482c6c67f2ae49c6ed62f44a8ba86a900
diff --git a/components/autofill/core/browser/form_parsing/internal_resources b/components/autofill/core/browser/form_parsing/internal_resources
index b0d7376..103ef5e 160000
--- a/components/autofill/core/browser/form_parsing/internal_resources
+++ b/components/autofill/core/browser/form_parsing/internal_resources
@@ -1 +1 @@
-Subproject commit b0d7376c49e618262acef6723c9dbdc7c41d31e8
+Subproject commit 103ef5ee6ebd7611908d3685faf5dc42d79b569d
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index 108f59f5..b5781b8 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -1255,6 +1255,12 @@
     <message name="IDS_AUTOFILL_BOTTOM_SHEET_MANAGE_PAYMENT_METHODS" desc="An option in the autofill bottom sheet that triggers navigation to the payment settings." formatter_data="android_java">
       Manage payment methods
     </message>
+    <message name="IDS_AUTOFILL_BOTTOM_SHEET_ALL_YOUR_LOYALTY_CARDS" desc="An option in the autofill bottom sheet that shows all loyalty cards of a user." formatter_data="android_java">
+      All your loyalty cards
+    </message>
+    <message name="IDS_AUTOFILL_BOTTOM_SHEET_EXPAND_ALL_LOYALTY_CARDS_DESCRIPTION" desc="Accessibility string for the icon that allows entering the screen with all loyalty cards of a user." formatter_data="android_java">
+      Change to all loyalty cards under this menu item.
+    </message>
     <message name="IDS_AUTOFILL_BOTTOM_SHEET_MANAGE_LOYALTY_CARDS" desc="An option in the autofill bottom sheet that triggers navigation to the loyalty cards management UI in Google Wallet." formatter_data="android_java">
       Manage loyalty cards
     </message>
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_BOTTOM_SHEET_ALL_YOUR_LOYALTY_CARDS.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_BOTTOM_SHEET_ALL_YOUR_LOYALTY_CARDS.png.sha1
new file mode 100644
index 0000000..bb5ff45
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_BOTTOM_SHEET_ALL_YOUR_LOYALTY_CARDS.png.sha1
@@ -0,0 +1 @@
+cdd7298b45b7d29538ee7a6eb32001715b1289e5
\ No newline at end of file
diff --git a/components/autofill_payments_strings_grdp/IDS_AUTOFILL_BOTTOM_SHEET_EXPAND_ALL_LOYALTY_CARDS_DESCRIPTION.png.sha1 b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_BOTTOM_SHEET_EXPAND_ALL_LOYALTY_CARDS_DESCRIPTION.png.sha1
new file mode 100644
index 0000000..d397937
--- /dev/null
+++ b/components/autofill_payments_strings_grdp/IDS_AUTOFILL_BOTTOM_SHEET_EXPAND_ALL_LOYALTY_CARDS_DESCRIPTION.png.sha1
@@ -0,0 +1 @@
+b76735e955dc00cb226322e907171847022bcbd8
\ No newline at end of file
diff --git a/components/capture_mode/camera_video_frame_handler.cc b/components/capture_mode/camera_video_frame_handler.cc
index 94845a2..3bf22eb 100644
--- a/components/capture_mode/camera_video_frame_handler.cc
+++ b/components/capture_mode/camera_video_frame_handler.cc
@@ -175,8 +175,8 @@
     auto& frame_info = buffer->frame_info;
     auto frame = media::VideoFrame::WrapExternalData(
         frame_info->pixel_format, frame_info->coded_size,
-        frame_info->visible_rect, frame_info->visible_rect.size(), mapping,
-        frame_info->timestamp);
+        frame_info->visible_rect, frame_info->visible_rect.size(),
+        mapping.GetMemoryAs<uint8_t>(), mapping.size(), frame_info->timestamp);
 
     if (frame) {
       frame->AddDestructionObserver(base::DoNothingWithBoundArgs(mapping_));
diff --git a/components/chromeos_camera/jpeg_encode_accelerator_unittest.cc b/components/chromeos_camera/jpeg_encode_accelerator_unittest.cc
index 98e16bae3..a849f01 100644
--- a/components/chromeos_camera/jpeg_encode_accelerator_unittest.cc
+++ b/components/chromeos_camera/jpeg_encode_accelerator_unittest.cc
@@ -658,9 +658,8 @@
       media::VideoFrame::WrapExternalData(
           media::PIXEL_FORMAT_I420, test_image->visible_size,
           gfx::Rect(test_image->visible_size), test_image->visible_size,
-          in_shm_->mapping.GetMemoryAsSpan<uint8_t>().first(
-              test_image->image_data.size()),
-          base::TimeDelta());
+          static_cast<uint8_t*>(in_shm_->mapping.memory()),
+          test_image->image_data.size(), base::TimeDelta());
   LOG_ASSERT(input_frame_.get());
   input_frame_->BackWithSharedMemory(&in_shm_->region);
 
diff --git a/components/chromeos_camera/mojo_jpeg_encode_accelerator_service.cc b/components/chromeos_camera/mojo_jpeg_encode_accelerator_service.cc
index c994a33..eaca32b 100644
--- a/components/chromeos_camera/mojo_jpeg_encode_accelerator_service.cc
+++ b/components/chromeos_camera/mojo_jpeg_encode_accelerator_service.cc
@@ -262,12 +262,15 @@
     return;
   }
 
+  const uint8_t* input_shm_memory =
+      input_mapping.GetMemoryAsSpan<uint8_t>().data();
   scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
       media::PIXEL_FORMAT_I420,  // format
       coded_size,                // coded_size
       gfx::Rect(coded_size),     // visible_rect
       coded_size,                // natural_size
-      input_mapping,             // data
+      input_shm_memory,          // data
+      input_buffer_size,         // data_size
       base::TimeDelta());        // timestamp
   if (!frame.get()) {
     LOG(ERROR) << "Could not create VideoFrame for buffer id " << task_id;
diff --git a/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.cc b/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.cc
index 9269556..b174a4c 100644
--- a/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.cc
+++ b/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.cc
@@ -238,12 +238,14 @@
     return;
   }
 
+  uint8_t* shm_memory = mapping.GetMemoryAsSpan<uint8_t>().data();
   scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
       media::PIXEL_FORMAT_I420,  // format
       coded_size,                // coded_size
       gfx::Rect(coded_size),     // visible_rect
       coded_size,                // natural_size
-      mapping,                   // data
+      shm_memory,                // data
+      output_buffer_size,        // data_size
       base::TimeDelta());        // timestamp
   if (!frame.get()) {
     LOG(ERROR) << "Could not create VideoFrame for input buffer id "
diff --git a/components/gwp_asan/client/extreme_lightweight_detector_quarantine.h b/components/gwp_asan/client/extreme_lightweight_detector_quarantine.h
index ea522f0..060e562 100644
--- a/components/gwp_asan/client/extreme_lightweight_detector_quarantine.h
+++ b/components/gwp_asan/client/extreme_lightweight_detector_quarantine.h
@@ -42,11 +42,11 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
-#include "base/component_export.h"
 #include "base/memory/raw_ref.h"
 #include "base/rand_util.h"
 #include "base/thread_annotations.h"
 #include "base/trace_event/malloc_dump_provider.h"
+#include "components/gwp_asan/client/export.h"
 #include "partition_alloc/internal_allocator_forward.h"
 #include "partition_alloc/partition_alloc_forward.h"
 #include "partition_alloc/partition_lock.h"
@@ -63,7 +63,7 @@
 
 class ExtremeLightweightDetectorQuarantineBranch;
 
-class COMPONENT_EXPORT(GWP_ASAN) ExtremeLightweightDetectorQuarantineRoot {
+class GWP_ASAN_EXPORT ExtremeLightweightDetectorQuarantineRoot {
  public:
   explicit ExtremeLightweightDetectorQuarantineRoot(
       partition_alloc::PartitionRoot& allocator_root)
@@ -100,7 +100,7 @@
   friend class ExtremeLightweightDetectorQuarantineBranch;
 };
 
-class COMPONENT_EXPORT(GWP_ASAN) ExtremeLightweightDetectorQuarantineBranch {
+class GWP_ASAN_EXPORT ExtremeLightweightDetectorQuarantineBranch {
  public:
   using Root = ExtremeLightweightDetectorQuarantineRoot;
 
diff --git a/components/metrics/metrics_log.cc b/components/metrics/metrics_log.cc
index 1be7e486..8b5980f 100644
--- a/components/metrics/metrics_log.cc
+++ b/components/metrics/metrics_log.cc
@@ -203,6 +203,8 @@
       return metrics::SystemProfileProto::OS::XFCE;
     case base::nix::DesktopEnvironment::DESKTOP_ENVIRONMENT_LXQT:
       return metrics::SystemProfileProto::OS::LXQT;
+    case base::nix::DesktopEnvironment::DESKTOP_ENVIRONMENT_COSMIC:
+      return metrics::SystemProfileProto::OS::COSMIC;
   }
 
   NOTREACHED();
diff --git a/components/mirroring/service/video_capture_client.cc b/components/mirroring/service/video_capture_client.cc
index 7be99d2..5c21e7d 100644
--- a/components/mirroring/service/video_capture_client.cc
+++ b/components/mirroring/service/video_capture_client.cc
@@ -235,7 +235,8 @@
       frame = media::VideoFrame::WrapExternalData(
           buffer->info->pixel_format, buffer->info->coded_size,
           buffer->info->visible_rect, buffer->info->visible_rect.size(),
-          mapping, buffer->info->timestamp);
+          mapping.GetMemoryAs<uint8_t>(), frame_allocation_size,
+          buffer->info->timestamp);
     }
     buffer_finished_callback =
         base::BindPostTaskToCurrentDefault(base::BindOnce(
@@ -253,7 +254,8 @@
       frame = media::VideoFrame::WrapExternalData(
           buffer->info->pixel_format, buffer->info->coded_size,
           buffer->info->visible_rect, buffer->info->visible_rect.size(),
-          mapping, buffer->info->timestamp);
+          mapping.GetMemoryAs<uint8_t>(), frame_allocation_size,
+          buffer->info->timestamp);
       if (frame) {
         frame->BackWithOwnedSharedMemory(std::move(shm_region),
                                          std::move(mapping));
diff --git a/components/os_crypt/async/browser/freedesktop_secret_key_provider.cc b/components/os_crypt/async/browser/freedesktop_secret_key_provider.cc
index 4eb26590..0e22f80 100644
--- a/components/os_crypt/async/browser/freedesktop_secret_key_provider.cc
+++ b/components/os_crypt/async/browser/freedesktop_secret_key_provider.cc
@@ -330,6 +330,7 @@
       case base::nix::DESKTOP_ENVIRONMENT_UNITY:
       case base::nix::DESKTOP_ENVIRONMENT_XFCE:
       case base::nix::DESKTOP_ENVIRONMENT_LXQT:
+      case base::nix::DESKTOP_ENVIRONMENT_COSMIC:
         InitializeFreedesktopSecretService();
         break;
     }
diff --git a/components/os_crypt/sync/key_storage_util_linux.cc b/components/os_crypt/sync/key_storage_util_linux.cc
index bec399d7..66680df 100644
--- a/components/os_crypt/sync/key_storage_util_linux.cc
+++ b/components/os_crypt/sync/key_storage_util_linux.cc
@@ -59,6 +59,7 @@
     case base::nix::DESKTOP_ENVIRONMENT_UKUI:
     case base::nix::DESKTOP_ENVIRONMENT_UNITY:
     case base::nix::DESKTOP_ENVIRONMENT_XFCE:
+    case base::nix::DESKTOP_ENVIRONMENT_COSMIC:
       return SelectedLinuxBackend::GNOME_LIBSECRET;
     // KDE3 didn't use DBus, which our KWallet store uses.
     case base::nix::DESKTOP_ENVIRONMENT_KDE3:
diff --git a/components/test/data/autofill/heuristics-json/internal b/components/test/data/autofill/heuristics-json/internal
index 059ed8f..adc0282 160000
--- a/components/test/data/autofill/heuristics-json/internal
+++ b/components/test/data/autofill/heuristics-json/internal
@@ -1 +1 @@
-Subproject commit 059ed8f0e91c6377aded5f0b3826ffe6f8717ab0
+Subproject commit adc0282015eb4a9fa58b09adb2e1dcd75a522b7f
diff --git a/components/user_education/webui/whats_new_registry.h b/components/user_education/webui/whats_new_registry.h
index 843ea8c..87d86834 100644
--- a/components/user_education/webui/whats_new_registry.h
+++ b/components/user_education/webui/whats_new_registry.h
@@ -214,6 +214,10 @@
     return editions_;
   }
 
+  WhatsNewStorageService* GetMutableStorageServiceForTesting() {
+    return storage_service_.get();
+  }
+
  private:
   std::unique_ptr<WhatsNewStorageService> storage_service_;
   std::map<std::string, WhatsNewModule> modules_;
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index e05b4da2..15758d49 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -440,13 +440,16 @@
     const gfx::Rect& rect,
     const gfx::Rect& visible_rect,
     const gfx::Rect& foreground_rect) {
-  auto memory = base::AlignedUninit<uint8_t>(
-      rect.size().GetArea() * 2, media::VideoFrame::kFrameAddressAlignment);
+  std::unique_ptr<unsigned char, base::AlignedFreeDeleter> memory(
+      static_cast<unsigned char*>(
+          base::AlignedAlloc(rect.size().GetArea() * 2,
+                             media::VideoFrame::kFrameAddressAlignment)));
   const gfx::Rect video_visible_rect = gfx::Rect(rect.width(), rect.height());
   scoped_refptr<media::VideoFrame> video_frame =
       media::VideoFrame::WrapExternalData(
           media::PIXEL_FORMAT_Y16, rect.size(), video_visible_rect,
-          visible_rect.size(), memory, base::TimeDelta());
+          visible_rect.size(), memory.get(), rect.size().GetArea() * 2,
+          base::TimeDelta());
   DCHECK_EQ(video_frame->rows(0) % 2, 0);
   DCHECK_EQ(video_frame->stride(0) % 2, 0ul);
 
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
index 2a4e1f9f..d46a6153 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
@@ -273,7 +273,8 @@
       ASSERT_LE(required_bytes_to_hold_planes, mapping.size());
       frame = media::VideoFrame::WrapExternalData(
           info->pixel_format, info->coded_size, info->visible_rect,
-          info->visible_rect.size(), mapping, info->timestamp);
+          info->visible_rect.size(), mapping.GetMemoryAs<const uint8_t>(),
+          mapping.size(), info->timestamp);
       ASSERT_TRUE(frame);
       frame->AddDestructionObserver(
           base::BindOnce([](base::ReadOnlySharedMemoryMapping mapping) {},
diff --git a/components/viz/service/frame_sinks/video_capture/shared_memory_video_frame_pool.cc b/components/viz/service/frame_sinks/video_capture/shared_memory_video_frame_pool.cc
index bd9c3e1..5217379 100644
--- a/components/viz/service/frame_sinks/video_capture/shared_memory_video_frame_pool.cc
+++ b/components/viz/service/frame_sinks/video_capture/shared_memory_video_frame_pool.cc
@@ -113,9 +113,10 @@
   // and 2) the mapped memory remains valid until the
   // WritableSharedMemoryMapping goes out-of-scope (when the OnceClosure is
   // destroyed).
-  scoped_refptr<VideoFrame> frame =
-      VideoFrame::WrapExternalData(format, size, gfx::Rect(size), size,
-                                   pooled_buffer.mapping, base::TimeDelta());
+  scoped_refptr<VideoFrame> frame = VideoFrame::WrapExternalData(
+      format, size, gfx::Rect(size), size,
+      static_cast<uint8_t*>(pooled_buffer.mapping.memory()),
+      pooled_buffer.mapping.size(), base::TimeDelta());
   CHECK(frame);
   // Sanity-check the assumption being made for SetMarkedBuffer():
   CHECK_EQ(frame->data(0), pooled_buffer.mapping.memory());
diff --git a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
index b3dbd48..6031954 100644
--- a/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
+++ b/components/webauthn/android/java/src/org/chromium/components/webauthn/AuthenticatorImpl.java
@@ -12,7 +12,6 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Build;
 import android.os.Bundle;
 import android.util.Pair;
 
@@ -248,9 +247,7 @@
     }
 
     private boolean couldSupportConditionalMediation() {
-        return GmsCoreUtils.isWebauthnSupported()
-                && isChrome(mWebContents)
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
+        return GmsCoreUtils.isWebauthnSupported() && isChrome(mWebContents);
     }
 
     private boolean couldSupportUvpaa() {
diff --git a/components/webauthn/android/junit/src/org/chromium/components/webauthn/Fido2CredentialRequestRobolectricTest.java b/components/webauthn/android/junit/src/org/chromium/components/webauthn/Fido2CredentialRequestRobolectricTest.java
index a416462..4c50629 100644
--- a/components/webauthn/android/junit/src/org/chromium/components/webauthn/Fido2CredentialRequestRobolectricTest.java
+++ b/components/webauthn/android/junit/src/org/chromium/components/webauthn/Fido2CredentialRequestRobolectricTest.java
@@ -48,7 +48,6 @@
 import org.chromium.base.Callback;
 import org.chromium.base.FeatureOverrides;
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.blink.mojom.AuthenticatorStatus;
 import org.chromium.blink.mojom.Mediation;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
@@ -78,7 +77,6 @@
         shadows = {
             ShadowCredentialManager.class,
         })
-@MinAndroidSdkLevel(Build.VERSION_CODES.P)
 public class Fido2CredentialRequestRobolectricTest {
     private static final String TEST_CHANNEL_EXTRA = "stable";
     private static final Boolean TEST_INCOGNITO_EXTRA = true;
diff --git a/components/webauthn/android/junit/src/org/chromium/components/webauthn/IdentityCredentialsHelperRobolectricTest.java b/components/webauthn/android/junit/src/org/chromium/components/webauthn/IdentityCredentialsHelperRobolectricTest.java
index ad93aa9..c436369 100644
--- a/components/webauthn/android/junit/src/org/chromium/components/webauthn/IdentityCredentialsHelperRobolectricTest.java
+++ b/components/webauthn/android/junit/src/org/chromium/components/webauthn/IdentityCredentialsHelperRobolectricTest.java
@@ -9,7 +9,6 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.os.Build;
 import android.os.Bundle;
 
 import androidx.test.filters.SmallTest;
@@ -23,12 +22,10 @@
 import org.mockito.MockitoAnnotations;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
 import org.chromium.content_public.browser.RenderFrameHost;
 
 @RunWith(BaseRobolectricTestRunner.class)
-@MinAndroidSdkLevel(Build.VERSION_CODES.P)
 public class IdentityCredentialsHelperRobolectricTest {
     private static final String ORIGIN_STRING = "https://subdomain.coolwebsitekayserispor.com";
     private static final byte[] CLIENT_DATA_HASH = new byte[] {1, 2, 3};
diff --git a/components/webauthn/android/junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java b/components/webauthn/android/junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java
index 452b0d5..c6a514d1 100644
--- a/components/webauthn/android/junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java
+++ b/components/webauthn/android/junit/src/org/chromium/components/webauthn/cred_man/CredManHelperRobolectricTest.java
@@ -26,7 +26,6 @@
 import android.credentials.GetCredentialRequest;
 import android.credentials.GetCredentialResponse;
 import android.credentials.PrepareGetCredentialResponse;
-import android.os.Build;
 import android.os.Bundle;
 
 import androidx.test.filters.SmallTest;
@@ -45,7 +44,6 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.test.BaseRobolectricTestRunner;
-import org.chromium.base.test.util.MinAndroidSdkLevel;
 import org.chromium.blink.mojom.AuthenticatorStatus;
 import org.chromium.blink.mojom.Mediation;
 import org.chromium.blink.mojom.PublicKeyCredentialCreationOptions;
@@ -86,7 +84,6 @@
             ShadowPrepareGetCredentialResponse.class,
             ShadowWebContentsStatics.class
         })
-@MinAndroidSdkLevel(Build.VERSION_CODES.P)
 public class CredManHelperRobolectricTest {
     private CredManHelper mCredManHelper;
     private Fido2ApiTestHelper.AuthenticatorCallback mCallback;
diff --git a/content/browser/devtools/devtools_video_consumer.cc b/content/browser/devtools/devtools_video_consumer.cc
index 3e4467a..cb1e5f4 100644
--- a/content/browser/devtools/devtools_video_consumer.cc
+++ b/content/browser/devtools/devtools_video_consumer.cc
@@ -178,7 +178,7 @@
   // portion of the frame that contains content is used.
   scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
       info->pixel_format, info->coded_size, content_rect, content_rect.size(),
-      mapping_memory, info->timestamp);
+      mapping_memory.data(), mapping_memory.size(), info->timestamp);
   if (!frame) {
     DLOG(ERROR) << "Unable to create VideoFrame wrapper around the shmem.";
     return;
diff --git a/content/browser/media/capture/fake_video_capture_stack.cc b/content/browser/media/capture/fake_video_capture_stack.cc
index feeca83..97d9cef 100644
--- a/content/browser/media/capture/fake_video_capture_stack.cc
+++ b/content/browser/media/capture/fake_video_capture_stack.cc
@@ -148,7 +148,8 @@
     auto video_frame = media::VideoFrame::WrapExternalData(
         frame.frame_info->pixel_format, frame.frame_info->coded_size,
         frame.frame_info->visible_rect, frame.frame_info->visible_rect.size(),
-        mapping, frame.frame_info->timestamp);
+        mapping.GetMemoryAs<const uint8_t>(), mapping.size(),
+        frame.frame_info->timestamp);
     CHECK(video_frame);
 
     video_frame->set_metadata(frame.frame_info->metadata);
diff --git a/content/browser/service_host/utility_process_sandbox_browsertest.cc b/content/browser/service_host/utility_process_sandbox_browsertest.cc
index ccdf187..0eb83fd0 100644
--- a/content/browser/service_host/utility_process_sandbox_browsertest.cc
+++ b/content/browser/service_host/utility_process_sandbox_browsertest.cc
@@ -129,7 +129,6 @@
       case Sandbox::kIme:
       case Sandbox::kTts:
       case Sandbox::kNearby:
-      case Sandbox::kShapeDetection:
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
       case Sandbox::kLibassistant:
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
diff --git a/content/browser/service_host/utility_sandbox_delegate.cc b/content/browser/service_host/utility_sandbox_delegate.cc
index fe8ef22..5ff3c5d 100644
--- a/content/browser/service_host/utility_sandbox_delegate.cc
+++ b/content/browser/service_host/utility_sandbox_delegate.cc
@@ -86,7 +86,6 @@
       sandbox_type_ == sandbox::mojom::Sandbox::kIme ||
       sandbox_type_ == sandbox::mojom::Sandbox::kTts ||
       sandbox_type_ == sandbox::mojom::Sandbox::kNearby ||
-      sandbox_type_ == sandbox::mojom::Sandbox::kShapeDetection ||
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
       sandbox_type_ == sandbox::mojom::Sandbox::kLibassistant ||
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
@@ -154,7 +153,6 @@
       sandbox_type_ == sandbox::mojom::Sandbox::kIme ||
       sandbox_type_ == sandbox::mojom::Sandbox::kTts ||
       sandbox_type_ == sandbox::mojom::Sandbox::kNearby ||
-      sandbox_type_ == sandbox::mojom::Sandbox::kShapeDetection ||
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
       sandbox_type_ == sandbox::mojom::Sandbox::kLibassistant ||
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/media/capture/ScreenCapture.java b/content/public/android/java/src/org/chromium/content_public/browser/media/capture/ScreenCapture.java
index f42bcc4..44bbdb5 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/media/capture/ScreenCapture.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/media/capture/ScreenCapture.java
@@ -4,6 +4,8 @@
 
 package org.chromium.content_public.browser.media.capture;
 
+import static org.chromium.build.NullUtil.assumeNonNull;
+
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -25,9 +27,10 @@
 import org.jni_zero.JNINamespace;
 import org.jni_zero.NativeMethods;
 
-import org.chromium.base.ContextUtils;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.ui.base.WindowAndroid;
 
 import java.nio.ByteBuffer;
 import java.util.concurrent.atomic.AtomicReference;
@@ -38,10 +41,20 @@
 public class ScreenCapture {
     private static final String TAG = "ScreenCapture";
 
+    private static class PickState {
+        final WebContents mWebContents;
+        final ActivityResult mActivityResult;
+
+        PickState(WebContents webContents, ActivityResult activityResult) {
+            mWebContents = webContents;
+            mActivityResult = activityResult;
+        }
+    }
+
     // Starting a MediaProjection session involves plumbing the results from the content picker,
     // which is done via ActivityResult. This class does not handle how that is achieved, but
-    // requires the ActivityResult to begin the session.
-    private static final AtomicReference<ActivityResult> sNextResult = new AtomicReference(null);
+    // requires this state to begin the session.
+    private static final AtomicReference<PickState> sNextPickState = new AtomicReference<>(null);
 
     // Starting a MediaProjection session requires a foreground service to be running. This class
     // does not handle how that is achieved, but `sLatch` provides a way for this class to wait
@@ -55,6 +68,7 @@
 
     private final HandlerThread mBackgroundThread = new HandlerThread("ScreenCapture");
     private @Nullable Handler mBackgroundHandler;
+    private @Nullable WebContents mWebContents;
     private @Nullable MediaProjection mMediaProjection;
 
     // While capture is running these references should only be modified on the background thread.
@@ -79,11 +93,19 @@
      *
      * <p>The {@link ActivityResult} is consumed by a subsequent call to {@link #startCapture()}.
      *
-     * @param nextResult The {@link ActivityResult} from the MediaProjection API.
+     * @param webContents The {@link WebContents} initiating the capture.
+     * @param activityResult The {@link ActivityResult} from the MediaProjection API.
      */
-    public static void onPick(ActivityResult nextResult) {
-        var oldResult = sNextResult.getAndSet(nextResult);
-        assert oldResult == null;
+    public static void onPick(WebContents webContents, ActivityResult activityResult) {
+        final PickState oldPickState =
+                sNextPickState.getAndSet(new PickState(webContents, activityResult));
+        assert oldPickState == null;
+    }
+
+    private @Nullable Context maybeGetContext() {
+        final WindowAndroid window = assumeNonNull(mWebContents).getTopLevelNativeWindow();
+        if (window == null) return null;
+        return window.getContext().get();
     }
 
     @CalledByNative
@@ -93,24 +115,28 @@
 
     @CalledByNative
     boolean startCapture() {
-        var nextResult = sNextResult.getAndSet(null);
-        assert nextResult != null;
-        assert nextResult.getData() != null;
+        final PickState pickState = sNextPickState.getAndSet(null);
+        assert pickState != null;
+        mWebContents = pickState.mWebContents;
+
+        final ActivityResult activityResult = pickState.mActivityResult;
+        assert activityResult.getData() != null;
 
         // We need to wait for the foreground service to start before trying to use the
         // MediaProjection API. It's okay to block here since we are on the desktop capturer thread.
         sLatch.block();
 
-        // TODO(crbug.com/352187279): Use the specific activity context for the captured target
-        // here.
-        final Context context = ContextUtils.getApplicationContext();
+        // TODO(crbug.com/352187279): Update the context if the WebContents is reparented.
+        final Context context = maybeGetContext();
+        if (context == null) return false;
 
         var manager =
                 (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
         if (manager == null) return false;
 
         mMediaProjection =
-                manager.getMediaProjection(nextResult.getResultCode(), nextResult.getData());
+                manager.getMediaProjection(
+                        activityResult.getResultCode(), activityResult.getData());
         if (mMediaProjection == null) return false;
 
         mBackgroundThread.start();
diff --git a/content/renderer/pepper/pepper_video_capture_host.cc b/content/renderer/pepper/pepper_video_capture_host.cc
index c961733..970254e 100644
--- a/content/renderer/pepper/pepper_video_capture_host.cc
+++ b/content/renderer/pepper/pepper_video_capture_host.cc
@@ -165,11 +165,8 @@
         scoped_refptr<media::VideoFrame> dst_frame =
             media::VideoFrame::WrapExternalData(
                 media::PIXEL_FORMAT_I420, frame->natural_size(),
-                gfx::Rect(frame->natural_size()), frame->natural_size(),
-                // TODO(crbug.com/40511450): Remove PPAPI altogether
-                UNSAFE_TODO(
-                    base::span<uint8_t>(dst, buffers_[i].buffer->size())),
-                frame->timestamp());
+                gfx::Rect(frame->natural_size()), frame->natural_size(), dst,
+                buffers_[i].buffer->size(), frame->timestamp());
         media::EncoderStatus status =
             frame_converter_.ConvertAndScale(*mapped_frame, *dst_frame);
         if (!status.is_ok()) {
diff --git a/content/renderer/pepper/pepper_video_encoder_host.cc b/content/renderer/pepper/pepper_video_encoder_host.cc
index 7231220..f5a86f57 100644
--- a/content/renderer/pepper/pepper_video_encoder_host.cc
+++ b/content/renderer/pepper/pepper_video_encoder_host.cc
@@ -524,11 +524,8 @@
   // ppapi/shared_impl/media_stream_buffer_manager.h for details.
   scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
       media_input_format_, input_coded_size_, gfx::Rect(input_coded_size_),
-      input_coded_size_,
-      // TODO(crbug.com/40511450): Remove PPAPI altogether
-      UNSAFE_TODO(
-          base::span<uint8_t>(buffer->video.data, buffer->video.data_size)),
-      base::TimeDelta());
+      input_coded_size_, static_cast<uint8_t*>(buffer->video.data),
+      buffer->video.data_size, base::TimeDelta());
   if (!frame) {
     NotifyPepperError(PP_ERROR_FAILED);
     return frame;
diff --git a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
index 43b26c6..afb49277 100644
--- a/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/context_lost_expectations.txt
@@ -229,6 +229,10 @@
 crbug.com/365565140 [ angle-metal asan graphite-enabled intel-0x3e9b arch-x86_64 no-clang-coverage release sonoma ] ContextLost_WebGPUUnblockedAfterUserInitiatedReload [ Failure ]
 crbug.com/365565140 [ angle-opengl debug graphite-disabled intel-0x3e9b arch-x86_64 no-asan no-clang-coverage sonoma ] ContextLost_WebGPUUnblockedAfterUserInitiatedReload [ Failure ]
 
+# Renderer Hung Possibly just Slow
+crbug.com/338574390 [ mac intel-0x3e9b angle-metal asan graphite-disabled ] ContextLost_WebGPUUnblockedAfterUserInitiatedReload [ Failure ]
+crbug.com/338574390 [ mac intel-0x3e9b angle-metal asan graphite-disabled ] GpuCrash_GPUProcessCrashesExactlyOncePerVisitToAboutGpuCrash [ Failure ]
+
 
 #######################################################################
 # Automated Entries After This Point - Do Not Manually Add Below Here #
diff --git a/content/utility/utility_main.cc b/content/utility/utility_main.cc
index c1c42c5..86cb9fd 100644
--- a/content/utility/utility_main.cc
+++ b/content/utility/utility_main.cc
@@ -89,8 +89,6 @@
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
 #include "chromeos/ash/services/libassistant/libassistant_sandbox_hook.h"  // nogncheck
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
-
-#include "services/shape_detection/shape_detection_sandbox_hook.h"
 #endif  // BUILDFLAG(IS_CHROMEOS)
 
 #if BUILDFLAG(IS_MAC)
@@ -351,10 +349,6 @@
     case sandbox::mojom::Sandbox::kTts:
       pre_sandbox_hook = base::BindOnce(&chromeos::tts::TtsPreSandboxHook);
       break;
-    case sandbox::mojom::Sandbox::kShapeDetection:
-      pre_sandbox_hook =
-          base::BindOnce(&shape_detection::ShapeDetectionPreSandboxHook);
-      break;
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
     case sandbox::mojom::Sandbox::kLibassistant:
       pre_sandbox_hook =
diff --git a/docs/android_build_instructions.md b/docs/android_build_instructions.md
index 072e2bc..f41183a2 100644
--- a/docs/android_build_instructions.md
+++ b/docs/android_build_instructions.md
@@ -124,7 +124,8 @@
 
 ## Setting up the build
 
-Chromium uses [Ninja](https://ninja-build.org) as its main build tool along with
+Chromium uses [Siso](https://pkg.go.dev/go.chromium.org/infra/build/siso#section-readme)
+as its main build tool along with
 a tool called [GN](https://gn.googlesource.com/gn/+/main/docs/quick_start.md)
 to generate `.ninja` files. You can create any number of *build directories*
 with different configurations. To create a build directory which builds Chrome
@@ -138,7 +139,7 @@
 is_component_build = false  # Unless you do a lot of native code edits. See "Faster Builds".
 ```
 
-* You only have to run this once for each new build directory, Ninja will
+* You only have to run this once for each new build directory, Siso will
   update the build files as needed.
 * You can replace `Default` with another name, but
   it should be a subdirectory of `out`.
@@ -174,14 +175,14 @@
 
 ## Build Chromium
 
-Build Chromium with Ninja using the command:
+Build Chromium with Siso or Ninja using the command:
 
 ```shell
 autoninja -C out/Default chrome_public_apk
 ```
 
 (`autoninja` is a wrapper that automatically provides optimal values for the
-arguments passed to `ninja`.)
+arguments passed to `siso` or `ninja`.)
 
 You can get a list of all of the other build targets from GN by running `gn ls
 out/Default` from the command line. To compile one, pass the GN label to Ninja
@@ -371,7 +372,7 @@
 
 Args that affect build speed:
  * `use_remoteexec = true` *(default=false)*
-   * What it does: Enables distributed builds via Reclient
+   * What it does: Enables distributed builds with remote exec API.
  * `symbol_level = 0` *(default=1)*
    * What it does: Disables most debug information in native code.
      * Stack traces will still show, but be missing frames for inlined functions and source lines.
@@ -421,10 +422,10 @@
 build/android/fast_local_dev_server.py --print-status-all
 ```
 
-### Use Reclient
+### Use Remote Execution
 
 *** note
-**Warning:** If you are a Google employee, do not follow the Reclient instructions
+**Warning:** If you are a Google employee, do not follow the instructions
 in this section. Set up remote execution as described in
 [go/building-android-chrome](https://goto.google.com/building-android-chrome)
 instead.
@@ -435,8 +436,8 @@
 you to benefit from remote caching and executing many build actions in parallel
 on a shared cluster of workers.
 
-To use Reclient, follow the corresponding
-[Linux build instructions](linux/build_instructions.md#use-reclient).
+To use Remote Execution, follow the corresponding
+[Linux build instructions](linux/build_instructions.md#use-remote-execution).
 
 ### Incremental Install
 [Incremental Install](/build/android/incremental_install/README.md) uses
diff --git a/extensions/browser/api/declarative/declarative_api.cc b/extensions/browser/api/declarative/declarative_api.cc
index 9c2254f..b11fad0f 100644
--- a/extensions/browser/api/declarative/declarative_api.cc
+++ b/extensions/browser/api/declarative/declarative_api.cc
@@ -202,7 +202,7 @@
 EventsEventAddRulesFunction::~EventsEventAddRulesFunction() = default;
 
 bool EventsEventAddRulesFunction::CreateParams() {
-  ConvertBinaryListElementsToBase64(mutable_args());
+  ConvertBinaryListElementsToBase64(GetMutableArgs());
   params_ = AddRules::Params::Create(args());
   return params_.has_value();
 }
diff --git a/extensions/browser/api/storage/storage_api.cc b/extensions/browser/api/storage/storage_api.cc
index d2f6050..527433af 100644
--- a/extensions/browser/api/storage/storage_api.cc
+++ b/extensions/browser/api/storage/storage_api.cc
@@ -103,10 +103,12 @@
   EXTENSION_FUNCTION_PRERUN_VALIDATE(args().size() >= 1);
   EXTENSION_FUNCTION_PRERUN_VALIDATE(args()[0].is_string());
 
-  // Not a ref since we remove the underlying value after.
-  std::string storage_area_string = args()[0].GetString();
+  base::ListValue& mutable_args = GetMutableArgs();
 
-  mutable_args().erase(args().begin());
+  // Not a ref since we remove the underlying value after.
+  const std::string storage_area_string(std::move(mutable_args[0].GetString()));
+
+  mutable_args.erase(mutable_args.begin());
   storage_area_ = StorageAreaFromString(storage_area_string);
   EXTENSION_FUNCTION_PRERUN_VALIDATE(storage_area_ !=
                                      StorageAreaNamespace::kInvalid);
@@ -187,8 +189,10 @@
     return RespondNow(BadMessage());
   }
 
-  base::Value input = std::move(mutable_args()[0]);
-  mutable_args().erase(args().begin());
+  base::ListValue& mutable_args = GetMutableArgs();
+
+  base::Value input = std::move(mutable_args[0]);
+  mutable_args.erase(args().begin());
 
   std::optional<std::vector<std::string>> keys;
   std::optional<base::Value::Dict> defaults;
@@ -343,9 +347,11 @@
     return RespondNow(BadMessage());
   }
 
+  base::ListValue& mutable_args = GetMutableArgs();
+
   // Retrieve and delete input from `args_` since they will be moved to storage.
-  base::Value input = std::move(mutable_args()[0]);
-  mutable_args().erase(args().begin());
+  base::Value input = std::move(mutable_args[0]);
+  mutable_args.erase(args().begin());
 
   StorageFrontend* frontend = StorageFrontend::Get(browser_context());
   frontend->Set(
diff --git a/extensions/browser/extension_function.cc b/extensions/browser/extension_function.cc
index 98771c8..3499d764 100644
--- a/extensions/browser/extension_function.cc
+++ b/extensions/browser/extension_function.cc
@@ -11,6 +11,7 @@
 
 #include "base/dcheck_is_on.h"
 #include "base/debug/crash_logging.h"
+#include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/logging.h"
 #include "base/memory/raw_ptr.h"
@@ -46,6 +47,7 @@
 #include "extensions/browser/service_worker/service_worker_keepalive.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_api.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/mojom/renderer.mojom.h"
 #include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_object.mojom-forward.h"
@@ -607,6 +609,22 @@
   return false;
 }
 
+const base::Value::List& ExtensionFunction::GetOriginalArgs() const {
+  CHECK(base::FeatureList::IsEnabled(
+      extensions_features::kAvoidCloneArgsOnExtensionFunctionDispatch));
+
+  if (original_args_.has_value()) {
+    // Return `original_args_`, which were copied from `args_` on the first call
+    // to GetMutableArgs().
+    return *original_args_;
+  }
+
+  // Return `args_`, which haven't been modified since they were set by
+  // SetArgs(), since GetMutableArgs() was never called.
+  DCHECK(args_.has_value());
+  return *args_;
+}
+
 void ExtensionFunction::OnResponseAck() {
   // Derived classes must override this if they require and implement an
   // ACK from the renderer.
@@ -698,6 +716,19 @@
   transferred_blobs_ = std::move(blobs);
 }
 
+base::Value::List& ExtensionFunction::GetMutableArgs() {
+  DCHECK(args_);
+  if (!original_args_.has_value() &&
+      base::FeatureList::IsEnabled(
+          extensions_features::kAvoidCloneArgsOnExtensionFunctionDispatch)) {
+    // Preserve original args before allowing modification of `args_`. Not
+    // needed when `kAvoidCloneArgsOnExtensionFunctionDispatch` is disabled
+    // since GetOriginalArgs() is disallowed in that configuration.
+    original_args_ = args_->Clone();
+  }
+  return *args_;
+}
+
 void ExtensionFunction::SendResponseImpl(bool success) {
   DCHECK(!response_callback_.is_null());
   DCHECK(!did_respond()) << name_;
diff --git a/extensions/browser/extension_function.h b/extensions/browser/extension_function.h
index 5ca5c7ee..ead131bb 100644
--- a/extensions/browser/extension_function.h
+++ b/extensions/browser/extension_function.h
@@ -449,6 +449,12 @@
   // via `AddResponseTarget()`.
   virtual void OnResponseAck();
 
+  // Returns original args, as they were set by SetArgs() (doesn't include
+  // modifications via GetMutableArgs()). Can only be called when the
+  // "AvoidCloneArgsOnExtensionFunctionDispatch" feature is enabled (otherwise
+  // the `ExtensionFunction` owner has preserved the original args).
+  const base::Value::List& GetOriginalArgs() const;
+
   // Sets did_respond_ to true so that the function won't DCHECK if it never
   // sends a response. Typically, this shouldn't be used, even in testing. It's
   // only for when you want to test functionality that doesn't exercise the
@@ -593,15 +599,14 @@
 
   bool has_args() const { return args_.has_value(); }
 
+  // Returns args. They may have been modified via GetMutableArgs() since they
+  // were set with SetArgs().
   const base::Value::List& args() const {
     DCHECK(args_);
     return *args_;
   }
 
-  base::Value::List& mutable_args() {
-    DCHECK(args_);
-    return *args_;
-  }
+  base::Value::List& GetMutableArgs();
 
   // The extension that called this function.
   scoped_refptr<const extensions::Extension> extension_;
@@ -629,9 +634,19 @@
   // failed, `error_` should be set.
   void SendResponseImpl(bool success);
 
-  // The arguments to the API. Only non-null if arguments were specified.
+  // The arguments to the API. Populated by SetArgs(). May be modified via
+  // GetMutableArgs().
   std::optional<base::Value::List> args_;
 
+  // Original arguments to the API. Populated from `args_` when GetMutableArgs()
+  // is first invoked, otherwise nullopt. This exists because an extension
+  // function may modify its args via GetMutableArgs(), but the owner of this
+  // object may need to access the original args via GetOriginalArgs() (the
+  // owner could also copy the args before passing them to the extension
+  // function, but that would result in an unnecessary copy when the extension
+  // function doesn't modify its args).
+  std::optional<base::Value::List> original_args_;
+
   base::ElapsedTimer timer_;
 
   // The results of the API. This should be populated through the Respond()/
diff --git a/extensions/browser/extension_function_dispatcher.cc b/extensions/browser/extension_function_dispatcher.cc
index 742acc3..0191514 100644
--- a/extensions/browser/extension_function_dispatcher.cc
+++ b/extensions/browser/extension_function_dispatcher.cc
@@ -48,6 +48,7 @@
 #include "extensions/browser/service_worker/service_worker_keepalive.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_api.h"
+#include "extensions/common/extension_features.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/extension_urls.h"
 #include "extensions/common/mojom/context_type.mojom.h"
@@ -198,7 +199,7 @@
 
   // TODO(crbug.com/40056469): Validate (or remove) `params.source_url`.
   DispatchWithCallbackInternal(
-      *params, &frame, *frame.GetProcess(),
+      std::move(params), &frame, *frame.GetProcess(),
       base::BindOnce(
           [](mojom::LocalFrameHost::RequestCallback callback,
              ExtensionFunction::ResponseType type, base::Value::List results,
@@ -254,7 +255,7 @@
   }
 
   DispatchWithCallbackInternal(
-      *params, nullptr, *rph,
+      std::move(params), nullptr, *rph,
       base::BindOnce(
           [](mojom::ServiceWorkerHost::RequestWorkerCallback callback,
              ExtensionFunction::ResponseType type, base::Value::List results,
@@ -268,7 +269,7 @@
 }
 
 void ExtensionFunctionDispatcher::DispatchWithCallbackInternal(
-    const mojom::RequestParams& params,
+    mojom::RequestParamsPtr params,
     content::RenderFrameHost* render_frame_host,
     content::RenderProcessHost& render_process_host,
     ExtensionFunction::ResponseCallback callback) {
@@ -293,10 +294,10 @@
 
   ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
   const Extension* extension =
-      registry->enabled_extensions().GetByID(params.extension_id);
+      registry->enabled_extensions().GetByID(params->extension_id);
   // Check if the call is from a hosted app. Hosted apps can only make call from
   // render frames, so we can use `render_frame_host_url`.
-  // TODO(devlin): Isn't `params.extension_id` still populated for hosted app
+  // TODO(devlin): Isn't `params->extension_id` still populated for hosted app
   // calls?
   if (!extension && render_frame_host_url) {
     extension = registry->enabled_extensions().GetHostedAppByURL(
@@ -304,7 +305,7 @@
   }
 
   if (!process_map->CanProcessHostContextType(extension, render_process_host,
-                                              params.context_type)) {
+                                              params->context_type)) {
     // TODO(crbug.com/40055126): Ideally, we'd be able to mark some
     // of these as bad messages. We can't do that in all cases because there
     // are times some of these might legitimately fail (for instance, during
@@ -318,7 +319,7 @@
     return;
   }
 
-  if (params.context_type == mojom::ContextType::kUntrustedWebUi) {
+  if (params->context_type == mojom::ContextType::kUntrustedWebUi) {
     // TODO(crbug.com/40265193): We should, at minimum, be using an
     // origin here. It'd be even better if we could have a more robust way of
     // checking that a process can host untrusted webui.
@@ -333,11 +334,19 @@
     }
   }
 
-  const bool is_worker_request = IsRequestFromServiceWorker(params);
+  const bool is_worker_request = IsRequestFromServiceWorker(*params);
+
+  base::ListValue arguments;
+  if (base::FeatureList::IsEnabled(
+          extensions_features::kAvoidCloneArgsOnExtensionFunctionDispatch)) {
+    arguments = std::move(params->arguments);
+  } else {
+    arguments = params->arguments.Clone();
+  }
 
   scoped_refptr<ExtensionFunction> function = CreateExtensionFunction(
-      params, extension, render_process_id, is_worker_request,
-      render_frame_host_url, params.context_type,
+      *params, std::move(arguments), extension, render_process_id,
+      is_worker_request, render_frame_host_url, params->context_type,
       ExtensionAPI::GetSharedInstance(), std::move(callback),
       render_frame_host);
   if (!function.get()) {
@@ -373,18 +382,29 @@
   // Fetch the ProcessManager before |this| is possibly invalidated.
   ProcessManager* process_manager = ProcessManager::Get(browser_context_);
 
+  // TODO(crbug.com/424432184): When the
+  // `kAvoidCloneArgsOnExtensionFunctionDispatch` feature is cleaned up, this
+  // lambda can be removed and references to it can be replaced with
+  // `function->GetOriginalArgs()`.
+  auto original_args = [&]() -> const base::ListValue& {
+    if (base::FeatureList::IsEnabled(
+            extensions_features::kAvoidCloneArgsOnExtensionFunctionDispatch)) {
+      return function->GetOriginalArgs();
+    }
+    return params->arguments;
+  };
+
   ExtensionSystem* extension_system = ExtensionSystem::Get(browser_context_);
   QuotaService* quota = extension_system->quota_service();
-  std::string violation_error =
-      quota->Assess(extension->id(), function.get(), params.arguments,
-                    base::TimeTicks::Now());
+  std::string violation_error = quota->Assess(
+      extension->id(), function.get(), original_args(), base::TimeTicks::Now());
 
   function->set_request_uuid(base::Uuid::GenerateRandomV4());
 
   // Increment the keepalive to ensure the extension doesn't shut down while
   // it's executing an API function. This is balanced in
   // `OnExtensionFunctionCompleted()`.
-  if (IsRequestFromServiceWorker(params)) {
+  if (is_worker_request) {
     CHECK(function->worker_id());
     content::ServiceWorkerExternalRequestTimeoutType timeout_type =
         function->ShouldKeepWorkerAliveIndefinitely()
@@ -404,7 +424,7 @@
   if (violation_error.empty()) {
     // See crbug.com/39178.
     ExtensionsBrowserClient::Get()->PermitExternalProtocolHandler();
-    NotifyApiFunctionCalled(extension->id(), params.name, params.arguments,
+    NotifyApiFunctionCalled(extension->id(), params->name, original_args(),
                             browser_context_);
 
     // Since sandboxed frames listed in the manifest don't get access to the
@@ -432,7 +452,7 @@
       }
     }
 
-    if (IsRequestFromServiceWorker(params)) {
+    if (is_worker_request) {
       base::UmaHistogramSparse(
           "Extensions.Functions.ExtensionServiceWorkerCalls",
           function->histogram_value());
@@ -535,7 +555,8 @@
 
 scoped_refptr<ExtensionFunction>
 ExtensionFunctionDispatcher::CreateExtensionFunction(
-    const mojom::RequestParams& params,
+    const mojom::RequestParams& params_without_args,
+    base::ListValue arguments,
     const Extension* extension,
     int requesting_process_id,
     bool is_worker_request,
@@ -547,16 +568,17 @@
   constexpr char kCreationFailed[] = "Access to extension API denied.";
 
   scoped_refptr<ExtensionFunction> function =
-      ExtensionFunctionRegistry::GetInstance().NewFunction(params.name);
+      ExtensionFunctionRegistry::GetInstance().NewFunction(
+          params_without_args.name);
   if (!function) {
-    LOG(ERROR) << "Unknown Extension API - " << params.name;
+    LOG(ERROR) << "Unknown Extension API - " << params_without_args.name;
     ResponseCallbackOnError(std::move(callback),
                             ExtensionFunction::ResponseType::kFailed,
                             kCreationFailed);
     return nullptr;
   }
 
-  function->SetArgs(params.arguments.Clone());
+  function->SetArgs(std::move(arguments));
 
   // Determine the source URL. When possible, prefer fetching this value from
   // the RenderFrameHost, but fallback to the value in the `params` object if
@@ -566,17 +588,17 @@
   if (is_worker_request) {
     // TODO(crbug.com/40056469): Validate this URL further. Or, better,
     // remove it from `mojom::RequestParams`.
-    function->set_source_url(params.source_url);
+    function->set_source_url(params_without_args.source_url);
   } else {
     DCHECK(render_frame_host_url);
     function->set_source_url(*render_frame_host_url);
   }
 
-  function->set_has_callback(params.has_callback);
-  function->set_user_gesture(params.user_gesture);
+  function->set_has_callback(params_without_args.has_callback);
+  function->set_user_gesture(params_without_args.user_gesture);
   function->set_extension(extension);
-  if (params.js_callstack.has_value()) {
-    function->set_js_callstack(*params.js_callstack);
+  if (params_without_args.js_callstack.has_value()) {
+    function->set_js_callstack(*params_without_args.js_callstack);
   }
   function->set_response_callback(std::move(callback));
   function->set_source_context_type(context_type);
@@ -584,8 +606,8 @@
   if (is_worker_request) {
     CHECK(extension);
     WorkerId worker_id;
-    worker_id.thread_id = params.worker_thread_id;
-    worker_id.version_id = params.service_worker_version_id;
+    worker_id.thread_id = params_without_args.worker_thread_id;
+    worker_id.version_id = params_without_args.service_worker_version_id;
     worker_id.render_process_id = requesting_process_id;
     worker_id.extension_id = extension->id();
     function->set_worker_id(std::move(worker_id));
@@ -599,7 +621,7 @@
   function->SetDispatcher(weak_ptr_factory_.GetWeakPtr());
 
   if (!function->HasPermission()) {
-    LOG(ERROR) << "Permission denied for " << params.name;
+    LOG(ERROR) << "Permission denied for " << params_without_args.name;
     function->RespondWithError(kCreationFailed);
     return nullptr;
   }
diff --git a/extensions/browser/extension_function_dispatcher.h b/extensions/browser/extension_function_dispatcher.h
index 45fc4eb1..e588d0d 100644
--- a/extensions/browser/extension_function_dispatcher.h
+++ b/extensions/browser/extension_function_dispatcher.h
@@ -12,6 +12,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/values.h"
 #include "extensions/browser/extension_function.h"
 #include "extensions/common/features/feature.h"
 #include "extensions/common/mojom/context_type.mojom-forward.h"
@@ -121,7 +122,8 @@
   // `params`.
   // Does not set subclass properties, or include_incognito.
   scoped_refptr<ExtensionFunction> CreateExtensionFunction(
-      const mojom::RequestParams& params,
+      const mojom::RequestParams& params_without_args,
+      base::ListValue arguments,
       const Extension* extension,
       int requesting_process_id,
       bool is_worker_request,
@@ -132,7 +134,7 @@
       content::RenderFrameHost* render_frame_host);
 
   void DispatchWithCallbackInternal(
-      const mojom::RequestParams& params,
+      mojom::RequestParamsPtr params,
       content::RenderFrameHost* render_frame_host,
       content::RenderProcessHost& render_process_host,
       ExtensionFunction::ResponseCallback callback);
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 1a1e4f2..04c9252 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -252,4 +252,8 @@
              "OptimizeServiceWorkerStartRequests",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+BASE_FEATURE(kAvoidCloneArgsOnExtensionFunctionDispatch,
+             "AvoidCloneArgsOnExtensionFunctionDispatch",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 }  // namespace extensions_features
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index 67312520..e239773 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -281,6 +281,12 @@
 // initiating a start.
 BASE_DECLARE_FEATURE(kOptimizeServiceWorkerStartRequests);
 
+// When enabled, a call to base::ListValue::Clone is avoided when dispatching an
+// extension function. Behind a feature to assess impact
+// (go/chrome-performance-work-should-be-finched).
+// TODO(crbug.com/424432184): Clean up when experiment is complete.
+BASE_DECLARE_FEATURE(kAvoidCloneArgsOnExtensionFunctionDispatch);
+
 }  // namespace extensions_features
 
 #endif  // EXTENSIONS_COMMON_EXTENSION_FEATURES_H_
diff --git a/gpu/command_buffer/client/client_shared_image.cc b/gpu/command_buffer/client/client_shared_image.cc
index 635d976..1a973d3 100644
--- a/gpu/command_buffer/client/client_shared_image.cc
+++ b/gpu/command_buffer/client/client_shared_image.cc
@@ -25,6 +25,7 @@
 #include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "ui/gfx/buffer_format_util.h"
 #include "ui/gfx/buffer_types.h"
+#include "ui/gfx/gpu_memory_buffer.h"
 
 namespace gpu {
 
@@ -424,6 +425,25 @@
   }
 }
 
+size_t ClientSharedImage::GetStrideForVideoFrame(uint32_t plane_index) const {
+  CHECK(gpu_memory_buffer_);
+  return gpu_memory_buffer_->stride(plane_index);
+}
+
+// Returns whether the underlying resource is shared memory without needing to
+// Map() the shared image. This method is supposed to be used by VideoFrame
+// temporarily as mentioned above in ::GetStrideForVideoFrame().
+bool ClientSharedImage::IsSharedMemoryForVideoFrame() const {
+  CHECK(gpu_memory_buffer_);
+  return gpu_memory_buffer_->GetType() ==
+         gfx::GpuMemoryBufferType::SHARED_MEMORY_BUFFER;
+}
+
+bool ClientSharedImage::AsyncMappingIsNonBlocking() const {
+  CHECK(gpu_memory_buffer_);
+  return gpu_memory_buffer_->AsyncMappingIsNonBlocking();
+}
+
 std::unique_ptr<ClientSharedImage::ScopedMapping> ClientSharedImage::Map() {
   std::unique_ptr<ClientSharedImage::ScopedMapping> scoped_mapping;
   if (shared_memory_mapping_.IsValid()) {
@@ -445,6 +465,12 @@
                                   std::move(result_cb));
 }
 
+gfx::GpuMemoryBufferHandle ClientSharedImage::CloneGpuMemoryBufferHandle()
+    const {
+  CHECK(gpu_memory_buffer_);
+  return gpu_memory_buffer_->CloneHandle();
+}
+
 #if BUILDFLAG(IS_APPLE)
 void ClientSharedImage::SetColorSpaceOnNativeBuffer(
     const gfx::ColorSpace& color_space) {
@@ -628,6 +654,22 @@
       std::nullopt, std::nullopt, texture_target));
 }
 
+// static
+scoped_refptr<ClientSharedImage> ClientSharedImage::CreateForTesting(
+    const Mailbox& mailbox,
+    const SharedImageMetadata& metadata,
+    const SyncToken& sync_token,
+    std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer,
+    gfx::BufferUsage buffer_usage,
+    scoped_refptr<SharedImageInterfaceHolder> sii_holder) {
+  SharedImageInfo info(metadata, "CSICreateForTesting");
+  auto client_si = base::MakeRefCounted<ClientSharedImage>(
+      mailbox, info, sync_token, sii_holder, gpu_memory_buffer->GetType());
+  client_si->gpu_memory_buffer_ = std::move(gpu_memory_buffer);
+  client_si->buffer_usage_ = buffer_usage;
+  return client_si;
+}
+
 ClientSharedImage::HelperGpuMemoryBufferManager::HelperGpuMemoryBufferManager(
     ClientSharedImage* client_shared_image)
     : client_shared_image_(client_shared_image) {
diff --git a/gpu/command_buffer/client/client_shared_image.h b/gpu/command_buffer/client/client_shared_image.h
index 0984add..93057b6 100644
--- a/gpu/command_buffer/client/client_shared_image.h
+++ b/gpu/command_buffer/client/client_shared_image.h
@@ -24,9 +24,19 @@
 #include "third_party/skia/include/core/SkImageInfo.h"
 #include "third_party/skia/include/core/SkPixmap.h"
 #include "ui/gfx/color_space.h"
-#include "ui/gfx/gpu_memory_buffer.h"
 #include "ui/gfx/gpu_memory_buffer_handle.h"
 
+namespace base {
+namespace trace_event {
+class ProcessMemoryDump;
+class MemoryAllocatorDumpGuid;
+}  // namespace trace_event
+}  // namespace base
+
+namespace gfx {
+class GpuMemoryBuffer;
+}
+
 namespace media {
 class VideoFrame;
 }  // namespace media
@@ -133,10 +143,7 @@
   // Returns a clone of the GpuMemoryBufferHandle associated with this ClientSI.
   // Valid to call only if this instance was created with a non-null
   // GpuMemoryBuffer.
-  gfx::GpuMemoryBufferHandle CloneGpuMemoryBufferHandle() const {
-    CHECK(gpu_memory_buffer_);
-    return gpu_memory_buffer_->CloneHandle();
-  }
+  gfx::GpuMemoryBufferHandle CloneGpuMemoryBufferHandle() const;
 
 #if BUILDFLAG(IS_APPLE)
   // Sets the color space in which the native buffer backing this SharedImage
@@ -217,14 +224,7 @@
       const SyncToken& sync_token,
       std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer,
       gfx::BufferUsage buffer_usage,
-      scoped_refptr<SharedImageInterfaceHolder> sii_holder) {
-    SharedImageInfo info(metadata, "CSICreateForTesting");
-    auto client_si = base::MakeRefCounted<ClientSharedImage>(
-        mailbox, info, sync_token, sii_holder, gpu_memory_buffer->GetType());
-    client_si->gpu_memory_buffer_ = std::move(gpu_memory_buffer);
-    client_si->buffer_usage_ = buffer_usage;
-    return client_si;
-  }
+      scoped_refptr<SharedImageInterfaceHolder> sii_holder);
 
   const SyncToken& creation_sync_token() const { return creation_sync_token_; }
 
@@ -341,24 +341,14 @@
   // VF can be refactored to behave like OPAQUE storage which does not need
   // layout info and hence stride. This method will then no longer needed and
   // can be removed.
-  size_t GetStrideForVideoFrame(uint32_t plane_index) const {
-    CHECK(gpu_memory_buffer_);
-    return gpu_memory_buffer_->stride(plane_index);
-  }
+  size_t GetStrideForVideoFrame(uint32_t plane_index) const;
 
   // Returns whether the underlying resource is shared memory without needing to
   // Map() the shared image. This method is supposed to be used by VideoFrame
   // temporarily as mentioned above in ::GetStrideForVideoFrame().
-  bool IsSharedMemoryForVideoFrame() const {
-    CHECK(gpu_memory_buffer_);
-    return gpu_memory_buffer_->GetType() ==
-           gfx::GpuMemoryBufferType::SHARED_MEMORY_BUFFER;
-  }
+  bool IsSharedMemoryForVideoFrame() const;
 
-  bool AsyncMappingIsNonBlocking() const {
-    CHECK(gpu_memory_buffer_);
-    return gpu_memory_buffer_->AsyncMappingIsNonBlocking();
-  }
+  bool AsyncMappingIsNonBlocking() const;
 
   // This pair of functions are used by SharedImageTexture to notify
   // ClientSharedImage of the beginning and the end of a scoped access.
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index eaba446..2ff91f3 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -1765,6 +1765,9 @@
         adapter.HasFeature(wgpu::FeatureName::SharedTextureMemoryIOSurface);
 #elif BUILDFLAG(IS_ANDROID)
     if (adapter_info.backendType == wgpu::BackendType::OpenGLES) {
+      if (!base::FeatureList::IsEnabled(features::kWebGPUAndroidOpenGLES)) {
+        return false;
+      }
       supports_external_textures = native_adapter.SupportsExternalImages();
     } else {
       supports_external_textures = adapter.HasFeature(
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index ab4af0d..69ffaad1 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -910,4 +910,8 @@
              "WebGPUCompatibilityMode",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
+BASE_FEATURE(kWebGPUAndroidOpenGLES,
+             "WebGPUAndroidOpenGLES",
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 }  // namespace features
diff --git a/gpu/config/gpu_finch_features.h b/gpu/config/gpu_finch_features.h
index 0e73f6a..50ba5554 100644
--- a/gpu/config/gpu_finch_features.h
+++ b/gpu/config/gpu_finch_features.h
@@ -95,6 +95,7 @@
 GPU_CONFIG_EXPORT BASE_DECLARE_FEATURE(kWebGPUUseTintIR);
 GPU_CONFIG_EXPORT BASE_DECLARE_FEATURE(kWebGPUUseVulkanMemoryModel);
 GPU_CONFIG_EXPORT BASE_DECLARE_FEATURE(kWebGPUEnableRangeAnalysisForRobustness);
+GPU_CONFIG_EXPORT BASE_DECLARE_FEATURE(kWebGPUAndroidOpenGLES);
 GPU_CONFIG_EXPORT extern const base::FeatureParam<std::string>
     kWebGPUDisabledToggles;
 GPU_CONFIG_EXPORT extern const base::FeatureParam<std::string>
diff --git a/gpu/config/webgpu_blocklist_impl.cc b/gpu/config/webgpu_blocklist_impl.cc
index 09605cb..195c047 100644
--- a/gpu/config/webgpu_blocklist_impl.cc
+++ b/gpu/config/webgpu_blocklist_impl.cc
@@ -52,10 +52,6 @@
   }
 
 #if BUILDFLAG(IS_ANDROID)
-  if (info.backendType == wgpu::BackendType::OpenGLES) {
-    reason = reason | WebGPUBlocklistReason::AndroidGLES;
-  }
-
   constexpr uint32_t kARMVendorID = 0x13B5;
   constexpr uint32_t kQualcommVendorID = 0x5143;
   constexpr uint32_t kIntelVendorID = 0x8086;
@@ -180,8 +176,6 @@
           {WebGPUBlocklistReason::AndroidLimitedSupport,
            "crbug.com/40643150: Limited support / testing currently "
            "available on Android."},
-          {WebGPUBlocklistReason::AndroidGLES,
-           "crbug.com/333858788: OpenGLES not fully supported on Android."},
           {WebGPUBlocklistReason::WindowsARM,
            "crbug.com/42242119: Not supported on Windows arm yet."},
           {WebGPUBlocklistReason::IndirectComputeRootConstants,
diff --git "a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Builder \050dbg\051/targets/chromium.webrtc.fyi.json" "b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Builder \050dbg\051/targets/chromium.webrtc.fyi.json"
index 4df8ec6c..f706ed2 100644
--- "a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Builder \050dbg\051/targets/chromium.webrtc.fyi.json"
+++ "b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Builder \050dbg\051/targets/chromium.webrtc.fyi.json"
@@ -21,12 +21,10 @@
         "name": "content_browsertests",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -52,12 +50,10 @@
         "name": "content_browsertests_sequential",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git "a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Builder ARM64 \050dbg\051/targets/chromium.webrtc.fyi.json" "b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Builder ARM64 \050dbg\051/targets/chromium.webrtc.fyi.json"
index d2a5059e..23d5aaa 100644
--- "a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Builder ARM64 \050dbg\051/targets/chromium.webrtc.fyi.json"
+++ "b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Builder ARM64 \050dbg\051/targets/chromium.webrtc.fyi.json"
@@ -21,12 +21,10 @@
         "name": "content_browsertests",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -52,12 +50,10 @@
         "name": "content_browsertests_sequential",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git "a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Tests \050dbg\051/targets/chromium.webrtc.fyi.json" "b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Tests \050dbg\051/targets/chromium.webrtc.fyi.json"
index 04a11a8a..a73e27c 100644
--- "a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Tests \050dbg\051/targets/chromium.webrtc.fyi.json"
+++ "b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Tests \050dbg\051/targets/chromium.webrtc.fyi.json"
@@ -20,12 +20,10 @@
         "name": "content_browsertests",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -51,12 +49,10 @@
         "name": "content_browsertests_sequential",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git "a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Tests ARM64 \050dbg\051/targets/chromium.webrtc.fyi.json" "b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Tests ARM64 \050dbg\051/targets/chromium.webrtc.fyi.json"
index f02fc5e..b685d6a6 100644
--- "a/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Tests ARM64 \050dbg\051/targets/chromium.webrtc.fyi.json"
+++ "b/infra/config/generated/builders/webrtc.fyi/WebRTC Chromium FYI Android Tests ARM64 \050dbg\051/targets/chromium.webrtc.fyi.json"
@@ -20,12 +20,10 @@
         "name": "content_browsertests",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -51,12 +49,10 @@
         "name": "content_browsertests_sequential",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git a/infra/config/generated/builders/webrtc/WebRTC Chromium Android Builder/targets/chromium.webrtc.json b/infra/config/generated/builders/webrtc/WebRTC Chromium Android Builder/targets/chromium.webrtc.json
index 354fb3ed..5360e17 100644
--- a/infra/config/generated/builders/webrtc/WebRTC Chromium Android Builder/targets/chromium.webrtc.json
+++ b/infra/config/generated/builders/webrtc/WebRTC Chromium Android Builder/targets/chromium.webrtc.json
@@ -20,12 +20,10 @@
         "name": "content_browsertests",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -51,12 +49,10 @@
         "name": "content_browsertests_sequential",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git a/infra/config/generated/builders/webrtc/WebRTC Chromium Android Tester/targets/chromium.webrtc.json b/infra/config/generated/builders/webrtc/WebRTC Chromium Android Tester/targets/chromium.webrtc.json
index f01b27f..a5e1bd7 100644
--- a/infra/config/generated/builders/webrtc/WebRTC Chromium Android Tester/targets/chromium.webrtc.json
+++ b/infra/config/generated/builders/webrtc/WebRTC Chromium Android Tester/targets/chromium.webrtc.json
@@ -19,12 +19,10 @@
         "name": "content_browsertests",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
@@ -50,12 +48,10 @@
         "name": "content_browsertests_sequential",
         "swarming": {
           "dimensions": {
-            "device_os": "PQ3A.190801.002",
-            "device_os_flavor": "google",
+            "device_os": "AP2A.240705.004",
             "device_os_type": "userdebug",
-            "device_type": "walleye",
-            "os": "Android",
-            "pool": "chromium.tests"
+            "device_type": "panther",
+            "os": "Android"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
diff --git a/infra/config/subprojects/webrtc/webrtc.fyi.star b/infra/config/subprojects/webrtc/webrtc.fyi.star
index 17b7fd7..02b596a 100644
--- a/infra/config/subprojects/webrtc/webrtc.fyi.star
+++ b/infra/config/subprojects/webrtc/webrtc.fyi.star
@@ -249,7 +249,7 @@
             "webrtc_chromium_simple_gtests",
         ],
         mixins = [
-            "chromium_pixel_2_pie",
+            "panther_on_14",
         ],
         per_test_modifications = {
             "content_browsertests": targets.mixin(
@@ -293,7 +293,7 @@
             "webrtc_chromium_simple_gtests",
         ],
         mixins = [
-            "chromium_pixel_2_pie",
+            "panther_on_14",
         ],
         per_test_modifications = {
             "content_browsertests": targets.mixin(
diff --git a/infra/config/subprojects/webrtc/webrtc.star b/infra/config/subprojects/webrtc/webrtc.star
index fe691df7..e39e598 100644
--- a/infra/config/subprojects/webrtc/webrtc.star
+++ b/infra/config/subprojects/webrtc/webrtc.star
@@ -140,7 +140,7 @@
             "webrtc_chromium_simple_gtests",
         ],
         mixins = [
-            "chromium_pixel_2_pie",
+            "panther_on_14",
         ],
     ),
     targets_settings = targets.settings(
diff --git a/internal b/internal
index 81737b4..26795f3 160000
--- a/internal
+++ b/internal
@@ -1 +1 @@
-Subproject commit 81737b465ba8c0c5686ebf867a1adc05eecdc951
+Subproject commit 26795f31e4ce2586e12dfb1897cbd87573277cff
diff --git a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
index 972a614..1a4b203 100644
--- a/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/manual_fill/password_view_controller_egtest.mm
@@ -1336,6 +1336,11 @@
 // Tests that tapping the "Autofill Form" button in the all password list fills
 // the password form with the right data.
 - (void)testAutofillFormButtonInAllPasswordListFillsForm {
+  // TODO(crbug.com/426435086): Test consistently fails on ipad.
+  if ([ChromeEarlGrey isIPadIdiom]) {
+    EARL_GREY_TEST_DISABLED(@"Fails on iPad.");
+  }
+
   if (![AutofillAppInterface isKeyboardAccessoryUpgradeEnabled]) {
     EARL_GREY_TEST_DISABLED(@"This test is not relevant when the Keyboard "
                             @"Accessory Upgrade feature is disabled.")
diff --git a/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm b/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm
index 94345f1..5627e8c 100644
--- a/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm
+++ b/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm
@@ -446,11 +446,10 @@
     std::string collaboration_group_id,
     std::string access_token,
     base::OnceCallback<void(GURL)> callback) {
-  if (!browser_) {
+  if (!browser_ || !share_screen_callback_) {
     return;
   }
 
-  CHECK(share_screen_callback_);
   link_generation_callback_ = std::move(callback);
   data_sharing::GroupToken token(data_sharing::GroupId(collaboration_group_id),
                                  access_token);
diff --git a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.h b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.h
index fe20326..8bfb00a 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.h
+++ b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.h
@@ -281,13 +281,6 @@
   // The autocomplete controller.
   __weak OmniboxAutocompleteController* omnibox_autocomplete_controller_ = nil;
 
-  // The initial text representing the current URL suitable for editing.
-  std::u16string url_for_editing_;
-
-  // Used to know what should be displayed. Updated when e.g. the popup
-  // selection changes, the results change, on navigation, on tab switch etc; it
-  // should always be up-to-date.
-  AutocompleteMatch current_match_;
   // The popup view is nullptr when there's no popup, and is non-null when
   // a popup view exists (i.e. between calls to `set_popup_view`).
   raw_ptr<OmniboxPopupViewIOS> popup_view_ = nullptr;
diff --git a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.mm b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.mm
index c0a412e..1db4908d 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_edit_model_ios.mm
@@ -103,7 +103,7 @@
 AutocompleteMatch OmniboxEditModelIOS::CurrentMatch(
     GURL* alternate_nav_url) const {
   // If we have a valid match use it. Otherwise get one for the current text.
-  AutocompleteMatch match = current_match_;
+  AutocompleteMatch match = text_model_->current_match;
   if (!match.destination_url.is_valid()) {
     GetInfoForCurrentText(&match, alternate_nav_url);
   } else if (alternate_nav_url) {
@@ -117,7 +117,7 @@
 
 bool OmniboxEditModelIOS::ResetDisplayTexts() {
   const std::u16string old_display_text = GetPermanentDisplayText();
-  url_for_editing_ = controller_->client()->GetFormattedFullURL();
+  text_model_->url_for_editing = controller_->client()->GetFormattedFullURL();
   // When there's new permanent text, and the user isn't interacting with the
   // omnibox, we want to revert the edit to show the new text.  We could simply
   // define "interacting" as "the omnibox has focus", but we still allow updates
@@ -134,13 +134,13 @@
 }
 
 std::u16string OmniboxEditModelIOS::GetPermanentDisplayText() const {
-  return url_for_editing_;
+  return text_model_->url_for_editing;
 }
 
 void OmniboxEditModelIOS::SetUserText(const std::u16string& text) {
   [text_controller_ setInputInProgress:YES];
   text_model_->UpdateUserText(text);
-  GetInfoForCurrentText(&current_match_, nullptr);
+  GetInfoForCurrentText(&text_model_->current_match, nullptr);
   text_model_->paste_state = OmniboxPasteState::kNone;
 }
 
@@ -171,7 +171,7 @@
   omnibox::AdjustTextForCopy(
       sel_min, text,
       /*has_user_modified_text=*/text_model_->user_input_in_progress ||
-          *text != url_for_editing_,
+          *text != text_model_->url_for_editing,
       /*is_keyword_selected=*/false,
       PopupIsOpen() ? std::optional<AutocompleteMatch>(CurrentMatch(nullptr))
                     : std::nullopt,
@@ -179,28 +179,7 @@
 }
 
 void OmniboxEditModelIOS::Revert() {
-  [text_controller_ setInputInProgress:NO];
-  text_model_->input.Clear();
-  text_model_->paste_state = OmniboxPasteState::kNone;
-  text_model_->UpdateUserText(std::u16string());
-  size_t start, end;
-  if (text_controller_) {
-    [text_controller_ getSelectionBounds:&start end:&end];
-  }
-  current_match_ = AutocompleteMatch();
-  // First home the cursor, so view of text is scrolled to left, then correct
-  // it. `SetCaretPos()` doesn't scroll the text, so doing that first wouldn't
-  // accomplish anything.
-  std::u16string current_permanent_url = GetPermanentDisplayText();
-
-  [text_controller_ setWindowText:current_permanent_url
-                         caretPos:0
-                startAutocomplete:false
-                notifyTextChanged:true];
-  [text_controller_
-      setCaretPos:std::min(current_permanent_url.length(), start)];
-
-  controller_->client()->OnRevert();
+  [text_controller_ revertState];
 }
 
 void OmniboxEditModelIOS::OpenSelection(OmniboxPopupSelection selection,
@@ -260,8 +239,7 @@
     const std::u16string& inline_autocompletion,
     const std::u16string& additional_text,
     const AutocompleteMatch& new_match) {
-  current_match_ = new_match;
-
+  text_model_->current_match = new_match;
   text_model_->inline_autocompletion = inline_autocompletion;
 
   const std::u16string& user_text = text_model_->user_input_in_progress
@@ -324,7 +302,7 @@
     // user input is in progress.
     std::u16string text_for_match_generation =
         text_model_->user_input_in_progress ? text_model_->user_text
-                                            : url_for_editing_;
+                                            : text_model_->url_for_editing;
 
     controller_->client()->GetAutocompleteClassifier()->Classify(
         text_for_match_generation, false, true, GetPageClassification(), match,
@@ -439,8 +417,9 @@
 
   std::u16string input_text(pasted_text);
   if (input_text.empty()) {
-    input_text = text_model_->user_input_in_progress ? text_model_->user_text
-                                                     : url_for_editing_;
+    input_text = text_model_->user_input_in_progress
+                     ? text_model_->user_text
+                     : text_model_->url_for_editing;
   }
   // Create a dummy AutocompleteInput for use in calling VerbatimMatchForInput()
   // to create an alternate navigational match.
diff --git a/ios/chrome/browser/omnibox/model/omnibox_text_controller.h b/ios/chrome/browser/omnibox/model/omnibox_text_controller.h
index 8edd530..b9610bf 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_text_controller.h
+++ b/ios/chrome/browser/omnibox/model/omnibox_text_controller.h
@@ -81,6 +81,10 @@
 /// Updates the text model input_in_progress state.
 - (void)setInputInProgress:(BOOL)inProgress;
 
+/// Reverts the text model back to its unedited state (permanent text showing,
+/// no user input in progress).
+- (void)revertState;
+
 #pragma mark - Autocomplete event
 
 /// Sets the additional text.
diff --git a/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm b/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm
index cf4e128..cff86a04 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm
+++ b/ios/chrome/browser/omnibox/model/omnibox_text_controller.mm
@@ -204,11 +204,7 @@
 }
 
 - (void)revertAll {
-  // This will clear the model's `user_input_in_progress_`.
-  if (_omniboxEditModel) {
-    _omniboxEditModel->Revert();
-  }
-
+  [self revertState];
   // This will stop the `AutocompleteController`. This should happen after
   // `user_input_in_progress_` is cleared above; otherwise, closing the popup
   // will trigger unnecessary `AutocompleteClassifier::Classify()` calls to
@@ -238,6 +234,28 @@
   }
 }
 
+- (void)revertState {
+  [self setInputInProgress:NO];
+  _omniboxTextModel->input.Clear();
+  _omniboxTextModel->paste_state = OmniboxPasteState::kNone;
+  _omniboxTextModel->UpdateUserText(std::u16string());
+  size_t start, end;
+  [self getSelectionBounds:&start end:&end];
+  _omniboxTextModel->current_match = AutocompleteMatch();
+  // First home the cursor, so view of text is scrolled to left, then correct
+  // it. `SetCaretPos()` doesn't scroll the text, so doing that first wouldn't
+  // accomplish anything.
+  std::u16string current_permanent_url = _omniboxTextModel->url_for_editing;
+
+  [self setWindowText:current_permanent_url
+               caretPos:0
+      startAutocomplete:false
+      notifyTextChanged:true];
+  [self setCaretPos:std::min(current_permanent_url.length(), start)];
+
+  _omniboxController->client()->OnRevert();
+}
+
 #pragma mark - Autocomplete events
 
 - (void)setAdditionalText:(const std::u16string&)text {
diff --git a/ios/chrome/browser/omnibox/model/omnibox_text_model.h b/ios/chrome/browser/omnibox/model/omnibox_text_model.h
index 8e043d19..554ca9c 100644
--- a/ios/chrome/browser/omnibox/model/omnibox_text_model.h
+++ b/ios/chrome/browser/omnibox/model/omnibox_text_model.h
@@ -118,6 +118,12 @@
   // This is needed to properly update the SearchModel state when the user
   // presses escape.
   bool in_revert;
+  // Used to know what should be displayed. Updated when e.g. the popup
+  // selection changes, the results change, on navigation, on tab switch etc; it
+  // should always be up-to-date.
+  AutocompleteMatch current_match;
+  // The initial text representing the current URL suitable for editing.
+  std::u16string url_for_editing;
 };
 
 #endif  // IOS_CHROME_BROWSER_OMNIBOX_MODEL_OMNIBOX_TEXT_MODEL_H_
diff --git a/ios/chrome/browser/partial_translate/ui_bundled/BUILD.gn b/ios/chrome/browser/partial_translate/ui_bundled/BUILD.gn
index fa3c892..03f8877 100644
--- a/ios/chrome/browser/partial_translate/ui_bundled/BUILD.gn
+++ b/ios/chrome/browser/partial_translate/ui_bundled/BUILD.gn
@@ -20,6 +20,7 @@
     "//ios/chrome/browser/shared/public/commands",
     "//ios/chrome/browser/shared/public/features",
     "//ios/chrome/browser/shared/ui/symbols",
+    "//ios/chrome/browser/translate/model",
     "//ios/chrome/browser/web_selection/model",
     "//ios/public/provider/chrome/browser/partial_translate:partial_translate_api",
     "//ios/web/public",
diff --git a/ios/chrome/browser/partial_translate/ui_bundled/DEPS b/ios/chrome/browser/partial_translate/ui_bundled/DEPS
index d416d38..82139fac 100644
--- a/ios/chrome/browser/partial_translate/ui_bundled/DEPS
+++ b/ios/chrome/browser/partial_translate/ui_bundled/DEPS
@@ -1,5 +1,6 @@
 include_rules = [
   "+ios/chrome/browser/browser_container/ui_bundled",
   "+ios/chrome/browser/fullscreen/ui_bundled",
+  "+ios/chrome/browser/translate/model",
   "+ios/chrome/browser/web_selection/model",
 ]
diff --git a/ios/chrome/browser/partial_translate/ui_bundled/partial_translate_mediator.mm b/ios/chrome/browser/partial_translate/ui_bundled/partial_translate_mediator.mm
index d2b1e071..87fae5d4 100644
--- a/ios/chrome/browser/partial_translate/ui_bundled/partial_translate_mediator.mm
+++ b/ios/chrome/browser/partial_translate/ui_bundled/partial_translate_mediator.mm
@@ -18,6 +18,7 @@
 #import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
 #import "ios/chrome/browser/shared/public/features/features.h"
 #import "ios/chrome/browser/shared/ui/symbols/symbols.h"
+#import "ios/chrome/browser/translate/model/translate_service_ios.h"
 #import "ios/chrome/browser/web_selection/model/web_selection_response.h"
 #import "ios/chrome/browser/web_selection/model/web_selection_tab_helper.h"
 #import "ios/chrome/grit/ios_strings.h"
@@ -168,6 +169,10 @@
   if (!self.alertDelegate) {
     return;
   }
+  if (![self URLIsTranslatable]) {
+    ReportErrorOutcome(error, false);
+    return;
+  }
   NSString* message;
   switch (error) {
     case PartialTranslateError::kSelectionTooLong:
@@ -267,6 +272,16 @@
   return helper;
 }
 
+- (BOOL)URLIsTranslatable {
+  web::WebState* webState =
+      _webStateList ? _webStateList->GetActiveWebState() : nullptr;
+  if (!webState) {
+    return NO;
+  }
+  return TranslateServiceIOS::IsTranslatableURL(
+      webState->GetLastCommittedURL());
+}
+
 - (void)addItemWithCompletion:(ProceduralBlockWithItemArray)completion {
   if (![self canHandlePartialTranslateSelection]) {
     completion(@[]);
diff --git a/ios/chrome/browser/saved_tab_groups/ui/face_pile_view.mm b/ios/chrome/browser/saved_tab_groups/ui/face_pile_view.mm
index 8feb0da..a3d8294 100644
--- a/ios/chrome/browser/saved_tab_groups/ui/face_pile_view.mm
+++ b/ios/chrome/browser/saved_tab_groups/ui/face_pile_view.mm
@@ -17,8 +17,8 @@
 // Spacing between overlapping avatars.
 const CGFloat kAvatarSpacing = -6;
 
-// Substracted stroke around avatars.
-const CGFloat kAvatarSubstractredStroke = 2;
+// Substracted stroke around containers.
+const CGFloat kContainerSubstractredStroke = 2;
 
 // Font size of `plusXLabel`.
 const CGFloat kPlusXlabelFontSize = 9;
@@ -26,9 +26,6 @@
 // horizontal inner margin in for the `_plusXLabelContainer`.
 const CGFloat kPlusXlabelContainerHorizontalInnerMargin = 6;
 
-// Substracted stroke around `_plusXLabelContainer`.
-const CGFloat kPlusXLabelContainerSubstractredStroke = 2;
-
 // Alpha value of the `_plusXLabelContainer` background color.
 const CGFloat kPlusXlabelContainerBackgroundAlpha = 0.2;
 
@@ -54,6 +51,7 @@
 - (instancetype)init {
   self = [super initWithFrame:CGRectZero];
   if (self) {
+    self.layer.masksToBounds = YES;
     _facesStackView = [[UIStackView alloc] init];
     _facesStackView.translatesAutoresizingMaskIntoConstraints = NO;
     _facesStackView.spacing = kAvatarSpacing;
@@ -82,9 +80,8 @@
   if (!backgroundColor) {
     return;
   }
-  // TODO(crbug.com/422737259): Render an outer stroke when the
-  // `_facePileBackgroundColor` is not nil.
   _facePileBackgroundColor = backgroundColor;
+  self.backgroundColor = _facePileBackgroundColor;
 }
 
 - (void)updateWithFaces:(NSArray<id<ShareKitAvatarPrimitive>>*)faces
@@ -95,7 +92,7 @@
   }
 
   _avatars = faces;
-  CGFloat containerSize = _avatarSize + kAvatarSubstractredStroke * 2;
+  CGFloat containerSize = _avatarSize + kContainerSubstractredStroke * 2;
   NSInteger avatarsCount = static_cast<NSUInteger>(_avatars.count);
 
   for (NSInteger index = 0; index < avatarsCount; index++) {
@@ -138,6 +135,7 @@
     // This is needed to avoid anti-aliasing artifacts around the border itself.
     UIView* plusXContainerView =
         [self createCircularContainerWithSize:containerSize];
+    plusXContainerView.layer.cornerRadius = containerSize / 2.0;
     [plusXContainerView addSubview:plusXLabel];
 
     [_facesStackView addArrangedSubview:plusXContainerView];
@@ -148,10 +146,10 @@
       [plusXLabel.heightAnchor constraintEqualToConstant:_avatarSize],
       [plusXLabel.leadingAnchor
           constraintEqualToAnchor:plusXContainerView.leadingAnchor
-                         constant:kPlusXLabelContainerSubstractredStroke],
+                         constant:kContainerSubstractredStroke],
       [plusXLabel.trailingAnchor
           constraintEqualToAnchor:plusXContainerView.trailingAnchor
-                         constant:-kPlusXLabelContainerSubstractredStroke],
+                         constant:-kContainerSubstractredStroke],
 
       [plusXContainerView.heightAnchor constraintEqualToConstant:containerSize]
     ]];
@@ -161,6 +159,8 @@
 
 - (void)setAvatarSize:(CGFloat)avatarSize {
   _avatarSize = avatarSize;
+  CGFloat containerSize = _avatarSize + kContainerSubstractredStroke * 2;
+  self.layer.cornerRadius = containerSize / 2.0;
 }
 
 #pragma mark - Private
diff --git a/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator.mm b/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator.mm
index 92430b0..e32e53f6f 100644
--- a/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator.mm
+++ b/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator.mm
@@ -226,6 +226,10 @@
 }
 
 - (void)updateActiveTabSnapshot:(ProceduralBlock)callback {
+  if (!self.activeWebState) {
+    [self runSnapshotCallback:callback];
+    return;
+  }
   SnapshotTabHelper* snapshotTabHelper =
       SnapshotTabHelper::FromWebState(self.activeWebState);
   if (!snapshotTabHelper) {
diff --git a/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator_unittest.mm b/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator_unittest.mm
index fce0b37..6a31c9b 100644
--- a/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator_unittest.mm
+++ b/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_mediator_unittest.mm
@@ -86,9 +86,10 @@
     original_web_state->SetBrowserState(profile_.get());
     original_web_state_ = original_web_state.get();
 
-    int web_state_index = browser_->GetWebStateList()->InsertWebState(
+    active_web_state_index_ = browser_->GetWebStateList()->InsertWebState(
         std::move(original_web_state));
-    browser_->GetWebStateList()->ActivateWebStateAt(web_state_index);
+    browser_->GetWebStateList()->ActivateWebStateAt(
+        active_web_state_index_.value());
 
     side_swipe_mediator_ = [[SideSwipeMediator alloc]
         initWithWebStateList:browser_->GetWebStateList()];
@@ -99,6 +100,13 @@
 
   ~SideSwipeMediatorTest() override { [side_swipe_mediator_ disconnect]; }
 
+  void CloseActiveWebState() {
+    browser_->GetWebStateList()->CloseWebStateAt(
+        active_web_state_index_.value(),
+        WebStateList::ClosingFlags::CLOSE_NO_FLAGS);
+    active_web_state_index_.reset();
+  }
+
   web::WebTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
   std::unique_ptr<TestProfileIOS> profile_;
@@ -110,6 +118,7 @@
   WKWebView* web_view_ = nil;
   CRWWebViewContentView* content_view_ = nil;
   raw_ptr<web::WebState> original_web_state_ = nil;
+  std::optional<int> active_web_state_index_;
 };
 
 TEST_F(SideSwipeMediatorTest, TestConstructor) {
@@ -229,6 +238,22 @@
 }
 
 // Tests that the snapshot update runs the provided callback that updates
+// the snapshot state when the active web state is null.
+TEST_F(SideSwipeMediatorTest, SnapshotUpdatedWithoutActiveWebState) {
+  base::RunLoop run_loop;
+  int snapshot_updated = 0;
+  CloseActiveWebState();
+  [side_swipe_mediator_
+      updateActiveTabSnapshot:base::CallbackToBlock(
+                                  base::BindLambdaForTesting([&]() {
+                                    snapshot_updated++;
+                                    run_loop.Quit();
+                                  }))];
+  run_loop.Run();
+  EXPECT_EQ(1, snapshot_updated);
+}
+
+// Tests that the snapshot update runs the provided callback that updates
 // the snapshot state only once on completion.
 TEST_F(SideSwipeMediatorTest, SnapshotUpdatedOnceOnCallback) {
   SnapshotTabHelper::CreateForWebState(original_web_state_);
diff --git a/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_ui_controller.mm b/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_ui_controller.mm
index 8f7931b..3599467a 100644
--- a/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_ui_controller.mm
+++ b/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_ui_controller.mm
@@ -90,6 +90,7 @@
 
 - (void)disconnect {
   [_tabSideSwipeView disconnect];
+  [self removeHorizontalGestureRecognizers];
   _fullscreenController = nullptr;
   _webStateList = nullptr;
 }
@@ -114,6 +115,18 @@
   [view addGestureRecognizer:_panGestureRecognizer];
 }
 
+- (void)removeHorizontalGestureRecognizers {
+  if (_swipeGestureRecognizer) {
+    [_swipeGestureRecognizer.view
+        removeGestureRecognizer:_swipeGestureRecognizer];
+    _swipeGestureRecognizer = nil;
+  }
+  if (_panGestureRecognizer) {
+    [_panGestureRecognizer.view removeGestureRecognizer:_panGestureRecognizer];
+    _panGestureRecognizer = nil;
+  }
+}
+
 - (void)animateSwipe:(SwipeType)swipeType
          inDirection:(UISwipeGestureRecognizerDirection)direction {
   if (_inSwipe || [_sideSwipeUIControllerDelegate preventSideSwipe]) {
diff --git a/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_ui_controller_unittest.mm b/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_ui_controller_unittest.mm
index 30d1b30..220fb28 100644
--- a/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_ui_controller_unittest.mm
+++ b/ios/chrome/browser/side_swipe/ui_bundled/side_swipe_ui_controller_unittest.mm
@@ -10,10 +10,11 @@
 
 class SideSwipeUIControllerTest : public PlatformTest {
  public:
-  SideSwipeUIControllerTest() {
+  SideSwipeUIControllerTest() : view_([[UIView alloc] init]) {
     side_swipe_ui_controller_ = [[SideSwipeUIController alloc] init];
   }
 
+  UIView* view_;
   SideSwipeUIController* side_swipe_ui_controller_;
 };
 
@@ -66,3 +67,13 @@
   // leading edge navigation is enabled.
   EXPECT_TRUE(edgeNavigationIsEnabledOnRightDirection);
 }
+
+// Tests that gesture recognizers are removed from the original view that
+// requested these when the UIViewController is disconnected.
+TEST_F(SideSwipeUIControllerTest, TestGestureRecognizerRemovedOnDisconnect) {
+  [side_swipe_ui_controller_ addHorizontalGesturesToView:view_];
+  EXPECT_EQ(2U, view_.gestureRecognizers.count);
+
+  [side_swipe_ui_controller_ disconnect];
+  EXPECT_EQ(0U, view_.gestureRecognizers.count);
+}
diff --git a/ios/chrome/widget_kit_extension/deleted_account_views.swift b/ios/chrome/widget_kit_extension/deleted_account_views.swift
index 146ef30f..f84842d 100644
--- a/ios/chrome/widget_kit_extension/deleted_account_views.swift
+++ b/ios/chrome/widget_kit_extension/deleted_account_views.swift
@@ -7,11 +7,8 @@
 
 enum DeletedAccountUIConstants {
   static let cornerRadius: CGFloat = 22
-  static let smallWidgetHeight: CGFloat = 140
-  static let smallWidgetWidth: CGFloat = 140
-  static let mediumWidgetHeight: CGFloat = 140
-  static let mediumWidgetWidth: CGFloat = 320
-  static let padding: CGFloat = 7
+  static let padding: CGFloat = 3
+  static let whiteBorder: CGFloat = 11
 }
 
 // Store in NSUserDefaults that the deleted account view appeared.
@@ -24,10 +21,6 @@
   VStack {
     ZStack {
       RoundedRectangle(cornerRadius: DeletedAccountUIConstants.cornerRadius)
-        .frame(
-          width: DeletedAccountUIConstants.smallWidgetHeight,
-          height: DeletedAccountUIConstants.smallWidgetHeight
-        )
         .foregroundColor(Color("widget_search_bar_color"))
         .overlay(
           Text("IDS_IOS_WIDGET_KIT_EXTENSION_DELETED_ACCOUNT")
@@ -37,6 +30,8 @@
             .padding(DeletedAccountUIConstants.padding)
         )
     }
+    .frame(minWidth: 0, maxWidth: .infinity)
+    .padding(DeletedAccountUIConstants.whiteBorder)
   }
   .crContainerBackground(Color("widget_background_color").unredacted())
   .onAppear {
@@ -48,10 +43,6 @@
   VStack {
     ZStack {
       RoundedRectangle(cornerRadius: DeletedAccountUIConstants.cornerRadius)
-        .frame(
-          width: DeletedAccountUIConstants.mediumWidgetWidth,
-          height: DeletedAccountUIConstants.mediumWidgetHeight
-        )
         .foregroundColor(Color("widget_search_bar_color"))
         .overlay(
           VStack {
@@ -67,6 +58,8 @@
           }
         )
     }
+    .frame(minWidth: 0, maxWidth: .infinity)
+    .padding(DeletedAccountUIConstants.whiteBorder)
   }
   .crContainerBackground(Color("widget_background_color").unredacted())
   .onAppear {
diff --git a/media/base/encoder_status.cc b/media/base/encoder_status.cc
index 48f82674..f93b0a1 100644
--- a/media/base/encoder_status.cc
+++ b/media/base/encoder_status.cc
@@ -52,6 +52,8 @@
       return "No hardware encoder is available.";
     case EncoderStatus::Codes::kOutOfPlatformEncoders:
       return "The system ran out of platform encoders.";
+    case EncoderStatus::Codes::kBadReferenceBuffer:
+      return "Invalid reference buffer index is specified.";
     case EncoderStatus::Codes::kOk:
       NOTREACHED();
   }
diff --git a/media/base/encoder_status.h b/media/base/encoder_status.h
index ac87f47..0e27e26 100644
--- a/media/base/encoder_status.h
+++ b/media/base/encoder_status.h
@@ -58,7 +58,9 @@
     kEncoderAccelerationSupportMissing = 20,
     // The system ran out of platform encoders.
     kOutOfPlatformEncoders = 21,
-    kMaxValue = kOutOfPlatformEncoders,
+    // The client provided a non-existing reference buffer.
+    kBadReferenceBuffer = 22,
+    kMaxValue = kBadReferenceBuffer,
   };
   static constexpr StatusGroupType Group() { return "EncoderStatus"; }
 };
diff --git a/media/base/video_frame.cc b/media/base/video_frame.cc
index 30979af..fd3bb8c6 100644
--- a/media/base/video_frame.cc
+++ b/media/base/video_frame.cc
@@ -547,6 +547,22 @@
     const gfx::Size& coded_size,
     const gfx::Rect& visible_rect,
     const gfx::Size& natural_size,
+    const uint8_t* data,
+    size_t data_size,
+    base::TimeDelta timestamp) {
+  auto layout = GetDefaultLayout(format, coded_size);
+  if (!layout)
+    return nullptr;
+  return WrapExternalDataWithLayout(*layout, visible_rect, natural_size, data,
+                                    data_size, timestamp);
+}
+
+// static
+scoped_refptr<VideoFrame> VideoFrame::WrapExternalData(
+    VideoPixelFormat format,
+    const gfx::Size& coded_size,
+    const gfx::Rect& visible_rect,
+    const gfx::Size& natural_size,
     base::span<const uint8_t> data,
     base::TimeDelta timestamp) {
   auto layout = GetDefaultLayout(format, coded_size);
@@ -1574,15 +1590,6 @@
       WrapReleaseMailboxCB(std::move(release_mailbox_cb));
 }
 
-void VideoFrame::SetReleaseMailboxAndGpuMemoryBufferCB(
-    ReleaseMailboxAndGpuMemoryBufferCB release_mailbox_cb) {
-  // See remarks in SetReleaseMailboxCB.
-  DCHECK(release_mailbox_cb);
-  DCHECK(!mailbox_holder_and_gmb_release_cb_);
-  DCHECK(!wrapped_frame_);
-  mailbox_holder_and_gmb_release_cb_ = std::move(release_mailbox_cb);
-}
-
 bool VideoFrame::HasReleaseMailboxCB() const {
   return wrapped_frame_ ? wrapped_frame_->HasReleaseMailboxCB()
                         : !!mailbox_holder_and_gmb_release_cb_;
diff --git a/media/base/video_frame.h b/media/base/video_frame.h
index 6af1d64..700a346 100644
--- a/media/base/video_frame.h
+++ b/media/base/video_frame.h
@@ -277,6 +277,15 @@
       const gfx::Size& coded_size,
       const gfx::Rect& visible_rect,
       const gfx::Size& natural_size,
+      const uint8_t* data,
+      size_t data_size,
+      base::TimeDelta timestamp);
+
+  static scoped_refptr<VideoFrame> WrapExternalData(
+      VideoPixelFormat format,
+      const gfx::Size& coded_size,
+      const gfx::Rect& visible_rect,
+      const gfx::Size& natural_size,
       base::span<const uint8_t> data,
       base::TimeDelta timestamp);
 
@@ -378,7 +387,7 @@
   // For use in contexts where the GPUMemoryBuffer has no SharedImage
   // associated with it.
   // NOTE: Clients who want to set a callback on the VideoFrame being destroyed
-  // should call SetReleaseMailboxAndGpuMemoryBufferCB() after creating the
+  // should call SetReleaseMailboxCB() after creating the
   // VideoFrame via this entrypoint.
   static scoped_refptr<VideoFrame> WrapExternalGpuMemoryBuffer(
       const gfx::Rect& visible_rect,
@@ -731,7 +740,7 @@
   int GetDmabufFd(size_t i) const;
 #endif  // BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
 
-  // Sets the mailbox (and GpuMemoryBuffer, if desired) release callback.
+  // Sets the mailbox release callback.
   //
   // The callback may be run from ANY THREAD, and so it is up to the client to
   // ensure thread safety.
@@ -739,8 +748,6 @@
   // WARNING: This method is not thread safe; it should only be called if you
   // are still the only owner of this VideoFrame.
   void SetReleaseMailboxCB(ReleaseMailboxCB release_mailbox_cb);
-  void SetReleaseMailboxAndGpuMemoryBufferCB(
-      ReleaseMailboxAndGpuMemoryBufferCB release_mailbox_cb);
 
   // Tests whether a mailbox release callback is configured.
   bool HasReleaseMailboxCB() const;
diff --git a/media/base/video_frame_converter.cc b/media/base/video_frame_converter.cc
index 5759bca5..f1dea1d1 100644
--- a/media/base/video_frame_converter.cc
+++ b/media/base/video_frame_converter.cc
@@ -34,7 +34,9 @@
     scoped_refptr<VideoFrame> tmp_frame) {
   return VideoFrame::WrapExternalData(
       override_format, tmp_frame->coded_size(), tmp_frame->visible_rect(),
-      tmp_frame->natural_size(), tmp_frame->data_span(VideoFrame::Plane::kARGB),
+      tmp_frame->natural_size(),
+      tmp_frame->writable_data(VideoFrame::Plane::kARGB),
+      VideoFrame::AllocationSize(override_format, tmp_frame->coded_size()),
       tmp_frame->timestamp());
 }
 
diff --git a/media/base/video_frame_unittest.cc b/media/base/video_frame_unittest.cc
index e79e697..6a1af98 100644
--- a/media/base/video_frame_unittest.cc
+++ b/media/base/video_frame_unittest.cc
@@ -486,14 +486,14 @@
 
 // Create a frame that wraps unowned memory.
 TEST(VideoFrame, WrapExternalData) {
-  std::array<uint8_t, 2 * 256 * 256> memory{};
+  uint8_t memory[2 * 256 * 256];
   gfx::Size coded_size(256, 256);
   gfx::Rect visible_rect(coded_size);
-  CreateTestY16Frame(coded_size, visible_rect, memory.data());
+  CreateTestY16Frame(coded_size, visible_rect, memory);
   auto timestamp = base::Milliseconds(1);
-  auto frame =
-      VideoFrame::WrapExternalData(PIXEL_FORMAT_Y16, coded_size, visible_rect,
-                                   visible_rect.size(), memory, timestamp);
+  auto frame = VideoFrame::WrapExternalData(PIXEL_FORMAT_Y16, coded_size,
+                                            visible_rect, visible_rect.size(),
+                                            memory, sizeof(memory), timestamp);
 
   EXPECT_EQ(frame->coded_size(), coded_size);
   EXPECT_EQ(frame->visible_rect(), visible_rect);
@@ -513,7 +513,8 @@
   auto timestamp = base::Milliseconds(1);
   auto frame = VideoFrame::WrapExternalData(
       PIXEL_FORMAT_Y16, coded_size, visible_rect, visible_rect.size(),
-      mapped_region.mapping.GetMemoryAsSpan<uint8_t>(), timestamp);
+      mapped_region.mapping.GetMemoryAsSpan<uint8_t>().data(), kDataSize,
+      timestamp);
   EXPECT_EQ(frame->storage_type(), VideoFrame::STORAGE_UNOWNED_MEMORY);
 
   frame->BackWithSharedMemory(&mapped_region.region);
@@ -966,9 +967,9 @@
     pixels.resize(coded_size.GetArea() * 4);
 
     auto timestamp = base::Milliseconds(0);
-    auto frame =
-        VideoFrame::WrapExternalData(format, coded_size, visible_rect,
-                                     visible_rect.size(), pixels, timestamp);
+    auto frame = VideoFrame::WrapExternalData(
+        format, coded_size, visible_rect, visible_rect.size(), pixels.data(),
+        pixels.size(), timestamp);
 
     int plane_offset = 0;
     for (size_t plane = 0; plane < VideoFrame::NumPlanes(format); ++plane) {
diff --git a/media/cast/encoding/external_video_encoder.cc b/media/cast/encoding/external_video_encoder.cc
index 9d37ce5..b51aa567 100644
--- a/media/cast/encoding/external_video_encoder.cc
+++ b/media/cast/encoding/external_video_encoder.cc
@@ -285,8 +285,8 @@
           video_frame->format(), frame_coded_size_,
           gfx::Rect(video_frame->visible_rect().size()),
           video_frame->visible_rect().size(),
-          mapped_region.mapping.GetMemoryAsSpan<uint8_t>(),
-          video_frame->timestamp());
+          static_cast<uint8_t*>(mapped_region.mapping.memory()),
+          mapped_region.mapping.size(), video_frame->timestamp());
       if (!frame || !media::I420CopyWithPadding(*video_frame, frame.get())) {
         LOG(DFATAL) << "Error: ExternalVideoEncoder: copy failed.";
         AbortLatestEncodeAttemptDueToErrors();
diff --git a/media/gpu/test/local_gpu_memory_buffer_manager.cc b/media/gpu/test/local_gpu_memory_buffer_manager.cc
index c28afd1..9e2e96dde 100644
--- a/media/gpu/test/local_gpu_memory_buffer_manager.cc
+++ b/media/gpu/test/local_gpu_memory_buffer_manager.cc
@@ -105,7 +105,7 @@
 
 GpuMemoryBufferImplGbm::GpuMemoryBufferImplGbm(gfx::BufferFormat format,
                                                gbm_bo* buffer_object)
-    : format_(format), buffer_object_(buffer_object), mapped_(false) {
+    : buffer_object_(buffer_object), mapped_(false) {
   gfx::NativePixmapHandle native_pixmap_handle;
   for (size_t i = 0;
        i < static_cast<size_t>(gbm_bo_get_plane_count(buffer_object)); ++i) {
@@ -182,22 +182,10 @@
                    gbm_bo_get_height(buffer_object_));
 }
 
-gfx::BufferFormat GpuMemoryBufferImplGbm::GetFormat() const {
-  return format_;
-}
-
 int GpuMemoryBufferImplGbm::stride(size_t plane) const {
   return gbm_bo_get_stride_for_plane(buffer_object_, plane);
 }
 
-gfx::GpuMemoryBufferId GpuMemoryBufferImplGbm::GetId() const {
-  return handle_.id;
-}
-
-gfx::GpuMemoryBufferType GpuMemoryBufferImplGbm::GetType() const {
-  return gfx::NATIVE_PIXMAP;
-}
-
 gfx::GpuMemoryBufferHandle GpuMemoryBufferImplGbm::CloneHandle() const {
   DCHECK_EQ(handle_.type, gfx::NATIVE_PIXMAP);
   gfx::GpuMemoryBufferHandle handle(
@@ -206,17 +194,6 @@
   return handle;
 }
 
-void GpuMemoryBufferImplGbm::OnMemoryDump(
-    base::trace_event::ProcessMemoryDump* pmd,
-    const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid,
-    uint64_t tracing_process_id,
-    int importance) const {
-  auto shared_buffer_guid =
-      gfx::GetGenericSharedGpuMemoryGUIDForTracing(tracing_process_id, GetId());
-  pmd->CreateSharedGlobalAllocatorDump(shared_buffer_guid);
-  pmd->AddOwnershipEdge(buffer_dump_guid, shared_buffer_guid, importance);
-}
-
 LocalGpuMemoryBufferManager::LocalGpuMemoryBufferManager()
     : gbm_device_(CreateGbmDevice()) {}
 LocalGpuMemoryBufferManager::~LocalGpuMemoryBufferManager() = default;
diff --git a/media/gpu/test/local_gpu_memory_buffer_manager.h b/media/gpu/test/local_gpu_memory_buffer_manager.h
index 2333c53..98d62a2e 100644
--- a/media/gpu/test/local_gpu_memory_buffer_manager.h
+++ b/media/gpu/test/local_gpu_memory_buffer_manager.h
@@ -15,13 +15,6 @@
 #include "ui/gfx/gpu_memory_buffer_handle.h"
 #include "ui/gfx/linux/scoped_gbm_device.h"
 
-namespace base {
-namespace trace_event {
-class ProcessMemoryDump;
-class MemoryAllocatorDumpGuid;
-}  // namespace trace_event
-}  // namespace base
-
 namespace gfx {
 class GpuMemoryBuffer;
 struct NativePixmapHandle;
@@ -87,16 +80,8 @@
   void* memory(size_t plane);
   void Unmap();
   gfx::Size GetSize() const;
-  gfx::BufferFormat GetFormat() const;
   int stride(size_t plane) const;
-  gfx::GpuMemoryBufferId GetId() const;
-  gfx::GpuMemoryBufferType GetType() const;
   gfx::GpuMemoryBufferHandle CloneHandle() const;
-  void OnMemoryDump(
-      base::trace_event::ProcessMemoryDump* pmd,
-      const base::trace_event::MemoryAllocatorDumpGuid& buffer_dump_guid,
-      uint64_t tracing_process_id,
-      int importance) const;
 
  private:
   struct MappedPlane {
@@ -104,7 +89,6 @@
     raw_ptr<void> mapped_data;
   };
 
-  gfx::BufferFormat format_;
   raw_ptr<gbm_bo> buffer_object_;
   gfx::GpuMemoryBufferHandle handle_;
   bool mapped_;
diff --git a/media/gpu/windows/d3d12_video_encode_delegate.cc b/media/gpu/windows/d3d12_video_encode_delegate.cc
index 91c9007d..d807f5db 100644
--- a/media/gpu/windows/d3d12_video_encode_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_delegate.cc
@@ -509,6 +509,19 @@
 }
 
 template <size_t maxDpbSize>
+void D3D12VideoEncodeDecodedPictureBuffers<maxDpbSize>::EraseFrame(
+    size_t position) {
+  CHECK_LT(position, size());
+  base::span raw_resources_span =
+      base::span(raw_resources_).first(size()).subspan(position);
+  std::ranges::rotate(raw_resources_span,
+                      std::next(raw_resources_span.begin()));
+  base::span subresources_span =
+      base::span(subresources_).first(size()).subspan(position);
+  std::ranges::rotate(subresources_span, std::next(subresources_span.begin()));
+}
+
+template <size_t maxDpbSize>
 D3D12_VIDEO_ENCODE_REFERENCE_FRAMES D3D12VideoEncodeDecodedPictureBuffers<
     maxDpbSize>::ToD3D12VideoEncodeReferenceFrames() {
   return {
diff --git a/media/gpu/windows/d3d12_video_encode_delegate.h b/media/gpu/windows/d3d12_video_encode_delegate.h
index 817b1b0e..81a15c0d 100644
--- a/media/gpu/windows/d3d12_video_encode_delegate.h
+++ b/media/gpu/windows/d3d12_video_encode_delegate.h
@@ -202,11 +202,13 @@
   // Replace the picture buffer at |position| with the last picture buffer
   // returned by |GetCurrentFrame()|.
   void ReplaceWithCurrentFrame(size_t position);
+  // Move the picture buffer at |position| to |size() - 1|. And move the
+  // picture buffers with index greater than |position| to the previous index.
+  void EraseFrame(size_t position);
 
   // Return the |D3D12_VIDEO_ENCODE_REFERENCE_FRAMES| structure that D3D12 video
   // encode API expects.
-  virtual D3D12_VIDEO_ENCODE_REFERENCE_FRAMES
-  ToD3D12VideoEncodeReferenceFrames();
+  D3D12_VIDEO_ENCODE_REFERENCE_FRAMES ToD3D12VideoEncodeReferenceFrames();
 
  private:
   size_t size_ = 0;
diff --git a/media/gpu/windows/d3d12_video_encode_h264_delegate.cc b/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
index adddcaf..94f3fb3 100644
--- a/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
@@ -74,23 +74,22 @@
 D3D12VideoEncodeH264ReferenceFrameManager::
     ~D3D12VideoEncodeH264ReferenceFrameManager() = default;
 
-void D3D12VideoEncodeH264ReferenceFrameManager::EndFrame(
-    uint32_t frame_num,
-    uint32_t pic_order_cnt,
-    uint32_t temporal_layer_id) {
-  InsertCurrentFrame(0);
-  if (descriptors_.size() == size()) {
-    descriptors_.pop_back();
+uint32_t
+D3D12VideoEncodeH264ReferenceFrameManager::GetMaxLongTermFrameIndexPlus1()
+    const {
+  return max_long_term_frame_index_plus1_;
+}
+
+std::optional<uint32_t>
+D3D12VideoEncodeH264ReferenceFrameManager::GetLongTermReferenceFrameResourceId(
+    uint32_t long_term_frame_index) const {
+  for (const auto& descriptor : descriptors_) {
+    if (descriptor.IsLongTermReference &&
+        descriptor.LongTermPictureIdx == long_term_frame_index) {
+      return descriptor.ReconstructedPictureResourceIndex;
+    }
   }
-  descriptors_.insert(descriptors_.begin(),
-                      {
-                          .PictureOrderCountNumber = pic_order_cnt,
-                          .FrameDecodingOrderNumber = frame_num,
-                          .TemporalLayerIndex = temporal_layer_id,
-                      });
-  for (size_t i = 0; i < descriptors_.size(); i++) {
-    descriptors_[i].ReconstructedPictureResourceIndex = i;
-  }
+  return std::nullopt;
 }
 
 base::span<D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264>
@@ -98,6 +97,102 @@
   return descriptors_;
 }
 
+void D3D12VideoEncodeH264ReferenceFrameManager::
+    ProcessMemoryManagementControlOperation(
+        const D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264& pic_params) {
+  CHECK(pic_params.adaptive_ref_pic_marking_mode_flag);
+  if (pic_params.FrameType == D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME) {
+    max_long_term_frame_index_plus1_ = 1;
+    SetCurrentFrameLongTermReference(pic_params.FrameDecodingOrderNumber,
+                                     pic_params.PictureOrderCountNumber, 0);
+  } else {
+    // SAFETY: Callers should guarantee that |pRefPicMarkingOperationsCommands|
+    // contains at least |RefPicMarkingOperationsCommandsCount| elements.
+    for (auto& operation : UNSAFE_BUFFERS(
+             base::span(pic_params.pRefPicMarkingOperationsCommands,
+                        pic_params.RefPicMarkingOperationsCommandsCount))) {
+      // Table 7-9 – Memory management control operation
+      // (memory_management_control_operation) values
+      switch (operation.memory_management_control_operation) {
+        case 0:
+          // 0 End memory_management_control_operation syntax element loop
+          return;
+        case 2: {
+          // 2 Mark a long-term reference picture as "unused for reference"
+          auto resource_id =
+              GetLongTermReferenceFrameResourceId(operation.long_term_pic_num);
+          CHECK_LT(resource_id.value(), size());
+          EraseFrame(resource_id.value());
+          descriptors_.erase(
+              std::next(descriptors_.begin(), resource_id.value()));
+          for (size_t i = resource_id.value(); i < descriptors_.size(); i++) {
+            descriptors_[i].ReconstructedPictureResourceIndex = i;
+          }
+          break;
+        }
+        case 4:
+          // 4 Specify the maximum long-term frame index and mark all long-term
+          // reference pictures having long-term frame indices greater than the
+          // maximum value as "unused for reference"
+          CHECK_LE(operation.max_long_term_frame_idx_plus1, size());
+          max_long_term_frame_index_plus1_ =
+              operation.max_long_term_frame_idx_plus1;
+          break;
+        case 5:
+          // 5 Mark all reference pictures as "unused for reference" and set the
+          // MaxLongTermFrameIdx variable to "no long-term frame indices"
+          descriptors_.clear();
+          max_long_term_frame_index_plus1_ = 0;
+          break;
+        case 6:
+          // 6 Mark the current picture as "used for long-term reference" and
+          // assign a long-term frame index to it
+          CHECK_LT(operation.long_term_frame_idx,
+                   max_long_term_frame_index_plus1_);
+          SetCurrentFrameLongTermReference(pic_params.FrameDecodingOrderNumber,
+                                           pic_params.PictureOrderCountNumber,
+                                           operation.long_term_frame_idx);
+          break;
+        default:
+          // memory_management_control_operation being 1 and 3 is not used.
+          // 1 Mark a short-term reference picture as "unused for reference"
+          // 3 Mark a short-term reference picture as "used for long-term
+          // reference" and assign a long-term frame index to it
+          NOTREACHED();
+      }
+    }
+    NOTREACHED() << "RefPicMarkingOperations must end with "
+                    "memory_management_control_operation = 0";
+  }
+}
+
+void D3D12VideoEncodeH264ReferenceFrameManager::
+    SetCurrentFrameLongTermReference(uint32_t frame_num,
+                                     uint32_t pic_order_cnt,
+                                     uint32_t long_term_frame_index) {
+  CHECK_LT(long_term_frame_index, size());
+  for (auto& descriptor : descriptors_) {
+    if (descriptor.IsLongTermReference &&
+        descriptor.LongTermPictureIdx == long_term_frame_index) {
+      ReplaceWithCurrentFrame(descriptor.ReconstructedPictureResourceIndex);
+      descriptor.FrameDecodingOrderNumber = frame_num;
+      descriptor.PictureOrderCountNumber = pic_order_cnt;
+      return;
+    }
+  }
+
+  CHECK_LT(descriptors_.size(), size());
+  InsertCurrentFrame(descriptors_.size());
+  descriptors_.push_back({
+      .ReconstructedPictureResourceIndex =
+          static_cast<UINT>(descriptors_.size()),
+      .IsLongTermReference = true,
+      .LongTermPictureIdx = long_term_frame_index,
+      .PictureOrderCountNumber = pic_order_cnt,
+      .FrameDecodingOrderNumber = frame_num,
+  });
+}
+
 // static
 std::vector<std::pair<VideoCodecProfile, std::vector<VideoPixelFormat>>>
 D3D12VideoEncodeH264Delegate::GetSupportedProfiles(
@@ -148,6 +243,7 @@
   // start with 0.
   pic_params_.idr_pic_id = -1;
   pic_params_.FrameDecodingOrderNumber = -1;
+  pic_params_.adaptive_ref_pic_marking_mode_flag = 1;
   input_arguments_.SequenceControlDesc.CodecGopSequence = {
       .DataSize = sizeof(gop_structure_),
       .pH264GroupOfPictures = &gop_structure_,
@@ -217,11 +313,51 @@
   // https://github.com/microsoft/DirectX-Specs/blob/master/d3d/D3D12VideoEncoding.md#6120-struct-d3d12_video_encoder_input_arguments
 
   // Frame type, idr_pic_id, decoding order number, and reference frames.
-  if (++pic_params_.FrameDecodingOrderNumber == gop_structure_.GOPLength) {
+  if (++pic_params_.FrameDecodingOrderNumber == gop_structure_.GOPLength ||
+      options.key_frame) {
     pic_params_.FrameDecodingOrderNumber = 0;
   }
-  bool is_keyframe =
-      pic_params_.FrameDecodingOrderNumber == 0 || options.key_frame;
+  pic_params_.PictureOrderCountNumber =
+      pic_params_.FrameDecodingOrderNumber * 2;
+  bool is_keyframe = pic_params_.FrameDecodingOrderNumber == 0;
+
+  // TODO(crbug.com/40275246): Support multiple temporal layers.
+  absl::InlinedVector<uint8_t, 4> reference_buffers;
+  std::optional<uint8_t> update_buffer;
+  std::optional<uint8_t> destroy_buffer;
+  if (!is_keyframe) {
+    reference_buffers.push_back(0);
+  }
+  update_buffer = 0;
+  if (destroy_buffer.value_or(0) > 0) {
+    destroy_buffer = destroy_buffer.value() + 1;
+  }
+
+  if (update_buffer.has_value() &&
+      update_buffer.value() >= max_num_ref_frames_) {
+    return {EncoderStatus::Codes::kBadReferenceBuffer,
+            base::StringPrintf("Update buffer index %d is out of range [0, %d)",
+                               update_buffer.value(), max_num_ref_frames_)};
+  }
+  if (destroy_buffer.has_value() &&
+      !reference_frame_manager_.GetLongTermReferenceFrameResourceId(
+          destroy_buffer.value())) {
+    return {EncoderStatus::Codes::kBadReferenceBuffer,
+            base::StringPrintf("Destroy buffer index %d is not found",
+                               destroy_buffer.value())};
+  }
+
+  // at most 5 operations: 4 operations for each reference buffer, 1 operation
+  // for ending op-0.
+  absl::InlinedVector<
+      D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_REFERENCE_PICTURE_LIST_MODIFICATION_OPERATION,
+      5>
+      reordering_flags;
+  // at most 3 operations: op-4, op-6, op-0
+  absl::InlinedVector<
+      D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_REFERENCE_PICTURE_MARKING_OPERATION,
+      3>
+      mmco;
   if (is_keyframe) {
     H264SPS sps = ToSPS();
     H264PPS pps = ToPPS(sps);
@@ -232,15 +368,43 @@
     input_arguments_.PictureControlDesc.ReferenceFrames = {};
     pic_params_.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME;
     ++pic_params_.idr_pic_id;
-    pic_params_.FrameDecodingOrderNumber = 0;
     pic_params_.ReferenceFramesReconPictureDescriptorsCount = 0;
     pic_params_.pReferenceFramesReconPictureDescriptors = nullptr;
     pic_params_.List0ReferenceFramesCount = 0;
     pic_params_.pList0ReferenceFrames = nullptr;
+    pic_params_.List0RefPicModificationsCount = 0;
+    pic_params_.pList0RefPicModifications = nullptr;
+    // Alternatively, if encoding an IDR frame and setting
+    // adaptive_ref_pic_marking_mode_flag = 1, the driver will assume that the
+    // client is attempting to set the H264 slice header
+    // long_term_reference_flag and will do so in the output bitstream for the
+    // EncodeFrame call.
+    // https://learn.microsoft.com/en-us/windows/win32/api/d3d12video/ns-d3d12video-d3d12_video_encoder_picture_control_codec_data_h264_reference_picture_marking_operation#remarks
   } else {
     pic_params_.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME;
-    list0_reference_frames_[0] = 0;
-    pic_params_.List0ReferenceFramesCount = 1;
+    for (size_t i = 0; i < reference_buffers.size(); i++) {
+      std::optional<uint32_t> descriptor_index =
+          reference_frame_manager_.GetLongTermReferenceFrameResourceId(
+              reference_buffers[i]);
+      if (!descriptor_index.has_value()) {
+        return {EncoderStatus::Codes::kBadReferenceBuffer,
+                base::StringPrintf(
+                    "Long term reference frame index %d is not found",
+                    reference_buffers[i])};
+      }
+      reordering_flags.push_back({.modification_of_pic_nums_idc = 2,
+                                  .long_term_pic_num = reference_buffers[i]});
+      list0_reference_frames_[i] = descriptor_index.value();
+    }
+    if (!reordering_flags.empty()) {
+      reordering_flags.push_back({.modification_of_pic_nums_idc = 3});
+      pic_params_.List0RefPicModificationsCount = reordering_flags.size();
+      pic_params_.pList0RefPicModifications = reordering_flags.data();
+    } else {
+      pic_params_.List0RefPicModificationsCount = 0;
+      pic_params_.pList0RefPicModifications = nullptr;
+    }
+    pic_params_.List0ReferenceFramesCount = reference_buffers.size();
     pic_params_.pList0ReferenceFrames = list0_reference_frames_.data();
     base::span<D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264>
         descriptors = reference_frame_manager_.ToReferencePictureDescriptors();
@@ -252,8 +416,24 @@
     input_arguments_.PictureControlDesc.ReferenceFrames.NumTexture2Ds =
         descriptors.size();
   }
-  pic_params_.PictureOrderCountNumber =
-      pic_params_.FrameDecodingOrderNumber * 2;
+  if (destroy_buffer.has_value()) {
+    mmco.push_back({.memory_management_control_operation = 2,
+                    .long_term_pic_num = destroy_buffer.value()});
+  }
+  if (update_buffer.has_value()) {
+    if (update_buffer.value() >=
+        reference_frame_manager_.GetMaxLongTermFrameIndexPlus1()) {
+      mmco.push_back({.memory_management_control_operation = 4,
+                      .max_long_term_frame_idx_plus1 =
+                          static_cast<UINT>(update_buffer.value()) + 1});
+    }
+    mmco.push_back({.memory_management_control_operation = 6,
+                    .long_term_frame_idx = update_buffer.value()});
+  }
+  mmco.push_back({.memory_management_control_operation = 0});
+  // The adaptive_ref_pic_marking_mode_flag has been set in the constructor.
+  pic_params_.pRefPicMarkingOperationsCommands = mmco.data();
+  pic_params_.RefPicMarkingOperationsCommandsCount = mmco.size();
 
   // Rate control.
   int qp = -1;
@@ -290,25 +470,28 @@
   }
 
   // Input and output textures.
-  input_arguments_.PictureControlDesc.Flags =
-      D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE;
   input_arguments_.pInputFrame = input_frame;
   input_arguments_.InputFrameSubresource = input_frame_subresource;
-  D3D12PictureBuffer reconstructed_picture =
-      reference_frame_manager_.GetCurrentFrame();
-  EncoderStatus result = video_encoder_wrapper_->Encode(
-      input_arguments_,
-      {
-          .pReconstructedPicture = reconstructed_picture.resource_,
-          .ReconstructedPictureSubresource = reconstructed_picture.subresource_,
-      });
+  D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE output_arguments{};
+  if (update_buffer) {
+    input_arguments_.PictureControlDesc.Flags =
+        D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE;
+    D3D12PictureBuffer reconstructed_picture =
+        reference_frame_manager_.GetCurrentFrame();
+    output_arguments.pReconstructedPicture = reconstructed_picture.resource_;
+    output_arguments.ReconstructedPictureSubresource =
+        reconstructed_picture.subresource_;
+  } else {
+    input_arguments_.PictureControlDesc.Flags =
+        D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_NONE;
+  }
+  EncoderStatus result =
+      video_encoder_wrapper_->Encode(input_arguments_, output_arguments);
   if (!result.is_ok()) {
     return result;
   }
 
-  reference_frame_manager_.EndFrame(pic_params_.FrameDecodingOrderNumber,
-                                    pic_params_.PictureOrderCountNumber,
-                                    pic_params_.TemporalLayerIndex);
+  reference_frame_manager_.ProcessMemoryManagementControlOperation(pic_params_);
 
   metadata_.key_frame = is_keyframe;
   metadata_.qp = qp;
diff --git a/media/gpu/windows/d3d12_video_encode_h264_delegate.h b/media/gpu/windows/d3d12_video_encode_h264_delegate.h
index 8427704..4f356f9 100644
--- a/media/gpu/windows/d3d12_video_encode_h264_delegate.h
+++ b/media/gpu/windows/d3d12_video_encode_h264_delegate.h
@@ -26,26 +26,37 @@
 
 namespace media {
 
-class D3D12VideoEncodeH264ReferenceFrameManager
+class MEDIA_GPU_EXPORT D3D12VideoEncodeH264ReferenceFrameManager
     : public D3D12VideoEncodeDecodedPictureBuffers<H264DPB::kDPBMaxSize> {
  public:
   D3D12VideoEncodeH264ReferenceFrameManager();
   ~D3D12VideoEncodeH264ReferenceFrameManager() override;
 
-  void EndFrame(uint32_t frame_num,
-                uint32_t pic_order_cnt,
-                uint32_t temporal_layer_id);
+  uint32_t GetMaxLongTermFrameIndexPlus1() const;
+
+  // Get the index in the descriptors and picture buffers of the frame with
+  // |long_term_frame_index|.
+  std::optional<uint32_t> GetLongTermReferenceFrameResourceId(
+      uint32_t long_term_frame_index) const;
 
   base::span<D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264>
   ToReferencePictureDescriptors();
 
+  void ProcessMemoryManagementControlOperation(
+      const D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264& pic_params);
+
  private:
   using D3D12VideoEncodeDecodedPictureBuffers::InsertCurrentFrame;
   using D3D12VideoEncodeDecodedPictureBuffers::ReplaceWithCurrentFrame;
 
+  void SetCurrentFrameLongTermReference(uint32_t frame_num,
+                                        uint32_t pic_order_cnt,
+                                        uint32_t long_term_frame_index);
+
   absl::InlinedVector<D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264,
                       H264DPB::kDPBMaxSize>
       descriptors_;
+  uint32_t max_long_term_frame_index_plus1_ = 0;
 };
 
 class MEDIA_GPU_EXPORT D3D12VideoEncodeH264Delegate
diff --git a/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc b/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
index 1011f045..8831904 100644
--- a/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
+++ b/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
@@ -19,6 +19,13 @@
 
 namespace media {
 
+class D3D12VideoEncodeH264ReferenceFrameManagerTest : public ::testing::Test {
+ protected:
+  void SetUp() override { device_ = MakeComPtr<NiceMock<D3D12DeviceMock>>(); }
+
+  Microsoft::WRL::ComPtr<D3D12DeviceMock> device_;
+};
+
 class D3D12VideoEncodeH264DelegateTest
     : public D3D12VideoEncodeDelegateTestBase {
  protected:
@@ -154,6 +161,118 @@
   Microsoft::WRL::ComPtr<D3D12VideoDevice3Mock> video_device3_;
 };
 
+TEST_F(D3D12VideoEncodeH264ReferenceFrameManagerTest,
+       ProcessMemoryManagementControlOperation) {
+  // Initialization
+  D3D12VideoEncodeH264ReferenceFrameManager reference_manager;
+  ASSERT_TRUE(reference_manager.InitializeTextureArray(
+      device_.Get(), {1280, 720}, DXGI_FORMAT_NV12, 4));
+  EXPECT_EQ(reference_manager.GetMaxLongTermFrameIndexPlus1(), 0u);
+  EXPECT_EQ(reference_manager.GetLongTermReferenceFrameResourceId(0),
+            std::nullopt);
+  EXPECT_EQ(reference_manager.ToReferencePictureDescriptors().size(), 0u);
+
+  // IDR frame #0 with adaptive_ref_pic_marking_mode_flag = true
+  D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 pic_params0{
+      .FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME,
+      .PictureOrderCountNumber = 0,
+      .FrameDecodingOrderNumber = 0,
+      .adaptive_ref_pic_marking_mode_flag = true,
+  };
+  reference_manager.ProcessMemoryManagementControlOperation(pic_params0);
+  EXPECT_EQ(reference_manager.GetMaxLongTermFrameIndexPlus1(), 1u);
+  EXPECT_NE(reference_manager.GetLongTermReferenceFrameResourceId(0),
+            std::nullopt);
+  base::span<D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264>
+      descriptors = reference_manager.ToReferencePictureDescriptors();
+  ASSERT_EQ(descriptors.size(), 1u);
+  EXPECT_TRUE(descriptors[0].IsLongTermReference);
+  EXPECT_EQ(descriptors[0].LongTermPictureIdx, 0u);
+  EXPECT_EQ(descriptors[0].ReconstructedPictureResourceIndex, 0u);
+  EXPECT_EQ(descriptors[0].PictureOrderCountNumber,
+            pic_params0.PictureOrderCountNumber);
+  EXPECT_EQ(descriptors[0].FrameDecodingOrderNumber,
+            pic_params0.FrameDecodingOrderNumber);
+
+  // P frame #1 with mmco 4 and 6
+  D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_REFERENCE_PICTURE_MARKING_OPERATION
+  mmco1[] = {
+      {.memory_management_control_operation = 4,
+       .max_long_term_frame_idx_plus1 = 2},
+      {.memory_management_control_operation = 6, .long_term_frame_idx = 1},
+      {.memory_management_control_operation = 0},
+  };
+  D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 pic_params1{
+      .FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME,
+      .PictureOrderCountNumber = 2,
+      .FrameDecodingOrderNumber = 1,
+      .adaptive_ref_pic_marking_mode_flag = true,
+      .RefPicMarkingOperationsCommandsCount = std::size(mmco1),
+      .pRefPicMarkingOperationsCommands = mmco1,
+  };
+  reference_manager.ProcessMemoryManagementControlOperation(pic_params1);
+  EXPECT_EQ(reference_manager.GetMaxLongTermFrameIndexPlus1(), 2u);
+  EXPECT_NE(reference_manager.GetLongTermReferenceFrameResourceId(1),
+            std::nullopt);
+  descriptors = reference_manager.ToReferencePictureDescriptors();
+  ASSERT_EQ(descriptors.size(), 2u);
+  EXPECT_TRUE(descriptors[1].IsLongTermReference);
+  EXPECT_EQ(descriptors[1].LongTermPictureIdx, 1u);
+  EXPECT_EQ(descriptors[1].ReconstructedPictureResourceIndex, 1u);
+  EXPECT_EQ(descriptors[1].PictureOrderCountNumber,
+            pic_params1.PictureOrderCountNumber);
+  EXPECT_EQ(descriptors[1].FrameDecodingOrderNumber,
+            pic_params1.FrameDecodingOrderNumber);
+
+  // P frame #2 with mmco 2
+  D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_REFERENCE_PICTURE_MARKING_OPERATION
+  mmco2[] = {
+      {.memory_management_control_operation = 2, .long_term_pic_num = 0},
+      {.memory_management_control_operation = 0},
+  };
+  D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 pic_params2{
+      .FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME,
+      .PictureOrderCountNumber = 4,
+      .FrameDecodingOrderNumber = 2,
+      .adaptive_ref_pic_marking_mode_flag = true,
+      .RefPicMarkingOperationsCommandsCount = std::size(mmco2),
+      .pRefPicMarkingOperationsCommands = mmco2,
+  };
+  reference_manager.ProcessMemoryManagementControlOperation(pic_params2);
+  EXPECT_EQ(reference_manager.GetMaxLongTermFrameIndexPlus1(), 2u);
+  EXPECT_EQ(reference_manager.GetLongTermReferenceFrameResourceId(0),
+            std::nullopt);
+  descriptors = reference_manager.ToReferencePictureDescriptors();
+  ASSERT_EQ(descriptors.size(), 1u);
+  EXPECT_EQ(descriptors[0].LongTermPictureIdx, 1u);
+  EXPECT_EQ(descriptors[0].ReconstructedPictureResourceIndex, 0u);
+  EXPECT_EQ(descriptors[0].PictureOrderCountNumber,
+            pic_params1.PictureOrderCountNumber);
+  EXPECT_EQ(descriptors[0].FrameDecodingOrderNumber,
+            pic_params1.FrameDecodingOrderNumber);
+
+  // P frame #3 with mmco 5
+  D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_REFERENCE_PICTURE_MARKING_OPERATION
+  mmco3[] = {
+      {.memory_management_control_operation = 5},
+      {.memory_management_control_operation = 0},
+  };
+  D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 pic_params3{
+      .FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME,
+      .PictureOrderCountNumber = 6,
+      .FrameDecodingOrderNumber = 3,
+      .adaptive_ref_pic_marking_mode_flag = true,
+      .RefPicMarkingOperationsCommandsCount = std::size(mmco3),
+      .pRefPicMarkingOperationsCommands = mmco3,
+  };
+  reference_manager.ProcessMemoryManagementControlOperation(pic_params3);
+  EXPECT_EQ(reference_manager.GetMaxLongTermFrameIndexPlus1(), 0u);
+  EXPECT_EQ(reference_manager.GetLongTermReferenceFrameResourceId(1),
+            std::nullopt);
+  descriptors = reference_manager.ToReferencePictureDescriptors();
+  EXPECT_EQ(descriptors.size(), 0u);
+}
+
 TEST_F(D3D12VideoEncodeH264DelegateTest, UnsupportedCodec) {
   ON_CALL(*video_device3_.Get(),
           CheckFeatureSupport(D3D12_FEATURE_VIDEO_ENCODER_CODEC, _, _))
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
index 53dbec53..f79244b 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
@@ -60,20 +60,52 @@
 D3D12VideoEncodeH265ReferenceFrameManager::
     ~D3D12VideoEncodeH265ReferenceFrameManager() = default;
 
-void D3D12VideoEncodeH265ReferenceFrameManager::EndFrame(
-    uint32_t pic_order_count,
-    uint32_t temporal_layer_id) {
-  InsertCurrentFrame(0);
-  if (descriptors_.size() == size()) {
-    descriptors_.pop_back();
+std::optional<uint32_t>
+D3D12VideoEncodeH265ReferenceFrameManager::GetReferenceFrameId(
+    uint8_t buffer_id) const {
+  for (size_t i = 0; i < buffer_ids_.size(); i++) {
+    if (buffer_ids_[i] == buffer_id) {
+      return i;
+    }
   }
-  descriptors_.insert(descriptors_.begin(),
-                      {
-                          .PictureOrderCountNumber = pic_order_count,
-                          .TemporalLayerIndex = temporal_layer_id,
-                      });
+  return std::nullopt;
+}
+
+void D3D12VideoEncodeH265ReferenceFrameManager::MarkCurrentFrameReferenced(
+    uint32_t pic_order_count,
+    uint8_t buffer_id,
+    bool long_term_reference) {
+  CHECK_LT(buffer_id, size());
   for (size_t i = 0; i < descriptors_.size(); i++) {
-    descriptors_[i].ReconstructedPictureResourceIndex = i;
+    if (buffer_ids_[i] == buffer_id) {
+      ReplaceWithCurrentFrame(
+          descriptors_[i].ReconstructedPictureResourceIndex);
+      descriptors_[i].PictureOrderCountNumber = pic_order_count;
+      descriptors_[i].IsLongTermReference = long_term_reference;
+      return;
+    }
+  }
+
+  CHECK_LT(descriptors_.size(), size());
+  InsertCurrentFrame(descriptors_.size());
+  descriptors_.push_back({
+      .ReconstructedPictureResourceIndex =
+          static_cast<UINT>(descriptors_.size()),
+      .IsLongTermReference = long_term_reference,
+      .PictureOrderCountNumber = pic_order_count,
+  });
+  buffer_ids_.push_back(buffer_id);
+}
+
+void D3D12VideoEncodeH265ReferenceFrameManager::MarkFrameUnreferenced(
+    uint8_t buffer_id) {
+  for (size_t i = 0; i < descriptors_.size(); i++) {
+    if (buffer_ids_[i] == buffer_id) {
+      EraseFrame(descriptors_[i].ReconstructedPictureResourceIndex);
+      descriptors_.erase(std::next(descriptors_.begin(), i));
+      buffer_ids_.erase(std::next(buffer_ids_.begin(), i));
+      return;
+    }
   }
 }
 
@@ -211,11 +243,32 @@
   // Filling the |input_arguments_| according to
   // https://github.com/microsoft/DirectX-Specs/blob/master/d3d/D3D12VideoEncoding.md#6120-struct-d3d12_video_encoder_input_arguments
 
-  if (++pic_params_.PictureOrderCountNumber == gop_structure_.GOPLength) {
+  if (++pic_params_.PictureOrderCountNumber == gop_structure_.GOPLength ||
+      options.key_frame) {
     pic_params_.PictureOrderCountNumber = 0;
   }
-  bool is_keyframe =
-      pic_params_.PictureOrderCountNumber == 0 || options.key_frame;
+  bool is_keyframe = pic_params_.PictureOrderCountNumber == 0;
+
+  absl::InlinedVector<uint8_t, 4> reference_buffers;
+  std::optional<uint8_t> update_buffer;
+  std::optional<uint8_t> destroy_buffer;
+  if (!is_keyframe) {
+    reference_buffers.push_back(0);
+  }
+  update_buffer = 0;
+  if (update_buffer.has_value() &&
+      update_buffer.value() >= max_num_ref_frames_) {
+    return {EncoderStatus::Codes::kBadReferenceBuffer,
+            base::StringPrintf("Update buffer index %d is out of range [0, %d)",
+                               update_buffer.value(), max_num_ref_frames_)};
+  }
+  if (destroy_buffer.has_value() &&
+      !reference_frame_manager_.GetReferenceFrameId(destroy_buffer.value())) {
+    return {EncoderStatus::Codes::kBadReferenceBuffer,
+            base::StringPrintf("Destroy buffer index %d is not found",
+                               destroy_buffer.value())};
+  }
+
   if (is_keyframe) {
     H265VPS vps = ToVPS();
     H265SPS sps = ToSPS(vps);
@@ -227,19 +280,29 @@
 
     input_arguments_.PictureControlDesc.ReferenceFrames = {};
     pic_params_.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_HEVC_IDR_FRAME;
-    pic_params_.PictureOrderCountNumber = 0;
     pic_params_.ReferenceFramesReconPictureDescriptorsCount = 0;
     pic_params_.pReferenceFramesReconPictureDescriptors = nullptr;
     pic_params_.List0ReferenceFramesCount = 0;
     pic_params_.pList0ReferenceFrames = nullptr;
   } else {
     pic_params_.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_HEVC_P_FRAME;
-    list0_reference_frames_[0] = 0;
-    pic_params_.List0ReferenceFramesCount = 1;
+    CHECK_LE(reference_buffers.size(), list0_reference_frames_.size());
+    for (size_t i = 0; i < reference_buffers.size(); i++) {
+      std::optional<uint32_t> descriptor_index =
+          reference_frame_manager_.GetReferenceFrameId(reference_buffers[i]);
+      if (!descriptor_index.has_value()) {
+        return {EncoderStatus::Codes::kBadReferenceBuffer,
+                base::StringPrintf("Reference buffer #%d is never updated",
+                                   reference_buffers[i])};
+      }
+      list0_reference_frames_[i] = descriptor_index.value();
+    }
+    pic_params_.List0ReferenceFramesCount = reference_buffers.size();
     pic_params_.pList0ReferenceFrames = list0_reference_frames_.data();
     reference_frame_manager_
         .WriteReferencePictureDescriptorsToPictureParameters(
-            &pic_params_, base::span(list0_reference_frames_).first(1u));
+            &pic_params_, base::span(list0_reference_frames_)
+                              .first(reference_buffers.size()));
     input_arguments_.PictureControlDesc.ReferenceFrames =
         reference_frame_manager_.ToD3D12VideoEncodeReferenceFrames();
     input_arguments_.PictureControlDesc.ReferenceFrames.NumTexture2Ds =
@@ -280,24 +343,35 @@
         current_rate_control_.GetD3D12VideoEncoderRateControl();
   }
 
-  input_arguments_.PictureControlDesc.Flags =
-      D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE;
   input_arguments_.pInputFrame = input_frame;
   input_arguments_.InputFrameSubresource = input_frame_subresource;
-  D3D12PictureBuffer reconstructed_picture =
-      reference_frame_manager_.GetCurrentFrame();
-  EncoderStatus result = video_encoder_wrapper_->Encode(
-      input_arguments_,
-      {
-          .pReconstructedPicture = reconstructed_picture.resource_,
-          .ReconstructedPictureSubresource = reconstructed_picture.subresource_,
-      });
+  D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE output_arguments{};
+  if (update_buffer) {
+    input_arguments_.PictureControlDesc.Flags =
+        D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE;
+    D3D12PictureBuffer reconstructed_picture =
+        reference_frame_manager_.GetCurrentFrame();
+    output_arguments.pReconstructedPicture = reconstructed_picture.resource_;
+    output_arguments.ReconstructedPictureSubresource =
+        reconstructed_picture.subresource_;
+  } else {
+    input_arguments_.PictureControlDesc.Flags =
+        D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_NONE;
+  }
+
+  EncoderStatus result =
+      video_encoder_wrapper_->Encode(input_arguments_, output_arguments);
   if (!result.is_ok()) {
     return result;
   }
 
-  reference_frame_manager_.EndFrame(pic_params_.PictureOrderCountNumber,
-                                    pic_params_.TemporalLayerIndex);
+  if (destroy_buffer.has_value()) {
+    reference_frame_manager_.MarkFrameUnreferenced(destroy_buffer.value());
+  }
+  if (update_buffer.has_value()) {
+    reference_frame_manager_.MarkCurrentFrameReferenced(
+        pic_params_.PictureOrderCountNumber, update_buffer.value(), false);
+  }
 
   metadata_.key_frame = is_keyframe;
   metadata_.qp = qp;
@@ -369,6 +443,7 @@
   }
   codec_config_hevc_ = {
       .ConfigurationFlags =
+          D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_ENABLE_LONG_TERM_REFERENCES |
           (codec_config_support_hevc.SupportFlags &
                    D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_SUPPORT_HEVC_FLAG_SAO_FILTER_SUPPORT
                ? D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_HEVC_FLAG_ENABLE_SAO_FILTER
@@ -415,7 +490,7 @@
           D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME,
       .ResolutionsListCount = 1,
       .pResolutionList = &input_size_,
-      .MaxReferenceFramesInDPB = static_cast<UINT>(max_num_ref_frames_),
+      .MaxReferenceFramesInDPB = max_num_ref_frames_,
       .SuggestedProfile = {.DataSize = sizeof(suggested_profile),
                            .pHEVCProfile = &suggested_profile},
       .SuggestedLevel = {.DataSize = sizeof(suggested_level),
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate.h b/media/gpu/windows/d3d12_video_encode_h265_delegate.h
index 1ec3eca..8c7fe78b 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate.h
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate.h
@@ -25,13 +25,20 @@
 
 namespace media {
 
-class D3D12VideoEncodeH265ReferenceFrameManager
+class MEDIA_GPU_EXPORT D3D12VideoEncodeH265ReferenceFrameManager
     : public D3D12VideoEncodeDecodedPictureBuffers<kMaxDpbSize> {
  public:
   D3D12VideoEncodeH265ReferenceFrameManager();
   ~D3D12VideoEncodeH265ReferenceFrameManager() override;
 
-  void EndFrame(uint32_t pic_order_count, uint32_t temporal_layer_id);
+  // Get the index in the descriptors of the frame with |picture_order_count|.
+  std::optional<uint32_t> GetReferenceFrameId(uint8_t buffer_id) const;
+
+  void MarkCurrentFrameReferenced(uint32_t pic_order_count,
+                                  uint8_t buffer_id,
+                                  bool long_term_reference);
+
+  void MarkFrameUnreferenced(uint8_t buffer_id);
 
   // Write the reference picture descriptors to |pic_params| according to the
   // ListxReferenceFrames variables.
@@ -46,6 +53,7 @@
   absl::InlinedVector<D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_HEVC,
                       kMaxDpbSize>
       descriptors_;
+  absl::InlinedVector<uint8_t, kMaxDpbSize> buffer_ids_;
 };
 
 class MEDIA_GPU_EXPORT D3D12VideoEncodeH265Delegate
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc b/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
index 5244ad3..70b8377 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
@@ -19,6 +19,13 @@
 
 namespace media {
 
+class D3D12VideoEncodeH265ReferenceFrameManagerTest : public ::testing::Test {
+ protected:
+  void SetUp() override { device_ = MakeComPtr<NiceMock<D3D12DeviceMock>>(); }
+
+  Microsoft::WRL::ComPtr<D3D12DeviceMock> device_;
+};
+
 class D3D12VideoEncodeH265DelegateTest
     : public D3D12VideoEncodeDelegateTestBase {
  protected:
@@ -191,6 +198,77 @@
   Microsoft::WRL::ComPtr<D3D12VideoDevice3Mock> video_device3_;
 };
 
+TEST_F(D3D12VideoEncodeH265ReferenceFrameManagerTest,
+       MarkReferenceFrameAndCheckDescriptors) {
+  D3D12VideoEncodeH265ReferenceFrameManager reference_manager;
+  ASSERT_TRUE(reference_manager.InitializeTextureArray(
+      device_.Get(), {1280, 720}, DXGI_FORMAT_NV12, 4));
+  EXPECT_EQ(reference_manager.GetReferenceFrameId(0), std::nullopt);
+
+  std::vector<uint32_t> list0_reference_frames;
+  D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_HEVC pic_params{};
+  reference_manager.WriteReferencePictureDescriptorsToPictureParameters(
+      &pic_params, list0_reference_frames);
+  EXPECT_EQ(pic_params.ReferenceFramesReconPictureDescriptorsCount, 0u);
+
+  // Mark frame #0 as short-term reference #0.
+  reference_manager.MarkCurrentFrameReferenced(0, 0, false);
+  EXPECT_EQ(reference_manager.GetReferenceFrameId(0), 0u);
+  list0_reference_frames = {0};
+  pic_params.List0ReferenceFramesCount = list0_reference_frames.size();
+  pic_params.pList0ReferenceFrames = list0_reference_frames.data();
+  reference_manager.WriteReferencePictureDescriptorsToPictureParameters(
+      &pic_params, list0_reference_frames);
+  ASSERT_EQ(pic_params.ReferenceFramesReconPictureDescriptorsCount, 1u);
+  // SAFETY: |pReferenceFramesReconPictureDescriptors| is guaranteed to have
+  // |ReferenceFramesReconPictureDescriptorsCount| elements.
+  base::span<const D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_HEVC>
+      descriptors = UNSAFE_BUFFERS(
+          base::span(pic_params.pReferenceFramesReconPictureDescriptors,
+                     pic_params.ReferenceFramesReconPictureDescriptorsCount));
+  EXPECT_EQ(descriptors[0].IsRefUsedByCurrentPic, true);
+  EXPECT_EQ(descriptors[0].IsLongTermReference, false);
+  EXPECT_EQ(descriptors[0].PictureOrderCountNumber, 0u);
+
+  // Mark frame #1 as long-term reference #2.
+  reference_manager.MarkCurrentFrameReferenced(1, 2, true);
+  EXPECT_EQ(reference_manager.GetReferenceFrameId(2), 1u);
+  list0_reference_frames = {1};
+  pic_params.List0ReferenceFramesCount = list0_reference_frames.size();
+  pic_params.pList0ReferenceFrames = list0_reference_frames.data();
+  reference_manager.WriteReferencePictureDescriptorsToPictureParameters(
+      &pic_params, list0_reference_frames);
+  ASSERT_EQ(pic_params.ReferenceFramesReconPictureDescriptorsCount, 2u);
+  // SAFETY: |pReferenceFramesReconPictureDescriptors| is guaranteed to have
+  // |ReferenceFramesReconPictureDescriptorsCount| elements.
+  descriptors = UNSAFE_BUFFERS(
+      base::span(pic_params.pReferenceFramesReconPictureDescriptors,
+                 pic_params.ReferenceFramesReconPictureDescriptorsCount));
+  EXPECT_EQ(descriptors[0].IsRefUsedByCurrentPic, false);
+  EXPECT_EQ(descriptors[1].IsRefUsedByCurrentPic, true);
+  EXPECT_EQ(descriptors[1].IsLongTermReference, true);
+  EXPECT_EQ(descriptors[1].PictureOrderCountNumber, 1u);
+
+  // Mark frame #0 as not referenced.
+  reference_manager.MarkFrameUnreferenced(0);
+  EXPECT_EQ(reference_manager.GetReferenceFrameId(0), std::nullopt);
+  EXPECT_EQ(reference_manager.GetReferenceFrameId(2), 0u);
+  list0_reference_frames = {0};
+  pic_params.List0ReferenceFramesCount = list0_reference_frames.size();
+  pic_params.pList0ReferenceFrames = list0_reference_frames.data();
+  reference_manager.WriteReferencePictureDescriptorsToPictureParameters(
+      &pic_params, list0_reference_frames);
+  ASSERT_EQ(pic_params.ReferenceFramesReconPictureDescriptorsCount, 1u);
+  // SAFETY: |pReferenceFramesReconPictureDescriptors| is guaranteed to have
+  // |ReferenceFramesReconPictureDescriptorsCount| elements.
+  descriptors = UNSAFE_BUFFERS(
+      base::span(pic_params.pReferenceFramesReconPictureDescriptors,
+                 pic_params.ReferenceFramesReconPictureDescriptorsCount));
+  EXPECT_EQ(descriptors[0].IsRefUsedByCurrentPic, true);
+  EXPECT_EQ(descriptors[0].IsLongTermReference, true);
+  EXPECT_EQ(descriptors[0].PictureOrderCountNumber, 1u);
+}
+
 TEST_F(D3D12VideoEncodeH265DelegateTest, UnsupportedCodec) {
   ON_CALL(*video_device3_.Get(),
           CheckFeatureSupport(D3D12_FEATURE_VIDEO_ENCODER_CODEC, _, _))
diff --git a/media/gpu/windows/mf_video_processor_accelerator_unittest.cc b/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
index 58ccf02..a4a6be4 100644
--- a/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
+++ b/media/gpu/windows/mf_video_processor_accelerator_unittest.cc
@@ -524,7 +524,8 @@
   auto timestamp = base::Milliseconds(0);
   auto frame = VideoFrame::WrapExternalData(
       VideoPixelFormat::PIXEL_FORMAT_XRGB, {kWidth, kHeight},
-      gfx::Rect(0, 0, kWidth, kHeight), {kWidth, kHeight}, image, timestamp);
+      gfx::Rect(0, 0, kWidth, kHeight), {kWidth, kHeight}, image.data(),
+      image.size(), timestamp);
 
   Microsoft::WRL::ComPtr<IMFSample> sample;
   ASSERT_HRESULT_SUCCEEDED(video_processor->Convert(frame, &sample));
diff --git a/media/mojo/clients/mojo_video_encode_accelerator_unittest.cc b/media/mojo/clients/mojo_video_encode_accelerator_unittest.cc
index fc05fd0..beb226d2 100644
--- a/media/mojo/clients/mojo_video_encode_accelerator_unittest.cc
+++ b/media/mojo/clients/mojo_video_encode_accelerator_unittest.cc
@@ -286,8 +286,8 @@
     ASSERT_TRUE(shmem.IsValid());
     const scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
         PIXEL_FORMAT_I420, kInputVisibleSize, gfx::Rect(kInputVisibleSize),
-        kInputVisibleSize, shmem.mapping.GetMemoryAsSpan<uint8_t>(),
-        base::TimeDelta());
+        kInputVisibleSize, static_cast<uint8_t*>(shmem.mapping.memory()),
+        shmem.mapping.size(), base::TimeDelta());
     video_frame->BackWithSharedMemory(&shmem.region);
     const bool is_keyframe = true;
 
diff --git a/media/mojo/test/mojo_video_encode_accelerator_integration_test.cc b/media/mojo/test/mojo_video_encode_accelerator_integration_test.cc
index ae74ed0..2e89fd9b 100644
--- a/media/mojo/test/mojo_video_encode_accelerator_integration_test.cc
+++ b/media/mojo/test/mojo_video_encode_accelerator_integration_test.cc
@@ -290,8 +290,8 @@
     ASSERT_TRUE(shmem.IsValid());
     const scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
         PIXEL_FORMAT_I420, kInputVisibleSize, gfx::Rect(kInputVisibleSize),
-        kInputVisibleSize, shmem.mapping.GetMemoryAsSpan<uint8_t>(),
-        base::TimeDelta());
+        kInputVisibleSize, static_cast<uint8_t*>(shmem.mapping.memory()),
+        shmem.mapping.size(), base::TimeDelta());
     video_frame->BackWithSharedMemory(&shmem.region);
     const bool is_keyframe = true;
 
@@ -335,7 +335,8 @@
     const scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
         PIXEL_FORMAT_I420, kInvalidInputVisibleSize,
         gfx::Rect(kInvalidInputVisibleSize), kInvalidInputVisibleSize,
-        shmem.mapping.GetMemoryAsSpan<uint8_t>(), base::TimeDelta());
+        static_cast<uint8_t*>(shmem.mapping.memory()), shmem.mapping.size(),
+        base::TimeDelta());
     video_frame->BackWithSharedMemory(&shmem.region);
     const bool is_keyframe = true;
 
diff --git a/media/renderers/paint_canvas_video_renderer_unittest.cc b/media/renderers/paint_canvas_video_renderer_unittest.cc
index 46e11f7..f0f7029 100644
--- a/media/renderers/paint_canvas_video_renderer_unittest.cc
+++ b/media/renderers/paint_canvas_video_renderer_unittest.cc
@@ -80,7 +80,7 @@
 
   return media::VideoFrame::WrapExternalData(
       media::PIXEL_FORMAT_Y16, coded_size, visible_rect, visible_rect.size(),
-      base::span(static_cast<uint8_t*>(external_memory), byte_size), timestamp);
+      static_cast<uint8_t*>(external_memory), byte_size, timestamp);
 }
 
 // Readback the contents of a RGBA texture into an array of RGBA values.
@@ -987,7 +987,7 @@
 
   auto video_frame = media::VideoFrame::WrapExternalData(
       media::PIXEL_FORMAT_Y16, coded_size, gfx::Rect(visible_size),
-      visible_size, memory, base::Milliseconds(4));
+      visible_size, &memory[0], fWidth * fHeight * 2, base::Milliseconds(4));
 
   cc::PaintFlags flags;
   PaintCanvasVideoRenderer::PaintParams params;
diff --git a/media/renderers/video_resource_updater_unittest.cc b/media/renderers/video_resource_updater_unittest.cc
index 4d5a3e7..0ec5f8b 100644
--- a/media/renderers/video_resource_updater_unittest.cc
+++ b/media/renderers/video_resource_updater_unittest.cc
@@ -96,9 +96,9 @@
   scoped_refptr<VideoFrame> CreateTestYUVVideoFrame(
       const gfx::Size& size = gfx::Size(10, 10)) {
     constexpr int kMaxDimension = 100;
-    static std::array<uint8_t, kMaxDimension * kMaxDimension> y_data{};
-    static std::array<uint8_t, kMaxDimension * kMaxDimension / 2> u_data{};
-    static std::array<uint8_t, kMaxDimension * kMaxDimension / 2> v_data{};
+    static uint8_t y_data[kMaxDimension * kMaxDimension] = {};
+    static uint8_t u_data[kMaxDimension * kMaxDimension / 2] = {};
+    static uint8_t v_data[kMaxDimension * kMaxDimension / 2] = {};
 
     CHECK_LE(size.width() * size.height(), kMaxDimension * kMaxDimension);
 
@@ -118,17 +118,43 @@
     return video_frame;
   }
 
+  scoped_refptr<VideoFrame> CreateWonkyTestYUVVideoFrame() {
+    const int kDimension = 10;
+    const int kYWidth = kDimension + 5;
+    const int kUWidth = (kYWidth + 1) / 2 + 200;
+    const int kVWidth = (kYWidth + 1) / 2 + 1;
+    static uint8_t y_data[kYWidth * kDimension] = {};
+    static uint8_t u_data[kUWidth * kDimension] = {};
+    static uint8_t v_data[kVWidth * kDimension] = {};
+
+    scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalYuvData(
+        PIXEL_FORMAT_I422,                        // format
+        gfx::Size(kYWidth, kDimension),           // coded_size
+        gfx::Rect(2, 0, kDimension, kDimension),  // visible_rect
+        gfx::Size(kDimension, kDimension),        // natural_size
+        -kYWidth,                                 // y_stride (negative)
+        kUWidth,                                  // u_stride
+        kVWidth,                                  // v_stride
+        y_data + kYWidth * (kDimension - 1),      // y_data
+        u_data,                                   // u_data
+        v_data,                                   // v_data
+        base::TimeDelta());                       // timestamp
+    EXPECT_TRUE(video_frame);
+    return video_frame;
+  }
+
   scoped_refptr<VideoFrame> CreateTestRGBVideoFrame(VideoPixelFormat format) {
     constexpr int kMaxDimension = 10;
     constexpr gfx::Size kSize = gfx::Size(kMaxDimension, kMaxDimension);
-    static std::array<uint8_t, 4 * kMaxDimension * kMaxDimension> rgb_data{};
-    scoped_refptr<VideoFrame> video_frame =
-        VideoFrame::WrapExternalData(format,              // format
-                                     kSize,               // coded_size
-                                     gfx::Rect(kSize),    // visible_rect
-                                     kSize,               // natural_size
-                                     rgb_data,            // data,
-                                     base::TimeDelta());  // timestamp
+    static uint32_t rgb_data[kMaxDimension * kMaxDimension] = {};
+    scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
+        format,                                // format
+        kSize,                                 // coded_size
+        gfx::Rect(kSize),                      // visible_rect
+        kSize,                                 // natural_size
+        reinterpret_cast<uint8_t*>(rgb_data),  // data,
+        sizeof(rgb_data),                      // data_size
+        base::TimeDelta());                    // timestamp
     EXPECT_TRUE(video_frame);
     return video_frame;
   }
@@ -138,23 +164,23 @@
     constexpr int kMaxDimension = 5;
     constexpr gfx::Size kSize = gfx::Size(kMaxDimension, kMaxDimension);
     constexpr gfx::Rect kVisibleRect = gfx::Rect(2, 1, 3, 3);
-#define PIX 0xFF, 0xFF, 0xFF, 0xFF
-    static std::array<uint8_t, 4 * kMaxDimension * kMaxDimension> rgb_data{
-        0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,  //
-        0, 0, 0, 0, 0, 0, 0, 0, PIX, PIX, PIX,                             //
-        0, 0, 0, 0, 0, 0, 0, 0, PIX, PIX, PIX,                             //
-        0, 0, 0, 0, 0, 0, 0, 0, PIX, PIX, PIX,                             //
-        0, 0, 0, 0, 0, 0, 0, 0, 0,   0,   0,   0, 0, 0, 0, 0, 0, 0, 0, 0,  //
+    constexpr uint32_t kPix = 0xFFFFFFFF;
+    static uint32_t rgb_data[kMaxDimension * kMaxDimension] = {
+        0x00, 0x00, 0x00, 0x00, 0x00,  //
+        0x00, 0x00, kPix, kPix, kPix,  //
+        0x00, 0x00, kPix, kPix, kPix,  //
+        0x00, 0x00, kPix, kPix, kPix,  //
+        0x00, 0x00, 0x00, 0x00, 0x00,  //
     };
-#undef PIX
 
-    scoped_refptr<VideoFrame> video_frame =
-        VideoFrame::WrapExternalData(format,               // format
-                                     kSize,                // coded_size
-                                     kVisibleRect,         // visible_rect
-                                     kVisibleRect.size(),  // natural_size
-                                     rgb_data,             // data,
-                                     base::TimeDelta());   // timestamp
+    scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
+        format,                                // format
+        kSize,                                 // coded_size
+        kVisibleRect,                          // visible_rect
+        kVisibleRect.size(),                   // natural_size
+        reinterpret_cast<uint8_t*>(rgb_data),  // data,
+        sizeof(rgb_data),                      // data_size
+        base::TimeDelta());                    // timestamp
     EXPECT_TRUE(video_frame);
     return video_frame;
   }
@@ -163,21 +189,23 @@
     constexpr int kMaxDimension = 5;
     constexpr gfx::Size kSize = gfx::Size(kMaxDimension, kMaxDimension);
     constexpr gfx::Rect kVisibleRect = gfx::Rect(2, 1, 3, 3);
-    static std::array<uint8_t, 2 * kMaxDimension * kMaxDimension> y16_data = {
-        0, 0, 0, 0, 0,    0,    0,    0,    0,    0,
-        0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-        0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-        0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
-        0, 0, 0, 0, 0,    0,    0,    0,    0,    0,
+    constexpr uint16_t kPix = 0xFFFF;
+    static uint16_t y16_data[kMaxDimension * kMaxDimension] = {
+        0x00, 0x00, 0x00, 0x00, 0x00,  //
+        0x00, 0x00, kPix, kPix, kPix,  //
+        0x00, 0x00, kPix, kPix, kPix,  //
+        0x00, 0x00, kPix, kPix, kPix,  //
+        0x00, 0x00, 0x00, 0x00, 0x00,  //
     };
 
-    scoped_refptr<VideoFrame> video_frame =
-        VideoFrame::WrapExternalData(PIXEL_FORMAT_Y16,
-                                     kSize,                // coded_size
-                                     kVisibleRect,         // visible_rect
-                                     kVisibleRect.size(),  // natural_size
-                                     y16_data,             // data,
-                                     base::TimeDelta());   // timestamp
+    scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData(
+        PIXEL_FORMAT_Y16,
+        kSize,                                 // coded_size
+        kVisibleRect,                          // visible_rect
+        kVisibleRect.size(),                   // natural_size
+        reinterpret_cast<uint8_t*>(y16_data),  // data,
+        sizeof(y16_data),                      // data_size
+        base::TimeDelta());                    // timestamp
     EXPECT_TRUE(video_frame);
     return video_frame;
   }
diff --git a/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc b/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
index 7c90eca..08835b285 100644
--- a/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
+++ b/media/video/renderable_gpu_memory_buffer_video_frame_pool.cc
@@ -114,15 +114,8 @@
 
   // Callback made when the VideoFrame is destroyed. This callback then either
   // returns |frame_resources| to |available_frame_resources_| or destroys it.
-  // TODO(crbug.com/40263579): Remove |gpu_memory_buffer| from this method once
-  // VideoFrame and all its clients are fully converted to use MappableSI
-  // instead of GpuMemoryBuffer. Currently for this client, VideoFrame runs
-  // this callback with null |gpu_memory_buffer| always as this client uses
-  // MappableSI.
-  void OnVideoFrameDestroyed(
-      std::unique_ptr<FrameResources> frame_resources,
-      const gpu::SyncToken& sync_token,
-      std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer);
+  void OnVideoFrameDestroyed(std::unique_ptr<FrameResources> frame_resources,
+                             const gpu::SyncToken& sync_token);
 
   const VideoPixelFormat format_;
   const std::unique_ptr<RenderableGpuMemoryBufferVideoFramePool::Context>
@@ -334,23 +327,18 @@
     return nullptr;
   }
 
-  // Set the ReleaseMailboxAndGpuMemoryBufferCB to return the GpuMemoryBuffer to
-  // the FrameResources, and return the FrameResources to the available pool. Do
-  // this on the calling thread.
+  // Set the ReleaseMailboxCB to return the FrameResources to the available
+  // pool. Do this on the calling thread.
   auto callback = base::BindOnce(&InternalRefCountedPool::OnVideoFrameDestroyed,
                                  this, std::move(frame_resources));
-  video_frame->SetReleaseMailboxAndGpuMemoryBufferCB(
+  video_frame->SetReleaseMailboxCB(
       base::BindPostTaskToCurrentDefault(std::move(callback), FROM_HERE));
   return video_frame;
 }
 
 void InternalRefCountedPool::OnVideoFrameDestroyed(
     std::unique_ptr<FrameResources> frame_resources,
-    const gpu::SyncToken& sync_token,
-    std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer) {
-  // |gpu_memory_buffer| returned here by VideoFrame should always be null
-  // since we use MappableSI.
-  CHECK(!gpu_memory_buffer);
+    const gpu::SyncToken& sync_token) {
   frame_resources->SetSharedImageReleaseSyncToken(sync_token);
 
   if (shutting_down_) {
diff --git a/media/video/video_encode_accelerator_adapter.cc b/media/video/video_encode_accelerator_adapter.cc
index f4ba7a1..7802b67 100644
--- a/media/video/video_encode_accelerator_adapter.cc
+++ b/media/video/video_encode_accelerator_adapter.cc
@@ -37,6 +37,7 @@
 #endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)
 #include "media/video/gpu_video_accelerator_factories.h"
 #include "media/video/video_encoder_info.h"
+#include "ui/gfx/gpu_memory_buffer.h"
 
 namespace media {
 
@@ -1055,7 +1056,8 @@
                               : src_frame;
   auto shared_frame = VideoFrame::WrapExternalData(
       PIXEL_FORMAT_I420, dest_coded_size, dest_visible_rect,
-      dest_visible_rect.size(), *mapping, src_frame->timestamp());
+      dest_visible_rect.size(), static_cast<const uint8_t*>(mapping->memory()),
+      mapping->size(), src_frame->timestamp());
 
   if (!shared_frame || !mapped_src_frame)
     return EncoderStatus(EncoderStatus::Codes::kSystemAPICallError);
diff --git a/net/proxy_resolution/proxy_config_service_linux.cc b/net/proxy_resolution/proxy_config_service_linux.cc
index 792a75e..6fb562a1 100644
--- a/net/proxy_resolution/proxy_config_service_linux.cc
+++ b/net/proxy_resolution/proxy_config_service_linux.cc
@@ -1243,6 +1243,7 @@
     case base::nix::DESKTOP_ENVIRONMENT_PANTHEON:
     case base::nix::DESKTOP_ENVIRONMENT_UKUI:
     case base::nix::DESKTOP_ENVIRONMENT_UNITY:
+    case base::nix::DESKTOP_ENVIRONMENT_COSMIC:
 #if defined(USE_GIO)
     {
       auto gs_getter = std::make_unique<SettingGetterImplGSettings>();
diff --git a/net/socket/client_socket_pool_manager.h b/net/socket/client_socket_pool_manager.h
index 058a28c03..8198ee2 100644
--- a/net/socket/client_socket_pool_manager.h
+++ b/net/socket/client_socket_pool_manager.h
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 //
 // ClientSocketPoolManager manages access to all ClientSocketPools.  It's a
-// simple container for all of them.  Most importantly, it handles the lifetime
+// simple container for all of them. Most importantly, it handles the lifetime
 // and destruction order properly.
 
 #ifndef NET_SOCKET_CLIENT_SOCKET_POOL_MANAGER_H_
@@ -76,9 +76,7 @@
 // A helper method that uses the passed in proxy information to initialize a
 // ClientSocketHandle with the relevant socket pool. Use this method for
 // HTTP/HTTPS requests. `allowed_bad_certs` is only used if the request
-// uses SSL. `resolution_callback` will be invoked after the the hostname is
-// resolved. If `resolution_callback` does not return OK, then the connection
-// will be aborted with that value.
+// uses SSL.
 int InitSocketHandleForHttpRequest(
     url::SchemeHostPort endpoint,
     int request_load_flags,
@@ -98,11 +96,8 @@
 
 // A helper method that uses the passed in proxy information to initialize a
 // ClientSocketHandle with the relevant socket pool. Use this method for
-// HTTP/HTTPS requests for WebSocket handshake.
-// `ssl_config_for_origin` is only used if the request uses SSL.
-// `resolution_callback` will be invoked after the the hostname is resolved. If
-// `resolution_callback` does not return OK, then the connection will be aborted
-// with that value. This function uses WEBSOCKET_SOCKET_POOL socket pools.
+// HTTP/HTTPS requests for WebSocket handshake. This function uses
+// WEBSOCKET_SOCKET_POOL socket pools.
 int InitSocketHandleForWebSocketRequest(
     url::SchemeHostPort endpoint,
     int request_load_flags,
diff --git a/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc b/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc
index 209bd71..b673ffe 100644
--- a/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc
+++ b/sandbox/policy/linux/sandbox_seccomp_bpf_linux.cc
@@ -242,8 +242,6 @@
       return std::make_unique<TtsProcessPolicy>();
     case sandbox::mojom::Sandbox::kNearby:
       return std::make_unique<NearbyProcessPolicy>();
-    case sandbox::mojom::Sandbox::kShapeDetection:
-      return std::make_unique<UtilityProcessPolicy>();
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
     case sandbox::mojom::Sandbox::kLibassistant:
       return std::make_unique<LibassistantProcessPolicy>();
@@ -302,7 +300,6 @@
     case sandbox::mojom::Sandbox::kIme:
     case sandbox::mojom::Sandbox::kTts:
     case sandbox::mojom::Sandbox::kNearby:
-    case sandbox::mojom::Sandbox::kShapeDetection:
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
     case sandbox::mojom::Sandbox::kLibassistant:
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
diff --git a/sandbox/policy/mojom/sandbox.mojom b/sandbox/policy/mojom/sandbox.mojom
index 59d1555b..8d0c29b 100644
--- a/sandbox/policy/mojom/sandbox.mojom
+++ b/sandbox/policy/mojom/sandbox.mojom
@@ -153,7 +153,4 @@
   // On Windows the kService sandbox type is used.
   // TODO(crbug.com/340778819): Implement sandboxing on other platforms.
   [EnableIf=is_linux|is_mac] kOnDeviceTranslation,
-
-  // Like kUtility but allows loading of the shape detection internal library.
-  [EnableIf=is_chromeos] kShapeDetection,
 };
diff --git a/sandbox/policy/sandbox_type.cc b/sandbox/policy/sandbox_type.cc
index 882462b..59155ee6 100644
--- a/sandbox/policy/sandbox_type.cc
+++ b/sandbox/policy/sandbox_type.cc
@@ -83,7 +83,6 @@
 constexpr char kImeSandbox[] = "ime";
 constexpr char kTtsSandbox[] = "tts";
 constexpr char kNearbySandbox[] = "nearby";
-constexpr char kShapeDetectionSandbox[] = "shape_detection";
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
 constexpr char kLibassistantSandbox[] = "libassistant";
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
@@ -172,7 +171,6 @@
     case Sandbox::kIme:
     case Sandbox::kTts:
     case Sandbox::kNearby:
-    case Sandbox::kShapeDetection:
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
     case Sandbox::kLibassistant:
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
@@ -337,8 +335,6 @@
       return kTtsSandbox;
     case Sandbox::kNearby:
       return kNearbySandbox;
-    case Sandbox::kShapeDetection:
-      return kShapeDetectionSandbox;
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
     case Sandbox::kLibassistant:
       return kLibassistantSandbox;
@@ -468,9 +464,6 @@
   if (sandbox_string == kNearbySandbox) {
     return Sandbox::kNearby;
   }
-  if (sandbox_string == kShapeDetectionSandbox) {
-    return Sandbox::kShapeDetection;
-  }
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
   if (sandbox_string == kLibassistantSandbox) {
     return Sandbox::kLibassistant;
diff --git a/services/device/public/mojom/BUILD.gn b/services/device/public/mojom/BUILD.gn
index 707c08a..346959e57 100644
--- a/services/device/public/mojom/BUILD.gn
+++ b/services/device/public/mojom/BUILD.gn
@@ -59,11 +59,8 @@
     ]
   }
 
-  if (is_ios) {
-    enabled_features = []
-    if (is_ios && target_platform == "iphoneos") {
-      enabled_features += [ "is_ios_iphoneos" ]
-    }
+  if (is_ios && target_platform == "iphoneos") {
+    enabled_features = [ "is_ios_iphoneos" ]
   }
 
   cpp_typemaps = [
diff --git a/services/shape_detection/BUILD.gn b/services/shape_detection/BUILD.gn
index b2ee546..294c94c 100644
--- a/services/shape_detection/BUILD.gn
+++ b/services/shape_detection/BUILD.gn
@@ -2,13 +2,10 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//services/shape_detection/features.gni")
 import("//testing/test.gni")
 import("//third_party/jni_zero/jni_zero.gni")
 
-source_set("api") {
-  public = [ "chrome_shape_detection_api.h" ]
-}
+use_barhopper = is_chrome_branded && is_chromeos
 
 source_set("lib") {
   sources = [
@@ -24,12 +21,6 @@
     "//ui/gfx/geometry",
   ]
 
-  public_deps = [
-    "//base",
-    "//media/capture",
-    "//services/shape_detection/public/mojom",
-  ]
-
   if (is_mac) {
     sources += [
       "detection_utils_mac.h",
@@ -80,18 +71,14 @@
     ]
   } else if (is_android) {
     # No C++ sources needed, barcode detection is provided by Java.
-  } else if (build_with_internal_shape_detection) {
+  } else if (use_barhopper) {
     sources += [
       "barcode_detection_impl_chrome.cc",
       "barcode_detection_impl_chrome.h",
       "barcode_detection_provider_chrome.cc",
       "barcode_detection_provider_chrome.h",
-      "shape_detection_library_holder.cc",
-      "shape_detection_library_holder.h",
     ]
-    public_deps += [ ":api" ]
-    data_deps =
-        [ "//services/shape_detection/internal:shape_detection_internal" ]
+    deps += [ "//third_party/barhopper" ]
   } else {
     # Otherwise, use a stub implementation.
     sources += [
@@ -102,14 +89,14 @@
 
   configs += [ "//build/config/compiler:wexit_time_destructors" ]
 
+  public_deps = [
+    "//base",
+    "//media/capture",
+    "//services/shape_detection/public/mojom",
+  ]
+
   if (is_android) {
     deps += [ ":shape_detection_jni_headers" ]
-  } else if (is_chromeos) {
-    sources += [
-      "shape_detection_sandbox_hook.cc",
-      "shape_detection_sandbox_hook.h",
-    ]
-    deps += [ "//sandbox/policy" ]
   }
 }
 
@@ -175,7 +162,7 @@
     ]
   }
 
-  if (build_with_internal_shape_detection) {
+  if (use_barhopper) {
     sources += [ "barcode_detection_impl_chrome_unittest.cc" ]
   }
 
diff --git a/services/shape_detection/DEPS b/services/shape_detection/DEPS
index bac6e29..205187d 100644
--- a/services/shape_detection/DEPS
+++ b/services/shape_detection/DEPS
@@ -4,10 +4,5 @@
   "+ui/gfx/codec",
   "+ui/gfx/geometry/rect_f.h",
   "+ui/gl/gl_switches.h",
+  "+third_party/barhopper/barhopper",
 ]
-
-specific_include_rules = {
-  "shape_detection_sandbox_hook\.[cc|h]": [
-    "+sandbox/policy/linux",
-  ],
-}
diff --git a/services/shape_detection/OWNERS b/services/shape_detection/OWNERS
index 6728fdf..47d63bc 100644
--- a/services/shape_detection/OWNERS
+++ b/services/shape_detection/OWNERS
@@ -1,3 +1 @@
 file://third_party/blink/renderer/modules/shapedetection/OWNERS
-
-per-file shape_detection_sandbox_hook.*=file://sandbox/linux/OWNERS
diff --git a/services/shape_detection/barcode_detection_impl_chrome.cc b/services/shape_detection/barcode_detection_impl_chrome.cc
index c02f4f7b..d21015b 100644
--- a/services/shape_detection/barcode_detection_impl_chrome.cc
+++ b/services/shape_detection/barcode_detection_impl_chrome.cc
@@ -10,11 +10,10 @@
 #include <memory>
 #include <vector>
 
-#include "base/containers/span.h"
 #include "base/logging.h"
 #include "base/numerics/checked_math.h"
 #include "services/shape_detection/public/mojom/barcodedetection.mojom-shared.h"
-#include "services/shape_detection/shape_detection_library_holder.h"
+#include "third_party/barhopper/barhopper/barcode.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect_f.h"
 
@@ -23,7 +22,7 @@
 namespace {
 
 gfx::RectF CornerPointsToBoundingBox(
-    base::span<const ChromePointF>& corner_points) {
+    std::vector<barhopper::Point>& corner_points) {
   float xmin = std::numeric_limits<float>::infinity();
   float ymin = std::numeric_limits<float>::infinity();
   float xmax = -std::numeric_limits<float>::infinity();
@@ -37,115 +36,116 @@
   return gfx::RectF(xmin, ymin, (xmax - xmin), (ymax - ymin));
 }
 
-mojom::BarcodeFormat BarcodeFormatToMojo(ChromeBarcodeFormat format) {
+mojom::BarcodeFormat BarhopperFormatToMojo(barhopper::BarcodeFormat format) {
   switch (format) {
-    case CHROME_BARCODE_FORMAT_UNKNOWN:
-      return mojom::BarcodeFormat::UNKNOWN;
-    case CHROME_BARCODE_FORMAT_AZTEC:
+    case barhopper::BarcodeFormat::AZTEC:
       return mojom::BarcodeFormat::AZTEC;
-    case CHROME_BARCODE_FORMAT_CODE_128:
+    case barhopper::BarcodeFormat::CODE_128:
       return mojom::BarcodeFormat::CODE_128;
-    case CHROME_BARCODE_FORMAT_CODE_39:
+    case barhopper::BarcodeFormat::CODE_39:
       return mojom::BarcodeFormat::CODE_39;
-    case CHROME_BARCODE_FORMAT_CODE_93:
+    case barhopper::BarcodeFormat::CODE_93:
       return mojom::BarcodeFormat::CODE_93;
-    case CHROME_BARCODE_FORMAT_CODABAR:
+    case barhopper::BarcodeFormat::CODABAR:
       return mojom::BarcodeFormat::CODABAR;
-    case CHROME_BARCODE_FORMAT_DATA_MATRIX:
+    case barhopper::BarcodeFormat::DATA_MATRIX:
       return mojom::BarcodeFormat::DATA_MATRIX;
-    case CHROME_BARCODE_FORMAT_EAN_13:
+    case barhopper::BarcodeFormat::EAN_13:
       return mojom::BarcodeFormat::EAN_13;
-    case CHROME_BARCODE_FORMAT_EAN_8:
+    case barhopper::BarcodeFormat::EAN_8:
       return mojom::BarcodeFormat::EAN_8;
-    case CHROME_BARCODE_FORMAT_ITF:
+    case barhopper::BarcodeFormat::ITF:
       return mojom::BarcodeFormat::ITF;
-    case CHROME_BARCODE_FORMAT_PDF417:
+    case barhopper::BarcodeFormat::PDF417:
       return mojom::BarcodeFormat::PDF417;
-    case CHROME_BARCODE_FORMAT_QR_CODE:
+    case barhopper::BarcodeFormat::QR_CODE:
       return mojom::BarcodeFormat::QR_CODE;
-    case CHROME_BARCODE_FORMAT_UPC_A:
+    case barhopper::BarcodeFormat::UPC_A:
       return mojom::BarcodeFormat::UPC_A;
-    case CHROME_BARCODE_FORMAT_UPC_E:
+    case barhopper::BarcodeFormat::UPC_E:
       return mojom::BarcodeFormat::UPC_E;
+    case barhopper::BarcodeFormat::UNRECOGNIZED:
+      return mojom::BarcodeFormat::UNKNOWN;
     default:
       NOTREACHED() << "Invalid barcode format";
   }
 }
 
-ChromeBarcodeFormat GetExpectedFormats(
+barhopper::RecognitionOptions GetRecognitionOptions(
     const shape_detection::mojom::BarcodeDetectorOptionsPtr& options) {
-  ChromeBarcodeFormat expected_formats = CHROME_BARCODE_FORMAT_UNKNOWN;
+  barhopper::RecognitionOptions recognition_options;
   if (options->formats.empty()) {
-    expected_formats =
-        CHROME_BARCODE_FORMAT_AZTEC | CHROME_BARCODE_FORMAT_CODE_128 |
-        CHROME_BARCODE_FORMAT_CODE_39 | CHROME_BARCODE_FORMAT_CODE_93 |
-        CHROME_BARCODE_FORMAT_CODABAR | CHROME_BARCODE_FORMAT_DATA_MATRIX |
-        CHROME_BARCODE_FORMAT_EAN_13 | CHROME_BARCODE_FORMAT_EAN_8 |
-        CHROME_BARCODE_FORMAT_ITF | CHROME_BARCODE_FORMAT_PDF417 |
-        CHROME_BARCODE_FORMAT_QR_CODE | CHROME_BARCODE_FORMAT_UPC_A |
-        CHROME_BARCODE_FORMAT_UPC_E;
-    return expected_formats;
+    recognition_options.barcode_formats =
+        barhopper::BarcodeFormat::AZTEC | barhopper::BarcodeFormat::CODE_128 |
+        barhopper::BarcodeFormat::CODE_39 | barhopper::BarcodeFormat::CODE_93 |
+        barhopper::BarcodeFormat::CODABAR |
+        barhopper::BarcodeFormat::DATA_MATRIX |
+        barhopper::BarcodeFormat::EAN_13 | barhopper::BarcodeFormat::EAN_8 |
+        barhopper::BarcodeFormat::ITF | barhopper::BarcodeFormat::PDF417 |
+        barhopper::BarcodeFormat::QR_CODE | barhopper::BarcodeFormat::UPC_A |
+        barhopper::BarcodeFormat::UPC_E;
+    return recognition_options;
   }
 
+  int recognition_formats = 0;
   for (const auto& format : options->formats) {
     switch (format) {
       case mojom::BarcodeFormat::AZTEC:
-        expected_formats |= CHROME_BARCODE_FORMAT_AZTEC;
+        recognition_formats |= barhopper::BarcodeFormat::AZTEC;
         break;
       case mojom::BarcodeFormat::CODE_128:
-        expected_formats |= CHROME_BARCODE_FORMAT_CODE_128;
+        recognition_formats |= barhopper::BarcodeFormat::CODE_128;
         break;
       case mojom::BarcodeFormat::CODE_39:
-        expected_formats |= CHROME_BARCODE_FORMAT_CODE_39;
+        recognition_formats |= barhopper::BarcodeFormat::CODE_39;
         break;
       case mojom::BarcodeFormat::CODE_93:
-        expected_formats |= CHROME_BARCODE_FORMAT_CODE_93;
+        recognition_formats |= barhopper::BarcodeFormat::CODE_93;
         break;
       case mojom::BarcodeFormat::CODABAR:
-        expected_formats |= CHROME_BARCODE_FORMAT_CODABAR;
+        recognition_formats |= barhopper::BarcodeFormat::CODABAR;
         break;
       case mojom::BarcodeFormat::DATA_MATRIX:
-        expected_formats |= CHROME_BARCODE_FORMAT_DATA_MATRIX;
+        recognition_formats |= barhopper::BarcodeFormat::DATA_MATRIX;
         break;
       case mojom::BarcodeFormat::EAN_13:
-        expected_formats |= CHROME_BARCODE_FORMAT_EAN_13;
+        recognition_formats |= barhopper::BarcodeFormat::EAN_13;
         break;
       case mojom::BarcodeFormat::EAN_8:
-        expected_formats |= CHROME_BARCODE_FORMAT_EAN_8;
+        recognition_formats |= barhopper::BarcodeFormat::EAN_8;
         break;
       case mojom::BarcodeFormat::ITF:
-        expected_formats |= CHROME_BARCODE_FORMAT_ITF;
+        recognition_formats |= barhopper::BarcodeFormat::ITF;
         break;
       case mojom::BarcodeFormat::PDF417:
-        expected_formats |= CHROME_BARCODE_FORMAT_PDF417;
+        recognition_formats |= barhopper::BarcodeFormat::PDF417;
         break;
       case mojom::BarcodeFormat::QR_CODE:
-        expected_formats |= CHROME_BARCODE_FORMAT_QR_CODE;
+        recognition_formats |= barhopper::BarcodeFormat::QR_CODE;
         break;
       case mojom::BarcodeFormat::UPC_E:
-        expected_formats |= CHROME_BARCODE_FORMAT_UPC_E;
+        recognition_formats |= barhopper::BarcodeFormat::UPC_E;
         break;
       case mojom::BarcodeFormat::UPC_A:
-        expected_formats |= CHROME_BARCODE_FORMAT_UPC_A;
+        recognition_formats |= barhopper::BarcodeFormat::UPC_A;
         break;
       case mojom::BarcodeFormat::UNKNOWN:
-        expected_formats |= CHROME_BARCODE_FORMAT_UNKNOWN;
+        recognition_formats |= barhopper::BarcodeFormat::UNRECOGNIZED;
         break;
     }
   }
-
-  return expected_formats;
+  recognition_options.barcode_formats = recognition_formats;
+  return recognition_options;
 }
 
 }  // namespace
 
 BarcodeDetectionImplChrome::BarcodeDetectionImplChrome(
     mojom::BarcodeDetectorOptionsPtr options)
-    : expected_formats_(GetExpectedFormats(options)) {}
+    : recognition_options_(GetRecognitionOptions(options)) {}
 
 BarcodeDetectionImplChrome::~BarcodeDetectionImplChrome() = default;
 
-DISABLE_CFI_DLSYM
 void BarcodeDetectionImplChrome::Detect(
     const SkBitmap& bitmap,
     shape_detection::mojom::BarcodeDetection::DetectCallback callback) {
@@ -162,40 +162,21 @@
       luminances[y * width + x] = luminance / 8;
     }
   }
+  std::vector<barhopper::Barcode> barcodes;
+  barhopper::Barhopper::Recognize(width, height, luminances.data(),
+                                  recognition_options_, &barcodes);
 
-  ChromeBarcodeDetectionResult* detection_results;
-  size_t num_results;
-  const auto* holder = ShapeDetectionLibraryHolder::GetInstance();
-  // Holder should be valid if it passed pre-sandbox initialization.
-  CHECK(holder);
-  holder->api().DetectBarcodes(width, height, luminances.data(),
-                               expected_formats_, &detection_results,
-                               &num_results);
-
-  // SAFTY: `detection_results` was allocated with `num_results` by
-  // ChromeShapeDetectionAPI.
-  UNSAFE_BUFFERS(
-      base::span<ChromeBarcodeDetectionResult> detection_results_span(
-          detection_results, num_results);)
   std::vector<mojom::BarcodeDetectionResultPtr> results;
-  for (const auto& barcode : detection_results_span) {
+  for (auto& barcode : barcodes) {
     auto result = shape_detection::mojom::BarcodeDetectionResult::New();
-
-    // SAFTY: `barcode.corner_points` was allocated with `barcode.
-    // corner_points_size` by ChromeShapeDetectionAPI.
-    UNSAFE_BUFFERS(base::span<const ChromePointF> corner_points_span(
-        barcode.corner_points, barcode.corner_points_size));
-    result->bounding_box = CornerPointsToBoundingBox(corner_points_span);
-    for (auto& corner_point : corner_points_span) {
+    result->bounding_box = CornerPointsToBoundingBox(barcode.corner_point);
+    for (auto& corner_point : barcode.corner_point) {
       result->corner_points.emplace_back(corner_point.x, corner_point.y);
     }
-    result->raw_value = std::string(
-        reinterpret_cast<const char*>(barcode.value), barcode.value_size);
-    result->format = BarcodeFormatToMojo(barcode.format);
+    result->raw_value = barcode.raw_value;
+    result->format = BarhopperFormatToMojo(barcode.format);
     results.push_back(std::move(result));
   }
-
-  holder->api().DestroyDetectionResults(detection_results, num_results);
   std::move(callback).Run(std::move(results));
 }
 
diff --git a/services/shape_detection/barcode_detection_impl_chrome.h b/services/shape_detection/barcode_detection_impl_chrome.h
index fba3837..8abd222f 100644
--- a/services/shape_detection/barcode_detection_impl_chrome.h
+++ b/services/shape_detection/barcode_detection_impl_chrome.h
@@ -5,9 +5,9 @@
 #ifndef SERVICES_SHAPE_DETECTION_BARCODE_DETECTION_IMPL_CHROME_H_
 #define SERVICES_SHAPE_DETECTION_BARCODE_DETECTION_IMPL_CHROME_H_
 
-#include "services/shape_detection/chrome_shape_detection_api.h"
 #include "services/shape_detection/public/mojom/barcodedetection.mojom.h"
 #include "services/shape_detection/public/mojom/barcodedetection_provider.mojom.h"
+#include "third_party/barhopper/barhopper/barhopper.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
 namespace shape_detection {
@@ -29,7 +29,7 @@
   GetSupportedFormats();
 
  private:
-  const ChromeBarcodeFormat expected_formats_;
+  barhopper::RecognitionOptions recognition_options_;
 };
 
 }  // namespace shape_detection
diff --git a/services/shape_detection/barcode_detection_impl_chrome_unittest.cc b/services/shape_detection/barcode_detection_impl_chrome_unittest.cc
index 701b449aca..487ea33 100644
--- a/services/shape_detection/barcode_detection_impl_chrome_unittest.cc
+++ b/services/shape_detection/barcode_detection_impl_chrome_unittest.cc
@@ -17,48 +17,27 @@
 
 namespace shape_detection {
 
-constexpr size_t kMaxNumExpectedValues = 2;
-
-struct ExpectedValue {
-  std::string_view raw_value;
+constexpr struct TestParams {
+  base::FilePath::StringViewType filename;
+  std::string_view expected_value;
   float x;
   float y;
   float width;
   float height;
-};
-
-constexpr struct TestParams {
-  base::FilePath::StringViewType filename;
-  size_t num_expected_values;
-  std::array<ExpectedValue, kMaxNumExpectedValues> expected_value;
 } kTestParams[] = {
-    {FILE_PATH_LITERAL("codabar.png"),
-     1u,
-     {{{"A6.2831853B", 24, 24, 448, 95}}}},
-    {FILE_PATH_LITERAL("code_39.png"), 1u, {{{"CHROMIUM", 20, 20, 318, 75}}}},
-    {FILE_PATH_LITERAL("code_93.png"), 1u, {{{"CHROMIUM", 20, 20, 216, 75}}}},
-    {FILE_PATH_LITERAL("code_128.png"), 1u, {{{"Chromium", 20, 20, 246, 75}}}},
-    {FILE_PATH_LITERAL("data_matrix.png"),
-     1u,
-     {{{"Chromium", 11, 11, 53, 53}}}},
-    {FILE_PATH_LITERAL("ean_8.png"), 1u, {{{"62831857", 14, 10, 134, 75}}}},
-    {FILE_PATH_LITERAL("ean_13.png"),
-     1u,
-     {{{"6283185307179", 27, 10, 190, 75}}}},
-    {FILE_PATH_LITERAL("itf.png"), 1u, {{{"62831853071795", 10, 10, 135, 39}}}},
-    {FILE_PATH_LITERAL("pdf417.png"), 1u, {{{"Chromium", 20, 20, 240, 44}}}},
-    {FILE_PATH_LITERAL("qr_code.png"),
-     1u,
-     {{{"https://chromium.org", 40, 40, 250, 250}}}},
-    {FILE_PATH_LITERAL("upc_a.png"), 1u, {{{"628318530714", 23, 10, 190, 75}}}},
-    {FILE_PATH_LITERAL("upc_e.png"), 1u, {{{"06283186", 23, 10, 102, 75}}}},
-    {FILE_PATH_LITERAL("two_upc_a.png"),
-     2u,
-     {{
-         {"326565565892", 191, 265, 358, 174},
-         {"565656545454", 731, 260, 357, 5},
-     }}},
-};
+    {FILE_PATH_LITERAL("codabar.png"), "A6.2831853B", 24, 24, 448, 95},
+    {FILE_PATH_LITERAL("code_39.png"), "CHROMIUM", 20, 20, 318, 75},
+    {FILE_PATH_LITERAL("code_93.png"), "CHROMIUM", 20, 20, 216, 75},
+    {FILE_PATH_LITERAL("code_128.png"), "Chromium", 20, 20, 246, 75},
+    {FILE_PATH_LITERAL("data_matrix.png"), "Chromium", 11, 11, 53, 53},
+    {FILE_PATH_LITERAL("ean_8.png"), "62831857", 14, 10, 134, 75},
+    {FILE_PATH_LITERAL("ean_13.png"), "6283185307179", 27, 10, 190, 75},
+    {FILE_PATH_LITERAL("itf.png"), "62831853071795", 10, 10, 135, 39},
+    {FILE_PATH_LITERAL("pdf417.png"), "Chromium", 20, 20, 240, 44},
+    {FILE_PATH_LITERAL("qr_code.png"), "https://chromium.org", 40, 40, 250,
+     250},
+    {FILE_PATH_LITERAL("upc_a.png"), "628318530714", 23, 10, 190, 75},
+    {FILE_PATH_LITERAL("upc_e.png"), "06283186", 23, 10, 102, 75}};
 
 class BarcodeDetectionImplChromeTest
     : public testing::TestWithParam<struct TestParams> {
@@ -125,17 +104,12 @@
                    run_loop.Quit();
                  }));
   run_loop.Run();
-  EXPECT_EQ(GetParam().num_expected_values, results.size());
-  ASSERT_LE(results.size(), kMaxNumExpectedValues);
-  for (size_t i = 0; i < results.size(); ++i) {
-    EXPECT_EQ(GetParam().expected_value[i].raw_value, results[i]->raw_value);
-    EXPECT_EQ(GetParam().expected_value[i].x, results[i]->bounding_box.x());
-    EXPECT_EQ(GetParam().expected_value[i].y, results[i]->bounding_box.y());
-    EXPECT_EQ(GetParam().expected_value[i].width,
-              results[i]->bounding_box.width());
-    EXPECT_EQ(GetParam().expected_value[i].height,
-              results[i]->bounding_box.height());
-  }
+  EXPECT_EQ(1u, results.size());
+  EXPECT_EQ(GetParam().expected_value, results.front()->raw_value);
+  EXPECT_EQ(GetParam().x, results.front()->bounding_box.x());
+  EXPECT_EQ(GetParam().y, results.front()->bounding_box.y());
+  EXPECT_EQ(GetParam().width, results.front()->bounding_box.width());
+  EXPECT_EQ(GetParam().height, results.front()->bounding_box.height());
 }
 
 INSTANTIATE_TEST_SUITE_P(,
diff --git a/services/shape_detection/chrome_shape_detection_api.h b/services/shape_detection/chrome_shape_detection_api.h
deleted file mode 100644
index 6715d2e..0000000
--- a/services/shape_detection/chrome_shape_detection_api.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SERVICES_SHAPE_DETECTION_CHROME_SHAPE_DETECTION_API_H_
-#define SERVICES_SHAPE_DETECTION_CHROME_SHAPE_DETECTION_API_H_
-
-#include <cstddef>
-#include <cstdint>
-
-// This header defines the public interface to the Chrome Shape Detection shared
-// library.
-
-extern "C" {
-
-#define CHROME_BARCODE_FORMAT_UNKNOWN (0)
-#define CHROME_BARCODE_FORMAT_AZTEC (1 << 0)
-#define CHROME_BARCODE_FORMAT_CODE_128 (1 << 1)
-#define CHROME_BARCODE_FORMAT_CODE_39 (1 << 2)
-#define CHROME_BARCODE_FORMAT_CODE_93 (1 << 3)
-#define CHROME_BARCODE_FORMAT_CODABAR (1 << 4)
-#define CHROME_BARCODE_FORMAT_DATA_MATRIX (1 << 5)
-#define CHROME_BARCODE_FORMAT_EAN_13 (1 << 6)
-#define CHROME_BARCODE_FORMAT_EAN_8 (1 << 7)
-#define CHROME_BARCODE_FORMAT_ITF (1 << 8)
-#define CHROME_BARCODE_FORMAT_PDF417 (1 << 9)
-#define CHROME_BARCODE_FORMAT_QR_CODE (1 << 10)
-#define CHROME_BARCODE_FORMAT_UPC_A (1 << 11)
-#define CHROME_BARCODE_FORMAT_UPC_E (1 << 12)
-
-// Bitmap of expected barcode formats.
-using ChromeBarcodeFormat = uint32_t;
-
-struct ChromePointF {
-  float x;
-  float y;
-};
-
-struct ChromeBarcodeDetectionResult {
-  uint8_t* value;
-  size_t value_size;
-  ChromePointF* corner_points;
-  size_t corner_points_size;
-  ChromeBarcodeFormat format;
-};
-
-// IMPORTANT: All functions that call ChromeShapeDetectionAPI should be
-// annotated with DISABLE_CFI_DLSYM.
-
-// Table of C API functions defined within the library.
-struct ChromeShapeDetectionAPI {
-  // Detects barcodes in the given grayscale image.
-  void (*DetectBarcodes)(size_t width,
-                         size_t height,
-                         uint8_t* data,
-                         ChromeBarcodeFormat expected_formats,
-                         ChromeBarcodeDetectionResult** results,
-                         size_t* results_size);
-
-  void (*DestroyDetectionResults)(ChromeBarcodeDetectionResult* results,
-                                  size_t size);
-};
-
-// Signature of the GetChromeShapeDetectionAPI() function which the shared
-// library exports.
-using ChromeShapeDetectionAPIGetter = const ChromeShapeDetectionAPI* (*)();
-
-}  // extern "C"
-
-#endif  // SERVICES_SHAPE_DETECTION_CHROME_SHAPE_DETECTION_API_H_
diff --git a/services/shape_detection/features.gni b/services/shape_detection/features.gni
deleted file mode 100644
index 11c5139..0000000
--- a/services/shape_detection/features.gni
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright 2025 The Chromium Authors
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/chrome_build.gni")
-import("//build/config/compiler/compiler.gni")
-
-declare_args() {
-  if (is_chrome_branded && is_chromeos) {
-    build_with_internal_shape_detection = true
-  } else {
-    build_with_internal_shape_detection = false
-  }
-}
diff --git a/services/shape_detection/internal b/services/shape_detection/internal
deleted file mode 160000
index 8fd3ed0..0000000
--- a/services/shape_detection/internal
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 8fd3ed03363d2155b038613ff3e2d094a2ad98a3
diff --git a/services/shape_detection/public/mojom/shape_detection_service.mojom b/services/shape_detection/public/mojom/shape_detection_service.mojom
index 29be00d5..e6502d1 100644
--- a/services/shape_detection/public/mojom/shape_detection_service.mojom
+++ b/services/shape_detection/public/mojom/shape_detection_service.mojom
@@ -10,8 +10,7 @@
 import "services/shape_detection/public/mojom/textdetection.mojom";
 
 [EnableIf=has_shape_detection_utility]
-const sandbox.mojom.Sandbox kShapeDetectionSandbox
-    = sandbox.mojom.Sandbox.kShapeDetection;
+const sandbox.mojom.Sandbox kShapeDetectionSandbox = sandbox.mojom.Sandbox.kUtility;
 [EnableIfNot=has_shape_detection_utility]
 const sandbox.mojom.Sandbox kShapeDetectionSandbox = sandbox.mojom.Sandbox.kGpu;
 
diff --git a/services/shape_detection/shape_detection_library_holder.cc b/services/shape_detection/shape_detection_library_holder.cc
deleted file mode 100644
index 7883699..0000000
--- a/services/shape_detection/shape_detection_library_holder.cc
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/shape_detection/shape_detection_library_holder.h"
-
-#include <memory>
-
-#include "base/base_paths.h"
-#include "base/files/file_path.h"
-#include "base/logging.h"
-#include "base/no_destructor.h"
-#include "base/path_service.h"
-
-namespace shape_detection {
-
-namespace {
-constexpr std::string_view kChromeShapeDetectionLibraryName =
-    "shape_detection_internal";
-}  // namespace
-
-base::FilePath GetChromeShapeDetectionPath() {
-  base::FilePath base_dir;
-  CHECK(base::PathService::Get(base::DIR_MODULE, &base_dir));
-  return base_dir.AppendASCII(
-      base::GetNativeLibraryName(kChromeShapeDetectionLibraryName));
-}
-
-ShapeDetectionLibraryHolder::ShapeDetectionLibraryHolder(
-    base::PassKey<ShapeDetectionLibraryHolder>,
-    base::ScopedNativeLibrary library,
-    const ChromeShapeDetectionAPI* api)
-    : library_(std::move(library)), api_(api) {}
-
-ShapeDetectionLibraryHolder::~ShapeDetectionLibraryHolder() = default;
-
-// static
-ShapeDetectionLibraryHolder* ShapeDetectionLibraryHolder::GetInstance() {
-  static base::NoDestructor<std::unique_ptr<ShapeDetectionLibraryHolder>>
-      holder{Create()};
-  return holder->get();
-}
-
-// static
-DISABLE_CFI_DLSYM
-std::unique_ptr<ShapeDetectionLibraryHolder>
-ShapeDetectionLibraryHolder::Create() {
-  base::NativeLibraryLoadError error;
-  base::NativeLibrary library =
-      base::LoadNativeLibrary(GetChromeShapeDetectionPath(), &error);
-  if (!library) {
-    LOG(ERROR) << "Error loading native library: " << error.ToString();
-    return {};
-  }
-
-  base::ScopedNativeLibrary scoped_library(library);
-  auto get_api = reinterpret_cast<ChromeShapeDetectionAPIGetter>(
-      scoped_library.GetFunctionPointer("GetChromeShapeDetectionAPI"));
-  if (!get_api) {
-    LOG(ERROR) << "Unable to resolve GetChromeShapeDetectionAPI symbol.";
-    return {};
-  }
-
-  const ChromeShapeDetectionAPI* api = get_api();
-  if (!api) {
-    LOG(ERROR) << "GetChromeShapeDetectionAPI() returned null.";
-    return {};
-  }
-
-  return std::make_unique<ShapeDetectionLibraryHolder>(
-      base::PassKey<ShapeDetectionLibraryHolder>(), std::move(scoped_library),
-      api);
-}
-
-}  // namespace shape_detection
diff --git a/services/shape_detection/shape_detection_library_holder.h b/services/shape_detection/shape_detection_library_holder.h
deleted file mode 100644
index fc279ae..0000000
--- a/services/shape_detection/shape_detection_library_holder.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SERVICES_SHAPE_DETECTION_SHAPE_DETECTION_LIBRARY_HOLDER_H_
-#define SERVICES_SHAPE_DETECTION_SHAPE_DETECTION_LIBRARY_HOLDER_H_
-
-#include <memory>
-
-#include "base/memory/raw_ptr.h"
-#include "base/scoped_native_library.h"
-#include "base/types/pass_key.h"
-#include "services/shape_detection/chrome_shape_detection_api.h"
-
-namespace shape_detection {
-
-base::FilePath GetChromeShapeDetectionPath();
-
-// A ShapeDetectionLibraryHolder object encapsulates a reference to the
-// ChromeShapeDetectionAPI shared library, exposing the library's API
-// functions to callers and ensuring that the library remains loaded and usable
-// throughout the object's lifetime.
-class ShapeDetectionLibraryHolder {
- public:
-  ShapeDetectionLibraryHolder(base::PassKey<ShapeDetectionLibraryHolder>,
-                              base::ScopedNativeLibrary library,
-                              const ChromeShapeDetectionAPI* api);
-  ShapeDetectionLibraryHolder(const ShapeDetectionLibraryHolder& other) =
-      delete;
-  ShapeDetectionLibraryHolder& operator=(
-      const ShapeDetectionLibraryHolder& other) = delete;
-  ShapeDetectionLibraryHolder(ShapeDetectionLibraryHolder&& other) = default;
-  ShapeDetectionLibraryHolder& operator=(ShapeDetectionLibraryHolder&& other) =
-      default;
-  ~ShapeDetectionLibraryHolder();
-
-  // Returns the singleton ShapeDetectionLibraryHolder. Creates it if it does
-  // not exist. May return nullopt if the underlying library could not be
-  // loaded.
-  static ShapeDetectionLibraryHolder* GetInstance();
-
-  // Exposes the raw ChromeShapeDetectionAPI functions defined by the library.
-  const ChromeShapeDetectionAPI& api() const { return *api_; }
-
- private:
-  static std::unique_ptr<ShapeDetectionLibraryHolder> Create();
-
-  base::ScopedNativeLibrary library_;
-  raw_ptr<const ChromeShapeDetectionAPI> api_;
-};
-
-}  // namespace shape_detection
-
-#endif  // SERVICES_SHAPE_DETECTION_SHAPE_DETECTION_LIBRARY_HOLDER_H_
diff --git a/services/shape_detection/shape_detection_sandbox_hook.cc b/services/shape_detection/shape_detection_sandbox_hook.cc
deleted file mode 100644
index 27725212..0000000
--- a/services/shape_detection/shape_detection_sandbox_hook.cc
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "services/shape_detection/shape_detection_sandbox_hook.h"
-
-#include <dlfcn.h>
-
-#include "build/branding_buildflags.h"
-
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-#include "services/shape_detection/shape_detection_library_holder.h"
-#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
-
-namespace shape_detection {
-
-bool ShapeDetectionPreSandboxHook(
-    sandbox::policy::SandboxLinux::Options options) {
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  // Ensure the shape_detection_internal shared library is loaded before the
-  // sandbox is initialized.
-  const auto path = shape_detection::GetChromeShapeDetectionPath();
-  // We don't want to unload the library so not using
-  // `ShapeDetectionLibraryHolder` here.
-  void* dl =
-      dlopen(path.value().c_str(), RTLD_NOW | RTLD_GLOBAL | RTLD_NODELETE);
-  if (!dl) {
-    LOG(ERROR) << "Failed to open Chrome Shape Detection shared library!";
-    return false;
-  } else {
-    DVLOG(1) << "Successfully opened Chrome Shape Detection shared library.";
-  }
-#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
-  auto* instance = sandbox::policy::SandboxLinux::GetInstance();
-  instance->EngageNamespaceSandboxIfPossible();
-  return true;
-}
-
-}  // namespace shape_detection
diff --git a/services/shape_detection/shape_detection_sandbox_hook.h b/services/shape_detection/shape_detection_sandbox_hook.h
deleted file mode 100644
index 33da3ee..0000000
--- a/services/shape_detection/shape_detection_sandbox_hook.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2025 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef SERVICES_SHAPE_DETECTION_SHAPE_DETECTION_SANDBOX_HOOK_H_
-#define SERVICES_SHAPE_DETECTION_SHAPE_DETECTION_SANDBOX_HOOK_H_
-
-#include "sandbox/policy/linux/sandbox_linux.h"
-
-namespace shape_detection {
-
-bool ShapeDetectionPreSandboxHook(
-    sandbox::policy::SandboxLinux::Options options);
-
-}  // namespace shape_detection
-
-#endif  // SERVICES_SHAPE_DETECTION_SHAPE_DETECTION_SANDBOX_HOOK_H_
diff --git a/services/test/data/two_upc_a.png b/services/test/data/two_upc_a.png
deleted file mode 100644
index 0911b7b..0000000
--- a/services/test/data/two_upc_a.png
+++ /dev/null
Binary files differ
diff --git a/services/webnn/ort/graph_builder_ort.cc b/services/webnn/ort/graph_builder_ort.cc
index 9c6352b..e35ba42 100644
--- a/services/webnn/ort/graph_builder_ort.cc
+++ b/services/webnn/ort/graph_builder_ort.cc
@@ -190,7 +190,7 @@
                         operand_id);
 }
 
-std::string GraphBuilderOrt::GenerateOperationName(std::string_view label) {
+std::string GraphBuilderOrt::GenerateNodeName(std::string_view label) {
   return base::JoinString({label, base::NumberToString(next_operation_id_++)},
                           kUnderscore);
 }
@@ -234,7 +234,7 @@
 template <typename T>
 void GraphBuilderOrt::AddBinaryOperation(const T& operation,
                                          base::cstring_view op_type) {
-  const std::string node = GenerateOperationName(operation.label);
+  const std::string node_name = GenerateNodeName(operation.label);
   const std::string lhs = GetOperandNameById(operation.lhs_operand_id);
   const std::string rhs = GetOperandNameById(operation.rhs_operand_id);
   const std::string output = GetOperandNameById(operation.output_operand_id);
@@ -242,24 +242,24 @@
   std::array<const char*, 2> inputs = {lhs.c_str(), rhs.c_str()};
   std::array<const char*, 1> outputs = {output.c_str()};
 
-  model_editor_.AddNode(op_type, node, inputs, outputs);
+  model_editor_.AddNode(op_type, node_name, inputs, outputs);
 }
 
 template <typename T>
 void GraphBuilderOrt::AddUnaryOperation(const T& operation,
                                         base::cstring_view op_type) {
-  const std::string node = GenerateOperationName(operation.label);
+  const std::string node_name = GenerateNodeName(operation.label);
   const std::string input = GetOperandNameById(operation.input_operand_id);
   const std::string output = GetOperandNameById(operation.output_operand_id);
 
   std::array<const char*, 1> inputs = {input.c_str()};
   std::array<const char*, 1> outputs = {output.c_str()};
 
-  model_editor_.AddNode(op_type, node, inputs, outputs);
+  model_editor_.AddNode(op_type, node_name, inputs, outputs);
 }
 
 void GraphBuilderOrt::AddCastOperation(const mojom::ElementWiseUnary& cast) {
-  const std::string node = GenerateOperationName(cast.label);
+  const std::string node_name = GenerateNodeName(cast.label);
   const std::string input = GetOperandNameById(cast.input_operand_id);
   const std::string output = GetOperandNameById(cast.output_operand_id);
 
@@ -274,11 +274,11 @@
   std::array<ScopedOrtOpAttr, 1> attributes = {
       model_editor_.CreateAttribute(kAttrTo, attr_to_data)};
 
-  model_editor_.AddNode(kOpTypeCast, node, inputs, outputs, attributes);
+  model_editor_.AddNode(kOpTypeCast, node_name, inputs, outputs, attributes);
 }
 
 void GraphBuilderOrt::AddConv2dOperation(const mojom::Conv2d& conv2d) {
-  const std::string node_name = GenerateOperationName(conv2d.label);
+  const std::string node_name = GenerateNodeName(conv2d.label);
   const std::string input = GetOperandNameById(conv2d.input_operand_id);
   const std::string filter = GetOperandNameById(conv2d.filter_operand_id);
   const std::string output = GetOperandNameById(conv2d.output_operand_id);
@@ -506,7 +506,7 @@
 }
 
 void GraphBuilderOrt::AddClampOperation(const mojom::Clamp& clamp) {
-  const std::string node = GenerateOperationName(clamp.label);
+  const std::string node_name = GenerateNodeName(clamp.label);
   const std::string input = GetOperandNameById(clamp.input_operand_id);
   const std::string output = GetOperandNameById(clamp.output_operand_id);
 
@@ -582,11 +582,11 @@
   std::array<const char*, 3> inputs = {input.c_str(), min.c_str(), max.c_str()};
   std::array<const char*, 1> outputs = {output.c_str()};
 
-  model_editor_.AddNode(kOpTypeClamp, node, inputs, outputs);
+  model_editor_.AddNode(kOpTypeClamp, node_name, inputs, outputs);
 }
 
 void GraphBuilderOrt::AddGemmOperation(const mojom::Gemm& gemm) {
-  const std::string node = GenerateOperationName(gemm.label);
+  const std::string node_name = GenerateNodeName(gemm.label);
   const std::string input_a = GetOperandNameById(gemm.a_operand_id);
   const std::string input_b = GetOperandNameById(gemm.b_operand_id);
   const std::string output = GetOperandNameById(gemm.output_operand_id);
@@ -625,7 +625,7 @@
       model_editor_.CreateAttribute(kAttrTransB,
                                     static_cast<int64_t>(gemm.b_transpose))};
 
-  model_editor_.AddNode(kOpTypeGemm, node, inputs, outputs, attributes);
+  model_editor_.AddNode(kOpTypeGemm, node_name, inputs, outputs, attributes);
 }
 
 void GraphBuilderOrt::AddPool2dOperation(const mojom::Pool2d& pool2d) {
@@ -704,17 +704,17 @@
     }
   }
 
-  const std::string node = GenerateOperationName(pool2d.label);
+  const std::string node_name = GenerateNodeName(pool2d.label);
   const std::string input = GetOperandNameById(pool2d.input_operand_id);
   const std::string output = GetOperandNameById(pool2d.output_operand_id);
   std::array<const char*, 1> inputs = {input.c_str()};
   std::array<const char*, 1> outputs = {output.c_str()};
 
-  model_editor_.AddNode(op_type, node, inputs, outputs, attributes);
+  model_editor_.AddNode(op_type, node_name, inputs, outputs, attributes);
 }
 
 void GraphBuilderOrt::AddReshapeOperation(const mojom::Reshape& reshape) {
-  const std::string node = GenerateOperationName(reshape.label);
+  const std::string node_name = GenerateNodeName(reshape.label);
   const std::string input = GetOperandNameById(reshape.input_operand_id);
   const std::string output = GetOperandNameById(reshape.output_operand_id);
 
@@ -734,11 +734,11 @@
   std::array<const char*, 2> inputs = {input.c_str(), new_shape.c_str()};
   std::array<const char*, 1> outputs = {output.c_str()};
 
-  model_editor_.AddNode(kOpTypeReshape, node, inputs, outputs);
+  model_editor_.AddNode(kOpTypeReshape, node_name, inputs, outputs);
 }
 
 void GraphBuilderOrt::AddSoftmaxOperation(const mojom::Softmax& softmax) {
-  const std::string node = GenerateOperationName(softmax.label);
+  const std::string node_name = GenerateNodeName(softmax.label);
   const std::string input = GetOperandNameById(softmax.input_operand_id);
   const std::string output = GetOperandNameById(softmax.output_operand_id);
 
@@ -752,11 +752,11 @@
   std::array<ScopedOrtOpAttr, 1> attributes = {model_editor_.CreateAttribute(
       kAttrAxis, static_cast<int64_t>(softmax.axis))};
 
-  model_editor_.AddNode(kOpTypeSoftmax, node, inputs, outputs, attributes);
+  model_editor_.AddNode(kOpTypeSoftmax, node_name, inputs, outputs, attributes);
 }
 
 void GraphBuilderOrt::AddTransposeOperation(const mojom::Transpose& transpose) {
-  const std::string node = GenerateOperationName(transpose.label);
+  const std::string node_name = GenerateNodeName(transpose.label);
   const std::string input = GetOperandNameById(transpose.input_operand_id);
   const std::string output = GetOperandNameById(transpose.output_operand_id);
 
@@ -772,7 +772,8 @@
   std::array<ScopedOrtOpAttr, 1> attributes = {
       model_editor_.CreateAttribute(kAttrPerm, perm_value)};
 
-  model_editor_.AddNode(kOpTypeTranspose, node, inputs, outputs, attributes);
+  model_editor_.AddNode(kOpTypeTranspose, node_name, inputs, outputs,
+                        attributes);
 }
 
 [[nodiscard]] base::expected<std::unique_ptr<ModelEditor::ModelInfo>,
diff --git a/services/webnn/ort/graph_builder_ort.h b/services/webnn/ort/graph_builder_ort.h
index d1438b7..5963693b 100644
--- a/services/webnn/ort/graph_builder_ort.h
+++ b/services/webnn/ort/graph_builder_ort.h
@@ -89,10 +89,9 @@
   // "inserted" and `next_operand_id_`, and then increase `next_operand_id_`.
   std::string GenerateOperandName();
 
-  // Generate a unique name for a newly created operation by combining
-  // `label` and `next_operation_id_`. ORT model doesn't allow duplicate
-  // names.
-  std::string GenerateOperationName(std::string_view label);
+  // Generate a unique name for a newly created node by combining `label` and
+  // `next_operation_id_`. ORT model doesn't allow duplicate names.
+  std::string GenerateNodeName(std::string_view label);
 
   // Create a new initializer for the graph with the given shape and data,
   // returning the name of the initializer.
diff --git a/services/webnn/tflite/graph_builder_tflite.cc b/services/webnn/tflite/graph_builder_tflite.cc
index 96ce6a3d..b50d7f1 100644
--- a/services/webnn/tflite/graph_builder_tflite.cc
+++ b/services/webnn/tflite/graph_builder_tflite.cc
@@ -1851,6 +1851,99 @@
 }
 
 std::optional<GraphBuilderTflite::TensorInfo>
+GraphBuilderTflite::CanFuseQuantizeAndGetOutput(const mojom::Gemm& gemm) {
+  // TODO(crbug.com/372932099): Fuse quantized gemm when gemm.alpha or gemm.beta
+  // isn't 1.0.
+  if (!IsDequantizeOutput(gemm.a_operand_id) ||
+      !IsDequantizeOutput(gemm.b_operand_id) || gemm.alpha != 1.0f ||
+      gemm.beta != 1.0f) {
+    return std::nullopt;
+  }
+
+  // The a operand scale and output scale have to be scaler, see quantization
+  // requirements of FULLY_CONNECTED at
+  // https://ai.google.dev/edge/litert/models/quantization_spec#int8_quantized_operator_specifications
+  const mojom::DequantizeLinear& a_dequantize =
+      GetDequantizeOp(gemm.a_operand_id);
+  const mojom::DequantizeLinear& b_dequantize =
+      GetDequantizeOp(gemm.b_operand_id);
+  const OperandDataType quantized_type =
+      GetOperand(a_dequantize.input_operand_id).descriptor.data_type();
+  // TODO(crbug.com/425746878): Support int4 quantization for b operand.
+  if (!IsInts8AndScalarScale(a_dequantize) ||
+      GetOperand(b_dequantize.input_operand_id).descriptor.data_type() !=
+          quantized_type) {
+    return std::nullopt;
+  }
+
+  // The c operand must be optional or int32 data type.
+  // https://source.chromium.org/chromium/chromium/src/+/main:third_party/tflite/src/tensorflow/lite/kernels/fully_connected.cc;drc=7930f629a820b2233128fb591789f4d8a41be8d9;l=216
+  if (gemm.c_operand_id) {
+    if (!IsDequantizeOutput(*gemm.c_operand_id)) {
+      return std::nullopt;
+    }
+    const mojom::DequantizeLinear& c_dequantize =
+        GetDequantizeOp(*gemm.c_operand_id);
+    if (GetOperand(c_dequantize.input_operand_id).descriptor.data_type() !=
+        OperandDataType::kInt32) {
+      return std::nullopt;
+    }
+  }
+
+  std::optional<std::pair<OperationId, QuantizateParametersOffset>> next_op =
+      IsNextOpQuantize(gemm.output_operand_id, {quantized_type});
+  if (!next_op) {
+    return std::nullopt;
+  }
+  const mojom::QuantizeLinear& output_quantize = GetQuantizeOp(next_op->first);
+  if (!IsInts8AndScalarScale(output_quantize)) {
+    return std::nullopt;
+  }
+
+  // Only Int8 is supported for per-channel quantization.
+  // https://source.chromium.org/chromium/chromium/src/+/main:third_party/tflite/src/tensorflow/lite/kernels/fully_connected.cc;l=446;drc=997022c9de8c1e4ed9081b8789c1057d0fce0e28
+  size_t number_of_b_scale =
+      GetOperand(b_dequantize.scale_operand_id).descriptor.NumberOfElements();
+  const bool per_channel_quantization = number_of_b_scale != 1;
+  if (per_channel_quantization && quantized_type != OperandDataType::kInt8) {
+    return std::nullopt;
+  }
+  // The transpose operation will be inserted if gemm.b_transpose is false, but
+  // quantized transpose only supports per-tensor quantization.
+  // https://source.chromium.org/chromium/chromium/src/+/main:services/webnn/tflite/graph_builder_tflite.cc;drc=87413efa62e18726d73e7f283efef63d4bfd1023;l=4581
+  if (per_channel_quantization && !gemm.b_transpose) {
+    return std::nullopt;
+  }
+
+  // The a_scale * b_scale should be about the same as c_scale for per-tensor
+  // quantization.
+  // https://source.chromium.org/chromium/chromium/src/+/main:third_party/tflite/src/tensorflow/lite/kernels/kernel_util.cc;l=303;drc=492dc9719f6e1845f4f5c0553cd5c7651115f671
+  if (!per_channel_quantization && gemm.c_operand_id) {
+    base::span<const float> a_scale_values =
+        GetConstantValue<float>(a_dequantize.scale_operand_id);
+    base::span<const float> b_scale_values =
+        GetConstantValue<float>(b_dequantize.scale_operand_id);
+    const mojom::DequantizeLinear& c_dequantize =
+        GetDequantizeOp(*gemm.c_operand_id);
+    base::span<const float> c_scale_values =
+        GetConstantValue<float>(c_dequantize.scale_operand_id);
+    base::span<const float> output_scale_values =
+        GetConstantValue<float>(output_quantize.scale_operand_id);
+    const double a_scale = static_cast<double>(a_scale_values[0]);
+    const double output_scale = static_cast<double>(output_scale_values[0]);
+    auto a_product_b =
+        base::MakeCheckedNum<double>(a_scale) * b_scale_values[0];
+    auto scale_diff = a_product_b - static_cast<double>(c_scale_values[0]);
+    scale_diff = scale_diff.Abs() / output_scale;
+    if (!scale_diff.IsValid() || scale_diff.ValueOrDie() > 0.02) {
+      return std::nullopt;
+    }
+  }
+
+  return SerializeQuantizedOutput(*next_op);
+}
+
+std::optional<GraphBuilderTflite::TensorInfo>
 GraphBuilderTflite::CanFuseQuantizeAndGetOutput(const mojom::Pad& pad) {
   // For edge padding mode, it is not supported in tflite schema.
   //
@@ -3326,7 +3419,8 @@
     output_shape[i] = input_tensor_info.dimensions[permutation[i]];
   }
   const TensorIndex output_tensor_index =
-      SerializeTemporaryTensor(output_shape, input_tensor_info.data_type);
+      SerializeTemporaryTensor(output_shape, input_tensor_info.data_type,
+                               input_tensor_info.quantize_params);
   operators_.emplace_back(
       SerializeTransposeOperation(input_tensor_info.index, output_tensor_index,
                                   input_tensor_info.dimensions, permutation));
@@ -4548,8 +4642,31 @@
       {GetOperand(gemm.a_operand_id).descriptor,
        GetOperand(gemm.b_operand_id).descriptor}));
 
+  // The TFLite fully connected operator only supports a 1-D bias tensor with
+  // `output_channels` dimensions.
+  // https://source.chromium.org/chromium/chromium/src/+/main:third_party/tflite/src/tensorflow/lite/kernels/fully_connected.cc;drc=7930f629a820b2233128fb591789f4d8a41be8d9;l=425
+  bool is_emulated_c_expression = false;
+  if (gemm.c_operand_id) {
+    const std::vector<uint32_t>& output_shape =
+        GetOperand(gemm.output_operand_id).descriptor.shape();
+    CHECK_EQ(output_shape.size(), 2u);
+    const uint32_t output_channels = output_shape[1];
+    const std::vector<uint32_t>& c_shape =
+        GetOperand(*gemm.c_operand_id).descriptor.shape();
+    if (c_shape.size() != 1 || c_shape[0] != output_channels) {
+      is_emulated_c_expression = true;
+    }
+  }
+
+  std::optional<TensorInfo> quantized_output =
+      is_emulated_c_expression ? std::nullopt
+                               : CanFuseQuantizeAndGetOutput(gemm);
+  const bool fuse_dequantize = quantized_output.has_value();
   ASSIGN_OR_RETURN(const TensorInfo& a_tensor_info,
-                   SerializeInputTensorInfo(gemm.a_operand_id));
+                   SerializeInputTensorInfo(
+                       gemm.a_operand_id,
+                       /*quantize_params=*/0,
+                       /*operation_supports_float16=*/false, fuse_dequantize));
   TensorIndex a_tensor_index = a_tensor_info.index;
   // The permutation transpose first or second 2-D tensor.
   static constexpr std::array<uint32_t, 2> permutation = {1u, 0u};
@@ -4577,7 +4694,10 @@
   // input_channels], so the Transpose operator need to be inserted before
   // Gemm When bTranspose option is false.
   ASSIGN_OR_RETURN(const TensorInfo& b_tensor_info,
-                   SerializeInputTensorInfo(gemm.b_operand_id));
+                   SerializeInputTensorInfo(
+                       gemm.b_operand_id,
+                       /*quantize_params=*/0,
+                       /*operation_supports_float16=*/false, fuse_dequantize));
   TensorIndex b_tensor_index = b_tensor_info.index;
   if (!gemm.b_transpose) {
     b_tensor_index = InsertTransposeOperation(b_tensor_info, permutation);
@@ -4585,15 +4705,29 @@
   std::vector<TensorIndex> fully_connected_inputs = {a_tensor_index,
                                                      b_tensor_index};
 
-  const TensorInfo output_tensor_info =
-      SerializeOutputTensorInfo(gemm.output_operand_id);
-  CHECK_EQ(output_tensor_info.dimensions.size(), 2u);
+  TensorIndex output_tensor_index;
+  std::vector<int32_t> output_tensor_dimensions;
+  ::tflite::TensorType output_tensor_type;
+  if (fuse_dequantize) {
+    output_tensor_index = quantized_output->index;
+  } else {
+    const TensorInfo output_tensor_info =
+        SerializeOutputTensorInfo(gemm.output_operand_id);
+    CHECK_EQ(output_tensor_info.dimensions.size(), 2u);
+    output_tensor_index = output_tensor_info.index;
+    output_tensor_dimensions = std::move(output_tensor_info.dimensions);
+    output_tensor_type = output_tensor_info.data_type;
+  }
   std::optional<TensorIndex> c_tensor_index;
   if (gemm.c_operand_id && gemm.beta != 0.0f) {
     CHECK(context_properties_.data_type_limits.gemm_c.Supports(
         GetOperand(gemm.c_operand_id.value()).descriptor));
-    ASSIGN_OR_RETURN(const TensorInfo& c_tensor_info,
-                     SerializeInputTensorInfo(*gemm.c_operand_id));
+    ASSIGN_OR_RETURN(
+        const TensorInfo& c_tensor_info,
+        SerializeInputTensorInfo(*gemm.c_operand_id,
+                                 /*quantize_params=*/0,
+                                 /*operation_supports_float16=*/false,
+                                 fuse_dequantize));
     c_tensor_index = c_tensor_info.index;
     if (gemm.beta != 1.0f) {
       const TensorIndex beta_tensor_index = SerializeTensorWithBuffer<float>(
@@ -4607,37 +4741,33 @@
       c_tensor_index = output_tensor_index_of_mul;
     }
 
-    // The TFLite fully connected operator only supports a 1-D bias tensor with
-    // `output_channels` dimensions.
-    const int32_t output_channels = output_tensor_info.dimensions[1];
-    if (c_tensor_info.dimensions.size() == 1 &&
-        c_tensor_info.dimensions[0] == output_channels) {
+    if (!is_emulated_c_expression) {
       fully_connected_inputs.push_back(*c_tensor_index);
     }
   }
 
   // Add the `beta * C` subexpression if it's not fused into FULLY_CONNECTED
   // operator.
-  const bool addition_c_expression =
-      c_tensor_index && fully_connected_inputs.size() == 2;
-  TensorIndex output_tensor_index = output_tensor_info.index;
-  if (addition_c_expression) {
-    output_tensor_index = SerializeTemporaryTensor(
-        output_tensor_info.dimensions, output_tensor_info.data_type);
+  TensorIndex addition_c_tensor_index = output_tensor_index;
+  if (is_emulated_c_expression) {
+    CHECK(!fuse_dequantize);
+    addition_c_tensor_index =
+        SerializeTemporaryTensor(output_tensor_dimensions, output_tensor_type);
   }
   const OperatorCodeIndex operator_code_index =
       GetOperatorCodeIndex(::tflite::BuiltinOperator_FULLY_CONNECTED);
-  const std::array<TensorIndex, 1> op_outputs = {output_tensor_index};
+  const std::array<TensorIndex, 1> op_outputs = {addition_c_tensor_index};
   OperatorOffset operator_offset = ::tflite::CreateOperator(
       builder_, operator_code_index,
       builder_.CreateVector<TensorIndex>(fully_connected_inputs),
       builder_.CreateVector<TensorIndex>(op_outputs));
 
-  if (addition_c_expression) {
+  if (is_emulated_c_expression) {
+    CHECK(!fuse_dequantize);
     operators_.push_back(operator_offset);
     operator_offset = SerializeBinaryOperation(
-        ::tflite::BuiltinOperator_ADD, output_tensor_index, *c_tensor_index,
-        output_tensor_info.index);
+        ::tflite::BuiltinOperator_ADD, addition_c_tensor_index, *c_tensor_index,
+        output_tensor_index);
   }
   return operator_offset;
 }
diff --git a/services/webnn/tflite/graph_builder_tflite.h b/services/webnn/tflite/graph_builder_tflite.h
index c15b40c..9cadc2b8 100644
--- a/services/webnn/tflite/graph_builder_tflite.h
+++ b/services/webnn/tflite/graph_builder_tflite.h
@@ -745,6 +745,8 @@
   std::optional<TensorInfo> CanFuseQuantizeAndGetOutput(const mojom::Elu& elu);
   std::optional<TensorInfo> CanFuseQuantizeAndGetOutput(
       const mojom::Gather& gather);
+  std::optional<TensorInfo> CanFuseQuantizeAndGetOutput(
+      const mojom::Gemm& gemm);
   std::optional<TensorInfo> CanFuseQuantizeAndGetOutput(const mojom::Pad& pad);
   std::optional<TensorInfo> CanFuseQuantizeAndGetOutput(
       const mojom::Pool2d& pool2d);
diff --git a/testing/buildbot/filters/layer_list_mode.cc_unittests.filter b/testing/buildbot/filters/layer_list_mode.cc_unittests.filter
index 6c420d6..4f59b0e 100644
--- a/testing/buildbot/filters/layer_list_mode.cc_unittests.filter
+++ b/testing/buildbot/filters/layer_list_mode.cc_unittests.filter
@@ -846,7 +846,6 @@
 -CommitToActiveTreeScrollbarLayerTest.UpdatePropertiesOfScrollBarWhenThumbRemoved
 -DontUpdateLayersWithEmptyBounds.RunMultiThread_DelegatingRenderer
 -DontUpdateLayersWithEmptyBounds.RunSingleThread_DelegatingRenderer
--DroppedFrameCounterNoDropTest.RunMultiThread_DelegatingRenderer
 -HudWithRootLayerChange.RunMultiThread_DelegatingRenderer
 -HudWithRootLayerChange.RunSingleThread_DelegatingRenderer
 -ImplSideInvalidationWithoutCommitTestFilter.RunMultiThread_DelegatingRenderer
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index e79f1fd..ede79ee 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3509,6 +3509,21 @@
             ]
         }
     ],
+    "BocaOnTaskMuteArcAudio": [
+        {
+            "platforms": [
+                "chromeos"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "BocaOnTaskMuteArcAudio"
+                    ]
+                }
+            ]
+        }
+    ],
     "BookmarksUseBinaryTreeInTitledUrlIndex": [
         {
             "platforms": [
diff --git a/third_party/android_build_tools/README.chromium b/third_party/android_build_tools/README.chromium
index 0d842c1..ca29bb39 100644
--- a/third_party/android_build_tools/README.chromium
+++ b/third_party/android_build_tools/README.chromium
@@ -2,6 +2,7 @@
 Short Name:  Android tools
 Version: N/A
 Revision: DEPS
+Update Mechanism: Autoroll
 License: Apache-2.0
 URL: https://developer.android.com/studio
 Security Critical: No
diff --git a/third_party/android_build_tools/aapt2/README.chromium b/third_party/android_build_tools/aapt2/README.chromium
index 1a2541f8..7bb114e7 100644
--- a/third_party/android_build_tools/aapt2/README.chromium
+++ b/third_party/android_build_tools/aapt2/README.chromium
@@ -2,6 +2,7 @@
 Short name: aapt2
 Version: N/A
 Revision: DEPS
+Update Mechanism: Autoroll
 URL:  https://dl.google.com/dl/android/maven2/com/android/tools/build/aapt2/${Version}/aapt2-${Version}-linux.jar
 Security Critical: no
 Shipped: no
diff --git a/third_party/android_build_tools/error_prone_javac/README.chromium b/third_party/android_build_tools/error_prone_javac/README.chromium
index 22854ef0..dfa16db 100644
--- a/third_party/android_build_tools/error_prone_javac/README.chromium
+++ b/third_party/android_build_tools/error_prone_javac/README.chromium
@@ -1,6 +1,7 @@
 Name: Error Prone Javac
 Short Name: Error Prone Javac
 Version: 9+181-r4173-1
+Update Mechanism: Autoroll
 License: GPL-2.0-with-classpath-exception
 License File: LICENSE
 Security Critical: No
diff --git a/third_party/android_build_tools/lint/README.chromium b/third_party/android_build_tools/lint/README.chromium
index 215cc45..427b7ef6 100644
--- a/third_party/android_build_tools/lint/README.chromium
+++ b/third_party/android_build_tools/lint/README.chromium
@@ -2,6 +2,7 @@
 Short Name: lint
 Version: N/A
 Revision: DEPS
+Update Mechanism: Autoroll
 License: Apache-2.0
 License File: LICENSE
 Security Critical: No
diff --git a/third_party/androidx/build.gradle b/third_party/androidx/build.gradle
index 2d00e592..1de158f 100644
--- a/third_party/androidx/build.gradle
+++ b/third_party/androidx/build.gradle
@@ -307,7 +307,7 @@
     google()
     maven {
         // This URL is generated by the fetch_all_androidx.py script.
-        url 'https://androidx.dev/snapshots/builds/13670804/artifacts/repository'
+        url 'https://androidx.dev/snapshots/builds/13672893/artifacts/repository'
     }
     mavenCentral()
 }
diff --git a/third_party/angle b/third_party/angle
index 6495149..9e629bb 160000
--- a/third_party/angle
+++ b/third_party/angle
@@ -1 +1 @@
-Subproject commit 649514930e65cb7a9baa489092431010df5e8b4a
+Subproject commit 9e629bbb0f732b071f3c7ca6d13b4692b02f6cef
diff --git a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
index 189331d..56f604e 100644
--- a/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
+++ b/third_party/blink/renderer/bindings/scripts/bind_gen/interface.py
@@ -7360,6 +7360,8 @@
     ])
     source_node.accumulator.add_include_headers([
         "base/containers/span.h",
+        "base/notimplemented.h",
+        "base/notreached.h",
         "third_party/blink/renderer/platform/bindings/script_state.h",
         "third_party/blink/renderer/platform/bindings/v8_per_context_data.h",
         "third_party/blink/public/mojom/origin_trials/origin_trial_feature.mojom-shared.h",
diff --git a/third_party/blink/renderer/core/css/media_query_evaluator.cc b/third_party/blink/renderer/core/css/media_query_evaluator.cc
index 9351724..60c2ec6a 100644
--- a/third_party/blink/renderer/core/css/media_query_evaluator.cc
+++ b/third_party/blink/renderer/core/css/media_query_evaluator.cc
@@ -1712,7 +1712,7 @@
           state, value.GetCSSValue());
   query_fallback = ToPhysicalFallback(query_fallback, media_values);
   fallback = ToPhysicalFallback(fallback, media_values);
-  return fallback == query_fallback;
+  return fallback.Matches(query_fallback);
 }
 
 static MediaQueryOperator ReverseOperator(MediaQueryOperator op) {
diff --git a/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc b/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
index 7c12305..96bc537 100644
--- a/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
+++ b/third_party/blink/renderer/core/editing/ime/input_method_controller_test.cc
@@ -3677,7 +3677,7 @@
   Element* div =
       InsertHTMLElement("<div id='sample' contenteditable></div>", "sample");
 
-  // Add character U+200C: 'kZeroWidthNonJoinerCharacter' and Myanmar vowel
+  // Add character U+200C: 'kZeroWidthNonJoiner' and Myanmar vowel
   Controller().SetComposition(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"),
                               Vector<ImeTextSpan>(), 0, 0);
 
@@ -3691,7 +3691,7 @@
                           Vector<ImeTextSpan>(), 1);
   EXPECT_EQ(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"), div->innerHTML());
 
-  // Add character U+200C: 'kZeroWidthNonJoinerCharacter' and Myanmar vowel
+  // Add character U+200C: 'kZeroWidthNonJoiner' and Myanmar vowel
   Controller().SetComposition(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"),
                               Vector<ImeTextSpan>(), 2, 2);
   Controller().CommitText(String::FromUTF8("\xE2\x80\x8C\xE1\x80\xB1"),
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator.cc b/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
index 7406861..270534ed 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
@@ -486,7 +486,13 @@
           // sibling shadow root, if any.
           const auto* shadow_root = DynamicTo<ShadowRoot>(node_);
           if (!shadow_root) {
+#if !BUILDFLAG(IS_ANDROID)
+            // TODO(crbug.com/421311110): Hits at chrome://extensions,
+            // chrome://flags, etc.
             NOTREACHED();
+#endif  // !BUILDFLAG(IS_ANDROID)
+            should_stop_ = true;
+            return;
           }
           if (shadow_root->IsOpen()) {
             // We are the shadow root; exit from here and go back to
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
index 356e2f45..7829496c 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context.h
@@ -70,6 +70,7 @@
 
 namespace blink {
 
+class CanvasResourceProvider;
 class CanvasElementHitTestRegion;
 class ComputedStyle;
 class Document;
@@ -206,6 +207,11 @@
   }
   void DidDraw(const SkIRect& dirty_rect, CanvasPerformanceMonitor::DrawType);
 
+  virtual std::unique_ptr<CanvasResourceProvider>
+  CreateCanvasResourceProvider() {
+    NOTREACHED();
+  }
+
   // Returns a StaticBitmapImage containing the current content, or nullptr if
   // it was not possible to obtain that content.
   virtual scoped_refptr<StaticBitmapImage> PaintRenderingResultsToSnapshot(
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
index 6854f0a6..e9383d3a 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.cc
@@ -35,10 +35,6 @@
 
 namespace blink {
 
-BASE_FEATURE(kUseSharedBitmapProviderForSoftwareCompositing,
-             "UseSharedBitmapProviderForSoftwareCompositing",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 CanvasRenderingContextHost::CanvasRenderingContextHost(HostType host_type,
                                                        const gfx::Size& size)
     : CanvasResourceHost(size), host_type_(host_type) {}
@@ -171,7 +167,9 @@
   auto* provider = GetResourceProviderForWebGL();
   if (!provider && !did_fail_to_create_resource_provider_) {
     if (IsValidImageSize()) {
-      CreateCanvasResourceProviderWebGL();
+      resource_provider_for_webgl_ =
+          RenderingContext()->CreateCanvasResourceProvider();
+      UpdateMemoryUsage();
       provider = GetResourceProviderForWebGL();
     }
     if (!provider) {
@@ -219,91 +217,6 @@
   }
 }
 
-void CanvasRenderingContextHost::CreateCanvasResourceProviderWebGL() {
-  CHECK(!GetResourceProviderForWebGL());
-
-  base::WeakPtr<CanvasResourceDispatcher> dispatcher =
-      GetOrCreateResourceDispatcher()
-          ? GetOrCreateResourceDispatcher()->GetWeakPtr()
-          : nullptr;
-
-  std::unique_ptr<CanvasResourceProvider> provider;
-  const SkAlphaType alpha_type = GetRenderingContextAlphaType();
-  const viz::SharedImageFormat format = GetRenderingContextFormat();
-  const gfx::ColorSpace color_space = GetRenderingContextColorSpace();
-  // Do not initialize the CRP using Skia. The CRP can have bottom left origin
-  // in which case Skia Graphite won't be able to render into it, and WebGL is
-  // responsible for clearing the CRP when it renders anyway and we have clear
-  // rect tracking in the shared image system to enforce this.
-  constexpr auto kShouldInitialize =
-      CanvasResourceProvider::ShouldInitialize::kNo;
-  if (SharedGpuContext::IsGpuCompositingEnabled() && LowLatencyEnabled()) {
-    // If LowLatency is enabled, we need a resource that is able to perform well
-    // in such mode. It will first try a PassThrough provider and, if that is
-    // not possible, it will try a SharedImage with the appropriate flags.
-    bool using_swapchain =
-        RenderingContext() && RenderingContext()->UsingSwapChain();
-    bool using_webgl_image_chromium =
-        SharedGpuContext::MaySupportImageChromium() &&
-        (RuntimeEnabledFeatures::WebGLImageChromiumEnabled() ||
-         base::FeatureList::IsEnabled(features::kLowLatencyWebGLImageChromium));
-    if (using_swapchain || using_webgl_image_chromium) {
-      // If either SwapChain is enabled or WebGLImage mode is enabled, we can
-      // try a passthrough provider.
-      DCHECK(LowLatencyEnabled());
-      provider = CanvasResourceProvider::CreatePassThroughProvider(
-          Size(), format, alpha_type, color_space,
-          SharedGpuContext::ContextProviderWrapper(), this);
-    }
-    if (!provider) {
-      // If PassThrough failed, try a SharedImage with usage display enabled.
-      gpu::SharedImageUsageSet shared_image_usage_flags =
-          gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
-      provider = CanvasResourceProvider::CreateSharedImageProvider(
-          Size(), format, alpha_type, color_space, kShouldInitialize,
-          SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
-          shared_image_usage_flags, this);
-    }
-  } else if (SharedGpuContext::IsGpuCompositingEnabled()) {
-    // If there is no LowLatency mode, and GPU is enabled, will try a GPU
-    // SharedImage that should support Usage Display and probably Usage Scanout
-    // if WebGLImageChromium is enabled.
-    gpu::SharedImageUsageSet shared_image_usage_flags =
-        gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
-    if (SharedGpuContext::MaySupportImageChromium() &&
-        RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
-      shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
-    }
-    provider = CanvasResourceProvider::CreateSharedImageProvider(
-        Size(), format, alpha_type, color_space, kShouldInitialize,
-        SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
-        shared_image_usage_flags, this);
-  }
-
-  // If either of the other modes failed and / or it was not possible to do, we
-  // will backup with a software SharedImage, and if that was not possible with
-  // a Bitmap provider.
-  bool use_software_shared_image_provider =
-      base::FeatureList::IsEnabled(
-          kUseSharedBitmapProviderForSoftwareCompositing)
-          ? !SharedGpuContext::IsGpuCompositingEnabled()
-          : !!dispatcher;
-
-  if (!provider && use_software_shared_image_provider) {
-    provider =
-        CanvasResourceProvider::CreateSharedImageProviderForSoftwareCompositor(
-            Size(), format, alpha_type, color_space, kShouldInitialize,
-            SharedGpuContext::SharedImageInterfaceProvider(), this);
-  }
-  if (!provider) {
-    provider = CanvasResourceProvider::CreateBitmapProvider(
-        Size(), format, alpha_type, color_space, kShouldInitialize, this);
-  }
-
-  resource_provider_for_webgl_ = std::move(provider);
-  UpdateMemoryUsage();
-}
-
 void CanvasRenderingContextHost::CreateCanvasResourceProvider2D() {
   CHECK(!GetResourceProviderForCanvas2D());
 
@@ -377,13 +290,7 @@
   // If either of the other modes failed and / or it was not possible to do, we
   // will backup with a software SharedImage, and if that was not possible with
   // a Bitmap provider.
-  bool use_software_shared_image_provider =
-      base::FeatureList::IsEnabled(
-          kUseSharedBitmapProviderForSoftwareCompositing)
-          ? !SharedGpuContext::IsGpuCompositingEnabled()
-          : !!dispatcher;
-
-  if (!provider && use_software_shared_image_provider) {
+  if (!provider && !SharedGpuContext::IsGpuCompositingEnabled()) {
     // In this case, we are using CPU raster and CPU compositing. Create a
     // CanvasResourceProvider that uses a SharedImage backed by a shared-memory
     // buffer that can be written by canvas raster and read by the compositor.
diff --git a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
index ecb1cc2..8869c4d 100644
--- a/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
+++ b/third_party/blink/renderer/core/html/canvas/canvas_rendering_context_host.h
@@ -203,7 +203,6 @@
 
  private:
   void CreateCanvasResourceProvider2D();
-  void CreateCanvasResourceProviderWebGL();
   void CreateCanvasResourceProviderWebGPU();
 
   std::unique_ptr<CanvasResourceProvider> resource_provider_for_canvas2d_;
diff --git a/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc b/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
index 9f6b2d3..84168eb 100644
--- a/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/layout/grid/grid_break_token_data.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_item.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_track_sizing_algorithm.h"
+#include "third_party/blink/renderer/core/layout/layout_utils.h"
 #include "third_party/blink/renderer/core/layout/length_utils.h"
 #include "third_party/blink/renderer/core/layout/logical_box_fragment.h"
 #include "third_party/blink/renderer/core/layout/relative_utils.h"
@@ -1938,34 +1939,6 @@
 
 namespace {
 
-// Returns the alignment offset for either the inline or block direction.
-LayoutUnit AlignmentOffset(LayoutUnit container_size,
-                           LayoutUnit size,
-                           LayoutUnit margin_start,
-                           LayoutUnit margin_end,
-                           LayoutUnit baseline_offset,
-                           AxisEdge axis_edge,
-                           bool is_overflow_safe) {
-  LayoutUnit free_space = container_size - size - margin_start - margin_end;
-  // If overflow is 'safe', we have to make sure we don't overflow the
-  // 'start' edge (potentially cause some data loss as the overflow is
-  // unreachable).
-  if (is_overflow_safe)
-    free_space = free_space.ClampNegativeToZero();
-  switch (axis_edge) {
-    case AxisEdge::kStart:
-      return margin_start;
-    case AxisEdge::kCenter:
-      return margin_start + (free_space / 2);
-    case AxisEdge::kEnd:
-      return margin_start + free_space;
-    case AxisEdge::kFirstBaseline:
-    case AxisEdge::kLastBaseline:
-      return baseline_offset;
-  }
-  NOTREACHED();
-}
-
 void AlignmentOffsetForOutOfFlow(AxisEdge inline_axis_edge,
                                  AxisEdge block_axis_edge,
                                  LogicalSize container_size,
diff --git a/third_party/blink/renderer/core/layout/layout_utils.cc b/third_party/blink/renderer/core/layout/layout_utils.cc
index 9dc118d..2c1eba9 100644
--- a/third_party/blink/renderer/core/layout/layout_utils.cc
+++ b/third_party/blink/renderer/core/layout/layout_utils.cc
@@ -689,4 +689,32 @@
   return true;
 }
 
+LayoutUnit AlignmentOffset(LayoutUnit container_size,
+                           LayoutUnit size,
+                           LayoutUnit margin_start,
+                           LayoutUnit margin_end,
+                           LayoutUnit baseline_offset,
+                           AxisEdge axis_edge,
+                           bool is_overflow_safe) {
+  LayoutUnit free_space = container_size - size - margin_start - margin_end;
+  // If overflow is 'safe', we have to make sure we don't overflow the
+  // 'start' edge (potentially cause some data loss as the overflow is
+  // unreachable).
+  if (is_overflow_safe) {
+    free_space = free_space.ClampNegativeToZero();
+  }
+  switch (axis_edge) {
+    case AxisEdge::kStart:
+      return margin_start;
+    case AxisEdge::kCenter:
+      return margin_start + (free_space / 2);
+    case AxisEdge::kEnd:
+      return margin_start + free_space;
+    case AxisEdge::kFirstBaseline:
+    case AxisEdge::kLastBaseline:
+      return baseline_offset;
+  }
+  NOTREACHED();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_utils.h b/third_party/blink/renderer/core/layout/layout_utils.h
index cb831eb..9aee587 100644
--- a/third_party/blink/renderer/core/layout/layout_utils.h
+++ b/third_party/blink/renderer/core/layout/layout_utils.h
@@ -8,6 +8,7 @@
 #include <optional>
 
 #include "third_party/blink/renderer/core/layout/block_node.h"
+#include "third_party/blink/renderer/core/layout/grid/grid_item.h"
 
 namespace blink {
 
@@ -61,6 +62,15 @@
     LayoutUnit* block_offset_delta,
     MarginStrut* end_margin_strut);
 
+// Returns the alignment offset for either the inline or block direction.
+LayoutUnit AlignmentOffset(LayoutUnit container_size,
+                           LayoutUnit size,
+                           LayoutUnit margin_start,
+                           LayoutUnit margin_end,
+                           LayoutUnit baseline_offset,
+                           AxisEdge axis_edge,
+                           bool is_overflow_safe);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_LAYOUT_UTILS_H_
diff --git a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
index 6674d4f..ee8bfed6 100644
--- a/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/masonry/masonry_layout_algorithm.cc
@@ -9,6 +9,7 @@
 #include "third_party/blink/renderer/core/layout/grid/grid_item.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_track_collection.h"
 #include "third_party/blink/renderer/core/layout/grid/grid_track_sizing_algorithm.h"
+#include "third_party/blink/renderer/core/layout/layout_utils.h"
 #include "third_party/blink/renderer/core/layout/logical_box_fragment.h"
 #include "third_party/blink/renderer/core/layout/masonry/masonry_running_positions.h"
 
@@ -103,23 +104,6 @@
 
 namespace {
 
-LayoutUnit CalculateAlignmentOffset(AxisEdge alignment, LayoutUnit free_space) {
-  if (!free_space) {
-    return LayoutUnit();
-  }
-
-  switch (alignment) {
-    case AxisEdge::kCenter:
-      return free_space / 2;
-    case AxisEdge::kEnd:
-      return free_space;
-    case AxisEdge::kStart:
-      return LayoutUnit();
-    default:
-      NOTREACHED();
-  }
-}
-
 // TODO(almaher): Should we consolidate this with LayoutGridItemForMeasure()?
 const LayoutResult* LayoutMasonryItemForMeasure(
     const GridItemData& masonry_item,
@@ -197,41 +181,50 @@
         masonry_item, track_collection, &containing_rect);
 
     const auto& item_node = masonry_item.node;
+    const auto& item_style = item_node.Style();
     const auto* result = item_node.Layout(space);
     const auto& physical_fragment =
         To<PhysicalBoxFragment>(result->GetPhysicalFragment());
     const LogicalBoxFragment fragment(container_writing_direction,
                                       physical_fragment);
 
-    // Adjust item's position in the track based on style.
-    auto FreeSpace = [&]() -> LayoutUnit {
-      const auto free_space =
-          is_for_columns
-              ? containing_rect.size.inline_size - fragment.InlineSize()
-              : containing_rect.size.block_size - fragment.BlockSize();
-
-      // If overflow is 'safe', make sure we don't overflow the 'start' edge
-      // (potentially causing some data loss as the overflow is unreachable).
-      return masonry_item.IsOverflowSafe(grid_axis_direction)
-                 ? free_space.ClampNegativeToZero()
-                 : free_space;
-    };
-    const auto offset = CalculateAlignmentOffset(
-        masonry_item.Alignment(grid_axis_direction), FreeSpace());
-    (is_for_columns ? containing_rect.offset.inline_offset
-                    : containing_rect.offset.block_offset) += offset;
+    // TODO(celestepan): Account for extra margins from sub-masonry items.
+    //
+    // Adjust item's position in the track based on style. We only want offset
+    // applied to the grid axis at the moment.
+    //
+    // TODO(celestepan): Update alignment logic if needed once we resolve on
+    // https://github.com/w3c/csswg-drafts/issues/10275.
+    const auto margins = ComputeMarginsFor(space, item_style, container_space);
+    const auto inline_alignment =
+        is_for_columns ? masonry_item.Alignment(kForColumns) : AxisEdge::kStart;
+    const auto block_alignment =
+        is_for_columns ? AxisEdge::kStart : masonry_item.Alignment(kForRows);
+    containing_rect.offset += LogicalOffset(
+        AlignmentOffset(containing_rect.size.inline_size, fragment.InlineSize(),
+                        margins.inline_start, margins.inline_end,
+                        /*baseline_offset=*/LayoutUnit(), inline_alignment,
+                        masonry_item.IsOverflowSafe(kForColumns)),
+        AlignmentOffset(containing_rect.size.block_size, fragment.BlockSize(),
+                        margins.block_start, margins.block_end,
+                        /*baseline_offset=*/LayoutUnit(), block_alignment,
+                        masonry_item.IsOverflowSafe(kForRows)));
 
     // Update `running_positions` of the tracks that the items spans to include
-    // the size of the item + the size of the gap in the stacking axis.
+    // the size of the item, the size of the gap in the stacking axis, and the
+    // margin.
+    //
+    // TODO(celestepan): Once we account for writing direction, we may have to
+    // ensure that we are adding the block/inline size of the item based on
+    // whether or not it is parallel to the direction of the masonry axis.
     auto new_running_position =
         max_position + stacking_axis_gap +
-        (is_for_columns ? fragment.BlockSize() : fragment.InlineSize());
+        (is_for_columns ? fragment.BlockSize() + margins.BlockSum()
+                        : fragment.InlineSize() + margins.InlineSum());
     running_positions.UpdateRunningPositionsForSpan(item_span,
                                                     new_running_position);
 
-    container_builder_.AddResult(
-        *result, containing_rect.offset,
-        ComputeMarginsFor(space, item_node.Style(), container_space));
+    container_builder_.AddResult(*result, containing_rect.offset, margins);
   }
   if (is_for_columns) {
     // Remove last gap that was added, since there is no item after it.
diff --git a/third_party/blink/renderer/core/style/position_try_fallbacks.cc b/third_party/blink/renderer/core/style/position_try_fallbacks.cc
index f51f69dc29..8f221b1 100644
--- a/third_party/blink/renderer/core/style/position_try_fallbacks.cc
+++ b/third_party/blink/renderer/core/style/position_try_fallbacks.cc
@@ -12,6 +12,23 @@
          position_area_ == other.position_area_;
 }
 
+bool PositionTryFallback::Matches(const PositionTryFallback& other) const {
+  AtomicString name;
+  AtomicString other_name;
+  // TODO(crbug.com/417621241): Currently, TreeScope is ignored, which means
+  // anchored(fallback: --foo) will match --foo from any tree, regardless of
+  // where the @container rule or position-try-fallbacks property value
+  // originates from.
+  if (position_try_name_) {
+    name = position_try_name_->GetName();
+  }
+  if (other.position_try_name_) {
+    other_name = other.position_try_name_->GetName();
+  }
+  return tactic_list_ == other.tactic_list_ && name == other_name &&
+         position_area_ == other.position_area_;
+}
+
 void PositionTryFallback::Trace(Visitor* visitor) const {
   visitor->Trace(position_try_name_);
 }
diff --git a/third_party/blink/renderer/core/style/position_try_fallbacks.h b/third_party/blink/renderer/core/style/position_try_fallbacks.h
index aee1eb84..6fdd3d3 100644
--- a/third_party/blink/renderer/core/style/position_try_fallbacks.h
+++ b/third_party/blink/renderer/core/style/position_try_fallbacks.h
@@ -39,6 +39,12 @@
 
   bool operator==(const PositionTryFallback& other) const;
 
+  // Returns true if this fallback matches 'other' for anchored(fallback)
+  // container queries. This differs from operator== in that this method handles
+  // tree-scoped names per spec, and does not require the TreeScopes to be the
+  // same when matching @position-try names.
+  bool Matches(const PositionTryFallback& other) const;
+
   bool IsNone() const {
     return !position_try_name_ && tactic_list_[0] == TryTactic::kNone &&
            position_area_.IsNone();
diff --git a/third_party/blink/renderer/core/xml/parser/xml_document_parser.cc b/third_party/blink/renderer/core/xml/parser/xml_document_parser.cc
index 7cbaf11a..080bb17 100644
--- a/third_party/blink/renderer/core/xml/parser/xml_document_parser.cc
+++ b/third_party/blink/renderer/core/xml/parser/xml_document_parser.cc
@@ -1491,9 +1491,9 @@
   auto utf16_entity = base::span(entity.data).first(entity.length);
   auto entity_buffer =
       base::as_writable_bytes(base::span(g_shared_xhtml_entity_result));
-  WTF::unicode::ConversionResult conversion_result =
-      WTF::unicode::ConvertUTF16ToUTF8(utf16_entity, entity_buffer);
-  if (conversion_result.status != WTF::unicode::kConversionOK) {
+  unicode::ConversionResult conversion_result =
+      unicode::ConvertUtf16ToUtf8(utf16_entity, entity_buffer);
+  if (conversion_result.status != unicode::kConversionOK) {
     return {};
   }
 
diff --git a/third_party/blink/renderer/core/xml/xslt_processor_libxslt.cc b/third_party/blink/renderer/core/xml/xslt_processor_libxslt.cc
index ebfe0279..e7fbb09 100644
--- a/third_party/blink/renderer/core/xml/xslt_processor_libxslt.cc
+++ b/third_party/blink/renderer/core/xml/xslt_processor_libxslt.cc
@@ -193,10 +193,10 @@
       UNSAFE_BUFFERS(base::span(buffer, base::checked_cast<size_t>(len)));
 
   StringBuffer<UChar> string_buffer(len);
-  WTF::unicode::ConversionResult result = WTF::unicode::ConvertUTF8ToUTF16(
+  unicode::ConversionResult result = unicode::ConvertUtf8ToUtf16(
       base::as_bytes(source_buffer), string_buffer.Span());
-  CHECK(result.status == WTF::unicode::kConversionOK ||
-        result.status == WTF::unicode::kSourceExhausted);
+  CHECK(result.status == unicode::kConversionOK ||
+        result.status == unicode::kSourceExhausted);
 
   StringBuilder& result_output = *static_cast<StringBuilder*>(context);
   result_output.Append(result.converted);
diff --git a/third_party/blink/renderer/modules/storage/cached_storage_area.cc b/third_party/blink/renderer/modules/storage/cached_storage_area.cc
index 7f5c363..2aa99d1 100644
--- a/third_party/blink/renderer/modules/storage/cached_storage_area.cc
+++ b/third_party/blink/renderer/modules/storage/cached_storage_area.cc
@@ -813,11 +813,10 @@
           return Vector<uint8_t>();
         Vector<uint8_t> buffer_vector(length * 3);
 
-        WTF::unicode::ConversionResult result =
-            WTF::unicode::ConvertLatin1ToUTF8(input.Span8(),
-                                              base::span(buffer_vector));
+        unicode::ConversionResult result = unicode::ConvertLatin1ToUtf8(
+            input.Span8(), base::span(buffer_vector));
         // (length * 3) should be sufficient for any conversion
-        DCHECK_NE(result.status, WTF::unicode::kTargetExhausted);
+        DCHECK_NE(result.status, unicode::kTargetExhausted);
         buffer_vector.Shrink(static_cast<wtf_size_t>(result.converted.size()));
         return buffer_vector;
       }
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
index d01b1e2..254cc63 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc
@@ -1908,6 +1908,84 @@
   return nullptr;
 }
 
+std::unique_ptr<CanvasResourceProvider>
+WebGLRenderingContextBase::CreateCanvasResourceProvider() {
+  base::WeakPtr<CanvasResourceDispatcher> dispatcher =
+      Host()->GetOrCreateResourceDispatcher()
+          ? Host()->GetOrCreateResourceDispatcher()->GetWeakPtr()
+          : nullptr;
+
+  std::unique_ptr<CanvasResourceProvider> provider;
+  const SkAlphaType alpha_type = GetAlphaType();
+  const viz::SharedImageFormat format = GetSharedImageFormat();
+  const gfx::ColorSpace color_space = GetColorSpace();
+  // Do not initialize the CRP using Skia. The CRP can have bottom left origin
+  // in which case Skia Graphite won't be able to render into it, and WebGL is
+  // responsible for clearing the CRP when it renders anyway and we have clear
+  // rect tracking in the shared image system to enforce this.
+  constexpr auto kShouldInitialize =
+      CanvasResourceProvider::ShouldInitialize::kNo;
+  if (SharedGpuContext::IsGpuCompositingEnabled() &&
+      Host()->LowLatencyEnabled()) {
+    // If LowLatency is enabled, we need a resource that is able to perform well
+    // in such mode. It will first try a PassThrough provider and, if that is
+    // not possible, it will try a SharedImage with the appropriate flags.
+    bool using_swapchain = UsingSwapChain();
+    bool using_webgl_image_chromium =
+        SharedGpuContext::MaySupportImageChromium() &&
+        (RuntimeEnabledFeatures::WebGLImageChromiumEnabled() ||
+         base::FeatureList::IsEnabled(features::kLowLatencyWebGLImageChromium));
+    if (using_swapchain || using_webgl_image_chromium) {
+      // If either SwapChain is enabled or WebGLImage mode is enabled, we can
+      // try a passthrough provider.
+      DCHECK(Host()->LowLatencyEnabled());
+      provider = CanvasResourceProvider::CreatePassThroughProvider(
+          Host()->Size(), format, alpha_type, color_space,
+          SharedGpuContext::ContextProviderWrapper(), Host());
+    }
+    if (!provider) {
+      // If PassThrough failed, try a SharedImage with usage display enabled.
+      gpu::SharedImageUsageSet shared_image_usage_flags =
+          gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
+      provider = CanvasResourceProvider::CreateSharedImageProvider(
+          Host()->Size(), format, alpha_type, color_space, kShouldInitialize,
+          SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
+          shared_image_usage_flags, Host());
+    }
+  } else if (SharedGpuContext::IsGpuCompositingEnabled()) {
+    // If there is no LowLatency mode, and GPU is enabled, will try a GPU
+    // SharedImage that should support Usage Display and probably Usage Scanout
+    // if WebGLImageChromium is enabled.
+    gpu::SharedImageUsageSet shared_image_usage_flags =
+        gpu::SHARED_IMAGE_USAGE_DISPLAY_READ;
+    if (SharedGpuContext::MaySupportImageChromium() &&
+        RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) {
+      shared_image_usage_flags |= gpu::SHARED_IMAGE_USAGE_SCANOUT;
+    }
+    provider = CanvasResourceProvider::CreateSharedImageProvider(
+        Host()->Size(), format, alpha_type, color_space, kShouldInitialize,
+        SharedGpuContext::ContextProviderWrapper(), RasterMode::kGPU,
+        shared_image_usage_flags, Host());
+  }
+
+  // If either of the other modes failed and / or it was not possible to do, we
+  // will backup with a software SharedImage, and if that was not possible with
+  // a Bitmap provider.
+  if (!provider && !SharedGpuContext::IsGpuCompositingEnabled()) {
+    provider =
+        CanvasResourceProvider::CreateSharedImageProviderForSoftwareCompositor(
+            Host()->Size(), format, alpha_type, color_space, kShouldInitialize,
+            SharedGpuContext::SharedImageInterfaceProvider(), Host());
+  }
+  if (!provider) {
+    provider = CanvasResourceProvider::CreateBitmapProvider(
+        Host()->Size(), format, alpha_type, color_space, kShouldInitialize,
+        Host());
+  }
+
+  return provider;
+}
+
 CanvasResourceProvider*
 WebGLRenderingContextBase::PaintRenderingResultsToCanvas(
     SourceDrawingBuffer source_buffer,
@@ -9068,6 +9146,10 @@
     }
   }
 
+  if (!Host()) {
+    return buffer_count;
+  }
+
   auto* provider = Host()->GetResourceProviderForWebGL();
   if (provider) {
     buffer_count++;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
index 27c0eaf..c182f60 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.h
@@ -714,6 +714,8 @@
   bool IsAccelerated() const override;
   bool UsingSwapChain() const override;
   void PageVisibilityChanged() override;
+  std::unique_ptr<CanvasResourceProvider> CreateCanvasResourceProvider()
+      override;
   scoped_refptr<StaticBitmapImage> PaintRenderingResultsToSnapshot(
       SourceDrawingBuffer source_buffer,
       FlushReason reason) override;
diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.cc
index d8fa1d8..e7903b7 100644
--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_webgpu_base.cc
@@ -3493,8 +3493,13 @@
 
 int WebGLRenderingContextWebGPUBase::AllocatedBufferCountPerPixel() {
   // Front and back buffers.
+  // TODO(413078308): Add support configuring MSAA and depth-stencil.
   int buffer_count = 2;
 
+  if (!Host()) {
+    return buffer_count;
+  }
+
   auto* provider = Host()->GetResourceProviderForWebGL();
   if (provider) {
     buffer_count++;
@@ -3507,7 +3512,6 @@
     }
   }
 
-  // TODO(413078308): Add support configuring MSAA and depth-stencil.
   return buffer_count;
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
index a4de956b..b3f4416 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
@@ -173,7 +173,7 @@
       resource_provider_(std::move(resource_provider)),
       roughness_reporter_(std::make_unique<cc::VideoPlaybackRoughnessReporter>(
           std::move(roughness_reporting_callback))),
-      frame_trackers_(false, nullptr) {
+      frame_trackers_(false) {
   frame_sorter_.AddObserver(&frame_trackers_);
   DETACH_FROM_THREAD(thread_checker_);
 }
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
index acc48c8..c72713e4 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_encoder.cc
@@ -2112,7 +2112,8 @@
   // The timestamp is set later in EncodeOneFrame().
   auto frame = media::VideoFrame::WrapExternalData(
       media::PIXEL_FORMAT_I420, input_frame_coded_size_,
-      gfx::Rect(input_visible_size_), input_visible_size_, mapping,
+      gfx::Rect(input_visible_size_), input_visible_size_,
+      static_cast<uint8_t*>(mapping.memory()), mapping.size(),
       base::TimeDelta());
   if (!frame) {
     NotifyErrorStatus({media::EncoderStatus::Codes::kEncoderFailedEncode,
diff --git a/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc b/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
index 50ac61d6..a49c2bc 100644
--- a/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
+++ b/third_party/blink/renderer/platform/video_capture/video_capture_impl.cc
@@ -48,6 +48,7 @@
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 #include "third_party/blink/renderer/platform/wtf/thread_safe_ref_counted.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
+#include "ui/gfx/gpu_memory_buffer.h"
 
 #if BUILDFLAG(IS_MAC)
 #include "media/base/mac/video_frame_mac.h"
@@ -64,17 +65,14 @@
 using VideoFrameBufferHandleType = media::mojom::blink::VideoBufferHandle::Tag;
 
 // A collection of all types of handles that we use to reference a camera buffer
-// backed with GpuMemoryBuffer.
+// backed with GpuMemoryBufferHandle.
 struct GpuMemoryBufferResources {
   explicit GpuMemoryBufferResources(gfx::GpuMemoryBufferHandle handle)
       : gpu_memory_buffer_handle(std::move(handle)) {}
   // Stores the GpuMemoryBufferHandle when a new buffer is first registered.
-  // |gpu_memory_buffer_handle| is converted to |gpu_memory_buffer| below when
+  // |gpu_memory_buffer_handle| is converted to |shared_image| below when
   // the camera frame is ready for the first time.
   gfx::GpuMemoryBufferHandle gpu_memory_buffer_handle;
-  // The GpuMemoryBuffer backing the camera frame.
-  std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer;
-  // The SharedImage created from |gpu_memory_buffer|.
   scoped_refptr<gpu::ClientSharedImage> shared_image;
   // The release sync token for |shared_images|.
   gpu::SyncToken release_sync_token;
@@ -142,14 +140,6 @@
     return gmb_resources_->gpu_memory_buffer_handle.Clone();
   }
 
-  void SetGpuMemoryBuffer(
-      std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer) {
-    gmb_resources_->gpu_memory_buffer = std::move(gpu_memory_buffer);
-  }
-  gfx::GpuMemoryBuffer* GetGpuMemoryBuffer() {
-    return gmb_resources_->gpu_memory_buffer.get();
-  }
-
   static void MailboxHolderReleased(
       scoped_refptr<BufferContext> buffer_context,
       const gpu::SyncToken& release_sync_token,
@@ -161,8 +151,8 @@
                          release_sync_token, std::move(gpu_memory_buffer)));
       return;
     }
+    CHECK(!gpu_memory_buffer);
     buffer_context->gmb_resources_->release_sync_token = release_sync_token;
-    // Free |gpu_memory_buffer|.
   }
 
   static void DestroyTextureOnMediaThread(
@@ -453,9 +443,9 @@
           gmb_handle.type == gfx::GpuMemoryBufferType::DXGI_SHARED_HANDLE;
 #endif
 
-      // Convert the GpuMemoryBuffer to a VideoFrame by posting a task on media
-      // thread. This is because SharedImageInterface is only accessible on
-      // media thread.
+      // Convert the GpuMemoryBufferHandle to a VideoFrame by posting a task on
+      // media thread. This is because SharedImageInterface is only accessible
+      // on media thread.
       media_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(
@@ -532,7 +522,7 @@
   }
 #endif
 
-  // Create GPU texture and bind GpuMemoryBuffer to the texture.
+  // Create GPU texture and bind GpuMemoryBufferHandle to the texture.
   auto* sii = video_frame_init_data.buffer_context->gpu_factories()
                   ->SharedImageInterface();
   if (!sii) {
@@ -607,7 +597,7 @@
       video_frame_init_data.ready_buffer->info->timestamp);
 
   if (!frame) {
-    LOG(ERROR) << "Can't wrap GpuMemoryBuffer as VideoFrame";
+    LOG(ERROR) << "Can't wrap SharedImage as VideoFrame";
     return false;
   }
   frame->set_metadata(video_frame_init_data.ready_buffer->info->metadata);
@@ -956,7 +946,7 @@
   // Process the `buffer` to convert it into a media::VideoFrame directly or via
   // creating GpuMemoryBuffers.
   if (!ProcessBuffer(std::move(buffer))) {
-    // Error during initialization of the VideoFrame or GpuMemoryBuffer.
+    // Error during initialization of the VideoFrame or SharedImage.
     OnFrameDropped(media::VideoCaptureFrameDropReason::
                        kVideoCaptureImplFailedToWrapDataAsMediaVideoFrame);
     GetVideoCaptureHost()->ReleaseBuffer(device_id_, buffer_id,
@@ -973,7 +963,7 @@
   scoped_refptr<media::VideoFrame> video_frame = video_frame_init_data.frame;
 
   // If we don't have a media::VideoFrame here then we've failed to convert the
-  // gfx::GpuMemoryBuffer, dropping frame.
+  // gfx::GpuMemoryBufferHandle, dropping frame.
   if (!video_frame) {
     OnFrameDropped(media::VideoCaptureFrameDropReason::
                        kVideoCaptureImplFailedToWrapDataAsMediaVideoFrame);
@@ -1012,9 +1002,9 @@
 
   const auto& cb_iter = client_buffers_.find(buffer_id);
   if (cb_iter != client_buffers_.end()) {
-    // If the BufferContext is non-null, the GpuMemoryBuffer-backed frames can
-    // have more than one reference (held by MailboxHolderReleased). Otherwise,
-    // only one reference should be held.
+    // If the BufferContext is non-null, the GpuMemoryBufferHandle-backed frames
+    // can have more than one reference (held by MailboxHolderReleased).
+    // Otherwise, only one reference should be held.
     DCHECK(!cb_iter->second.get() ||
            cb_iter->second->buffer_type() ==
                VideoFrameBufferHandleType::kGpuMemoryBufferHandle ||
@@ -1062,7 +1052,7 @@
   BufferContext* const buffer_raw_ptr = buffer_context.get();
   buffer_context = nullptr;
   // For non-GMB case, there should be only one reference, from
-  // |client_buffers_|. This DCHECK is invalid for GpuMemoryBuffer backed
+  // |client_buffers_|. This DCHECK is invalid for GpuMemoryBufferHandle-backed
   // frames, because MailboxHolderReleased may hold on to a reference to
   // |buffer_context|.
   if (buffer_raw_ptr->buffer_type() !=
diff --git a/third_party/blink/renderer/platform/wtf/text/atomic_string_table.cc b/third_party/blink/renderer/platform/wtf/text/atomic_string_table.cc
index 47a691b1..5baa889 100644
--- a/third_party/blink/renderer/platform/wtf/text/atomic_string_table.cc
+++ b/third_party/blink/renderer/platform/wtf/text/atomic_string_table.cc
@@ -445,15 +445,15 @@
   bool seen_non_ascii = false;
   bool seen_non_latin1 = false;
 
-  unsigned utf16_length = unicode::CalculateStringLengthFromUTF8(
+  unsigned utf16_length = blink::unicode::CalculateStringLengthFromUtf8(
       characters_span, seen_non_ascii, seen_non_latin1);
   if (!seen_non_ascii) {
     return Add(characters_span.data(), utf16_length);
   }
 
   auto utf16_buf = base::HeapArray<UChar>::Uninit(utf16_length);
-  if (unicode::ConvertUTF8ToUTF16(characters_span, utf16_buf).status !=
-      unicode::kConversionOK) {
+  if (blink::unicode::ConvertUtf8ToUtf16(characters_span, utf16_buf).status !=
+      blink::unicode::kConversionOK) {
     NOTREACHED();
   }
 
diff --git a/third_party/blink/renderer/platform/wtf/text/string_view.cc b/third_party/blink/renderer/platform/wtf/text/string_view.cc
index 61a4083..47a54b2 100644
--- a/third_party/blink/renderer/platform/wtf/text/string_view.cc
+++ b/third_party/blink/renderer/platform/wtf/text/string_view.cc
@@ -65,6 +65,8 @@
 }
 
 std::string StringView::Utf8(Utf8ConversionMode mode) const {
+  using blink::unicode::ConversionResult;
+  using blink::unicode::ConversionStatus;
   unsigned length = this->length();
 
   if (!length)
@@ -86,10 +88,10 @@
   size_t buffer_written = 0;
 
   if (Is8Bit()) {
-    unicode::ConversionResult result = unicode::ConvertLatin1ToUTF8(
+    ConversionResult result = blink::unicode::ConvertLatin1ToUtf8(
         Span8(), base::as_writable_byte_span(buffer_vector));
     // (length * 3) should be sufficient for any conversion
-    DCHECK_NE(result.status, unicode::kTargetExhausted);
+    DCHECK_NE(result.status, ConversionStatus::kTargetExhausted);
     buffer_written = result.converted.size();
   } else {
     base::span<const UChar> characters = Span16();
@@ -98,14 +100,14 @@
     if (mode == Utf8ConversionMode::kStrictReplacingErrors) {
       while (!characters.empty()) {
         // Use strict conversion to detect unpaired surrogates.
-        unicode::ConversionResult result =
-            unicode::ConvertUTF16ToUTF8(characters, buffer, true);
-        DCHECK_NE(result.status, unicode::kTargetExhausted);
+        ConversionResult result =
+            blink::unicode::ConvertUtf16ToUtf8(characters, buffer, true);
+        DCHECK_NE(result.status, ConversionStatus::kTargetExhausted);
         buffer = buffer.subspan(result.converted.size());
         // Conversion fails when there is an unpaired surrogate.  Put
         // replacement character (U+FFFD) instead of the unpaired
         // surrogate.
-        if (result.status != unicode::kConversionOK) {
+        if (result.status != ConversionStatus::kConversionOK) {
           DCHECK_LE(0xD800, characters[result.consumed]);
           DCHECK_LE(characters[result.consumed], 0xDFFF);
           // There should be room left, since one UChar hasn't been
@@ -122,19 +124,19 @@
     } else {
       const bool strict = mode == Utf8ConversionMode::kStrict;
 
-      unicode::ConversionResult result =
-          unicode::ConvertUTF16ToUTF8(characters, buffer, strict);
+      ConversionResult result =
+          blink::unicode::ConvertUtf16ToUtf8(characters, buffer, strict);
       // (length * 3) should be sufficient for any conversion
-      DCHECK_NE(result.status, unicode::kTargetExhausted);
+      DCHECK_NE(result.status, ConversionStatus::kTargetExhausted);
 
       // Only produced from strict conversion.
-      if (result.status == unicode::kSourceIllegal) {
+      if (result.status == ConversionStatus::kSourceIllegal) {
         DCHECK(strict);
         return std::string();
       }
 
       // Check for an unconverted high surrogate.
-      if (result.status == unicode::kSourceExhausted) {
+      if (result.status == ConversionStatus::kSourceExhausted) {
         if (strict)
           return std::string();
         buffer = buffer.subspan(result.converted.size());
diff --git a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
index ec6de128..3a79a37c 100644
--- a/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
+++ b/third_party/blink/renderer/platform/wtf/text/text_codec_icu.cc
@@ -451,7 +451,7 @@
   // ICU decodes it as U+E5E5.
   if (encoding_.GetName() != "GBK") {
     if (EqualIgnoringASCIICase(encoding_.GetName(), "gb18030"))
-      result_string.Replace(0xE5E5, kIdeographicSpaceCharacter);
+      result_string.Replace(0xE5E5, uchar::kIdeographicSpace);
     // Make GBK compliant to the encoding spec and align with GB18030
     result_string.Replace(0x01F9, 0xE7C8);
     // FIXME: Once https://www.w3.org/Bugs/Public/show_bug.cgi?id=28740#c3
diff --git a/third_party/blink/renderer/platform/wtf/text/utf8.cc b/third_party/blink/renderer/platform/wtf/text/utf8.cc
index 616e8b9..dd49bbd7 100644
--- a/third_party/blink/renderer/platform/wtf/text/utf8.cc
+++ b/third_party/blink/renderer/platform/wtf/text/utf8.cc
@@ -34,12 +34,11 @@
 #include "third_party/blink/renderer/platform/wtf/text/character_names.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_hasher.h"
 
-namespace WTF {
-namespace unicode {
+namespace blink::unicode {
 
 namespace {
 
-inline size_t InlineUTF8SequenceLengthNonASCII(uint8_t b0) {
+inline size_t InlineUtf8SequenceLengthNonAscii(uint8_t b0) {
   if ((b0 & 0xC0) != 0xC0)
     return 0;
   if ((b0 & 0xE0) == 0xC0)
@@ -51,8 +50,8 @@
   return 0;
 }
 
-inline size_t InlineUTF8SequenceLength(uint8_t b0) {
-  return IsASCII(b0) ? 1 : InlineUTF8SequenceLengthNonASCII(b0);
+inline size_t InlineUtf8SequenceLength(uint8_t b0) {
+  return IsASCII(b0) ? 1 : InlineUtf8SequenceLengthNonAscii(b0);
 }
 
 // Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
@@ -63,7 +62,7 @@
 static constexpr std::array<uint8_t, 7> kFirstByteMark = {
     0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC};
 
-ConversionStatus ConvertLatin1ToUTF8Internal(base::span<const LChar>& source,
+ConversionStatus ConvertLatin1ToUtf8Internal(base::span<const LChar>& source,
                                              base::span<uint8_t>& target) {
   ConversionStatus status = kConversionOK;
   size_t source_cursor = 0;
@@ -109,7 +108,7 @@
   return status;
 }
 
-ConversionStatus ConvertUTF16ToUTF8Internal(base::span<const UChar>& source,
+ConversionStatus ConvertUtf16ToUtf8Internal(base::span<const UChar>& source,
                                             base::span<uint8_t>& target,
                                             bool strict) {
   ConversionStatus status = kConversionOK;
@@ -204,7 +203,7 @@
 // This must be called with the length pre-determined by the first byte.
 // If presented with a length > 4, this returns false.  The Unicode
 // definition of UTF-8 goes up to 4-byte sequences.
-bool IsLegalUTF8(const base::span<const uint8_t> source) {
+bool IsLegalUtf8(const base::span<const uint8_t> source) {
   uint8_t a;
   size_t src_cursor = source.size();
   switch (source.size()) {
@@ -263,7 +262,7 @@
 // Magic values subtracted from a buffer value during UTF8 conversion.
 // This table contains as many values as there might be trailing bytes
 // in a UTF-8 sequence.
-static constexpr std::array<UChar32, 6> kOffsetsFromUTF8 = {
+static constexpr std::array<UChar32, 6> kOffsetsFromUtf8 = {
     0x00000000UL,
     0x00003080UL,
     0x000E2080UL,
@@ -271,7 +270,7 @@
     static_cast<UChar32>(0xFA082080UL),
     static_cast<UChar32>(0x82082080UL)};
 
-inline UChar32 ReadUTF8Sequence(base::span<const uint8_t> source,
+inline UChar32 ReadUtf8Sequence(base::span<const uint8_t> source,
                                 size_t length) {
   UChar32 character = 0;
   size_t sequence_cursor = 0;
@@ -301,10 +300,10 @@
       character += source[sequence_cursor++];
   }
 
-  return character - kOffsetsFromUTF8[length - 1];
+  return character - kOffsetsFromUtf8[length - 1];
 }
 
-ConversionStatus ConvertUTF8ToUTF16Internal(base::span<const uint8_t>& source,
+ConversionStatus ConvertUtf8ToUtf16Internal(base::span<const uint8_t>& source,
                                             base::span<UChar>& target,
                                             bool strict) {
   ConversionStatus status = kConversionOK;
@@ -312,19 +311,19 @@
   size_t target_end = target.size();
 
   while (!source.empty()) {
-    size_t utf8_sequence_length = InlineUTF8SequenceLength(source[0]);
+    size_t utf8_sequence_length = InlineUtf8SequenceLength(source[0]);
     if (source.size() < utf8_sequence_length) {
       status = kSourceExhausted;
       break;
     }
     // Do this check whether lenient or strict
-    if (!IsLegalUTF8(source.first(utf8_sequence_length))) {
+    if (!IsLegalUtf8(source.first(utf8_sequence_length))) {
       status = kSourceIllegal;
       break;
     }
 
     auto original_source = source;
-    UChar32 character = ReadUTF8Sequence(source, utf8_sequence_length);
+    UChar32 character = ReadUtf8Sequence(source, utf8_sequence_length);
     source = source.subspan(utf8_sequence_length);
 
     if (target_cursor >= target_end) {
@@ -368,11 +367,11 @@
 
 }  // namespace
 
-ConversionResult<uint8_t> ConvertLatin1ToUTF8(base::span<const LChar> source,
+ConversionResult<uint8_t> ConvertLatin1ToUtf8(base::span<const LChar> source,
                                               base::span<uint8_t> target) {
   auto original_source = source;
   auto original_target = target;
-  auto status = ConvertLatin1ToUTF8Internal(source, target);
+  auto status = ConvertLatin1ToUtf8Internal(source, target);
   return {
       original_target.first(original_target.size() - target.size()),
       original_source.size() - source.size(),
@@ -380,12 +379,12 @@
   };
 }
 
-ConversionResult<uint8_t> ConvertUTF16ToUTF8(base::span<const UChar> source,
+ConversionResult<uint8_t> ConvertUtf16ToUtf8(base::span<const UChar> source,
                                              base::span<uint8_t> target,
                                              bool strict) {
   auto original_source = source;
   auto original_target = target;
-  auto status = ConvertUTF16ToUTF8Internal(source, target, strict);
+  auto status = ConvertUtf16ToUtf8Internal(source, target, strict);
   return {
       original_target.first(original_target.size() - target.size()),
       original_source.size() - source.size(),
@@ -393,12 +392,12 @@
   };
 }
 
-ConversionResult<UChar> ConvertUTF8ToUTF16(base::span<const uint8_t> source,
+ConversionResult<UChar> ConvertUtf8ToUtf16(base::span<const uint8_t> source,
                                            base::span<UChar> target,
                                            bool strict) {
   auto original_source = source;
   auto original_target = target;
-  auto status = ConvertUTF8ToUTF16Internal(source, target, strict);
+  auto status = ConvertUtf8ToUtf16Internal(source, target, strict);
   return {
       original_target.first(original_target.size() - target.size()),
       original_source.size() - source.size(),
@@ -406,7 +405,7 @@
   };
 }
 
-unsigned CalculateStringLengthFromUTF8(base::span<const uint8_t> data,
+unsigned CalculateStringLengthFromUtf8(base::span<const uint8_t> data,
                                        bool& seen_non_ascii,
                                        bool& seen_non_latin1) {
   seen_non_ascii = false;
@@ -429,18 +428,18 @@
 
     seen_non_ascii = true;
     size_t utf8_sequence_length =
-        InlineUTF8SequenceLengthNonASCII(data[data_cursor]);
+        InlineUtf8SequenceLengthNonAscii(data[data_cursor]);
 
     if (data_end - data_cursor < utf8_sequence_length) {
       return 0;
     }
 
-    if (!IsLegalUTF8(data.subspan(data_cursor, utf8_sequence_length))) {
+    if (!IsLegalUtf8(data.subspan(data_cursor, utf8_sequence_length))) {
       return 0;
     }
 
     UChar32 character =
-        ReadUTF8Sequence(data.subspan(data_cursor), utf8_sequence_length);
+        ReadUtf8Sequence(data.subspan(data_cursor), utf8_sequence_length);
     DCHECK(!IsASCII(character));
     data_cursor += utf8_sequence_length;
 
@@ -464,5 +463,4 @@
   return utf16_length;
 }
 
-}  // namespace unicode
-}  // namespace WTF
+}  // namespace blink::unicode
diff --git a/third_party/blink/renderer/platform/wtf/text/utf8.h b/third_party/blink/renderer/platform/wtf/text/utf8.h
index 666d518..717b4566 100644
--- a/third_party/blink/renderer/platform/wtf/text/utf8.h
+++ b/third_party/blink/renderer/platform/wtf/text/utf8.h
@@ -30,8 +30,7 @@
 #include "third_party/blink/renderer/platform/wtf/text/wtf_uchar.h"
 #include "third_party/blink/renderer/platform/wtf/wtf_export.h"
 
-namespace WTF {
-namespace unicode {
+namespace blink::unicode {
 
 typedef enum {
   kConversionOK,     // conversion successful
@@ -66,27 +65,26 @@
 // 0x10FFFF; in UTF-8, the 4-byte form is similarly unable to encode codepoints
 // higher than 0x10FFFF.
 
-WTF_EXPORT ConversionResult<UChar> ConvertUTF8ToUTF16(
+WTF_EXPORT ConversionResult<UChar> ConvertUtf8ToUtf16(
     base::span<const uint8_t> source,
     base::span<UChar> target,
     bool strict = true);
 
-WTF_EXPORT ConversionResult<uint8_t> ConvertLatin1ToUTF8(
+WTF_EXPORT ConversionResult<uint8_t> ConvertLatin1ToUtf8(
     base::span<const LChar> source,
     base::span<uint8_t> target);
 
-WTF_EXPORT ConversionResult<uint8_t> ConvertUTF16ToUTF8(
+WTF_EXPORT ConversionResult<uint8_t> ConvertUtf16ToUtf8(
     base::span<const UChar> source,
     base::span<uint8_t> target,
     bool strict = true);
 
 // Returns the number of UTF-16 code points.
-WTF_EXPORT unsigned CalculateStringLengthFromUTF8(
+WTF_EXPORT unsigned CalculateStringLengthFromUtf8(
     base::span<const uint8_t> data,
     bool& seen_non_ascii,
     bool& seen_non_latin1);
 
-}  // namespace unicode
-}  // namespace WTF
+}  // namespace blink::unicode
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_WTF_TEXT_UTF8_H_
diff --git a/third_party/blink/renderer/platform/wtf/text/wtf_string.cc b/third_party/blink/renderer/platform/wtf/text/wtf_string.cc
index 445e620..8f0135d2 100644
--- a/third_party/blink/renderer/platform/wtf/text/wtf_string.cc
+++ b/third_party/blink/renderer/platform/wtf/text/wtf_string.cc
@@ -474,9 +474,9 @@
 
   Vector<UChar, 1024> buffer(length);
 
-  unicode::ConversionResult result =
-      unicode::ConvertUTF8ToUTF16(bytes, base::span(buffer));
-  if (result.status != unicode::kConversionOK) {
+  blink::unicode::ConversionResult result =
+      blink::unicode::ConvertUtf8ToUtf16(bytes, base::span(buffer));
+  if (result.status != blink::unicode::kConversionOK) {
     return String();
   }
 
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index 6037da6..ebb0515 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -1897,3 +1897,9 @@
 crbug.com/41442253 [ Fuchsia ] external/wpt/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html [ Skip ]
 crbug.com/41442253 [ Mac ] external/wpt/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html [ Skip ]
 crbug.com/41442253 [ iOS18-simulator ] external/wpt/clipboard-apis/async-navigator-clipboard-change-event.tentative.https.html [ Skip ]
+
+# These tests are run exclusively in the prefetch-proxy VirtualTestSuite.
+virtual/prefetch/external/wpt/speculation-rules/prefetch/anonymous-client.https.html [ Skip ]
+virtual/prefetch/external/wpt/speculation-rules/prefetch/cross-origin-cookies-anonymous-client-ip-duplicate.https.html [ Skip ]
+virtual/prefetch-sw/external/wpt/speculation-rules/prefetch/anonymous-client.https.html [ Skip ]
+virtual/prefetch-sw/external/wpt/speculation-rules/prefetch/cross-origin-cookies-anonymous-client-ip-duplicate.https.html [ Skip ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index b9d4474..b4a909fb 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1124,17 +1124,12 @@
 crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/item-placement/masonry-item-placement-005.html [ Failure ]
 crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/item-placement/masonry-item-placement-006.html [ Failure ]
 crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/item-placement/masonry-item-placement-007.html [ Failure ]
-# Masonry tests failing because of missing margins during item placement
-crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/gap/masonry-gap-001.html [ Failure ]
-crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/gap/masonry-gap-002.html [ Failure ]
-crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/item-placement/masonry-item-placement-003.html [ Failure ]
-crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/order/masonry-order-001.html [ Failure ]
-crbug.com/1076027 wpt_internal/css/css-masonry/column-explicit-placement-001.html [ Failure ]
-crbug.com/1076027 wpt_internal/css/css-masonry/row-explicit-placement-002.html [ Failure ]
-crbug.com/1076027 wpt_internal/css/css-masonry/row-explicit-placement-006.html [ Failure ]
 # Masonry sizing test failures
 crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/intrinsic-sizing/* [ Crash Failure Skip ]
 crbug.com/1076027 wpt_internal/css/css-masonry/column-explicit-placement-002.html [ Failure ]
+crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/order/masonry-order-001.html [ Failure ]
+crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/gap/masonry-gap-002.html [ Failure ]
+crbug.com/1076027 wpt_internal/css/css-masonry/row-explicit-placement-006.html [ Failure ]
 # Masonry named line failures
 crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/grid-placement/masonry-grid-placement-named-lines-001.html [ Crash Failure ]
 crbug.com/1076027 external/wpt/css/css-grid/masonry/tentative/grid-placement/masonry-grid-placement-named-lines-002.html [ Crash Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 42ebc7b8..52edcdb 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -2613,6 +2613,32 @@
       "almaher@microsoft.com"
     ]
   },
+  "These tests involve requiring a prefetch proxy, but since WPT origins are ",
+  "not allowed to use the proxy in Chromium, we pass an argument telling ",
+  "Chromium to bypass the proxy and pretend it was used instead. See also",
+  "external/wpt/speculation-rules/prefetch/resources/utils.sub.js.",
+  "These tests are skipped in other configurations via text expectations.",
+  {
+    "prefix": "prefetch-proxy",
+    "owners": [
+      "domenic@chromium.org",
+      "nhiroki@chromium.org"
+    ],
+    "platforms": [
+      "Linux",
+      "Mac",
+      "Win"
+    ],
+    "bases": [
+      "external/wpt/speculation-rules/prefetch/anonymous-client.https.html",
+      "external/wpt/speculation-rules/prefetch/cross-origin-cookies-anonymous-client-ip-duplicate.https.html"
+    ],
+    "exclusive_tests": "ALL",
+    "args": [
+      "--bypass-prefetch-proxy-for-host=not-web-platform.test"
+    ],
+    "expires": "never"
+  },
   "These tests require the experimental speculative prefetch feature, and",
   "requires extra switches to enable content implementation of speculation",
   "rules prefetch.",
@@ -2633,8 +2659,7 @@
     ],
     "exclusive_tests": "ALL",
     "args": [
-      "--disable-features=PrefetchServiceWorker",
-      "--bypass-prefetch-proxy-for-host=not-web-platform.test"
+      "--disable-features=PrefetchServiceWorker"
     ],
     "expires": "Oct 1, 2025"
   },
@@ -2654,8 +2679,7 @@
     ],
     "exclusive_tests": "ALL",
     "args": [
-      "--enable-features=Prerender2FallbackPrefetchSpecRules,PrefetchServiceWorker",
-      "--bypass-prefetch-proxy-for-host=not-web-platform.test"
+      "--enable-features=Prerender2FallbackPrefetchSpecRules,PrefetchServiceWorker"
     ],
     "expires": "Oct 1, 2025"
   },
@@ -2734,7 +2758,6 @@
     "args": [
       "--enable-features=Prerender2FallbackPrefetchSpecRules",
       "--disable-features=PrefetchServiceWorker",
-      "--bypass-prefetch-proxy-for-host=not-web-platform.test",
       "--disable-threaded-compositing",
       "--disable-threaded-animation"
     ],
diff --git a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/qdq_subgraph.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/qdq_subgraph.https.any.js
index 6231213..9c7ef62 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/qdq_subgraph.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/conformance_tests/qdq_subgraph.https.any.js
@@ -1815,6 +1815,142 @@
     }
   },
   {
+    'name': 'quantized gemm with bias',
+    'graph': {
+      'inputs': {
+        'inputA': {
+          'data': [
+            49.1112174987793, 11.907459259033203, 11.115795135498047,
+            21.115795135498047, 70.7490005493164, 31.115795135498047
+          ],
+          'descriptor': {shape: [2, 3], dataType: 'float32'},
+          'constant': false
+        },
+        'inputAScale': {
+          'data': [0.003921568859368563],
+          'descriptor': {shape: [1], dataType: 'float32'},
+          'constant': true
+        },
+        'inputAZeroPoint': {
+          'data': [-128],
+          'descriptor': {shape: [1], dataType: 'int8'},
+          'constant': true
+        },
+        'inputB': {
+          'data': [
+            21, 24, 8, 15, 6, 7
+          ],
+          'descriptor': {shape: [3, 2], dataType: 'int8'},
+          'constant': true
+        },
+        'inputBScale': {
+          'data': [0.023458752938762234],
+          'descriptor': {shape: [1], dataType: 'float32'},
+          'constant': true
+        },
+        'inputBZeroPoint': {
+          'data': [0],
+          'descriptor': {shape: [1], dataType: 'int8'},
+          'constant': true
+        },
+        'inputC': {
+          'data': [
+            8, 15
+          ],
+          'descriptor': {shape: [2], dataType: 'int32'},
+          'constant': true
+        },
+        'inputCScale': {
+          'data': [0.000091995115004270],
+          'descriptor': {shape: [1], dataType: 'float32'},
+          'constant': true
+        },
+        'inputCZeroPoint': {
+          'data': [0],
+          'descriptor': {shape: [1], dataType: 'int32'},
+          'constant': true
+        },
+        'outputScale': {
+          'data': [0.3921568859368563],
+          'descriptor': {shape: [1], dataType: 'float32'},
+          'constant': true
+        },
+        'outputZeroPoint': {
+          'data': [16],
+          'descriptor': {shape: [1], dataType: 'int8'},
+          'constant': true
+        },
+      },
+      'operators': [
+        {
+          'name': 'quantizeLinear',
+          'arguments': [
+            {'input': 'inputA'},
+            {'scale': 'inputAScale', 'zeroPoint': 'inputAZeroPoint'}
+          ],
+          'outputs': 'quantizedInputA'
+        },
+        {
+          'name': 'dequantizeLinear',
+          'arguments': [
+            {'input': 'quantizedInputA'},
+            {'scale': 'inputAScale', 'zeroPoint': 'inputAZeroPoint'}
+          ],
+          'outputs': 'dequantizedInputA'
+        },
+        {
+          'name': 'dequantizeLinear',
+          'arguments': [
+            {'input': 'inputB'},
+            {'scale': 'inputBScale', 'zeroPoint': 'inputBZeroPoint'}
+          ],
+          'outputs': 'dequantizedInputB'
+        },
+        {
+          'name': 'dequantizeLinear',
+          'arguments': [
+            {'input': 'inputC'},
+            {'scale': 'inputCScale', 'zeroPoint': 'inputCZeroPoint'}
+          ],
+          'outputs': 'dequantizedInputC'
+        },
+        {
+          'name': 'gemm',
+          'arguments': [
+            {'a': 'dequantizedInputA'}, {'b': 'dequantizedInputB'},
+            {'options': {'c': 'dequantizedInputC'}}
+          ],
+          'outputs': 'gemmOutput'
+        },
+        {
+          'name': 'quantizeLinear',
+          'arguments': [
+            {'input': 'gemmOutput'},
+            {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'}
+          ],
+          'outputs': 'quantizedGemmOutput'
+        },
+        {
+          'name': 'dequantizeLinear',
+          'arguments': [
+            {'input': 'quantizedGemmOutput'},
+            {'scale': 'outputScale', 'zeroPoint': 'outputZeroPoint'}
+          ],
+          'outputs': 'output'
+        }
+      ],
+      'expectedOutputs': {
+        'output': {
+          'data': [
+            0.7843137979507446, 1.1764707565307617,
+            0.7843137979507446, 1.1764707565307617,
+          ],
+          'descriptor': {shape: [2, 2], dataType: 'float32'}
+        }
+      }
+    }
+  },
+  {
     'name': 'quantized transpose',
     'graph': {
       'inputs': {
diff --git a/third_party/blink/web_tests/virtual/prefetch-proxy/DIR_METADATA b/third_party/blink/web_tests/virtual/prefetch-proxy/DIR_METADATA
new file mode 100644
index 0000000..8138849
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch-proxy/DIR_METADATA
@@ -0,0 +1,3 @@
+buganizer_public: {
+  component_id: 1456735
+}
diff --git a/third_party/blink/web_tests/virtual/prefetch-proxy/README.md b/third_party/blink/web_tests/virtual/prefetch-proxy/README.md
new file mode 100644
index 0000000..38421ce
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/prefetch-proxy/README.md
@@ -0,0 +1,2 @@
+This test suite is for tests of speculation rules prefetch with the
+`requires: ["anonymous-client-ip-when-crossorigin"]` feature.
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-conditional/container-queries/anchored/anchored-fallback-name.html b/third_party/blink/web_tests/wpt_internal/css/css-conditional/container-queries/anchored/anchored-fallback-name.html
new file mode 100644
index 0000000..3bd1fac
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-conditional/container-queries/anchored/anchored-fallback-name.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<title>CSS Conditional Test: @container anchored(fallback) matching &lt;custom-ident&gt;</title>
+<link rel="help" href="https://drafts.csswg.org/css-conditional-5/#container-rule">
+<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/8171">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/css/css-conditional/container-queries/support/cq-testcommon.js"></script>
+<style>
+  #anchor {
+    anchor-name: --a;
+    width: 100px;
+    height: 100px;
+  }
+  @position-try --foo {
+    position-area: bottom;
+  }
+  @position-try --bar {
+    position-area: top;
+  }
+  .anchored {
+    position: absolute;
+    position-anchor: --a;
+    position-area: top;
+    width: 100px;
+    /* Too tall to fit over the anchor to trigger fallback */
+    height: 100px;
+    container-type: anchored;
+  }
+  #a1 {
+    position-try-fallbacks: --foo;
+  }
+  #t1 {
+    @container anchored(fallback: --foo) { --pass: yes; }
+  }
+  #a2 {
+    position-try-fallbacks: --bar, --bar flip-block;
+  }
+  #t2 {
+    @container anchored(fallback: --bar flip-block) { --pass: yes; }
+    @container anchored(fallback: --bar) { --pass: no; }
+  }
+</style>
+<div id="anchor"></div>
+<div id="a1" class="anchored">
+  <div id="t1"></div>
+</div>
+<div id="a2" class="anchored">
+  <div id="t2"></div>
+</div>
+<script>
+  test(() => {
+    assert_equals(getComputedStyle(t1).getPropertyValue("--pass"), "yes");
+  }, "@container anchored(fallback) matching name");
+
+  test(() => {
+    assert_equals(getComputedStyle(t2).getPropertyValue("--pass"), "yes");
+  }, "@container anchored(fallback) matching name with tactics, but not name");
+</script>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-001-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-001-ref.html
index 34493f5..85106196 100644
--- a/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-001-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-001-ref.html
@@ -2,6 +2,7 @@
 <html>
 <link rel="help" href="https://drafts.csswg.org/css-grid-3">
 <link rel="author" title="Ethan Jimenez" href="mailto:ethavar@microsoft.com">
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
 <style>
 .grid {
     display: grid;
@@ -26,6 +27,13 @@
 .third-track {
     background: lightgreen;
     grid-column-start: 3;
+    margin-top: 40px;
+    margin-right: 40px;
+}
+
+.fourth-track {
+    background: purple;
+    grid-column-start: 4;
 }
 </style>
 <body>
@@ -34,6 +42,7 @@
     <div class="first-track">This is some text</div>
     <div class="second-track">Some larger words in this sentence</div>
     <div class="third-track">The cat cannot be separated from milk</div>
+    <div class="fourth-track">The cat cannot be separated from milk</div>
   </div>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-001.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-001.html
index 5cc985a6b..0b4c746 100644
--- a/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-001.html
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/column-explicit-placement-001.html
@@ -3,6 +3,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-grid-3">
 <link rel="match" href="column-explicit-placement-001-ref.html">
 <link rel="author" title="Ethan Jimenez" href="mailto:ethavar@microsoft.com">
+<link rel="author" title="Celeste Pan" href="mailto:celestepan@microsoft.com">
 <style>
 .masonry {
     display: masonry;
@@ -28,6 +29,13 @@
 .third-track {
     background: lightgreen;
     grid-column-start: 3;
+    margin-top: 40px;
+    margin-right: 40px;
+}
+
+.fourth-track {
+    background: purple;
+    grid-column-start: 4;
 }
 </style>
 <body>
@@ -36,6 +44,7 @@
     <div class="first-track">This is some text</div>
     <div class="second-track">Some larger words in this sentence</div>
     <div class="third-track">The cat cannot be separated from milk</div>
+    <div class="fourth-track">The cat cannot be separated from milk</div>
   </div>
 </body>
 </html>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-auto-placement-001-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-auto-placement-001-ref.html
index f14a0ac..fa340f4 100644
--- a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-auto-placement-001-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-auto-placement-001-ref.html
@@ -5,7 +5,7 @@
 <style>
 .grid {
     display: grid;
-    grid-template-rows: 100px 100px 100px;
+    grid-template-rows: 100px auto 100px;
     align-items: start;
     background: gray;
     width: 300px;
@@ -17,6 +17,7 @@
     flex-direction: row;
     overflow: visible;
     flex-wrap: nowrap;
+    align-items: flex-start;
 }
 
 .square-100x100 {
@@ -25,31 +26,31 @@
 }
 </style>
 <body>
-  <p>Test that masonry items with auto placement are correctly positioned when the grid-axis is the block axis.</p>
+  <p>Test that masonry items with auto placement are correctly positioned when the grid-axis is the block axis. Ensure that margin affects placement.</p>
   <div class="grid">
     <div class="flex">
-      <div class="square-100x100" style="background: lightskyblue;">
+      <div class="square-100x100" style="background: lightskyblue; margin-right: 100px;">
         Number 1
     </div>
-      <div class="square-100x100" style="background: lightpink;">
-        Number 4
-    </div>
-    <div style="background: brown; width: 100px; height: 200px;">
+    <div style="background: brown; width: 100px; height: 210px;">
       Number 6
     </div>
     </div>
     <div class="flex">
-      <div class="square-100x100" style="background: lightcoral;">
+      <div class="square-100x100" style="background: lightcoral; margin-bottom: 10px;">
         Number 2
     </div>
-      <div class="square-100x100" style="background: lightpink;">
-        Number 5
+    <div class="square-100x100" style="background: lightpink; margin-bottom: 10px;">
+      Number 4
     </div>
     </div>
     <div class="flex">
-      <div class="square-100x100" style="background: lightgreen;">
+      <div class="square-100x100" style="background: lightgreen; margin-right: 40px;">
         Number 3
     </div>
+    <div class="square-100x100" style="background: lightpink; margin-left: -20px;">
+        Number 5
+    </div>
     </div>
   </div>
 </body>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-auto-placement-001.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-auto-placement-001.html
index 47eec2e2..267bb56 100644
--- a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-auto-placement-001.html
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-auto-placement-001.html
@@ -17,17 +17,17 @@
 
 .first-track {
     background: lightskyblue;
-    grid-column-start: 1;
+    grid-row-start: 1;
 }
 
 .second-track {
     background: lightcoral;
-    grid-column-start: 2;
+    grid-row-start: 2;
 }
 
 .third-track {
     background: lightgreen;
-    grid-column-start: 3;
+    grid-row-start: 3;
 }
 
 .square-100x100 {
@@ -36,21 +36,21 @@
 }
 </style>
 <body>
-  <p>Test that masonry items with auto placement are correctly positioned when the grid-axis is the block axis.</p>
+  <p>Test that masonry items with auto placement are correctly positioned when the grid-axis is the block axis. Ensure that margin affects placement.</p>
   <div class="masonry">
-    <div class="first-track square-100x100">
+    <div class="first-track square-100x100" style="margin-right: 40px;">
       Number 1
     </div>
-    <div class="second-track square-100x100">
+    <div class="second-track square-100x100" style="margin-bottom: 10px;">
       Number 2
     </div>
-    <div class="third-track square-100x100">
+    <div class="third-track square-100x100" style="margin-right: 40px;">
       Number 3
     </div>
     <div class="square-100x100" style="background-color: lightpink;">
       Number 4
     </div>
-    <div class="square-100x100" style="background-color: lightpink;">
+    <div class="square-100x100" style="background-color: lightpink; margin-left: -20px;">
       Number 5
     </div>
     <div style="grid-row: span 2; background: brown; width: 100px;">
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-defined-height.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-defined-height.html
index 9c2695c..6f62b0f 100644
--- a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-defined-height.html
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-defined-height.html
@@ -18,17 +18,17 @@
 
 .first-track {
     background: lightskyblue;
-    grid-column-start: 1;
+    grid-row-start: 1;
 }
 
 .second-track {
     background: lightcoral;
-    grid-column-start: 2;
+    grid-row-start: 2;
 }
 
 .third-track {
     background: lightgreen;
-    grid-column-start: 3;
+    grid-row-start: 3;
 }
 
 .square-100x100 {
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-002-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-002-ref.html
index 5409699..3c1c3558 100644
--- a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-002-ref.html
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-002-ref.html
@@ -14,6 +14,7 @@
 .first-track {
     background: lightskyblue;
     grid-row-start: 1;
+    margin: 20px;
 }
 
 .second-track {
@@ -21,11 +22,13 @@
     grid-row-start: 2;
     height: 80px;
     margin-top: 40px;
+    margin-bottom: 80px;
 }
 
 .third-track {
     background: lightgreen;
     grid-row-start: 3;
+    margin-left: 40px;
 }
 </style>
 <body>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-002.html b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-002.html
index 91ae275..446161e 100644
--- a/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-002.html
+++ b/third_party/blink/web_tests/wpt_internal/css/css-masonry/row-explicit-placement-002.html
@@ -18,6 +18,7 @@
 .first-track {
     background: lightskyblue;
     grid-row-start: 1;
+    margin: 20px;
 }
 
 .second-track {
@@ -25,11 +26,13 @@
     grid-row-start: 2;
     height: 80px;
     margin-top: 40px;
+    margin-bottom: 80px;
 }
 
 .third-track {
     background: lightgreen;
     grid-row-start: 3;
+    margin-left: 40px;
 }
 </style>
 <body>
diff --git a/third_party/compiler-rt/src b/third_party/compiler-rt/src
index 0ceada7..3b1c4b0e 160000
--- a/third_party/compiler-rt/src
+++ b/third_party/compiler-rt/src
@@ -1 +1 @@
-Subproject commit 0ceada7e63a869692256f956cc22869007469c55
+Subproject commit 3b1c4b0e4fee85b10a2272117315d520c607a328
diff --git a/third_party/crossbench b/third_party/crossbench
index 1fcf64f..76223ae 160000
--- a/third_party/crossbench
+++ b/third_party/crossbench
@@ -1 +1 @@
-Subproject commit 1fcf64fb04e2c8477aeda4ad6eaea8ac1ae9422d
+Subproject commit 76223aed1ca101a8c6ceabf50b2d912424860f74
diff --git a/third_party/dawn b/third_party/dawn
index 168d54a..6a294b9 160000
--- a/third_party/dawn
+++ b/third_party/dawn
@@ -1 +1 @@
-Subproject commit 168d54a050758eb88e25757111b850bb1e97ed90
+Subproject commit 6a294b931ac942984bdeb350d608c93c0fef8cae
diff --git a/third_party/depot_tools b/third_party/depot_tools
index 3f633ff..9a17210 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit 3f633ff2f98baa7bd2563b710bc4168691eda7bb
+Subproject commit 9a172103022289fcd1374c4d12d0192204343f59
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 5d9af44..89f560f 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 5d9af4427b5620b138701f9e9a12f70860cdae4d
+Subproject commit 89f560fc68315d20519c66ed54d61a368308d034
diff --git a/third_party/fuchsia-sdk/README.chromium b/third_party/fuchsia-sdk/README.chromium
index 148e507..8ea5c66 100644
--- a/third_party/fuchsia-sdk/README.chromium
+++ b/third_party/fuchsia-sdk/README.chromium
@@ -2,6 +2,7 @@
 URL: https://fuchsia.dev/fuchsia-src/development/sdk
 Version: N/A
 Revision: DEPS
+Update Mechanism: Autoroll
 Security Critical: yes
 Shipped: yes
 License: BSD-2-Clause, Apache-2.0, MIT
diff --git a/third_party/icu4j/README.chromium b/third_party/icu4j/README.chromium
index 0886bac..c71ef7f 100644
--- a/third_party/icu4j/README.chromium
+++ b/third_party/icu4j/README.chromium
@@ -1,6 +1,7 @@
 Name: International Components for Unicode for Java (ICU4J)
 URL: http://site.icu-project.org/
 Version: 53.1
+Update Mechanism: Autoroll
 License: Unicode-DFS-2015, NAIST-2003, BSD-3-Clause, BSD-2-Clause, ICU
 License File: LICENSE
 Security Critical: no
diff --git a/third_party/inspector_protocol/README.chromium b/third_party/inspector_protocol/README.chromium
index 8c9e1297f..a00221f 100644
--- a/third_party/inspector_protocol/README.chromium
+++ b/third_party/inspector_protocol/README.chromium
@@ -3,6 +3,7 @@
 URL: https://chromium.googlesource.com/deps/inspector_protocol/
 Version: N/A
 Revision: 6d1ae0f13aae6ad381ca31b17b88a0f5af29ca94
+Update Mechanism: Autoroll
 License: BSD-3-Clause
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/jetstream/README.chromium b/third_party/jetstream/README.chromium
index 6a358cd..a2a23bbd1 100644
--- a/third_party/jetstream/README.chromium
+++ b/third_party/jetstream/README.chromium
@@ -3,6 +3,7 @@
 URL: https://github.com/WebKit/JetStream
 Version: N/A
 Revision: DEPS
+Update Mechanism: Autoroll
 License: BSD-2-Clause
 License File: LICENSE
 Shipped: no
diff --git a/third_party/llvm-libc/src b/third_party/llvm-libc/src
index 66ae6e2..e09972e 160000
--- a/third_party/llvm-libc/src
+++ b/third_party/llvm-libc/src
@@ -1 +1 @@
-Subproject commit 66ae6e2bd8e09c7a20b8b623b7a0249a5262f87c
+Subproject commit e09972e0948f01cace57c08ba82b61fc171c739c
diff --git a/third_party/speedometer/README.chromium b/third_party/speedometer/README.chromium
index 67ef5b8a..29adf79 100644
--- a/third_party/speedometer/README.chromium
+++ b/third_party/speedometer/README.chromium
@@ -3,6 +3,7 @@
 URL: https://github.com/WebKit/Speedometer
 Version: N/A
 Revision: DEPS
+Update Mechanism: Autoroll
 License: BSD-2-Clause
 License File: LICENSE
 Shipped: no
diff --git a/third_party/vulkan-deps b/third_party/vulkan-deps
index 4e2ba67..316ed08 160000
--- a/third_party/vulkan-deps
+++ b/third_party/vulkan-deps
@@ -1 +1 @@
-Subproject commit 4e2ba677d4d655a0b0d69b596f1c076ee0d4b516
+Subproject commit 316ed08fbc9a673e93c8960324cad4801e24a5dd
diff --git a/third_party/vulkan-validation-layers/src b/third_party/vulkan-validation-layers/src
index 7cee394..4b207ee 160000
--- a/third_party/vulkan-validation-layers/src
+++ b/third_party/vulkan-validation-layers/src
@@ -1 +1 @@
-Subproject commit 7cee394fda75d7b33d52b06b9e637abeb23c991c
+Subproject commit 4b207eefa09f304a06c237fcf4913304e0b7d8d1
diff --git a/third_party/webgpu-cts/src b/third_party/webgpu-cts/src
index 9773380..2a8d4a8 160000
--- a/third_party/webgpu-cts/src
+++ b/third_party/webgpu-cts/src
@@ -1 +1 @@
-Subproject commit 97733807d3c40aeb165cf2fbe4137f23c92446ea
+Subproject commit 2a8d4a83f751286302ce34573409ad75cc318508
diff --git a/third_party/wpt_tools/README.chromium b/third_party/wpt_tools/README.chromium
index 9345d1d..8e70bab 100644
--- a/third_party/wpt_tools/README.chromium
+++ b/third_party/wpt_tools/README.chromium
@@ -3,6 +3,7 @@
 URL: https://github.com/web-platform-tests/wpt/
 Version: N/A
 Revision: d74262218de2e9419380ea4750e93e8993da81db
+Update Mechanism: Autoroll
 License: BSD-3-Clause
 Security Critical: no
 Shipped: no
diff --git a/tools/clang/spanify/Spanifier.cpp b/tools/clang/spanify/Spanifier.cpp
index b0b4541..e4637e1 100644
--- a/tools/clang/spanify/Spanifier.cpp
+++ b/tools/clang/spanify/Spanifier.cpp
@@ -84,6 +84,7 @@
   kAdaptBinaryOperationPrecedence,
   kEmitSingleVariableSpanPrecedence,
   kAdaptBinaryPlusEqOperationPrecedence,
+  kRewriteUnaryOperationPrecedence,
   // Higher priority (stronger ties to the target)
 };
 
@@ -1505,6 +1506,97 @@
 }
 
 // Rewrite:
+//   `ptr++` or `++ptr`
+// Into:
+//   `base::PreIncrementSpan(span)` or `base::PostIncrementSpan(span)`.
+void RewriteUnaryOperation(const MatchFinder::MatchResult& result) {
+  const clang::SourceManager& source_manager = *result.SourceManager;
+  const auto& lang_opts = result.Context->getLangOpts();
+
+  const clang::Expr* operand = nullptr;
+  bool is_prefix = false;
+  clang::SourceLocation operator_loc;
+
+  if (const auto* unary_op =
+          result.Nodes.getNodeAs<clang::UnaryOperator>("unaryOperator")) {
+    operand = unary_op->getSubExpr();
+    is_prefix = unary_op->isPrefix();
+    operator_loc = unary_op->getOperatorLoc();
+  } else if (const auto* cxx_op_call =
+                 result.Nodes.getNodeAs<clang::CXXOperatorCallExpr>(
+                     "raw_ptr_operator++")) {
+    operand = cxx_op_call->getArg(0);
+    const auto* method_decl =
+        clang::dyn_cast<clang::CXXMethodDecl>(cxx_op_call->getCalleeDecl());
+    assert(method_decl);
+    // For CXXOperatorCallExpr, prefix increment has 0 parameters (e.g.,
+    // operator++()) postfix increment has 1 parameter (e.g., operator++(int)).
+    is_prefix = (method_decl->getNumParams() == 0);
+    operator_loc = cxx_op_call->getOperatorLoc();
+  }
+
+  if (!operand) {
+    // This block should ideally not be reached if matchers are well-defined.
+    llvm::errs()
+        << "\n"
+        << "Error: RewriteUnaryOperation() encountered an unexpected match.\n"
+        << "Expected a unaryOperator or raw_ptr_operator++ to be bound.\n";
+    DumpMatchResult(result);
+    assert(false && "Unexpected match in RewriteUnaryOperation()");
+    return;
+  }
+
+  assert(operator_loc.isValid());
+
+  // Get the source range of the operand (the 'ptr' part).
+  clang::SourceRange operand_range =
+      getExprRange(operand->IgnoreParenImpCasts(), source_manager, lang_opts);
+  assert(operand_range.isValid());
+
+  clang::SourceLocation operator_end_loc = clang::Lexer::getLocForEndOfToken(
+      operator_loc, 0, source_manager, lang_opts);
+  assert(operator_end_loc.isValid());
+  clang::SourceRange op_token_range(operator_loc, operator_end_loc);
+
+  std::string begin_insert_text;
+  clang::SourceRange begin_replacement_range;
+  clang::SourceRange end_replacement_range;
+
+  if (is_prefix) {
+    begin_insert_text = "base::preIncrementSpan(";
+    // Replace the '++' with "base::preIncrementSpan(".
+    begin_replacement_range = op_token_range;
+    // Insert ")" at the end of the operand.
+    end_replacement_range =
+        clang::SourceRange(operand_range.getEnd(), operand_range.getEnd());
+  } else {
+    begin_insert_text = "base::postIncrementSpan(";
+    // Insert "base::postIncrementSpan(" at the beginning of the operand.
+    begin_replacement_range =
+        clang::SourceRange(operand_range.getBegin(), operand_range.getBegin());
+    // Replace "++"" with ")".
+    end_replacement_range = op_token_range;
+  }
+
+  assert(begin_replacement_range.isValid());
+  assert(end_replacement_range.isValid());
+
+  const std::string key = GetRHS(result);
+
+  EmitReplacement(key, GetReplacementDirective(
+                           begin_replacement_range, begin_insert_text,
+                           source_manager, kRewriteUnaryOperationPrecedence));
+
+  EmitReplacement(
+      key, GetReplacementDirective(end_replacement_range, ")", source_manager,
+                                   -kRewriteUnaryOperationPrecedence));
+
+  EmitReplacement(key,
+                  GetIncludeDirective(operand_range, source_manager,
+                                      kBaseAutoSpanificationHelperIncludePath));
+}
+
+// Rewrite:
 //   `sizeof(c_array)`
 // Into:
 //   `base::SpanificationSizeofForStdArray(std_array)`
@@ -2976,6 +3068,19 @@
                                          parmVarDecl())))));
     Match(buffer_to_external_func, AppendDataCall);
 
+    // Handles unary arithmetic operations (pre/post increment)
+    auto unary_op = traverse(
+        clang::TK_IgnoreUnlessSpelledInSource,
+        expr(ignoringParenCasts(anyOf(
+                 unaryOperator(hasOperatorName("++"), hasUnaryOperand(rhs_expr))
+                     .bind("unaryOperator"),
+                 cxxOperatorCallExpr(
+                     callee(cxxMethodDecl(ofClass(hasName("raw_ptr")))),
+                     hasOperatorName("++"), hasArgument(0, rhs_expr))
+                     .bind("raw_ptr_operator++"))))
+            .bind("unary_op"));
+    Match(unary_op, RewriteUnaryOperation);
+
     // Handles expressions of the form:
     // a + m, a + n + m, ...
     // which need to be rewritten to:
diff --git a/tools/clang/spanify/tests/assignment-expected.cc b/tools/clang/spanify/tests/assignment-expected.cc
index 70013779..586e062 100644
--- a/tools/clang/spanify/tests/assignment-expected.cc
+++ b/tools/clang/spanify/tests/assignment-expected.cc
@@ -5,6 +5,7 @@
 #include <cstdint>
 #include <vector>
 
+#include "base/containers/auto_spanification_helper.h"
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_span.h"
@@ -63,7 +64,9 @@
   // ee = dd;
   ee = dd;
 
-  ee++;  // Buffer usage, leads e to be rewritten.
+  // Expected rewrite:
+  // base::postIncrementSpan(ee);
+  base::postIncrementSpan(ee);  // Buffer usage, leads e to be rewritten.
 
   // Expected rewrite:
   // base::span<int> ff = {};
@@ -71,7 +74,9 @@
 
   ff = get<int>();
 
-  ++ff;  // Leads to ff being rewritten.
+  // Expected rewrite:
+  // base::preIncrementSpan(ff);
+  base::preIncrementSpan(ff);  // Leads to ff being rewritten.
 
   // Exptected rewrite:
   // base::span<int> gg = {};
diff --git a/tools/clang/spanify/tests/assignment-original.cc b/tools/clang/spanify/tests/assignment-original.cc
index 4d13411..4477cc4 100644
--- a/tools/clang/spanify/tests/assignment-original.cc
+++ b/tools/clang/spanify/tests/assignment-original.cc
@@ -59,6 +59,8 @@
   // ee = dd;
   ee = dd.get();
 
+  // Expected rewrite:
+  // base::postIncrementSpan(ee);
   ee++;  // Buffer usage, leads e to be rewritten.
 
   // Expected rewrite:
@@ -67,6 +69,8 @@
 
   ff = get<int>();
 
+  // Expected rewrite:
+  // base::preIncrementSpan(ff);
   ++ff;  // Leads to ff being rewritten.
 
   // Exptected rewrite:
diff --git a/tools/clang/spanify/tests/basics-expected.cc b/tools/clang/spanify/tests/basics-expected.cc
index 1a70414..ff85fab 100644
--- a/tools/clang/spanify/tests/basics-expected.cc
+++ b/tools/clang/spanify/tests/basics-expected.cc
@@ -8,6 +8,7 @@
 #include <cstring>
 #include <vector>
 
+#include "base/containers/auto_spanification_helper.h"
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/numerics/safe_conversions.h"
@@ -77,11 +78,11 @@
   }
   int index = buf.size() - 1;
   // Expected rewrite:
-  // char* temp = expected_data++.data();
-  char* temp = (expected_data++).data();
+  // char* temp = (base::postIncrementSpan(expected_data)).data();
+  char* temp = (base::postIncrementSpan(expected_data)).data();
   // Expected rewrite:
-  // temp = (++expected_data).data();
-  temp = (++expected_data).data();
+  // temp = (base::preIncrementSpan(expected_data)).data();
+  temp = (base::preIncrementSpan(expected_data)).data();
   // Expected rewrite:
   // temp = expected_data.subspan(1u).data();
   temp = expected_data.subspan(1u).data();
@@ -181,7 +182,9 @@
   // base::span<int> buf = malloc(4*sizeof(int));
   base::span<char> buf = (char*)malloc(4 * sizeof(int));
   // Leads buf to be rewritten.
-  buf++;
+  // Expected rewrite:
+  // base::postIncrementSpan(buf);
+  base::postIncrementSpan(buf);
 
   const char* buf2 = nullptr;
   (void)buf2[0];
@@ -241,8 +244,10 @@
   memcpy(buf2.data(), buf.data(), 10);
 
   // Expected rewrite:
-  // memcpy((buf2++).data(), (++buf).data(), 10)
-  memcpy((buf2++).data(), (++buf).data(), 10);
+  // memcpy((base::postIncrementSpan(buf2)).data(),
+  //   (base::preIncrementSpan(buf)).data(), 10);
+  memcpy((base::postIncrementSpan(buf2)).data(),
+         (base::preIncrementSpan(buf)).data(), 10);
 
   int index = 11;
   // Expected rewrite:
@@ -252,10 +257,10 @@
          buf.subspan(base::checked_cast<size_t>(index)).data(), 10);
 
   // Expected rewrite:
-  // int i = (buf++)[0];
-  int i = (buf++)[0];
-  // i = (++buf)[0]
-  i = (++buf)[0];
+  // int i = (base::postIncrementSpan(buf))[0];
+  int i = (base::postIncrementSpan(buf))[0];
+  // i = (base::preIncrementSpan(buf))[0];
+  i = (base::preIncrementSpan(buf))[0];
   // i = (buf.subspan(base::checked_cast<size_t>(index))[0]);
   i = (buf.subspan(base::checked_cast<size_t>(index))[0]);
   // i = buf[0];
diff --git a/tools/clang/spanify/tests/basics-original.cc b/tools/clang/spanify/tests/basics-original.cc
index 19cd862..e6ec74e 100644
--- a/tools/clang/spanify/tests/basics-original.cc
+++ b/tools/clang/spanify/tests/basics-original.cc
@@ -74,10 +74,10 @@
   }
   int index = buf.size() - 1;
   // Expected rewrite:
-  // char* temp = expected_data++.data();
+  // char* temp = (base::postIncrementSpan(expected_data)).data();
   char* temp = expected_data++;
   // Expected rewrite:
-  // temp = (++expected_data).data();
+  // temp = (base::preIncrementSpan(expected_data)).data();
   temp = ++expected_data;
   // Expected rewrite:
   // temp = expected_data.subspan(1u).data();
@@ -170,6 +170,8 @@
   // base::span<int> buf = malloc(4*sizeof(int));
   char* buf = (char*)malloc(4 * sizeof(int));
   // Leads buf to be rewritten.
+  // Expected rewrite:
+  // base::postIncrementSpan(buf);
   buf++;
 
   const char* buf2 = nullptr;
@@ -230,7 +232,8 @@
   memcpy(buf2, buf, 10);
 
   // Expected rewrite:
-  // memcpy((buf2++).data(), (++buf).data(), 10)
+  // memcpy((base::postIncrementSpan(buf2)).data(),
+  //   (base::preIncrementSpan(buf)).data(), 10);
   memcpy(buf2++, ++buf, 10);
 
   int index = 11;
@@ -240,9 +243,9 @@
   memcpy(buf2 + 1, buf + index, 10);
 
   // Expected rewrite:
-  // int i = (buf++)[0];
+  // int i = (base::postIncrementSpan(buf))[0];
   int i = *buf++;
-  // i = (++buf)[0]
+  // i = (base::preIncrementSpan(buf))[0];
   i = *++buf;
   // i = (buf.subspan(base::checked_cast<size_t>(index))[0]);
   i = *(buf + index);
diff --git a/tools/clang/spanify/tests/initializers-expected.cc b/tools/clang/spanify/tests/initializers-expected.cc
index dd684306..6c1d30b0 100644
--- a/tools/clang/spanify/tests/initializers-expected.cc
+++ b/tools/clang/spanify/tests/initializers-expected.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 #include <vector>
 
+#include "base/containers/auto_spanification_helper.h"
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_span.h"
@@ -14,8 +15,8 @@
   A(base::span<int> ptr) : member(ptr) {}
 
   // Expecte rewrite:
-  // int* advanceAndGet() { return member++.data(); }
-  int* advanceAndGet() { return member++.data(); }
+  // int* advanceAndGet() { return base::postIncrementSpan(member).data(); }
+  int* advanceAndGet() { return base::postIncrementSpan(member).data(); }
 
   // Expected rewrite:
   // int* get() { return member.data(); }
diff --git a/tools/clang/spanify/tests/initializers-original.cc b/tools/clang/spanify/tests/initializers-original.cc
index 34cb5ab..7c33487 100644
--- a/tools/clang/spanify/tests/initializers-original.cc
+++ b/tools/clang/spanify/tests/initializers-original.cc
@@ -12,7 +12,7 @@
   A(int* ptr) : member(ptr) {}
 
   // Expecte rewrite:
-  // int* advanceAndGet() { return member++.data(); }
+  // int* advanceAndGet() { return base::postIncrementSpan(member).data(); }
   int* advanceAndGet() { return member++; }
 
   // Expected rewrite:
diff --git a/tools/clang/spanify/tests/raw-ptr-fields-expected.cc b/tools/clang/spanify/tests/raw-ptr-fields-expected.cc
index d5c99148..973d33f 100644
--- a/tools/clang/spanify/tests/raw-ptr-fields-expected.cc
+++ b/tools/clang/spanify/tests/raw-ptr-fields-expected.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/containers/auto_spanification_helper.h"
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_span.h"
@@ -84,8 +85,8 @@
 
   int* get_and_advance() {
     // Expected rewrite:
-    // return ptr_++.data();
-    return ptr_++.data();
+    // return base::postIncrementSpan(ptr_).data();
+    return base::postIncrementSpan(ptr_).data();
   }
 
   // Expected rewrite:
diff --git a/tools/clang/spanify/tests/raw-ptr-fields-original.cc b/tools/clang/spanify/tests/raw-ptr-fields-original.cc
index 256653b..552c3a5 100644
--- a/tools/clang/spanify/tests/raw-ptr-fields-original.cc
+++ b/tools/clang/spanify/tests/raw-ptr-fields-original.cc
@@ -79,7 +79,7 @@
 
   int* get_and_advance() {
     // Expected rewrite:
-    // return ptr_++.data();
+    // return base::postIncrementSpan(ptr_).data();
     return ptr_++;
   }
 
diff --git a/tools/clang/spanify/tests/return-stmts-expected.cc b/tools/clang/spanify/tests/return-stmts-expected.cc
index 6df5b56..041fed7 100644
--- a/tools/clang/spanify/tests/return-stmts-expected.cc
+++ b/tools/clang/spanify/tests/return-stmts-expected.cc
@@ -4,6 +4,7 @@
 #include <cstdint>
 #include <vector>
 
+#include "base/containers/auto_spanification_helper.h"
 #include "base/containers/span.h"
 #include "base/numerics/safe_conversions.h"
 
@@ -36,7 +37,8 @@
   // Expected rewrite:
   // base::span<int> var1 = new int[1024];
   base::span<int> var1 = new int[1024];
-  return var1++;
+  // return base::postIncrementSpan(var1);
+  return base::postIncrementSpan(var1);
 }
 
 // Expected rewrite:
@@ -46,7 +48,8 @@
   // Expected rewrite:
   // base::span<int> var1 = new int[1024];
   base::span<int> var1 = new int[1024];
-  return ++var1;
+  // return base::preIncrementSpan(var1);
+  return base::preIncrementSpan(var1);
 }
 
 // Expected rewrite:
@@ -110,30 +113,36 @@
   // Expected rewrite:
   // base::span<int> v1 = fct1();
   base::span<int> v1 = fct1();
-  v1++;
+  // base::postIncrementSpan(v1);
+  base::postIncrementSpan(v1);
 
   // Expected rewrite:
   // base::span<int> v2 = fct2();
   base::span<int> v2 = fct2();
-  v2++;
+  // base::postIncrementSpan(v2);
+  base::postIncrementSpan(v2);
 
   // Expected rewrite:
   // base::span<int> v3 = fct3();
   base::span<int> v3 = fct3();
-  v3++;
+  // base::postIncrementSpan(v3);
+  base::postIncrementSpan(v3);
 
   // Expected rewrite:
   // base::span<int> v4 = fct4();
   base::span<int> v4 = fct4();
-  v4++;
+  // base::postIncrementSpan(v4);
+  base::postIncrementSpan(v4);
 
   // Expected rewrite:
   // base::span<int> v5 = fct5();
   base::span<int> v5 = fct5();
-  v5++;
+  // base::postIncrementSpan(v5);
+  base::postIncrementSpan(v5);
 
   // Expected rewrite:
   // base::span<char> v6 = fct6();
   base::span<char> v6 = fct6();
-  v6++;
+  // base::postIncrementSpan(v6);
+  base::postIncrementSpan(v6);
 }
diff --git a/tools/clang/spanify/tests/return-stmts-original.cc b/tools/clang/spanify/tests/return-stmts-original.cc
index 123d6f0..99c01594 100644
--- a/tools/clang/spanify/tests/return-stmts-original.cc
+++ b/tools/clang/spanify/tests/return-stmts-original.cc
@@ -32,6 +32,7 @@
   // Expected rewrite:
   // base::span<int> var1 = new int[1024];
   int* var1 = new int[1024];
+  // return base::postIncrementSpan(var1);
   return var1++;
 }
 
@@ -42,6 +43,7 @@
   // Expected rewrite:
   // base::span<int> var1 = new int[1024];
   int* var1 = new int[1024];
+  // return base::preIncrementSpan(var1);
   return ++var1;
 }
 
@@ -103,30 +105,36 @@
   // Expected rewrite:
   // base::span<int> v1 = fct1();
   int* v1 = fct1();
+  // base::postIncrementSpan(v1);
   v1++;
 
   // Expected rewrite:
   // base::span<int> v2 = fct2();
   int* v2 = fct2();
+  // base::postIncrementSpan(v2);
   v2++;
 
   // Expected rewrite:
   // base::span<int> v3 = fct3();
   int* v3 = fct3();
+  // base::postIncrementSpan(v3);
   v3++;
 
   // Expected rewrite:
   // base::span<int> v4 = fct4();
   int* v4 = fct4();
+  // base::postIncrementSpan(v4);
   v4++;
 
   // Expected rewrite:
   // base::span<int> v5 = fct5();
   int* v5 = fct5();
+  // base::postIncrementSpan(v5);
   v5++;
 
   // Expected rewrite:
   // base::span<char> v6 = fct6();
   char* v6 = fct6();
+  // base::postIncrementSpan(v6);
   v6++;
 }
diff --git a/tools/clang/spanify/tests/var-construction-expected.cc b/tools/clang/spanify/tests/var-construction-expected.cc
index 6e54c49..601479a 100644
--- a/tools/clang/spanify/tests/var-construction-expected.cc
+++ b/tools/clang/spanify/tests/var-construction-expected.cc
@@ -5,6 +5,7 @@
 #include <cstdint>
 #include <vector>
 
+#include "base/containers/auto_spanification_helper.h"
 #include "base/containers/span.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_span.h"
@@ -51,13 +52,17 @@
   // base::span<int> e = d;
   base::span<int> e = d;
 
-  e++;  // Buffer usage, leads e to be rewritten.
+  // Expected rewrite:
+  // base::postIncrementSpan(e);
+  base::postIncrementSpan(e);  // Buffer usage, leads e to be rewritten.
 
   // Expected rewrite:
   // base::span<int> f = get<int>();
   base::span<int> f = get<int>();
 
-  ++f;  // Leads to f being rewritten.
+  // Expected rewrite:
+  // base::preIncrementSpan(f);
+  base::preIncrementSpan(f);  // Leads to f being rewritten.
 
   // Exptected rewrite:
   // base::span<int> g = (condition) ? ctn1 : ctn2;
@@ -112,6 +117,7 @@
 
   // Expected rewrite:
   // base::span<char> buf2 = new char[5];
+  // buf2 = buf2.subspan(1);
   base::span<char> buf2 = new char[5];
   // Expected rewrite:
   // buf2 = buf2.subspan(1u);
@@ -119,6 +125,7 @@
 
   // Expected rewrite:
   // base::raw_span<char> buf3 = buf2;
+  // buf3 = buf3.subspan(1);
   base::raw_span<char> buf3 = buf2;
   // Expected rewrite:
   // buf3 = buf3.subspan(1u);
@@ -126,11 +133,13 @@
 
   // Expected rewrite:
   // base::raw_span<char> buf4 = buf3;
+  // base::postIncrementSpan(buf4);
   base::base::raw_span<char> buf4 = buf3;
-  buf4++;
+  base::postIncrementSpan(buf4);
 
   // Expected rewrite:
   // base::raw_span<char> buf5 = buf4;
+  // buf5 = buf5.subspan(1);
   base::raw_span<char> buf5 = buf4;
   // Expected rewrite:
   // buf5 = buf5.subspan(1u);
@@ -138,8 +147,9 @@
 
   // Expected rewrite:
   // base::raw_span<char> buf6 = buf5;
+  // base::preIncrementSpan(buf6);
   base::raw_span<char> buf6 = buf5;
-  ++buf6;
+  base::preIncrementSpan(buf6);
 
   // Expected rewrite:
   // buf6[0] = 'c';
diff --git a/tools/clang/spanify/tests/var-construction-original.cc b/tools/clang/spanify/tests/var-construction-original.cc
index 7502e8dc..16873af 100644
--- a/tools/clang/spanify/tests/var-construction-original.cc
+++ b/tools/clang/spanify/tests/var-construction-original.cc
@@ -47,12 +47,16 @@
   // base::span<int> e = d;
   int* e = d.get();
 
+  // Expected rewrite:
+  // base::postIncrementSpan(e);
   e++;  // Buffer usage, leads e to be rewritten.
 
   // Expected rewrite:
   // base::span<int> f = get<int>();
   int* f = get<int>();
 
+  // Expected rewrite:
+  // base::preIncrementSpan(f);
   ++f;  // Leads to f being rewritten.
 
   // Exptected rewrite:
@@ -108,6 +112,7 @@
 
   // Expected rewrite:
   // base::span<char> buf2 = new char[5];
+  // buf2 = buf2.subspan(1);
   char* buf2 = new char[5];
   // Expected rewrite:
   // buf2 = buf2.subspan(1u);
@@ -115,6 +120,7 @@
 
   // Expected rewrite:
   // base::raw_span<char> buf3 = buf2;
+  // buf3 = buf3.subspan(1);
   raw_ptr<char> buf3 = buf2;
   // Expected rewrite:
   // buf3 = buf3.subspan(1u);
@@ -122,11 +128,13 @@
 
   // Expected rewrite:
   // base::raw_span<char> buf4 = buf3;
+  // base::postIncrementSpan(buf4);
   base::raw_ptr<char> buf4 = buf3;
   buf4++;
 
   // Expected rewrite:
   // base::raw_span<char> buf5 = buf4;
+  // buf5 = buf5.subspan(1);
   raw_ptr<char> buf5 = buf4;
   // Expected rewrite:
   // buf5 = buf5.subspan(1u);
@@ -134,6 +142,7 @@
 
   // Expected rewrite:
   // base::raw_span<char> buf6 = buf5;
+  // base::preIncrementSpan(buf6);
   raw_ptr<char> buf6 = buf5;
   ++buf6;
 
diff --git a/tools/metrics/histograms/metadata/android/enums.xml b/tools/metrics/histograms/metadata/android/enums.xml
index 4a2bd76..0280a4c 100644
--- a/tools/metrics/histograms/metadata/android/enums.xml
+++ b/tools/metrics/histograms/metadata/android/enums.xml
@@ -799,7 +799,7 @@
   <int value="4" label="Hardware unavailable"/>
   <int value="5" label="Not enrolled"/>
   <int value="6" label="Security update required"/>
-  <int value="7" label="Android version not supported"/>
+  <int value="7" label="(obsolete) Android version not supported"/>
   <int value="8" label="Biometric auth is mandatory"/>
   <int value="9" label="Biometric auth is mandatory, but has an error"/>
 </enum>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index b7e5a099..f0578c8 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -1238,6 +1238,21 @@
   </summary>
 </histogram>
 
+<histogram name="Android.ChildProcessConnection.OnServiceConnectedCounts"
+    units="count" expires_after="M143">
+  <owner>boliu@chromium.org</owner>
+  <owner>kawasin@chromium.org</owner>
+  <owner>yfriedman@chromium.org</owner>
+  <summary>
+    Record the accumulated count of
+    ChildServiceConnectionDelegate.onServiceConnected. This is recorded every
+    time the callback is triggered. The metric with number more than 1 indicates
+    that the child process is restarted after the service process connected.
+    Note that the lower buckets double-counts the higher buckets since this is
+    recorded at each restart (e.g. when 4 is recorded, 1,2,3 are also recorded).
+  </summary>
+</histogram>
+
 <histogram name="Android.ChildProcessStartTimeV2.{Type}" units="ms"
     expires_after="2023-02-22">
   <owner>cduvall@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/autofill/enums.xml b/tools/metrics/histograms/metadata/autofill/enums.xml
index 0d1e312..d98f6d14 100644
--- a/tools/metrics/histograms/metadata/autofill/enums.xml
+++ b/tools/metrics/histograms/metadata/autofill/enums.xml
@@ -34,7 +34,7 @@
   </summary>
   <int value="0" label="Available and turned on in Chrome settings"/>
   <int value="1" label="Not allowed by policy"/>
-  <int value="2" label="Android OS version is too old"/>
+  <int value="2" label="(obsolete) Android OS version is too old"/>
   <int value="3" label="The system's AutofillManager is not available"/>
   <int value="4" label="The system does not support autofill"/>
   <int value="5" label="The system's AutofillService cannot be determined."/>
diff --git a/tools/perf/BUILD.gn b/tools/perf/BUILD.gn
index a5bb190f..7136fb22 100644
--- a/tools/perf/BUILD.gn
+++ b/tools/perf/BUILD.gn
@@ -22,11 +22,15 @@
     }
   }
 } else {
+  import("//build/config/android/config.gni")
   template("perf_android_template") {
     forward_variables_from(invoker, [ "telemetry_target_suffix" ])
     group(target_name) {
       testonly = true
       data = COMMON_PERF_DATA
+      if (enable_chrome_android_internal) {
+        data += [ "//clank/android_webview/tools/crossbench_config/" ]
+      }
       data_deps = [
         ":perf_without_chrome",
         "//chrome/test/chromedriver:chromedriver_server($host_toolchain)",
diff --git a/ui/compositor/compositor.cc b/ui/compositor/compositor.cc
index a63fea66..1cb8ea9 100644
--- a/ui/compositor/compositor.cc
+++ b/ui/compositor/compositor.cc
@@ -786,8 +786,8 @@
                                   weak_ptr_factory_.GetWeakPtr());
 }
 
-double Compositor::GetPercentDroppedFrames() const {
-  return host_->GetPercentDroppedFrames();
+double Compositor::GetAverageThroughput() const {
+  return host_->GetAverageThroughput();
 }
 
 std::unique_ptr<cc::EventsMetricsManager::ScopedMonitor>
diff --git a/ui/compositor/compositor.h b/ui/compositor/compositor.h
index 0470e28..f1a4674 100644
--- a/ui/compositor/compositor.h
+++ b/ui/compositor/compositor.h
@@ -386,8 +386,8 @@
   // Creates a CompositorMetricsTracker for tracking this Compositor.
   CompositorMetricsTracker RequestNewCompositorMetricsTracker();
 
-  // Returns a percentage of dropped frames of the last second.
-  double GetPercentDroppedFrames() const;
+  // Returns average throughput as measured by the FrameSorter.
+  double GetAverageThroughput() const;
 
   // Activates a scoped monitor for the current event to track its metrics.
   // `done_callback` is called when the monitor goes out of scope.
diff --git a/ui/linux/linux_ui_factory.cc b/ui/linux/linux_ui_factory.cc
index f31b4ab..f7c49e9 100644
--- a/ui/linux/linux_ui_factory.cc
+++ b/ui/linux/linux_ui_factory.cc
@@ -190,6 +190,7 @@
     case base::nix::DESKTOP_ENVIRONMENT_PANTHEON:
     case base::nix::DESKTOP_ENVIRONMENT_UNITY:
     case base::nix::DESKTOP_ENVIRONMENT_XFCE:
+    case base::nix::DESKTOP_ENVIRONMENT_COSMIC:
       return SystemTheme::kGtk;
     case base::nix::DESKTOP_ENVIRONMENT_KDE3:
     case base::nix::DESKTOP_ENVIRONMENT_KDE4: